grpc-gateway 启动后 404 或 503 的主因是 http 路由未注册或 grpc 后端未就绪;需在 .proto 中用 google.api.http 显式声明 rest 映射,启用 grpc-gateway 插件生成并 import pb.gw.go 文件,先启 grpc server 再初始化 gateway mux,确保路径参数名与 message 字段名严格一致,docker 部署时注意网络连通性与地址配置。

gRPC-Gateway 启动后 404 或 503 Service Unavailable
常见原因是 gRPC-Gateway 的 HTTP 路由未正确注册到 mux,或 gRPC 后端服务未就绪。它不自动代理所有 gRPC 方法,必须显式用 google.api.http 注解声明 REST 映射。
- 确保在
.proto文件中每个需暴露的 RPC 方法上添加option (google.api.http) = { ... };,比如get: "/v1/books/{id}" - 生成代码时必须启用
grpc-gateway插件(如protoc --grpc-gateway_out=... *.proto),且生成的xxx.pb.gw.go文件被实际 import - 启动时先运行 gRPC server(监听
localhost:9090这类地址),再初始化 Gateway mux 并调用runtime.NewServeMux(),最后用gwMux.HandlePath(...)注册生成的 handler - 检查
grpc.Dial地址是否可连通——Gateway 默认以 localhost 直连 gRPC server,若部署分离(如 Docker),需把grpc_server_addr改成容器名或宿主机 IP
如何让 GET /users/{id} 正确绑定到 GetUser 方法的 id 参数
路径参数不是自动提取的,必须和 proto message 字段名严格一致,且字段需出现在请求 body 外部(即不能藏在嵌套 message 里)。
- 定义 message 时,把路径变量单独提为 top-level 字段:
message GetUserRequest { String id = 1; },而非message Request { User user = 1; } - 在
http注解中写明占位符:get: "/v1/users/{id}",注意花括号内名字必须和字段名id完全相同(大小写敏感) - 如果要支持多段路径(如
/v1/{parent=projects/*}/books),需启用allow_repeated_fields并使用google.api.field_behavior = REQUIRED标注,否则生成器会跳过 - 避免字段类型是
int32却传字符串 ID:Gateway 默认不做类型转换,id: "123"匹配int32 id会静默失败,返回 404;应统一用string id
jsonpb 和 protojson 序列化行为差异导致字段丢失
旧版 jsonpb(已弃用)默认忽略零值字段(如 0、false、空字符串),新版 protojson 行为更可控但需手动配置。
- 用
protojson.MarshalOptions{EmitUnpopulated: true}才能输出零值字段;否则int32 status = 1;值为0时前端收不到该 key - 时间字段(
google.protobuf.timestamp)默认序列化为 RFC3339 字符串,但若 proto 中用了int64 seconds手动实现,则 Gateway 不识别,需改用标准类型 - enum 值默认输出数字(如
"status": 2),加UseEnumNumbers: false才输出名称("status": "ACTIVE"),但需前后端约定一致 - 不要混用
jsonpb和protojson的 unmarshaler:它们对未知字段、缺失字段的容忍策略不同,容易在 gateway → gRPC 转发时丢数据
Docker 部署时 connection refused 连不上 gRPC server
根本原因常是网络隔离:Gateway 容器默认无法解析 host.docker.internal 或访问 localhost:9090(那是它自己的 9090,不是宿主机的)。
- 启动 Gateway 容器时加
--network=host(linux)或--add-host=host.docker.internal:host-gateway(Mac/Win),让其能访问宿主机服务 - 更推荐方式:把 gRPC server 和 Gateway 放进同一 docker-compose.yml,用 service 名通信,比如
grpc_server:9090,并确保 gRPC server 监听0.0.0.0:9090而非127.0.0.1:9090 - 检查 gRPC server 是否启用了反射(
Reflection.register(server))——虽然 Gateway 不依赖它,但调试时grpcurl -plaintext localhost:9090 list能快速验证连通性 - 别在 Gateway 里硬编码
localhost:9090:用环境变量注入地址,比如GRPC_ADDR=grpc_server:9090,然后代码中读取os.Getenv("GRPC_ADDR")
最常卡住的地方其实是 proto 注解和生成步骤的耦合——少跑一次 protoc、漏 import 一个 pb.gw.go、或者注解里多打了个空格,都会让路由静默失效。建议把生成命令固化成 Makefile 一步执行,比反复手敲可靠得多。