Set execution order for the gRPC ServerInterceptors

Viewed 1392

In io.grpc.internal.AbstractServerImplBuilder you may add implementations of the ServerInterceptor to the final list

final List<ServerInterceptor> interceptors = new ArrayList<>();

then in default server implementation for the builder - io.grpc.internal.ServerImpl it calls them in the foreach loop

/** Never returns {@code null}. */
private <ReqT, RespT> ServerStreamListener startCall(ServerStream stream, String fullMethodName,
    ServerMethodDefinition<ReqT, RespT> methodDef, Metadata headers,
    Context.CancellableContext context, StatsTraceContext statsTraceCtx, Tag tag) {
  // TODO(ejona86): should we update fullMethodName to have the canonical path of the method?
  statsTraceCtx.serverCallStarted(
      new ServerCallInfoImpl<>(
          methodDef.getMethodDescriptor(), // notify with original method descriptor
          stream.getAttributes(),
          stream.getAuthority()));
  ServerCallHandler<ReqT, RespT> handler = methodDef.getServerCallHandler();
  for (ServerInterceptor interceptor : interceptors) {
    handler = InternalServerInterceptors.interceptCallHandler(interceptor, handler);
  }
  ServerMethodDefinition<ReqT, RespT> interceptedDef = methodDef.withServerCallHandler(handler);
  ServerMethodDefinition<?, ?> wMethodDef = binlog == null
      ? interceptedDef : binlog.wrapMethodDefinition(interceptedDef);
  return startWrappedCall(fullMethodName, wMethodDef, stream, headers, context, tag);
}

so it executes the interceptors chain from the last registered to the former one when received the request. Same order is followed when doing the response from the handler.

What I wonder and expected is that it will follow onion middleware pattern and you can explicitly set the execution order (similar to the servlet filters). Do I miss something?

1 Answers

There is no way to set the order. ServerBuilder.intercept() is a convenience version of ServerInterceptors.intercept(), and follows the same semantics. There is ServerInterceptors.interceptForward() which runs them in the opposite direction, but you can't have an API that adds interceptors individually and support both forms simultaneously.

The intercept API is a bit confusing because it does follow the onion principal, but the onion is around the service. Interceptors wrap a service, and so the last interceptor applied is executed first.

serviceDef = ServerInterceptors.intercept(serviceDef,
    interceptor1,
    interceptor2,
    interceptor3);

// is equivalent to

serviceDef = ServerInterceptors.intercept(serviceDef, interceptor1);
serviceDef = ServerInterceptors.intercept(serviceDef, interceptor2);
serviceDef = ServerInterceptors.intercept(serviceDef, interceptor3);
// the gRPC server sees the last 'serviceDef', so interceptor3 is
// called first
Related