如何基于给定Proto文件反序列化gRPC-Web请求字节数组
gRPC-Web字节数组反序列化指南
我有一段字节数组 [0x00, 0x00, 0x00, 0x00, 0x03, 0xf2, 0x3e, 0x00],通过gRPC-Web协议的Socket发送,请求头信息如下:
Method: POST Content-Type: application/grpc-web+proto Content-Length: 8 x-grpc-web: 1 Connection: Keep-Alive Accept-Encoding: gzip User-Agent: okhttp/3.12.12
我手头的Proto文件片段如下:
syntax = "proto3"; package Device; option go_package = "api/device"; import "api/common/status/status.proto"; import "api/device/command.proto"; import "api/device/common.proto"; import "api/device/dish.proto"; import "api/device/transceiver.proto"; import "api/device/wifi.proto"; message ToDevice { oneof message { Device.Request request = 1 [json_name="request"]; } } message FromDevice { oneof message { Device.Response response = 1 [json_name="response"]; Device.Event event = 2 [json_name="event"]; } } message Request { optional uint64 id = 1 [json_name="id"]; optional uint64 epoch_id = 14 [json_name="epochId"]; optional string target_id = 13 [json_name="targetId"]; oneof request { Device.SignedData signed_request = 15 [json_name="signedRequest"]; Device.GetNextIdRequest get_next_id = 1006 [json_name="getNextId"]; Device.AuthenticateRequest authenticate = 1005 [json_name="authenticate"]; Device.EnableFlowRequest enable_flow = 1018 [json_name="enableFlow"]; Device.FactoryResetRequest factory_reset = 1011 [json_name="factoryReset"]; Device.GetDeviceInfoRequest get_device_info = 1008 [json_name="getDeviceInfo"]; Device.GetHistoryRequest get_history = 1007 [json_name="getHistory"]; Device.GetLogRequest get_log = 1012 [json_name="getLog"]; Device.GetNetworkInterfacesRequest get_network_interfaces = 1015 [json_name="getNetworkInterfaces"]; Device.GetPingRequest get_ping = 1009 [json_name="getPing"]; Device.PingHostRequest ping_host = 1016 [json_name="pingHost"]; Device.GetStatusRequest get_status = 1004 [json_name="getStatus"]; Device.RebootRequest reboot = 1001 [json_name="reboot"]; Device.SetSkuRequest set_sku = 1013 [json_name="setSku"]; Device.SetTrustedKeysRequest set_trusted_keys = 1010 [json_name="setTrustedKeys"]; Device.SpeedTestRequest speed_test = 1003 [json_name="speedTest"]; Device.UpdateRequest update = 1014 [json_name="update"]; Device.GetLocationRequest get_location = 1017 [json_name="getLocation"]; Device.GetHeapDumpRequest get_heap_dump = 1019 [json_name="getHeapDump"]; Device.RestartControlRequest restart_control = 1020 [json_name="restartControl"]; Device.FuseRequest fuse = 1021 [json_name="fuse"]; Device.DishStowRequest dish_stow = 2002 [json_name="dishStow"]; Device.DishGetContextRequest dish_get_context = 2003 [json_name="dishGetContext"]; Device.DishGetObstructionMapRequest dish_get_obstruction_map = 2008 [json_name="dishGetObstructionMap"]; Device.DishEmcRequest dish_emc = 2007 [json_name="dishEmc"]; Device.TransceiverIFLoopbackTestRequest transceiver_if_loopback_test = 4001 [json_name="transceiverIfLoopbackTest"]; Device.TransceiverGetStatusRequest transceiver_get_status = 4003 [json_name="transceiverGetStatus"]; Device.TransceiverGetTelemetryRequest transceiver_get_telemetry = 4004 [json_name="transceiverGetTelemetry"]; Device.WifiGetClientsRequest wifi_get_clients = 3002 [json_name="wifiGetClients"]; Device.WifiGetDiagnosticsRequest wifi_get_diagnostics = 3008 [json_name="wifiGetDiagnostics"]; Device.WifiGetPingMetricsRequest wifi_get_ping_metrics = 3007 [json_name="wifiGetPingMetrics"]; Device.WifiSetConfigRequest wifi_set_config = 3001 [json_name="wifiSetConfig"]; Device.WifiGetConfigRequest wifi_get_config = 3009 [json_name="wifiGetConfig"]; Device.WifiSetupRequest wifi_setup = 3003 [json_name="wifiSetup"]; } }
现在需要基于这段Proto文件反序列化上述字节数组,获取Socket发送的数据,具体步骤如下:
1. 拆解gRPC-Web消息结构
gRPC-Web的二进制消息固定带有5字节前缀:
- 第1字节:消息类型标识(0代表REQUEST)
- 第2-5字节:大端序32位整数,代表后续Proto消息的长度
你的8字节数组拆分后:
- 前缀:
0x00, 0x00, 0x00, 0x00, 0x03→ 消息类型为请求,Proto消息长度为3字节 - 实际Proto负载:
0xf2, 0x3e, 0x00
2. 执行反序列化操作
步骤一:生成对应语言的Proto代码
使用protoc编译器结合对应语言插件生成可调用的代码,以Go语言为例:
protoc --go_out=. --go_opt=paths=source_relative api/device/*.proto api/common/status/*.proto
其他语言(Java/Python/JS)只需调整protoc命令的对应插件参数即可。
步骤二:编写反序列化代码
以Go语言为例,核心逻辑如下:
package main import ( "bytes" "fmt" "your-project-path/api/device" "google.golang.org/protobuf/proto" ) func main() { rawBytes := []byte{0x00, 0x00, 0x00, 0x00, 0x03, 0xf2, 0x3e, 0x00} // 跳过gRPC-Web前缀,提取Proto负载 payload := rawBytes[5:] // 尝试反序列化为Device.Request req := &device.Request{} err := proto.Unmarshal(payload, req) if err != nil { fmt.Printf("反序列化Request失败: %v\n", err) // 尝试反序列化为ToDevice toDev := &device.ToDevice{} err = proto.Unmarshal(payload, toDev) if err != nil { fmt.Printf("反序列化ToDevice失败: %v\n", err) return } fmt.Printf("ToDevice反序列化结果: %v\n", toDev) return } // 判断具体请求类型(oneof字段) switch req.Request.(type) { case *device.Request_DishStow: fmt.Println("请求类型:DishStow") fmt.Printf("请求内容:%v\n", req.GetDishStow()) case *device.Request_GetStatus: fmt.Println("请求类型:GetStatus") fmt.Printf("请求内容:%v\n", req.GetGetStatus()) // 按需添加其他oneof类型的判断 default: fmt.Printf("未知请求类型,原始消息: %v\n", req) } }
步骤三:手动辅助分析(可选)
从负载0xf2, 0x3e, 0x00的Proto编码来看,这是一个标识请求类型的空消息(比如DishStowRequest可能没有额外字段,仅通过类型标识传递指令),用代码反序列化是最准确的方式,手动解析容易因varint编码规则出错。
3. 注意事项
- 确保所有依赖的Proto文件(如
api/common/status/status.proto等)都存在,否则protoc编译会报错。 - 必须跳过gRPC-Web的5字节前缀,才能得到正确的Proto负载。
- 若反序列化失败,优先检查Proto文件的完整性和生成代码的正确性。
内容的提问来源于stack exchange,提问作者kdvb
相关产品推荐
相关产品推荐

