Skip to content
Advertisement

What is the proper way to return an error from gRPC ServerInterceptor

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);
     }
}
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement