3

I'm writing a grpc service behind grpc-gateway in python and In case of too many request by some user I want to raise a 429 response and give the captcha-token in body of response message.

Actually my problem is when I use the block of code below for raising status code 429, after that I can't send the response message.

context.set_code(grpc.StatusCode.RESOURCE_EXHAUSTED)
context.set_details('Too many requests')
return MyServiceResponse()

As I understood it's impossible with the only-grpc, But I think it may be possible with third parties.

Is there any solution for this?

Ali Ahmadi
  • 31
  • 1
  • 3

1 Answers1

2

Sending response with non-ok status is not allowed for unary-unary RPC (non-streaming on both sides). For streaming RPC, the server may send response before sending the error code, but is not recommended. Mixing normal response with error status could result in future maintainability issues, e.g. if the same error apply to multiple RPC, should all the response ProtoBuf message include those fields?

Back to your question, the "captcha-token" should be considered a part of the error status, hence it can be added as one of the trailing metadata. In your case, you may add the serialized proto message as a binary trailing metadata by adding a -bin suffix in your trailing metadata key.

Also, there is an official supported package grpcio-status that does this for you.

The server side packs rich error status into a "grpc_status.status_pb2.Status" proto message. The example below just uses the common error protos, but you may pack "any" proto into details, as long as your client understands them.

# Server side
from grpc_status import rpc_status
from google.protobuf import any_pb2

def ...Servicer(...):
    def AnRPCCall(request, context):
        ...
        detail = any_pb2.Any()
        detail.Pack(
            rpc_status.error_details_pb2.DebugInfo(
                stack_entries=traceback.format_stack(),
                detail="Can't recognize this argument",
            )
        )
        rich_status = grpc_status.status_pb2.Status(
            code=grpc_status.code_pb2.INVALID_ARGUMENT,
            message='Invalid argument',
            details=[detail]
        )
        context.abort_with_status(rpc_status.to_status(rich_status))
        # The method handler will abort

The client side decodes the error, and reacts on them.

# Client side
try:
    self._channel.unary_unary(_ERROR_DETAILS).with_call(_REQUEST)
except grpc.RpcError as rpc_error:
    status = rpc_status.from_call(rpc_error)
    for detail in status.details:
        if detail.Is(error_details_pb2.DebugInfo.DESCRIPTOR):
            info = error_details_pb2.DebugInfo()
            detail.Unpack(info)
            # Handle the debug info
        elif detail.Is(OtherErrorProto.DESCRIPTOR):
            # Handle the other error proto
        else:
            # Handle unknown error


Learn more about the rich status: https://github.com/grpc/proposal/blob/master/L44-python-rich-status.md

Lidi Zheng
  • 1,801
  • 8
  • 13
  • I'm only recently discovering grpc addons, such as grpcio-status and grpcio-reflection, and so on. Where is there a list of all of these grpc 1st party addons, specifically for grpc python? – Kevin S Mar 27 '19 at 13:38
  • 2
    The official supported packages are listed in the API reference document https://grpc.io/grpc/python/. I admit the docs for gRPC is not great now, but we are trying to add more content and examples. – Lidi Zheng Mar 28 '19 at 23:37
  • Indeed...the docs for some of those are basically, ok go look at how we did it in C++ – Kevin S Apr 02 '19 at 13:20
  • I ran into `StatusCode.RESOURCE_EXHAUSTED` errors because `to-be-sent trailing metadata size exceeds peer limit` because sometimes tracebacks are too long. You need to add `options=[('grpc.max_metadata_size', 16 * 1024 * 1024)]` when starting the server. For example like they [do it in this test](https://github.com/grpc/grpc/blob/c683d803306ec3b64bb2e868f8f5118bdecb27ea/src/python/grpcio_tests/tests/stress/unary_stream_benchmark.py#L56) – Boris Verkhovskiy Mar 05 '20 at 17:26