ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Twirp Wire Protocol v5 规范详解:基于 HTTP 与 Protobuf 的轻量 RPC 协议

Twirp Wire Protocol v5 规范详解:基于 HTTP 与 Protobuf 的轻量 RPC 协议 RPC框架后端微服务【免费下载链接】twirpA simple RPC framework with protobuf service definitions项目地址https://gitcode.com/gh_mirrors/tw/twirp点击查看免费下载本文以 Twirp 仓库中的协议规范文档 docs/spec_v5.md 为主体完整解析 Twirp wire protocol v5 的 URL 路由、请求/响应格式、双编码Protobuf / JSON机制与错误模型。读完本文你将掌握 Twirp 服务的端点 URL 如何组成、客户端如何发出 RPC 请求、服务端如何应答以及 16 种标准错误码与 HTTP 状态码的映射关系并能在实际项目中用 cURL 直接调试 Twirp 服务。什么是 Twirp Wire ProtocolTwirp wire protocol 是一套基于 HTTP 与 Protocol Buffersproto的简单 RPC 协议。它用 HTTP URL 直接指定 RPC 端点用 HTTP 请求/响应体承载 proto 消息因此任何 HTTP 客户端、任何 HTTP 版本都能与之通信v5 规范中明确支持 binary 与 JSON 两种消息编码。使用 Twirp 的流程是开发者先用.proto文件定义 API再用 Twirp 工具生成客户端与服务端库生成代码基于编程语言运行时或操作系统提供的标准 HTTP 库实现该 wire 协议。客户端与服务端各自实现完毕后通过发起 RPC 调用即可通信。仓库中的 example/service.proto 就是一个典型的 proto 定义package twitch.twirp.example下的Haberdasher服务与MakeHat方法。URL 结构RPC 端点的一一映射Twirp 用 HTTP URL 指定服务端上的 RPC 端点这种直接映射让请求路由简单高效。以 ABNF 语法 表述v5 的 URL 格式为URL :: Base-URL /twirp/ [ Package . ] Service / Method各组成部分含义如下Base-URLTwirp API 服务器的虚拟位置通常通过 API 文档或服务发现发布。当前只应包含 URLscheme与authority例如https://example.com。PackageAPI 的 protopackage名常被视作 API 版本例如example.calendar.v1。若 API 定义没有 package 名此部分省略。ServiceAPI 的 protoservice名例如CalendarService。MethodAPI 方法的 protorpc名例如CreateEvent。v5 中前缀固定为/twirp/如果 proto 中没有 packageURL 就变成Base-URL /twirp/ Service / Method。请求与响应POST Content-Type 驱动的双编码请求RequestsTwirp始终使用 HTTP POST 方法发送请求因为它最贴近 RPC 方法的语义。请求头就是普通 HTTP 头协议用到的关键头只有一个Content-Type标识 proto 消息的编码取值必须是application/protobuf或application/json之一。服务端依据该值决定如何解析请求体以及如何编码响应体。Request-Body即 HTTP 请求体中承载的编码后的请求消息其编码方式由Content-Type头指定。响应Responses响应头同样只是普通 HTTP 响应头协议使用的关键头Content-Type取值应为application/protobuf或application/json标识响应消息的编码且必须与请求中的 Content-Type 一致。响应体规范原文写为 Request-Body即 response body是编码后的响应消息编码由Content-Type头指定。完整示例Echo API 的四种 wire 报文规范以一个简单的 Echo API 为例假设服务器 base URL 为https://example.com其 proto 定义如下syntax proto3; package example.echoer; service Echo { rpc Hello(HelloRequest) returns (HelloResponse); } message HelloRequest { string message; } message HelloResponse { string message; }Protobuf 请求Content-Type: application/protobuf请求体为编码后的HelloRequestPOST /twirp/example.echoer.Echo/Hello HTTP/1.1 Host: example.com Content-Type: application/protobuf Content-Length: 15 encoded HelloRequestJSON 请求Content-Type: application/json请求体为可读的 JSONPOST /twirp/example.echoer.Echo/Hello HTTP/1.1 Host: example.com Content-Type: application/json Content-Length: 27 {message:Hello, World!}Protobuf 响应HTTP/1.1 200 OK Content-Type: application/protobuf Content-Length: 15 encoded HelloResponseJSON 响应HTTP/1.1 200 OK Content-Type: application/json Content-Length: 27 {message:Hello, World!}注意 URL 的完整形态/twirp/ packageexample.echoer. ServiceEcho/ MethodHello。仓库 docs/routing.md 中给出了真实项目的等价示例POST /twirp/twirp.example.haberdasher.Haberdasher/MakeHat。错误处理无论何种编码错误永远是 JSONTwirp 的错误响应始终以 JSON 编码返回与请求的 Content-Type 无关并带有Content-Type: application/json响应头。这样保证错误在任何场景下都对人可读。Twirp 错误是一个 JSON 对象包含以下键codeTwirp 错误码字符串见下文错误码表。msg描述错误的人类可读消息字符串。meta可选值为字符串的对象存放任意的附加错误元数据。基础错误示例{ code: internal, msg: Something went wrong }带元数据的错误示例{ code: permission_denied, msg: Thou shall not pass, meta: { target: Balrog, power: 999 } }错误码与 HTTP 状态码的完整映射Twirp 错误必须携带一个错误码它以字符串表示且必须是下表列出的固定集合之一。每个错误码都对应一个 HTTP 状态码服务端以某个错误码响应时必须把响应的 HTTP 状态码设为对应值。Twirp Error CodeHTTP StatusDescriptioncanceled408The operation was cancelled.unknown500An unknown error occurred. For example, this can be used when handling errors raised by APIs that do not return any error information.invalid_argument400The client specified an invalid argument. This indicates arguments that are invalid regardless of the state of the system (i.e. a malformed file name, required argument, number out of range, etc.).malformed400The client sent a message which could not be decoded. This may mean that the message was encoded improperly or that the client and server have incompatible message definitions.deadline_exceeded408Operation expired before completion. For operations that change the state of the system, this error may be returned even if the operation has completed successfully (timeout).not_found404Some requested entity was not found.bad_route404The requested URL path wasnt routable to a Twirp service and method. This is returned by generated server code and should not be returned by application code (use not_found or unimplemented instead).already_exists409An attempt to create an entity failed because one already exists.permission_denied403The caller does not have permission to execute the specified operation. It must not be used if the caller cannot be identified (use unauthenticated instead).unauthenticated401The request does not have valid authentication credentials for the operation.resource_exhausted403Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system is out of space.failed_precondition412The operation was rejected because the system is not in a state required for the operations execution. For example, doing an rmdir operation on a directory that is non-empty, or on a non-directory object, or when having conflicting read-modify-write on the same resource.aborted409The operation was aborted, typically due to a concurrency issue like sequencer check failures, transaction aborts, etc.out_of_range400The operation was attempted past the valid range. For example, seeking or reading past end of a paginated collection. Unlike invalid_argument, this error indicates a problem that may be fixed if the system state changes (i.e. adding more items to the collection). There is a fair bit of overlap between failed_precondition and out_of_range. We recommend using out_of_range (the more specific error) when it applies so that callers who are iterating through a space can easily look for an out_of_range error to detect when they are done.unimplemented501The operation is not implemented or not supported/enabled in this service.internal500When some invariants expected by the underlying system have been broken. In other words, something bad happened in the library or backend service. Twirp specific issues like wire and serialization problems are also reported as internal errors.unavailable503The service is currently unavailable. This is most likely a transient condition and may be corrected by retrying with a backoff.dataloss500The operation resulted in unrecoverable data loss or corruption.这些错误码与 gRPC 状态码语义基本对齐从 errors.go 的常量注释可确认 Most error types are equivalent to gRPC status codes and follow the same semantics。多数常见码如invalid_argument、not_found、permission_denied可直接复用malformed与bad_route是 Twirp 特有的前者表示客户端消息无法解码编码错误或客户端/服务端消息定义不兼容后者只能由生成的服务端代码返回应用代码应改用not_found或unimplemented。从源码看错误模型的落地实现Go 端的 Error 接口与构造函数在 Go 实现中任意实现twirp.Error接口的值都被视为 Twirp 错误接口提供Code()、Msg()、Meta(key)、MetaMap()、WithMeta(key, val)等方法见 errors.go。服务端返回错误时最简单的写法是使用ErrorCode.Error(msg)便捷构造器// (twirp.Code).Error(msg) 直接由错误码构造错误 twirp.Internal.Error(oops) twirp.NotFound.Error(user not found) twirp.InvalidArgument.Error(user_id must be alphanumeric) // (twirp.Code).Errorf(msg, ...args) 支持格式化与 %w 包装原始错误 twirp.Internal.Errorf(Failed to perform operation: %w, err) // 通用构造器 NewError twirp.NewError(twirp.InvalidArgument, user_id must be alphanumeric)错误码到 HTTP 状态码的映射集中在ServerHTTPStatusFromErrorCodeerrors.go例如Canceled→ 408、NotFound→ 404、PermissionDenied→ 403、Unavailable→ 503与规范表格一一对应。错误响应恒为 JSONWriteError 与序列化服务端把错误写成响应时强制使用 JSON。WriteErrorerrors.go设置Content-Type: application/json按错误码计算 HTTP 状态码并写出{code, msg, meta}结构的 JSON 体若传入的不是twirp.Error会自动用InternalErrorWith(err)包装。序列化结构体twerrJSONerrors.go的字段 tag 正是json:code、json:msg、json:meta,omitempty与规范中的 JSON 键名完全吻合。这就是请求用 Protobuf、错误响应却是 JSON这一设计在代码层面的落地。典型错误处理模式仓库 docs/errors.md 给出了服务端与客户端的完整用法。服务端在一个方法内返回各类错误func (s *Server) FindUser(ctx context.Context, req *pb.FindUserRequest) (*pb.FindUserResp, error) { // 校验错误 if req.UserId { return nil, twirp.InvalidArgument.Error(user_id is required) } if !isAuthorized(ctx, req.UserId) { return nil, twirp.PermissionDenied.Error(not allowed to access user profiles) } // 执行业务操作 user, err : s.DB.FindByID(ctx, req.UserID) if errors.Is(err, DB_NOT_FOUND) { return nil, twirp.NotFound.Error(user not found) } if err ! nil { return nil, twirp.Internal.Errorf(DB error: %w, err) } return pb.FindUserResp{Login: user.Login}, nil }如果端点返回的是普通非 Twirperror生成的服务端会使用twirp.InternalErrorWith(err)自动包装为internal错误errors.go中InternalErrorWith还会附带cause元数据记录原始错误类型errors.go。客户端侧收到的错误同样可断言为twirp.Errorresp, err : client.FindUser(ctx, req) if err ! nil { if twerr, ok : err.(twirp.Error); ok { if twerr.Code() twirp.NotFound { fmt.Println(not found) } } fmt.Printf(internal: %s, err) }也支持 Go 1.13 的errors.As/errors.Is解包例如对Internal错误进一步errors.Unwrap出传输层错误连接问题等。传输层错误如连接失败会被返回为internal错误若收到来自代理、负载均衡器等的非 200 响应无法反序列化为 Twirp 错误生成客户端会依据 HTTP 状态码猜测等效 Twirp 错误例如 404 →bad_route、503 →unavailable并附加http_error_from_intermediary、status_code、body、location等元数据便于识别。meta 元数据的用法WithMeta(key, val)是可链式调用的元数据添加方法返回错误副本原错误不变见 errors.go常用于在同一错误码下细分类型或传递附加信息if unavailable { return nil, twirp.Unavailable.Error(taking a nap ...). WithMeta(retryable, true). WithMeta(retry_after, 15s) }对应线上传输的 JSON{ code: unavailable, msg: taking a nap ..., meta: { retryable: true, retry_after: 15s } }客户端通过twerr.Meta(retry_after)读取。注意 meta 值只能是字符串这是为了简化多平台客户端的错误解析若需要复杂错误结构规范建议在成功响应的 protobuf 消息里携带业务错误或在自动生成客户端之上添加包装层。用 cURL 直接调用 v5 协议的 Twirp 服务由于 v5 协议只是HTTP POST 特定 URL Content-Type的组合调试时完全可以用 cURL 手工发起调用仓库 docs/curl.md 提供了完整示例。假设服务运行在http://localhost:8080使用默认/twirp前缀JSON 方式调试最方便curl --request POST \ --header Content-Type: application/json \ --data {subject: World} \ http://localhost:8080/twirp/example.helloworld.HelloWorld/HelloProtobuf 方式用protoc编解码更贴近生产echo subject:World \ | protoc --encode example.helloworld.HelloReq ./rpc/helloworld/service.proto \ | curl -s --request POST \ --header Content-Type: application/protobuf \ --data-binary - \ http://localhost:8080/twirp/example.helloworld.HelloWorld/Hello \ | protoc --decode example.helloworld.HelloResp ./rpc/haberdasher/service.proto调用不存在的路由时服务端返回的正是规范定义的 JSON 错误docs/routing.md 中的真实示例POST /twirp/twirp.example.haberdasher.Haberdasher/INVALIDROUTE 404 Not Found { code: bad_route, msg: no handler for path /twirp/twirp.example.haberdasher.Haberdasher/INVALIDROUTE, meta: {twirp_invalid_route: POST /twirp/twirp.example.haberdasher.Haberdasher/INVALIDROUTE} }v5 与后续版本的关系何时迁移v5 是 Twirp 首次公开发布的协议版本而仓库的 docs/spec_v7.md 定义了当前协议版本 v7。两者主要差异有两处URL 前缀v5 的 URL 前缀固定为/twirpv7 允许任意前缀或无前缀server_options.go 中的WithServerPathPrefix正是为此提供支持PathPrefix()默认值仍是/twirp。resource_exhausted的 HTTP 状态码v5 中为 403v7 中改为 429限流语义更准确。当前仓库 errors.go 的ServerHTTPStatusFromErrorCode返回的正是 429。另外仓库中还有一份从未发布的 v6 草案docs/spec_v6.mdv6 曾计划加入流式 API 与路由更新但因流式 API 需要对连接状态管理做出过多假设、且大多数用户并不需要最终未发布。因此v5 用户应直接升级到 v7而非等待 v6。关于版本兼容性仓库根目录的 version_constant.go 定义了TwirpPackageIsVersion7与TwirpPackageMinVersion_8_1_0编译期常量供生成代码断言与运行时的版本兼容。协议版本与各语言实现的对应关系可查阅 docs/version_matrix.md。如果你正在开发 Twirp 的新实现请以最新规范为准若只是使用现有 v5 服务本文的 URL、请求/响应、错误模型全部适用仅需留意上述两点差异即可。赞分享RPC框架后端微服务【免费下载链接】twirpA simple RPC framework with protobuf service definitions项目地址https://gitcode.com/gh_mirrors/tw/twirp点击查看免费下载相关推荐Twirp Wire Protocol v7 完全解读基于 HTTP 与 Protobuf 的轻量 RPC 协议规范Twirp Wire Protocol v7 完全解读基于 HTTP 与 Protobuf 的轻量 RPC 协议规范 Twirp 是一套基于 HTTP 与 PRPC框架后端微服务Twirp 线上协议Wire Protocolv7 详解基于 HTTP 与 Protobuf 的 RPC 通信规范Twirp 线上协议Wire Protocolv7 详解基于 HTTP 与 Protobuf 的 RPC 通信规范 本篇文章以 Twirp 仓库根目录下的RPC框架后端微服务DeBERTa-v3-large多GPU分布式训练指南高效利用计算资源的完整方案DeBERTa v3 large多GPU分布式训练指南高效利用计算资源的完整方案 DeBERTa v3 large作为一款先进的预训练语言模型在自然语言处理上一篇5分钟精通Scapy从零开始的Python网络包处理终极指南下一篇三个月复刻一块对讲机主板UV-K5 硬件逆向工程全流程揭秘创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表