首页 > 解决方案 > 从 Python 客户端发送的 GRPC 请求没有被 Golang 服务器正确接收

问题描述

我编写了一个 Python GRPC 客户端,可以连接到许多 GRPC Golang 服务。我已经能够像这样完成这项工作:

from alphausblue.connection.conn import grpc_client_connection
from alphausblue.iam.v1.iam_pb2 import WhoAmIRequest
from alphausblue.iam.v1.iam_pb2_grpc import IamStub

async def main():
    conn = grpc_client_connection(svc = "blue")
    stub = IamStub(conn)
    resp = await stub.WhoAmI(WhoAmIRequest())
    print(resp)

服务名称在哪里blue。但是,如果我尝试连接到不同的服务来请求这样的数据:

from alphausblue.connection.conn import grpc_client_connection
from alphausblue.cost.v1.cost_pb2 import ListAccountsRequest
from alphausblue.cost.v1.cost_pb2_grpc import CostStub

async def main():
    conn = grpc_client_connection(svc = "cost")
    stub = CostStub(conn)
    account = await stub.GetAccount(GetAccountRequest(vendor = 'aws', id = '731058950257'))
    print(account)

我得到一个未实现的响应。如果该服务不存在但它确实存在并且我的 Golang 客户端可以正常连接到它,这将是有意义的。此外,当我检查服务器的日志时,我可以清楚地看到请求到达了服务器。做了更多的研究,我在我的服务器上发现了这段代码:

type service struct {
    UserInfo *blueinterceptors.UserData

    cost.UnimplementedCostServer
}

func (s *service) GetAccount(ctx context.Context, in *cost.GetAccountRequest) (*api.Account, error) {
    switch in.Vendor {
    case "aws":
        // Do stuff
    default:
        return status.Errorf(codes.Unimplemented, "not implemented")
    }
}

这告诉我的是,正在调用该函数,但正在反序列化的有效负载缺少该vendor字段。但是,在调试时我可以看到这一行:

src/core/lib/security/transport/secure_endpoint.cc:296] WRITE 0000018E2C62FB80: 00 00 00 13 0a 03 61 77 73 12 0c 37 33 31 30 35 38 39 35 30 32 35 37 '......aws ..731058950257'

因此,数据通过 GRPC 发送到服务器,但被反序列化为缺少字段的对象。那么,这是什么原因呢?

更新

我查看了GetAccountRequest@blackgreen 建议的 Python 和 Golang 客户端的定义。

Golang 客户端代码:

// Request message for the Cost.GetAccount rpc.
type GetAccountRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Vendor string `protobuf:"bytes,1,opt,name=vendor,proto3" json:"vendor,omitempty"`
    Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"`
}

Python客户端代码:

_GETACCOUNTREQUEST = _descriptor.Descriptor(
  name='GetAccountRequest',
  full_name='blueapi.cost.v1.GetAccountRequest',
  filename=None,
  file=DESCRIPTOR,
  containing_type=None,
  create_key=_descriptor._internal_create_key,
  fields=[
    _descriptor.FieldDescriptor(
      name='vendor', full_name='blueapi.cost.v1.GetAccountRequest.vendor', index=0,
      number=1, type=9, cpp_type=9, label=1,
      has_default_value=False, default_value=b"".decode('utf-8'),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
    _descriptor.FieldDescriptor(
      name='id', full_name='blueapi.cost.v1.GetAccountRequest.id', index=1,
      number=2, type=9, cpp_type=9, label=1,
      has_default_value=False, default_value=b"".decode('utf-8'),
      message_type=None, enum_type=None, containing_type=None,
      is_extension=False, extension_scope=None,
      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
  ],
  extensions=[
  ],
  nested_types=[],
  enum_types=[
  ],
  serialized_options=None,
  is_extendable=False,
  syntax='proto3',
  extension_ranges=[],
  oneofs=[
  ],
  serialized_start=946,
  serialized_end=993,
)

很明显,这里的字段顺序相同,所以我认为这不是问题,除非 GRPC 使用index而不是number.

标签: pythongogrpc

解决方案


在确保您在 Pythonversion字段中设置的字符串不包含不可见字符后,Python 客户端导入的生成代码与 Go 服务器导入的代码可能存在版本不匹配。

特别是,如果两个版本都确实有一个字段,但 Go 代码不知何故无法“识别”它,则可能是由字段 tag numberversion不匹配引起的。

您显示的字节有效负载(hexa 0a 03 61 77 73 12 0c 37 33 31 30 35 38 39 35 30 32 35 37,base64 CgNhd3MSDDczMTA1ODk1MDI1Nw==)确实aws在带有标签号的 proto 字段中发送字符串1(我怎么 知道

所以让我们考虑一个人为的例子,在 Python 客户端中使用的 proto 模式GetAccountRequest可能是

message GetAccountRequest {
    string version = 1; // field tag number 1
}

Go服务器中使用的可能是:

message GetAccountRequest {
    string version = 3; // field tag number 3
}

在这种情况下,您将在线路上看到具有版本字段的消息,但是在针对具有不同标签号的 Go 结构反序列化它时,它将最终为空。客户端用 tag 发送它,1服务器期望它用 tag发送3

GetAccountRequest您可以通过检查 Go结构来验证这个假设。它应该如下所示:

type GetAccountRequest struct {
    // unexported fields

    // other fields, in order
    Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
}

反引号之间的部分`是结构标签,其中3是标签号。

我不知道 Python 中的 gRPC 代码生成是什么样的,但您应该能够以某种方式比较标签号。如果它们确实不同,请确保使用生成的具有相同标记号的结构更新客户端或服务器代码。


推荐阅读