Twirp是基于Protobuf和HTTP/1.1的轻量级RPC框架,不依赖gRPC生态与HTTP/2,适合浏览器、curl直调及老旧代理环境;它调试直观、代码简洁,但无流式支持、需自行集成服务发现与重试等基础设施。

Twirp 是什么,为什么选它而不是 gRPC
Twirp 是一个基于 Protobuf 的轻量级 RPC 框架,不依赖 gRPC-Go 的底层传输逻辑(比如 HTTP/2、ALPN),纯 HTTP/1.1 + JSON/Protobuf 编码。它生成的 Go 代码更简单、调试更直观,适合内部微服务间低延迟、高可控性的通信场景——尤其当你不想被 gRPC 的连接复用、流控、TLS 配置裹挟时。
常见误判是“Twirp 就是 gRPC 的简化版”,其实不是:Twirp 无 streaming 支持、不兼容 gRPC 客户端、服务发现和负载均衡需自行集成。它本质是「Protobuf over HTTP」的契约优先方案。
- 生成代码只有
client.go和server.go,没有grpc.ClientConn或Interceptor等抽象层 - 默认使用
Content-Type: application/protobuf,也可通过X-Twirp-Codecheader 切换到 JSON - 错误统一返回
400 Bad Request+application/json错误体,结构固定:{"code":"invalid_argument","msg":"..."}
用 twirp protoc 插件生成 Go 代码
Twirp 不自带 protoc 插件,必须手动安装 twirp 命令行工具,并确保 protoc 能调用它。路径、版本、插件名稍有偏差就会静默失败——这是最常卡住的一步。
- 安装插件:
go install github.com/twitchtv/twirp/protoc-gen-twirp@latest(注意不是twirp/protoc-gen-twirp) - 执行命令时必须显式指定
--twirp_out=.,且--go_out=.和--twirp_out=.必须同时存在,否则twirp生成器不触发 - proto 文件里 service 必须带
option go_package = "xxx";,否则生成的 client/server 会缺失 package 声明 - 若 proto 中定义了
package myapi;,但go_package是"github.com/org/project/api",则生成的client.go里 import 路径按后者走,别在 handler 里错用myapi.前缀
典型命令:
立即学习“go语言免费学习笔记(深入)”;
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
protoc -I . --go_out=. --twirp_out=. api/service.proto
注册 Twirp handler 到标准 net/http.ServeMux
Twirp server 是一个 http.Handler,但默认不处理 root path(/)或 OPTIONS 请求,直接挂到 http.DefaultServeMux 可能被其他路由覆盖,或因 CORS 失败而前端调不通。
- Twirp handler 注册路径必须以
/twirp/开头(如/twirp/myapi.ServiceName/MethodName),不能改前缀;你只能控制外层路由前缀,比如http.Handle("/api/", http.StripPrefix("/api/", twirpHandler)) - 如果用
gin或echo,需用适配器包装:gin.WrapH(twirpHandler),而非直接POST("/twirp/...")—— 否则 Twirp 的路径匹配逻辑失效 - CORS 需单独加 middleware,Twirp 不内置;推荐在 Twirp handler 外层 wrap 一层,检查
Origin并写入Access-Control-Allow-Origin等 header - 不要把 Twirp handler 和 REST handler 混在同一个
http.ServeMux下且无路径隔离,否则/health这类路径可能被 Twirp 的通配规则意外捕获
客户端调用时如何控制超时与重试
Twirp 默认 client 没有超时,也没有自动重试,生产环境直接用 twirp.NewClient(...) 会阻塞到 TCP connect timeout(通常数分钟),这在微服务链路中不可接受。
- 必须传入自定义
*http.Client,并设置Timeout、Transport的IdleConnTimeout和MaxIdleConnsPerHost - Twirp client 不提供 retry 逻辑,需自己 wrap:例如用
backoff.Retry包,在Do()失败时判断err.(twirp.Error).Code()是否为twirp.Unavailable再重试 - 若服务端返回非 2xx HTTP 状态码(如 503),Twirp client 会转成
twirp.InternalError,但不会暴露原始 status code,调试时得靠日志打resp.StatusCode - Header 透传需用
ctx = context.WithValue(ctx, twirp.HeaderKey("X-Request-ID"), "abc"),不是直接往http.Request.Header写
示例初始化:
client := myapi.NewServiceClient(&http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
IdleConnTimeout: 30 * time.Second,
},
})Twirp 的协议边界很清晰,但它的“轻量”意味着很多基础设施要自己补——比如健康检查 endpoint、metrics 上报 hook、context deadline 透传一致性。一旦服务规模上去,这些点容易变成隐性瓶颈。

















