twirp 并非 grpc 的轻量替代,而是基于 http/1.1+protobuf 的独立技术路径;其 handler 需手动挂载到 servemux 且路径须以 / 结尾,否则 404;需自行处理 cors、确保 protoc-gen-twirp 在 path 中、使用正确生成命令,并注意 protobuf 版本与 proto3 语法一致性。

Twirp 不是 gRPC 的“轻量替代”,而是另一条技术路径:它放弃 HTTP/2 和流式能力,换回纯 HTTP/1.1 + Protobuf 的简洁性。如果你的微服务全是 Go 写的、不需要跨语言、调试时总被 gRPC 的二进制 wire format 卡住,那 Twirp 确实更顺手。
为什么 Twirp 生成的 handler 不挂到 http.ServeMux 上就 404
Twirp 默认不注册路由,它只返回一个 http.Handler 实例,需要你手动挂载。常见错误是直接 http.ListenAndServe(":8080", nil),结果所有 Twirp 路径都返回 404。
- 正确做法是显式创建
http.ServeMux,再用twirpHandler := &OrderServiceServer{...}包装后调用twirpHandler.ServeHTTP - 或者更推荐:用
gorilla/mux或标准库http.NewServeMux(),然后mux.Handle("/twirp/", twirpHandler)—— 注意末尾斜杠,Twirp 要求 prefix path 以/结尾 - 别漏掉
OPTIONS预检支持:Twirp 不自动处理 CORS,前端调用 JSON 模式时容易卡在预检失败,得自己加中间件
protoc --twirp_out=. 生成空文件或报错 missing plugin
这不是 Protobuf 版本问题,而是 protoc-gen-twirp 二进制没被 protoc 找到。Go 1.21+ 默认把 go install 的可执行文件放在 $HOME/go/bin,而 protoc 只查 $PATH。
- 运行
which protoc-gen-twirp,如果为空,就补上:export PATH=$HOME/go/bin:$PATH - 确认
.proto文件里有option go_package = "orderservice";,否则生成器不知道包名,会跳过生成 - 别用
--twirp_out=plugins=twirp:.这种老写法,新版本只认--twirp_out=. - 生成后检查是否产出
order_twirp.go(不是order.pb.go)——后者是 Protobuf 生成的,前者才是 Twirp 的 handler 和 client
Twirp 客户端调用时返回 rpc: internal error,但服务端日志无报错
这通常是序列化层失败,而非业务逻辑崩溃。Twirp 默认用 Protobuf 编码,但错误信息被吞掉了,只暴露为泛化的 rpc: internal error。
- 先切到 JSON 模式测试:
curl -X POST http://localhost:8080/twirp/ecommerce.order.OrderService/CreateOrder -H "Content-Type: application/json" -d '{...}',能通说明是 Protobuf 兼容性问题 - 检查客户端和服务端的 Protobuf runtime 版本是否一致:
google.golang.org/protobufv1.33+ 是当前安全线,v1.28 以下有已知的嵌套 message 解析 bug - 确保
CreateOrderRequest中所有字段都是 proto3 语法定义的(比如string user_id = 1;),不要混用 proto2 的required或optional - 注意 Twirp 的错误响应体是 JSON 格式,即使你用 Protobuf 请求:出错时它会返回
{"code":"internal","msg":"..."},别指望看到 Protobuf error message
如何让 Twirp 服务同时支持 gRPC 客户端调用
不能。Twirp 和 gRPC 使用不同 HTTP 路由规则和消息头(twirp vs grpc),协议层不兼容。强行桥接只会增加维护成本。
- 如果已有 gRPC 生态,别为了“轻量”硬切 Twirp;反之,若团队全是 Go、没跨语言需求、讨厌
grpcurl和 TLS 双向认证配置,Twirp 就是更干净的选择 - 真要过渡,用反向代理做协议转换(如 Envoy 的
http_connection_manager+ custom filter),但复杂度远超收益 - Twirp 的真正优势不在“比 gRPC 轻”,而在于它的错误传播机制:每个 RPC 失败都会带明确
code(invalid_argument,not_found),且能被前端直接映射成 HTTP 状态码,这点比原生 net/rpc 或裸 HTTP 更可控
Twirp 最容易被忽略的点是它的错误语义:它把 Protobuf 的 status.proto 映射成 HTTP 状态码,但这个映射表是硬编码在生成代码里的,改不了。如果你依赖自定义错误码做重试策略,得在 client 层额外包装一层解析逻辑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











