Как отправить ответное сообщение со статусом не в порядке от службы python за grpc-шлюзом?

Я пишу сервис grpc за grpc-gateway на python и в случае слишком большого количества запросов со стороны какого-либо пользователя я хочу получить ответ 429 и выдать токен-капчу в теле ответного сообщения.

На самом деле моя проблема в том, что я использую приведенный ниже блок кода для повышения кода состояния 429, после чего я не могу отправить ответное сообщение.

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

Как я понял, это невозможно с единственным-grpc, но я думаю, что это возможно с третьими сторонами.

Есть ли решение для этого?

1 ответ

Отправка ответа со статусом "не в порядке" не допускается для унарно-унарного RPC (без потоковой передачи с обеих сторон). Для потокового RPC сервер может отправить ответ перед отправкой кода ошибки, но это не рекомендуется. Смешение нормального ответа с состоянием ошибки может привести к проблемам с удобством сопровождения в будущем, например, если одна и та же ошибка будет применена к нескольким RPC, должны ли все ответные сообщения ProtoBuf включать эти поля?

Возвращаясь к вашему вопросу, "капчу-токен" следует рассматривать как часть состояния ошибки, поэтому его можно добавить как один из конечных метаданных. В вашем случае вы можете добавить сериализованное прото-сообщение в виде двоичных конечных метаданных, добавив -bin суффикс в вашем конце ключа метаданных.

Также есть официальный поддерживаемый пакет grpcio-status это делает это для вас.

Серверная сторона упаковывает богатый статус ошибки в прототипное сообщение "grpc_status.status_pb2.Status". В приведенном ниже примере просто используются прототипы с общей ошибкой, но вы можете упаковать "любой" прототип в detailsДо тех пор, пока ваш клиент их понимает.

# 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

Клиентская сторона декодирует ошибки и реагирует на них.

# 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


Узнайте больше о расширенном статусе: https://github.com/grpc/proposal/blob/master/L44-python-rich-status.md

Другие вопросы по тегам