You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于给定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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 22:47:06