最常见原因是registerxxxhandlerfromendpoint未调用或runtime.newservemux()未正确挂载;需手动注册、确保ctx有效、endpoint可达,且mux须作为独立http.servemux使用,不可嵌入gin/echo路由树。

为什么 gRPC-Gateway 生成的 REST 接口返回 404?
最常见原因是未正确注册 runtime.NewServeMux() 与 gRPC 服务端的 HTTP 路由绑定。gRPC-Gateway 不是独立 HTTP 服务,它必须作为 http.ServeMux 的一部分,且需在 grpc.Server 启动后、通过 runtime.NewServeMux() 注册所有 gRPC 方法对应的 REST 路径。
典型错误写法是直接用 http.ListenAndServe 启动一个没挂载 gateway mux 的 server;或把 gateway mux 和 gRPC server 当成两个无关进程启动。
- 确保使用
grpc.Dial连接的是本地已启动的 gRPC server(通常是"localhost:9090"),不是 gateway 自己监听的端口 - REST 请求路径必须严格匹配
google.api.httpoption 中定义的get/post路径,比如option (google.api.http) = { get: "/v1/users/{id}" };→ 必须访问/v1/users/123,不能少/v1或拼错users - Protobuf 文件里没加
google/api/annotations.proto和google/api/http.protoimport,会导致生成代码缺失 HTTP 映射逻辑
如何生成兼容 gRPC-Gateway 的 Go 代码?
关键在于 protoc 插件链顺序和参数。只用 protoc-gen-go 不够,必须显式调用 protoc-gen-grpc-gateway,且两者输出目录要一致(否则 pb.gw.go 找不到对应的 pb.go 类型)。
推荐命令模板(假设 proto 在 api/proto 目录):
在 macOS 上通过 LaunchAgent 安装、更新、运行和移除 OpenClaw Gateway Monitor + Gateway Watchdog。适用于用户请求一键部署监控的场景。
protoc -I api/proto \ -I $GOPATH/src \ -I $GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ --go_out=plugins=grpc:api/gen \ --grpc-gateway_out=logtostderr=true:api/gen \ api/proto/service.proto
-
--go_out=plugins=grpc生成 gRPC Server/Client 接口和消息类型 -
--grpc-gateway_out生成RegisterXXXHandlerServer函数和 HTTP 路由注册逻辑 - 必须包含
third_party/googleapis路径,否则google/api/annotations.proto找不到 - 如果用了
grpc-gateway/v2,插件名是protoc-gen-openapiv2,但 handler 生成仍用grpc-gateway_out
如何让 gRPC-Gateway 正确转发 metadata 和错误码?
默认情况下,HTTP status code 会映射为 gRPC status code,但反向(gRPC error → HTTP status)需要手动配置。比如 status.Error(codes.NotFound, "user not found") 默认转成 500,不是 404。
解决方案是在注册 handler 时传入 runtime.WithErrorHandler 和 runtime.WithForwardResponseOption:
gwMux := runtime.NewServeMux( runtime.WithErrorHandler(customHTTPErrorHandler), runtime.WithForwardResponseOption(customResponseModifier), )
-
customHTTPErrorHandler需根据status.Code()返回对应http.Status*,例如codes.NotFound → http.StatusNotFound -
customResponseModifier可用于注入 CORS header、修改 JSON 字段名、或添加 trace-id 到 response header - 客户端传来的
Authorization、X-Request-ID等 header 默认不会透传到 gRPC server,需用runtime.WithIncomingHeaderMatcher显式放行
为什么 POST /json 请求 body 解析失败或字段为空?
根本原因是 gRPC-Gateway 默认期望 JSON key 与 Protobuf 字段名一致(snake_case),但 Go struct tag 里常写 json:"user_id",而 proto 定义是 string user_id = 1; → 生成的 pb.go 中字段名是 UserId,JSON unmarshal 时找不到匹配。
- 最稳妥做法:proto 中用
json_name显式指定,例如string user_id = 1 [json_name = "user_id"]; - 避免在 Go struct 上加额外
json:tag —— gRPC-Gateway 用的是生成的pb.go类型,不是你手写的 struct - 如果用了
google.api.HttpBody处理 raw JSON,需确保 gateway mux 注册时启用了runtime.WithMarshalerOption并配置jsonpbmarshaler - multipart/form-data、protobuf binary body 等非 JSON 类型不被默认支持,需自行扩展
runtime.Unmarshaller










