trpc.group/trpc-go/trpc-go@v1.0.3/restful/README.zh_CN.md (about) 1 [English](README.md) | 中文 2 3 # 前言 4 5 tRPC 框架使用 PB 定义服务,但是服务提供基于 HTTP 协议的 REST 风格 API 仍然是一个广泛的需求。RPC 和 REST 的统一是一件不容易的事情,tRPC-Go 框架本身的 HTTP RPC 协议,就是希望可以做到定义同一套 PB 文件,提供的服务既可以通过 RPC 方式调用(即通过桩代码提供的客户端 NewXXXClientProxy 调用),也可以通过原生 HTTP 请求调用,但这样的 HTTP 调用是不满足 RESTful 规范的,譬如说:无法自定义路由,不支持通配符,报错时 response body 为空(错误信息只能塞到 response header 里)等。所以我们额外支持了 RESTful 协议,而且不再尝试强行统一 RPC 和 REST,如果服务指定为 RESTful 协议,则其不支持用桩代码调用,仅支持 http 客户端调用,但是获得的好处是可以在同一套 PB 文件中通过 protobuf annotation 提供满足 RESTful 规范的 API,而且可以使用 tRPC 框架的各种 插件/filter 能力。 6 7 # 原理 8 9 ## 转码器 10 11 和 tRPC-Go 框架其他协议插件不同的是,RESTful 协议插件在 Transport 层就基于 tRPC HttpRule 实现了一个 tRPC 和 HTTP/JSON 的转码器,这样就不再需要走 Codec 编解码的流程,转码完成得到 PB 后直接到 trpc 工具为其专门生成的 REST Stub 中进行处理: 12 13 ![restful-overall-design](/.resources/user_guide/server/restful/restful-overall-design_zh_CN.png) 14 15 ## 转码器核心:HttpRule 16 17 同一套 PB 定义的服务,既要支持 RPC 调用,也要支持 REST 调用,需要一套规则来指明 RPC 和 REST 之间的映射,更确切的是:PB 和 HTTP/JSON 之间的转码。在业界,Google 定义了一套这样的规则,即 `HttpRule`,tRPC 的实现也参考了这个规则。tRPC 的 HttpRule 需要你在 PB 文件中以 Options 的方式指定:`option (trpc.api.http)`,这就是所谓的同一套 PB 定义的服务既支持 RPC 调用也支持 REST 调用。 18 19 下面,我们来看一个例子,如何给一个 Greeter 服务中的 SayHello 方法绑定 HttpRule: 20 21 ```protobuf 22 service Greeter { 23 rpc SayHello(HelloRequest) returns (HelloReply) { 24 option (trpc.api.http) = { 25 post: "/v1/foobar/{name}" 26 body: "*" 27 additional_bindings: { 28 post: "/v1/foo/{name=/x/y/**}" 29 body: "single_nested" 30 response_body: "message" 31 } 32 }; 33 } 34 } 35 message HelloRequest { 36 string name = 1; 37 Nested single_nested = 2; 38 oneof oneof_value { 39 google.protobuf.Empty oneof_empty = 3; 40 string oneof_string = 4; 41 } 42 } 43 message Nested { 44 string name = 1; 45 } 46 message HelloReply { 47 string message = 1; 48 } 49 ``` 50 51 通过上述例子,可见 HttpRule 有以下几个字段: 52 53 > - body 字段,表明 HTTP 请求 Body 中携带的是 PB 请求 Message 的哪个字段。 54 > - response_body 字段,表明 HTTP 响应 Body 中携带的是 PB 响应 Message 的哪个字段。 55 > - additional_bindings 字段,表示额外的 HttpRule,即一个 RPC 方法可以绑定多个 HttpRule。 56 57 **结合 HttpRule 的具体规则看一下上述例子中 HTTP 请求/响应 怎么映射到 HelloRequest 和 HelloReply 中:** 58 59 > 映射时 RPC 请求 Proto Message 里的 **"叶子字段"** (所谓叶子字段,即不能再继续嵌套遍历的字段,上述例子中 HelloRequest.Name 是叶子字段,HelloRequest.SingleNested 不是叶子字段,HelloRequest.SingleNested.Name 才是)分三种情况映射: 60 > 61 > - 叶子字段被 HttpRule 的 URL Path 引用:HttpRule 的 URL Path 引用了 RPC 请求 Message 中的一个或多个字段,则 RPC 请求 Message 的这些字段就通过 HTTP 请求 URL Path 传递。但这些字段必须是原生基础类型的非数组字段,不支持消息类型的字段,也不支持数组字段。在上述例子中,HttpRule selector 字段被定义为 post: "/v1/foobar/{name}",则 HTTP 请求:POST /v1/foobar/xyz 会把 HelloRequest.Name 字段值映射为 "xyz" 。 62 > - 叶子字段被 HttpRule 的 Body 引用:HttpRule 的 Body 里指明了映射的字段,则 RPC 请求 Message 的这个字段就通过 HTTP 请求 Body 传递。上述例子中,如果 HttpRule body 字段定义为 body: "name",则 HTTP 请求 Body: "xyz" 把 HelloRequest.Name 字段值映射为 "xyz" 63 > - 其他叶子字段:其他叶子字段都会自动成为 URL 查询参数,而且如果是 repeated 字段,则支持同一个 URL 查询参数多次查询。上述例子中,additional_bindings 里面 selector 如果指定了 post: "/v1/foo/{name=/x/y/**}",body 如果不指定 body: "",则 HelloRequest 里面除了 HelloRequest.Name 字段外的字段都通过 URL 查询参数传递,譬如说,HTTP 请求 POST /v1/foo/x/y/z/xyz?single_nested.name=abc 会把 HelloRequest.Name 字段值映射为 "/x/y/z/xyz",HelloRequest.SingleNested.Name 字段值映射为 "abc"。 64 > 65 > **补充:** 66 > 67 > - 如果 HttpRule 的 Body 里未指明字段,用 "*" 来定义,则没有被 URL Path 绑定的每个请求 Message 字段都通过 HTTP 请求的 Body 传递。即 URL 查询参数会失效。 68 > - 如果 HttpRule 的 Body 为空,则没有被 URL Path 绑定的每个请求 Message 字段都会自动成为 URL 查询参数。即 Body 失效。 69 > - 如果 HttpRule 的 response_body 为空,则整个 PB 响应 Message 会序列化到 HTTP 响应 Body 里,上述例子中,response_body: "",则 HTTP Response Body 是整个 HelloReply 的序列化 70 > - HttpRule body 和 response_body 字段若要引用 PB Message 的字段,可以是叶子字段,也可以不是,但必须是 PB Message 里面的第一层的字段,譬如对于 HelloRequest,可以定义 HttpRule body: "name",也可以定义 body: "single_nested",但不能定义 body: "single_nested.name" 71 72 下面我们再看几个例子,能更好地理解 HttpRule 到底要怎么使用: 73 74 **一、将 URL Path 里面匹配 messages/\* 的内容作为 name 字段值:** 75 76 ```protobuf 77 service Messaging { 78 rpc GetMessage(GetMessageRequest) returns (Message) { 79 option (trpc.api.http) = { 80 get: "/v1/{name=messages/*}" 81 }; 82 } 83 } 84 message GetMessageRequest { 85 string name = 1; // Mapped to URL path. 86 } 87 message Message { 88 string text = 1; // The resource content. 89 } 90 ``` 91 上述 HttpRule 可得以下映射: 92 93 | HTTP | tRPC | 94 | ----------------------- | ----------------------------------- | 95 | GET /v1/messages/123456 | GetMessage(name: "messages/123456") | 96 97 **二、较为复杂的嵌套 message 构造,URL Path 里的 123456 作为 message_id,sub.subfield 的值作为嵌套 message 里的 subfield:** 98 99 ```protobuf 100 service Messaging { 101 rpc GetMessage(GetMessageRequest) returns (Message) { 102 option (trpc.api.http) = { 103 get:"/v1/messages/{message_id}" 104 }; 105 } 106 } 107 message GetMessageRequest { 108 message SubMessage { 109 string subfield = 1; 110 } 111 string message_id = 1; // Mapped to URL path. 112 int64 revision = 2; // Mapped to URL query parameter `revision`. 113 SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. 114 } 115 ``` 116 117 上述 HttpRule 可得以下映射: 118 119 | HTTP | tRPC | 120 | --------------------------------------------------- | ----------------------------------------------------------------------------- | 121 | GET /v1/messages/123456?revision=2&sub.subfield=foo | GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: "foo")) | 122 123 **三、将 HTTP Body 的整体作为 Message 类型解析,即将 "Hi!" 作为 message.text 的值:** 124 125 ```protobuf 126 service Messaging { 127 rpc UpdateMessage(UpdateMessageRequest) returns (Message) { 128 option (trpc.api.http) = { 129 post: "/v1/messages/{message_id}" 130 body: "message" 131 }; 132 } 133 } 134 message UpdateMessageRequest { 135 string message_id = 1; // mapped to the URL 136 Message message = 2; // mapped to the body 137 } 138 ``` 139 140 141 上述 HttpRule 可得以下映射: 142 143 | HTTP | tRPC | 144 | ------------------------------------------ | ----------------------------------------------------------- | 145 | POST /v1/messages/123456 { "text": "Hi!" } | UpdateMessage(message_id: "123456" message { text: "Hi!" }) | 146 147 **四、将 HTTP Body 里的字段解析为 Message 的 text 字段:** 148 149 ```protobuf 150 service Messaging { 151 rpc UpdateMessage(Message) returns (Message) { 152 option (trpc.api.http) = { 153 post: "/v1/messages/{message_id}" 154 body: "*" 155 }; 156 } 157 } 158 message Message { 159 string message_id = 1; 160 string text = 2; 161 } 162 ``` 163 164 上述 HttpRule 可得以下映射: 165 166 | HTTP | tRPC | 167 | ----------------------------------------- | ----------------------------------------------- | 168 | POST/v1/messages/123456 { "text": "Hi!" } | UpdateMessage(message_id: "123456" text: "Hi!") | 169 170 **五、使用 additional_bindings 表示追加绑定的 API:** 171 172 ```protobuf 173 service Messaging { 174 rpc GetMessage(GetMessageRequest) returns (Message) { 175 option (trpc.api.http) = { 176 get: "/v1/messages/{message_id}" 177 additional_bindings { 178 get: "/v1/users/{user_id}/messages/{message_id}" 179 } 180 }; 181 } 182 } 183 message GetMessageRequest { 184 string message_id = 1; 185 string user_id = 2; 186 } 187 ``` 188 189 上述 HttpRule 可得以下映射: 190 191 | HTTP | tRPC | 192 | -------------------------------- | ---------------------------------------------- | 193 | GET /v1/messages/123456 | GetMessage(message_id: "123456") | 194 | GET /v1/users/me/messages/123456 | GetMessage(user_id: "me" message_id: "123456") | 195 196 # 实现 197 198 见 [trpc-go/restful](/restful) 199 200 # 示例 201 202 理解了 HttpRule 后,我们来看一下具体要如何开启 tRPC-Go 的 RESTful 服务。 203 204 **一、PB 定义** 205 206 先更新 `trpc-cmdline` 工具到最新版本,要使用 **trpc.api.http** 注解,需要 import 一个 proto 文件: 207 208 ```protobuf 209 import "trpc/api/annotations.proto"; 210 ``` 211 212 我们还是定义一个 Greeter 服务 的 PB: 213 214 ```protobuf 215 ... 216 import "trpc/api/annotations.proto"; 217 service Greeter { 218 rpc SayHello(HelloRequest) returns (HelloReply) { 219 option (trpc.api.http) = { 220 post: "/v1/foobar" 221 body: "*" 222 additional_bindings: { 223 post: "/v1/foo/{name}" 224 } 225 }; 226 } 227 } 228 message HelloRequest { 229 string name = 1; 230 ... 231 } 232 ... 233 ``` 234 235 **二、生成桩代码** 236 237 直接用 `trpc create` 命令生成桩代码。 238 239 **三、配置** 240 241 和其他协议配置一样,`trpc_go.yaml` 里面 service 的 protocol 配置成 `restful` 即可 242 243 ```yaml 244 server: 245 ... 246 service: 247 - name: trpc.test.helloworld.Greeter 248 ip: 127.0.0.1 249 # nic: eth0 250 port: 8080 251 network: tcp 252 protocol: restful 253 timeout: 1000 254 ``` 255 256 更普遍的场景是,我们会配置一个 tRPC 协议的 service,再加一个 RESTful 协议的 service,这样就能做到一套 PB 文件同时支持提供 RPC 服务和 RESTful 服务: 257 258 ```yaml 259 server: 260 ... 261 service: 262 - name: trpc.test.helloworld.Greeter1 263 ip: 127.0.0.1 264 # nic: eth0 265 port: 12345 266 network: tcp 267 protocol: trpc 268 timeout: 1000 269 - name: trpc.test.helloworld.Greeter2 270 ip: 127.0.0.1 271 # nic: eth0 272 port: 54321 273 network: tcp 274 protocol: restful 275 timeout: 1000 276 ``` 277 278 **注意:tRPC 每个 service 必须配置不同的端口。** 279 280 **四、启动服务** 281 282 启动服务和其他协议方式一致: 283 284 ```go 285 package main 286 import ( 287 ... 288 pb "trpc.group/trpc-go/trpc-go/examples/restful/helloworld" 289 ) 290 func main() { 291 s := trpc.NewServer() 292 pb.RegisterGreeterService(s, &greeterServerImpl{}) 293 // 启动 294 if err := s.Serve(); err != nil { 295 ... 296 } 297 } 298 ``` 299 **五、调用** 300 301 搭建的是 RESTful 服务,所以请用任意的 REST 客户端调用,不支持用 NewXXXClientProxy 的 RPC 方式调用: 302 303 ```go 304 package main 305 import "net/http" 306 func main() { 307 ... 308 // native HTTP invocation 309 req, err := http.NewRequest("POST", "http://127.0.0.1:8080/v1/foobar", bytes.Newbuffer([]byte(`{"name": "xyz"}`))) 310 if err != nil { 311 ... 312 } 313 cli := http.Client{} 314 resp, err := cli.Do(req) 315 if err != nil { 316 ... 317 } 318 ... 319 } 320 ``` 321 322 当然如果上面第三点【配置】中,如果配置了 tRPC 协议的 service,我们还是可以通过 NewXXXClientProxy 的 RPC 方式去调用 tRPC 协议的 service,注意区分端口。 323 324 **六、自定义 HTTP 头到 RPC Context 映射** 325 326 HttpRule 解决的是 tRPC Message Body 和 HTTP/JSON 之间的转码,那么 HTTP 请求如何传递 RPC 调用的上下文呢?这就需要定义 HTTP 头到 RPC Context 映射。 327 328 RESTful 服务的 HeaderMatcher 定义如下: 329 330 ```go 331 type HeaderMatcher func( 332 ctx context.Context, 333 w http.ResponseWriter, 334 r *http.Request, 335 serviceName, methodName string, 336 ) (context.Context, error) 337 ``` 338 339 默认的 HeaderMatcher 处理如下: 340 341 ```go 342 var defaultHeaderMatcher = func( 343 ctx context.Context, 344 w http.ResponseWriter, 345 req *http.Request, 346 serviceName, methodName string, 347 ) (context.Context, error) { 348 // It is recommended to customize and pass the codec.Msg in the ctx, and specify the target service and method name. 349 ctx, msg := codec.WithNewMessage(ctx) 350 msg.WithCalleeServiceName(service) 351 msg.WithServerRPCName(method) 352 msg.WithSerializationType(codec.SerializationTypePB) 353 return ctx, nil 354 } 355 ``` 356 357 用户可以通过 `WithOptions` 的方式设置 HeaderMatcher: 358 359 ```go 360 service := server.New(server.WithRESTOptions(restful.WithHeaderMatcher(xxx))) 361 ``` 362 363 **七、自定义回包处理 [设置请求处理成功的返回码]** 364 365 HttpRule 的 response_body 字段指定了 RPC 响应,譬如上面例子中的 HelloReply 要整个或者将其某个字段序列化到 HTTP Response Body 里面。但是用户可能想额外做一些自定义的操作,譬如:设置成功时候的响应码。 366 367 RESTful 服务的自定义回包处理函数定义如下: 368 369 ```go 370 type CustomResponseHandler func( 371 ctx context.Context, 372 w http.ResponseWriter, 373 r *http.Request, 374 resp proto.Message, 375 body []byte, 376 ) error 377 ``` 378 379 trpc-go/restful 包提供了一个让用户设置请求处理成功时候的响应码的函数: 380 381 ```go 382 func SetStatusCodeOnSucceed(ctx context.Context, code int) {} 383 ``` 384 385 默认的自定义回包处理函数如下: 386 387 ```go 388 var defaultResponseHandler = func( 389 ctx context.Context, 390 w http.ResponseWriter, 391 r *http.Request, 392 resp proto.Message, 393 body []byte, 394 ) error { 395 // compress 396 var writer io.Writer = w 397 _, compressor := compressorForRequest(r) 398 if compressor != nil { 399 writeCloser, err := compressor.Compress(w) 400 if err != nil { 401 return fmt.Errorf("failed to compress resp body: %w", err) 402 } 403 defer writeCloser.Close() 404 w.Header().Set(headerContentEncoding, compressor.ContentEncoding()) 405 writer = writeCloser 406 } 407 // Set StatusCode 408 statusCode := GetStatusCodeOnSucceed(ctx) 409 w.WriteHeader(statusCode) 410 // Set body 411 if statusCode != http.StatusNoContent && statusCode != http.StatusNotModified { 412 writer.Write(body) 413 } 414 return nil 415 } 416 ``` 417 418 如果使用默认自定义回包处理函数,则支持用户在自己的 RPC 处理函数中设置返回码(不设置则成功返回 200): 419 420 ```go 421 func (s *greeterServerImpl) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { 422 ... 423 restful.SetStatusCodeOnSucceed(ctx, 200) // Set the return code for success. 424 return rsp, nil 425 } 426 ``` 427 428 用户可以通过 `WithOptions` 的方式定义回包处理: 429 430 ```go 431 var xxxResponseHandler = func( 432 ctx context.Context, 433 w http.ResponseWriter, 434 r *http.Request, 435 resp proto.Message, 436 body []byte, 437 ) error { 438 reply, ok := resp.(*pb.HelloReply) 439 if !ok { 440 return errors.New("xxx") 441 } 442 ... 443 w.Header().Set("x", "y") 444 expiration := time.Now() 445 expiration := expiration.AddDate(1, 0, 0) 446 cookie := http.Cookie{Name: "abc", Value: "def", Expires: expiration} 447 http.SetCookie(w, &cookie) 448 w.Write(body) 449 return nil 450 } 451 ... 452 service := server.New(server.WithRESTOptions(restful.WithResponseHandler(xxxResponseHandler))) 453 ``` 454 455 **八、自定义错误处理 [错误码]** 456 457 RESTful 错误处理函数定义如下: 458 459 ```go 460 type ErrorHandler func(context.Context, http.ResponseWriter, *http.Request, error) 461 ``` 462 463 用户可以通过 `WithOptions` 的方式定义错误处理: 464 465 ```go 466 var xxxErrorHandler = func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) { 467 if err == errors.New("say hello failed") { 468 w.WriteHeader(500) 469 } 470 ... 471 } 472 service := server.New(server.WithRESTOptions(restful.WithErrorHandler(xxxErrorHandler))) 473 ``` 474 475 **建议使用 trpc-go/restful 包默认的错误处理函数,或者参考实现用户自己的错误处理函数。** 476 477 关于**错误码:** 478 479 如果 RPC 处理过程中返回了 trpc-go/errs 包定义的错误类型,trpc-go/restful 默认的错误处理函数会将 tRPC 的错误码都映射为 HTTP 错误码。如果用户想自己决定返回的某个错误用什么错误码,请使用 trpc-go/restful 包定义的 `WithStatusCode` : 480 481 ```go 482 type WithStatusCode struct { 483 StatusCode int 484 Err error 485 } 486 ``` 487 488 将自己的 error 包起来并返回,如: 489 490 ```go 491 func (s *greeterServerImpl) SayHello(ctx context.Context, req *hpb.HelloRequest, rsp *hpb.HelloReply) (err error) { 492 if req.Name != "xyz" { 493 return &restful.WithStatusCode{ 494 StatusCode: 400, 495 Err: errors.New("test error"), 496 } 497 } 498 return nil 499 } 500 ``` 501 502 如果错误类型不是 trpc-go/errs 的 Error 类型,也没用 trpc-go/restful 包定义的 `WithStatusCode` 包起来,则默认错误码返回 500。 503 504 **九、Body 序列化与压缩** 505 506 和普通 REST 请求一样,通过 HTTP 头指定,支持比较主流的几个。 507 508 > **序列化支持的 Content-Type (或 Accept):application/json,application/x-www-form-urlencoded,application/octet-stream。默认为 application/json。** 509 510 序列化接口定义如下: 511 512 ```go 513 type Serializer interface { 514 // Marshal serializes the tRPC message or one of its fields into the HTTP body. 515 Marshal(v interface{}) ([]byte, error) 516 // Unmarshal deserializes the HTTP body into the tRPC message or one of its fields. 517 Unmarshal(data []byte, v interface{}) error 518 // Name Serializer Name 519 Name() string 520 // ContentType is set when returning the HTTP response. 521 ContentType() string 522 } 523 ``` 524 525 **用户可自己实现并通过 `restful.RegisterSerializer()` 函数注册。** 526 527 > **压缩支持 Content-Encoding (或 Accept-Encoding): gzip。默认不压缩。** 528 529 压缩接口定义如下: 530 531 ```go 532 type Compressor interface { 533 // Compress 534 Compress(w io.Writer) (io.WriteCloser, error) 535 // Decompress 536 Decompress(r io.Reader) (io.Reader, error) 537 // Name represents the name of the compressor. 538 Name() string 539 // ContentEncoding represents the Content-Encoding that is set when returning the HTTP response. 540 ContentEncoding() string 541 } 542 ``` 543 544 **用户可自己实现并通过 `restful.RegisterCompressor()` 函数注册。** 545 546 **十、跨域请求** 547 548 RESTful 也支持 [trpc-filter/cors](https://github.com/trpc-ecosystem/go-filter/tree/main/cors) 跨域插件。使用时,需要在先 pb 中通过 `custom` 添加 HTTP OPTIONS 方法,比如: 549 550 ```protobuf 551 service HelloTrpcGo { 552 rpc Hello(HelloReq) returns (HelloRsp) { 553 option (trpc.api.http) = { 554 post: "/hello" 555 body: "*" 556 additional_bindings: { 557 get: "/hello/{name}" 558 } 559 additional_bindings: { 560 custom: { // use custom verb 561 kind: "OPTIONS" 562 path: "/hello" 563 } 564 } 565 }; 566 } 567 } 568 ``` 569 570 然后,通过 trpc-cmdline 命令行工具重新生成桩代码。 571 最后,在 service 拦载器中配上 CORS 插件。 572 573 如果不想修改 pb。RESTful 也提供了代码自定义跨域的方式。 574 RESTful 协议插件会为每个 Service 生成一个对应的 http.Handler,我们可以在启动监听前取出来,替换成我们自己的 http.Handler: 575 576 ```go 577 func allowCORS(h http.Handler) http.Handler { 578 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 579 if origin := r.Header.Get("Origin"); origin != "" { 580 w.Header().Set("Access-Control-Allow-Origin", origin) 581 if r.Method == "OPTIONS" && r.Header.Get("Access-Control-Request-Method") != "" { 582 preflightHandler(w, r) 583 return 584 } 585 } 586 h.ServeHTTP(w, r) 587 }) 588 } 589 func main() { 590 // set custom header matcher 591 s := trpc.NewServer() 592 // register service implementation 593 pb.RegisterPingService(s, &pingServiceImpl{}) 594 // retrieve restful.Router 595 router := restful.GetRouter(pb.PingServer_ServiceDesc.ServiceName) 596 // wrap it up and re-register it again 597 restful.RegisterRouter(pb.PingServer_ServiceDesc.ServiceName, allowCORS(router)) 598 // start 599 if err := s.Serve(); err != nil { 600 log.Fatal(err) 601 } 602 } 603 ``` 604 605 # 性能 606 607 为了提升性能,RESTful 协议插件额外支持基于 [fasthttp](https://github.com/valyala/fasthttp) 来处理 HTTP 包,RESTful 协议插件性能和注册的 URL 路径复杂度有关,和通过哪种方式传递 PB Message 字段也有关,这里仅给出最简单的 echo 测试场景下两种模式的对比: 608 609 测试 PB: 610 611 ```protobuf 612 service Greeter { 613 rpc SayHello(HelloRequest) returns (HelloReply) { 614 option (trpc.api.http) = { 615 get: "/v1/foobar/{name}" 616 }; 617 } 618 } 619 message HelloRequest { 620 string name = 1; 621 } 622 message HelloReply { 623 string message = 1; 624 } 625 ``` 626 627 Greeter 实现: 628 629 ```go 630 type greeterServiceImpl struct{} 631 func (s *greeterServiceImpl) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { 632 return &pb.HelloReply{Message: Name}, nil 633 } 634 ``` 635 636 测试机器:绑定 8 核 637 638 | 模式 | QPS when P99 < 10ms | 639 | ------------- | ------------------- | 640 | 基于 net/http | 16w | 641 | 基于 fasthttp | 25w | 642 643 - fasthttp 开启方式:代码里加一行(加在 `trpc.NewServer()` 前): 644 645 ```go 646 package main 647 import ( 648 "trpc.group/trpc-go/trpc-go/transport" 649 thttp "trpc.group/trpc-go/trpc-go/http" 650 ) 651 func main() { 652 transport.RegisterServerTransport("restful", thttp.NewRESTServerTransport(true)) 653 s := trpc.NewServer() 654 ... 655 } 656 ```