0

I'm trying to access the method name service/Method from within the a gRPC handler. I can't seem to find this data on the grpc.ServicerContext object (and I'm pretty sure it isn't there). My next thought was to use an interceptor since the method name is provided via the grpc.HandlerCallDetails. The issue is that there isn't any obvious way to forward this to the underlying handler.

Unfortunately, grpc.HandlerCallDetails doesn't have a constructor, so we can't take the method name and shove it in the metadata for it to be read in the handler. We'd have to create a named tuple that's the same interface as grpc.HandlerCallDetails and add a new metadata value to it.

It would be great if anyone has run into this and has a solution or if someone on the grpc team has a workaround.

Additionally, this should probably be something that's a bit easier as there is prior art for this kind of feature. grpc-go has the Method name to help with this https://pkg.go.dev/google.golang.org/grpc?tab=doc#Method. It seems like in c++ you could potentially pull this out of the census context.

Daniel Walker
  • 6,380
  • 5
  • 22
  • 45

1 Answers1

1

I think you have found the right component. HandlerCallDetails is used to let gRPC servicer match the method name and the handler.

One possible way to do this is through implement ServiceRpcHandler. You can implement your own handler and supply it to the gRPC server. In the service handler, you can provide the method name information as the third argument, or attach it to the service context, or assign to a thread local storage.

In addition, the ProtoBuf generated code hides this detail and it can be good reference to see how it is done.

Here is a copy of ProtoBuf generated code:

def add_GreeterServicer_to_server(servicer, server):
  rpc_method_handlers = {
      'SayHello': grpc.unary_unary_rpc_method_handler(
          servicer.SayHello,
          request_deserializer=helloworld__pb2.HelloRequest.FromString,
          response_serializer=helloworld__pb2.HelloReply.SerializeToString,
      ),
  }
  generic_handler = grpc.method_handlers_generic_handler(
      'helloworld.Greeter', rpc_method_handlers)
  server.add_generic_rpc_handlers((generic_handler,))

Here is the default service RPC handler implementation:

class DictionaryGenericHandler(grpc.ServiceRpcHandler):

    def __init__(self, service, method_handlers):
        self._name = service
        self._method_handlers = {
            _common.fully_qualified_method(service, method): method_handler
            for method, method_handler in six.iteritems(method_handlers)
        }

    def service_name(self):
        return self._name

    def service(self, handler_call_details):
        return self._method_handlers.get(handler_call_details.method)
Lidi Zheng
  • 1,801
  • 8
  • 13
  • Thanks for the answer, but it seems that this approach would require ditching the generated protobuf code. My solution was to use metaclasses + abc to force subclasses of my base Servicer mixin to specify the service name manually (fetched via the descriptor) and then just use the name of the current python method since that is the same as the gRPC method name and then join them together in my decorator that wraps all of the gRPC methods. Feels very hacky and the generated code should probably make this a bit easier. – Andrew Braunstein Aug 04 '20 at 17:41