- RPC框架
- 后端
- 微服务
【免费下载链接】twirp
A simple RPC framework with protobuf service definitions
Twirp 是一个强调简洁与极简的服务间通信框架:它从 Protobuf API 定义文件自动生成路由与序列化代码,让开发者专注业务逻辑,而无需操心 HTTP 方法与路径细节。本文以官方示例文档(docs/example.md)为主体,完整演示如何用 5 个步骤从零构建一个经典的Haberdasher(制帽匠)服务:编写.proto定义、用protoc生成代码、实现服务端接口、挂载 HTTP 服务、调用自动生成的强类型客户端。读完本文,你将掌握 Twirp 服务的完整开发闭环,并能对照仓库内可运行的 example 实例加深理解。
快速总览:5 步构建一个 Twirp 服务
Haberdasher服务只提供一个 RPC 方法MakeHat:给定帽子尺寸(英寸),返回一顶随机颜色的新帽子。整个流程分 5 步:
- 编写 Protobuf 服务定义(
.proto文件) - 用
protoc生成 Go 代码(.pb.go与.twirp.go) - 实现服务端接口(业务逻辑)
- 挂载并运行 HTTP 服务
- 使用自动生成的客户端调用
最终效果:跑起一个带强类型客户端的 Haberdasher 服务。仓库 example 目录下已有一个完整可运行的对应实现(cmd/server与cmd/client两个可执行程序),可以边读边对照。
动手前请先确认环境就绪:参照 docs/install.md 安装
protoc、protoc-gen-go与protoc-gen-twirp。
第 1 步:编写 Protobuf 服务定义
在rpc/haberdasher/service.proto中编写 proto3 定义:
syntax = "proto3"; package twirp.example.haberdasher; option go_package = "github.com/example/rpc/haberdasher"; // Haberdasher service makes hats for clients. service Haberdasher { // MakeHat produces a hat of mysterious, randomly-selected color! rpc MakeHat(Size) returns (Hat); } // Size of a Hat, in inches. message Size { int32 inches = 1; // must be > 0 } // A Hat is a piece of headwear made by a Haberdasher. message Hat { int32 inches = 1; string color = 2; // anything but "invisible" string name = 3; // i.e. "bowler" }几点建议与说明:
- 文件即文档:为
.proto文件补充注释非常值得,这些文件可以作为 API 的首要文档。注释还会原样出现在生成的 Go 类型上——打开 example/service.twirp.go 可以看到,// A Haberdasher makes hats for clients.与// MakeHat produces a hat of mysterious, randomly-selected color!都直接透传到了生成的Haberdasher接口上。 option go_package:指定生成的 Go 代码所属的包导入路径,是生成器解析跨文件 import 的关键依据(详见 docs/command_line.md)。- 字段约束约定:
inches必须大于 0、颜色不能是 "invisible",这些约束在 proto 中只是注释约定,真正强制执行要靠服务端业务代码(见第 3 步的参数校验)。
仓库 example/service.proto 中的定义与文档略有差异(消息字段名为size而非inches、包名为twitch.twirp.example),但结构完全一致,可互为参考。
第 2 步:生成代码
使用protoc编译器,同时指定--go_out(生成 Protobuf 消息代码)与--twirp_out(生成 Twirp 路由与客户端代码):
$ protoc --go_out=. --twirp_out=. \ --go_opt=paths=source_relative \ --twirp_opt=paths=source_relative \ rpc/haberdasher/service.proto生成的文件会落在.proto文件同目录下:
/rpc /haberdasher service.proto service.pb.go # generated by protoc-gen-go service.twirp.go # generated by protoc-gen-twirp关于生成参数的补充说明:
paths=source_relative让生成文件与.proto源文件保持相对路径关系;仓库 example/gen.go 中的go:generate指令正是这样写的:protoc --go_out=paths=source_relative:. --twirp_out=paths=source_relative:. service.proto。--twirp_out与--go_out支持相同的参数体系,包括import_prefix导入前缀和M导入映射(用于多 proto 文件跨包 import 时的路径替换),详细说明见 docs/command_line.md。
打开生成的.twirp.go文件,会看到类似这样的 Go 接口:
// A Haberdasher makes hats for clients. type Haberdasher interface { // MakeHat produces a hat of mysterious, randomly-selected color! MakeHat(context.Context, *Size) (*Hat, error) }此外生成文件还包含实例化客户端与服务端的代码。从 example/service.twirp.go 的源码可以看到生成物的完整结构:
- 客户端实现:
haberdasherProtobufClient与haberdasherJSONClient两个结构体,分别通过doProtobufRequest/doJSONRequest发起请求; - 服务端处理器:
haberdasherServer结构体实现了ServeHTTP,负责请求解析、路由匹配与方法分发(example/service.twirp.go); - 版本断言:文件开头有
const _ = twirp.TwirpPackageMinVersion_8_1_0,用于校验生成的代码与运行时库版本兼容(详见 docs/version_matrix.md)。
第 3 步:实现服务端
现在编写满足Haberdasher接口的业务代码,也就是处理请求的“后端逻辑”。实现可以放在internal/haberdasherserver/server.go:
package haberdasherserver import ( "context" "math/rand" "github.com/twitchtv/twirp" pb "github.com/example/rpc/haberdasher" ) // Server implements the Haberdasher service type Server struct {} func (s *Server) MakeHat(ctx context.Context, size *pb.Size) (hat *pb.Hat, err error) { if size.Inches <= 0 { return nil, twirp.InvalidArgumentError("inches", "I can't make a hat that small!") } return &pb.Hat{ Inches: size.Inches, Color: []string{"white", "black", "brown", "red", "blue"}[rand.Intn(5)], Name: []string{"bowler", "baseball cap", "top hat", "derby"}[rand.Intn(4)], }, nil }这里的实现要点:
- 接口实现即业务层:Twirp 的哲学是服务端只需实现生成接口,序列化、路由、错误包装全部由生成的处理器代劳。仓库 example/cmd/server/main.go 中的
randomHaberdasher是同一模式的真实实现。 - 参数校验与错误返回:当
Inches <= 0时返回twirp.InvalidArgumentError("inches", "I can't make a hat that small!")。查看 errors.go 的源码可知,该构造函数会创建InvalidArgument类型的错误,并把参数名写入错误元数据(meta"argument"),方便客户端定位是哪个参数校验失败。 - 随机选择:
rand.Intn从预置的颜色/款式列表中随机挑选,正好呼应 proto 注释里“神秘随机颜色”的设定。
第 4 步:挂载并运行服务
要基于 HTTP 提供 Haberdasher 服务,使用生成的New{{Service}}Server构造函数。对 Haberdasher 而言,其签名是:
func NewHaberdasherServer(svc Haberdasher, opts ...interface{}) TwirpServer这个构造函数把你的接口实现包装成一个TwirpServer——它本质是一个带额外能力的http.Handler。因此可以像挂载任何 HTTP handler 一样把它挂到标准库服务器上。在cmd/server/main.go中:
package main import ( "net/http" "github.com/example/internal/haberdasherserver" "github.com/example/rpc/haberdasher" ) func main() { server := &haberdasherserver.Server{} // implements Haberdasher interface twirpHandler := haberdasher.NewHaberdasherServer(server) http.ListenAndServe(":8080", twirpHandler) }运行go run ./cmd/server/main.go,服务即监听在localhost:8080。
底层机制与扩展点,从 example/service.twirp.go 的源码可以确认:
- 选项机制:
NewHaberdasherServer接受twirp.ServerOption修饰器(如twirp.WithServerHooks(hooks)),并通过ReadOpt读取jsonSkipDefaults、jsonCamelCase、pathPrefix等选项,其中pathPrefix默认值为/twirp。路由格式为[<prefix>]/<package>.<Service>/<Method>,例如POST /twirp/twitch.twirp.example.Haberdasher/MakeHat(参考 example/service.twirp.go 中导出的HaberdasherPathPrefix常量,更多路由细节见 docs/routing.md)。 - 只用 POST:
ServeHTTP会拒绝非 POST 请求并返回badRouteError(example/service.twirp.go),这是 Twirp 路由协议的一部分(见 docs/mux.md)。 - 可组合的中间件生态:
TwirpServer仍是普通http.Handler,可与其他中间件链式组合,也可通过 ServerHooks(如 example/cmd/server/main.go 里接入的statsd.NewStatsdServerHooks)观测指标——运行仓库示例服务时,控制台会打印类似incr twirp.MakeHat.requests、time twirp.MakeHat.response的统计日志(见 example/cmd/server/README.md)。
第 5 步:使用客户端
客户端存根是自动生成的。每个服务有 2 个客户端构造函数:
New{{Service}}ProtobufClient:使用 Protobuf 编码请求。New{{Service}}JSONClient:使用 JSON 编码请求。
推荐使用ProtobufClient(Protobuf 与 JSON 的取舍对比见 docs/protobuf_and_json.md)。其他语言也可通过各自语言的protoc插件生成客户端(例如--twirp_ruby_out),Twirp 社区提供了 Ruby、Python、Rust、TypeScript 等多语言实现。
要在另一个 Go 项目中调用Haberdasher服务,导入自动生成的客户端即可。例如在cmd/client/main.go中:
package main import ( "context" "net/http" "os" "fmt" "github.com/example/rpc/haberdasher" ) func main() { client := haberdasher.NewHaberdasherProtobufClient("http://localhost:8080", &http.Client{}) hat, err := client.MakeHat(context.Background(), &haberdasher.Size{Inches: 12}) if err != nil { fmt.Printf("oh no: %v", err) os.Exit(1) } fmt.Printf("I have a nice new hat: %+v", hat) }在另一个终端保持服务运行,然后执行go run ./cmd/client/main.go,就能拿到一顶新帽子。
客户端内部的实现细节,可以从生成的源码中进一步印证:
- URL 拼接:客户端构造时按
<baseURL>[<prefix>]/<package>.<Service>/<Method>拼接每个方法的 URL(example/service.twirp.go),默认前缀同样是/twirp,并可通过ClientOption调整pathPrefix、literalURLs等选项。 - 上下文标注:每个 RPC 调用都会通过
ctxsetters.WithPackageName/WithServiceName/WithMethodName把服务与方法信息注入context(example/service.twirp.go),供拦截器与 hooks 使用(见 ctxsetters/ctxsetters.go)。 - 拦截器支持:客户端支持
twirp.ChainInterceptors链式拦截器,在每次调用前后插入横切逻辑(example/service.twirp.go)。 - 错误处理:非 2xx 响应会被转换为
twirp.Error返回;仓库 example/cmd/client/main.go 演示了利用twerr.Meta("retryable")判断错误是否可重试、从而自动重试的用法。
小结与延伸阅读
至此,你已经走完了 Twirp 的完整开发闭环:写 proto → 生成代码 → 实现接口 → 挂载 HTTP → 调用客户端。核心体验是:路由、序列化、错误协议等“样板胶水”全部由代码生成器产出,你只需要专注MakeHat这样的业务方法本身。
如果希望进一步深入,仓库中的相关文档与源码值得继续探索:
- 生成器的全部命令行参数与导入映射:docs/command_line.md
- Twirp 错误模型与各类错误构造函数(如
InvalidArgumentError、InternalErrorWith):docs/errors.md 与 errors.go - 拦截器与 hooks 机制:docs/hooks.md、interceptors.go
- 运行库与服务端选项(
ServerOption、ClientOption):server_options.go、client_options.go - 完整可运行的参考实现:example/service.proto、example/cmd/server/main.go、example/cmd/client/main.go
- RPC框架
- 后端
- 微服务
【免费下载链接】twirp
A simple RPC framework with protobuf service definitions
相关推荐
Twirp项目实战:构建Haberdasher微服务教程
Twirp项目实战:构建Haberdasher微服务教程 前言 在微服务架构中,RPC(远程过程调用)框架扮演着重要角色。Twirp是一个轻量级的RPC框架,由
RPC框架后端微服务CyberStrikeAI 自定义 SQL 注入测试技能实战指南
CyberStrikeAI 自定义 SQL 注入测试技能实战指南 CyberStrikeAI 是一套 AI 原生的安全运营平台(意图进、受控执行出)。这篇文章带
网络安全渗透测试人工智能大模型AI AgentRAG后端前端MCP 服务漏洞扫描Dapr Proto 契约与代码生成实战指南:从 Protobuf 定义到 gRPC 客户端
Dapr Proto 契约与代码生成实战指南:从 Protobuf 定义到 gRPC 客户端 本文聚焦 Dapr 核心仓库(GitHub_Trending/da
后端微服务云原生消息队列AI Agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考