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

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

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

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

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

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

1 Ответ

1 голос
/ 13 марта 2019

Отправка ответа со статусом «не в порядке» не допускается для унарного-унарного 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


Подробнее о статусе rich: https://github.com/grpc/proposal/blob/master/L44-python-rich-status.md

...