I have a Quarkus application that uses gRPC. I have implemented a ServerInterceptor to look for and validate auth tokens.
@ApplicationScoped public class GrpcAuthInterceptor implements ServerInterceptor { @Inject AuthenticationService authenticationService; @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) { String token = metadata.get(AuthenticationConstants.AUTH_HEADER_KEY); Context context = Context.current(); if (!Strings.isNullOrEmpty(token)) { String fbToken = authenticationService.verifyIdToken(token); context = context.withValue(AuthenticationConstants.CONTEXT_FB_USER_KEY, fbToken); } else { serverCall.close(Status.UNAUTHENTICATED.withDescription("Auth Token Required"), metadata); } return Contexts.interceptCall( context, serverCall, metadata, serverCallHandler); } }
This works and sends a message to the client like this:
gRPC Error (code: 16, codeName: UNAUTHENTICATED, message: Auth Token Required, details: [], rawResponse: null, trailers: {})
but it logs this error on the server because I didn’t return from the method after sending the error:
2022-10-04 21:50:23,006 ERROR [io.qua.mut.run.MutinyInfrastructure] (vert.x-eventloop-thread-0) Mutiny had to drop the following exception: java.lang.IllegalStateException: call is closed
Several posts suggest I can return an empty ServerCall.Listener like this:
serverCall.close(Status.UNAUTHENTICATED.withDescription("Auth Token Required"), metadata); return new ServerCall.Listener<ReqT>() {};
this works and gets rid of the server-side error message BUT it breaks the message returned to the client. The client just gets this:
gRPC Error (code: 2, codeName: UNKNOWN, message: null, details: [], rawResponse: null, trailers: {})
So my question is: how do I return a meaningful error message to the client and not log an exception server-side?
Advertisement
Answer
You could catch it all in a try block and send the message just in there; The issue you face is that you are first closing the call before returning it, so it tries to process an already closed pipe.
In the second scenario, as you send the “void” message right after the “close” message, the detailed message gets overriden.
Try something like:
@Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> serverCall, Metadata metadata, ServerCallHandler<ReqT, RespT> serverCallHandler) { String token = metadata.get(AuthenticationConstants.AUTH_HEADER_KEY); Context context = Context.current(); boolean mustClose=false; try { if (!Strings.isNullOrEmpty(token)) { String fbToken = authenticationService.verifyIdToken(token); context = context.withValue(AuthenticationConstants.CONTEXT_FB_USER_KEY, fbToken); return Contexts.interceptCall(context,serverCall,metadata, serverCallHandler); } else { mustClose=true; return new ServerCall.Listener<ReqT>() {}; } } finally { if (mustClose) serverCall.close(Status.UNAUTHENTICATED.withDescription("Auth Token Required"), metadata); } }