trpc.group/trpc-go/trpc-go@v1.0.3/restful/README.md (about) 1 English | [中文](README.zh_CN.md) 2 3 # Introduction 4 5 The tRPC framework uses PB to define services, but it is still a common requirement to provide REST-style APIs based on 6 the HTTP protocol. Unifying RPC and REST is not an easy task, and the tRPC-Go framework's HTTP RPC protocol aims to 7 define the same set of PB files that can be called through RPC (through the client's NewXXXClientProxy provided by 8 the stub code) or through native HTTP requests. However, such HTTP calls do not comply with the RESTful specification, 9 for example, custom routes cannot be defined, wildcards are not supported, and the response body is empty when an 10 error occurs (the error message can only be placed in the response header). Therefore, trpc additionally support the 11 RESTful protocol and no longer attempt to force RPC and REST together. If the service is specified as RESTful 12 protocol, it does not support the use of stub code calls and only supports HTTP client calls. However, the benefit 13 of this approach is that it can provide APIs that comply with RESTful specification through the protobuf annotation 14 in the same set of PB files and can use various tRPC framework plugins or filters. 15 16 # Principles 17 ## Transcoder 18 19 Unlike other protocol plugins in the tRPC-Go framework, the RESTful protocol plugin implements a tRPC and HTTP/JSON 20 transcoder based on the tRPC HttpRule at the Transport layer. This eliminates the need for Codec encoding and decoding 21 processes as PB is directly obtained after transcoding and processed in the REST Stub generated by the trpc tool. 22 23 ![restful-overall-design](/.resources/user_guide/server/restful/restful-overall-design.png) 24 25 ## Transcoder Core: HttpRule 26 For a service defined using the same set of PB files, support for both RPC and REST calls requires a set of rules to 27 indicate the mapping between RPC and REST, or more precisely, the transcoding between PB and HTTP/JSON. In the industry, 28 Google has defined such rules, namely HttpRule, which tRPC's implementation also references. tRPC's HttpRule needs to 29 be specified in the PB file as an option: option (trpc.api.http), which means that the same set of PB-defined services 30 support both RPC and REST calls. 31 Now, let's take an example of how to bind HttpRule to the `SayHello` method in a Greeter service: 32 33 ```protobuf 34 service Greeter { 35 rpc SayHello(HelloRequest) returns (HelloReply) { 36 option (trpc.api.http) = { 37 post: "/v1/foobar/{name}" 38 body: "*" 39 additional_bindings: { 40 post: "/v1/foo/{name=/x/y/**}" 41 body: "single_nested" 42 response_body: "message" 43 } 44 }; 45 } 46 } 47 message HelloRequest { 48 string name = 1; 49 Nested single_nested = 2; 50 oneof oneof_value { 51 google.protobuf.Empty oneof_empty = 3; 52 string oneof_string = 4; 53 } 54 } 55 message Nested { 56 string name = 1; 57 } 58 message HelloReply { 59 string message = 1; 60 } 61 ``` 62 63 Through the above example, it can be seen that HttpRule has the following fields: 64 65 > - The "body" field indicates which field of the PB request message is carried in the HTTP request body. 66 > - The "response_body" field indicates which field of the PB response message is carried in the HTTP response body. 67 > - The "additional_bindings" field represents additional HttpRule, meaning that an RPC method can be bound to multiple HttpRules. 68 69 70 Combining the specific rules of HttpRule, let's take a look at how the HTTP request/response are mapped to HelloRequest and 71 HelloReply in the above example: 72 73 > When mapping, the "leaf fields" of the RPC request Proto Message (which refers to the fields that cannot be nested and 74 > traversed further, in the above example HelloRequest.Name is a leaf field, while HelloRequest.SingleNested is not, 75 > and only HelloRequest.SingleNested.Name is) are mapped in three ways: 76 > 77 > * The leaf fields are referenced by the URL Path of the HttpRule: If the URL Path of the HttpRule references one or 78 > more fields in the RPC request message, then these fields are passed through the HTTP request URL Path. However, these 79 > fields must be non-array fields of native basic types, and do not support fields of message types or array fields. 80 > In the above example, if the HttpRule selector field is defined as post: "/v1/foobar/{name}", then the value of the 81 > HelloRequest.Name field is mapped to "xyz" when the HTTP request POST /v1/foobar/xyz is made. 82 > * The leaf fields are referenced by the Body of the HttpRule: If the field to be mapped is specified in the Body of 83 > the HttpRule, then this field in the RPC request message is passed through the HTTP request Body. In the above example, 84 > if the HttpRule body field is defined as body: "name", then the value of the HelloRequest.Name field is mapped to 85 > "xyz" when the HTTP request Body is "xyz". 86 > * Other leaf fields: Other leaf fields are automatically turned into URL query parameters, and if they are repeated 87 > fields, multiple queries of the same URL query parameter are supported. In the above example, if the selector in 88 > the additional_bindings specifies post: "/v1/foo/{name=/x/y/*}", and the body is not specified as body: "", then 89 > all fields in HelloRequest except HelloRequest.Name are passed through URL query parameters. For example, if the 90 > HTTP request POST /v1/foo/x/y/z/xyz?single_nested.name=abc is made, the value of the HelloRequest.Name field is 91 > mapped to "/x/y/z/xyz", and the value of the HelloRequest.SingleNested.Name field is mapped to "abc". 92 > 93 > Supplement: 94 > * If the field is not specified in the Body of the HttpRule and is defined as "", then each field of the request 95 > message that is not bound by the URL path is passed through the Body of the HTTP request. That is, the URL query 96 > parameters are invalid. 97 > * If the Body of the HttpRule is empty, then every field of the request message that is not bound by the URL path 98 > becomes a URL query parameter. That is, the Body is invalid. 99 > * If the response_body of the HttpRule is empty, then the entire PB response message will be serialized into the 100 > HTTP response Body. In the above example, if response_body is "", then the serialized HelloReply is the HTTP response Body. 101 > * HttpRule body and response_body fields can reference fields of the PB Message, which may or may not be leaf fields, 102 > but must be first-level fields in the PB Message. For example, for HelloRequest, HttpRule body can be defined as 103 > "name" or "single_nested", but not as "single_nested.name". 104 105 Now let's take a look at a few more examples to better understand how to use HttpRule. 106 107 108 **1. Take the content that matches "messages/\*" inside the URL Path as the value of the "name" field:** 109 110 ```protobuf 111 service Messaging { 112 rpc GetMessage(GetMessageRequest) returns (Message) { 113 option (trpc.api.http) = { 114 get: "/v1/{name=messages/*}" 115 }; 116 } 117 } 118 message GetMessageRequest { 119 string name = 1; // Mapped to URL path. 120 } 121 message Message { 122 string text = 1; // The resource content. 123 } 124 ``` 125 126 The HttpRule above results in the following mapping: 127 128 | HTTP | tRPC | 129 | ----------------------- | ----------------------------------- | 130 | GET /v1/messages/123456 | GetMessage(name: "messages/123456") | 131 132 133 134 **2. A more complex nested message construction, using "123456" in the URL Path as the value of "message_id", and the 135 value of "sub.subfield" in the URL Path as the value of the "subfield" field in the nested message:** 136 137 ```protobuf 138 service Messaging { 139 rpc GetMessage(GetMessageRequest) returns (Message) { 140 option (trpc.api.http) = { 141 get:"/v1/messages/{message_id}" 142 }; 143 } 144 } 145 message GetMessageRequest { 146 message SubMessage { 147 string subfield = 1; 148 } 149 string message_id = 1; // Mapped to URL path. 150 int64 revision = 2; // Mapped to URL query parameter `revision`. 151 SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. 152 } 153 ``` 154 155 The HttpRule above results in the following mapping: 156 157 | HTTP | tRPC | 158 | --------------------------------------------------- | ----------------------------------------------------------------------------- | 159 | GET /v1/messages/123456?revision=2&sub.subfield=foo | GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: "foo")) | 160 161 162 163 **3. Parse the entire HTTP Body as a Message type, i.e. use "Hi!" as the value of "message.text":** 164 165 166 ```protobuf 167 service Messaging { 168 rpc UpdateMessage(UpdateMessageRequest) returns (Message) { 169 option (trpc.api.http) = { 170 post: "/v1/messages/{message_id}" 171 body: "message" 172 }; 173 } 174 } 175 message UpdateMessageRequest { 176 string message_id = 1; // mapped to the URL 177 Message message = 2; // mapped to the body 178 } 179 ``` 180 181 The HttpRule above results in the following mapping: 182 183 | HTTP | tRPC | 184 | ------------------------------------------ | ----------------------------------------------------------- | 185 | POST /v1/messages/123456 { "text": "Hi!" } | UpdateMessage(message_id: "123456" message { text: "Hi!" }) | 186 187 188 189 **4. Parse the field in the HTTP Body as the "text" field of the Message:** 190 191 ```protobuf 192 service Messaging { 193 rpc UpdateMessage(Message) returns (Message) { 194 option (trpc.api.http) = { 195 post: "/v1/messages/{message_id}" 196 body: "*" 197 }; 198 } 199 } 200 message Message { 201 string message_id = 1; 202 string text = 2; 203 } 204 ``` 205 206 The HttpRule above results in the following mapping: 207 208 | HTTP | tRPC | 209 | ----------------------------------------- | ----------------------------------------------- | 210 | POST/v1/messages/123456 { "text": "Hi!" } | UpdateMessage(message_id: "123456" text: "Hi!") | 211 212 **5. Using additional_bindings to indicate APIs with additional bindings:** 213 214 ```protobuf 215 service Messaging { 216 rpc GetMessage(GetMessageRequest) returns (Message) { 217 option (trpc.api.http) = { 218 get: "/v1/messages/{message_id}" 219 additional_bindings { 220 get: "/v1/users/{user_id}/messages/{message_id}" 221 } 222 }; 223 } 224 } 225 message GetMessageRequest { 226 string message_id = 1; 227 string user_id = 2; 228 } 229 ``` 230 231 The HttpRule above results in the following mapping: 232 233 | HTTP | tRPC | 234 | -------------------------------- | ---------------------------------------------- | 235 | GET /v1/messages/123456 | GetMessage(message_id: "123456") | 236 | GET /v1/users/me/messages/123456 | GetMessage(user_id: "me" message_id: "123456") | 237 238 # Implementation 239 240 Please refer to the [trpc-go/restful](/restful). 241 242 # Examples 243 244 After understanding HttpRule, let's take a look at how to enable tRPC-Go's RESTful service. 245 246 **1. PB Definition** 247 248 First, update the `trpc-cmdline` tool to the latest version. To use the **trpc.api.http** annotation, you need 249 to import a proto file: 250 251 ```protobuf 252 import "trpc/api/annotations.proto"; 253 ``` 254 255 Let's define a PB for a Greeter service: 256 257 ```protobuf 258 ... 259 import "trpc/api/annotations.proto"; 260 service Greeter { 261 rpc SayHello(HelloRequest) returns (HelloReply) { 262 option (trpc.api.http) = { 263 post: "/v1/foobar" 264 body: "*" 265 additional_bindings: { 266 post: "/v1/foo/{name}" 267 } 268 }; 269 } 270 } 271 message HelloRequest { 272 string name = 1; 273 ... 274 } 275 ... 276 ``` 277 278 **2. Generating Stub Code** 279 280 Use the `trpc create` command to generate stub code directly. 281 282 **3. Configuration** 283 284 Just like configuring other protocols, set the protocol configuration of the service in `trpc_go.yaml` to `restful`. 285 286 ```yaml 287 server: 288 ... 289 service: 290 - name: trpc.test.helloworld.Greeter 291 ip: 127.0.0.1 292 # nic: eth0 293 port: 8080 294 network: tcp 295 protocol: restful 296 timeout: 1000 297 ``` 298 299 A more common scenario is to configure a tRPC protocol service and add a RESTful protocol service, so that one set of 300 PB files can simultaneously support providing both RPC services and RESTful services. 301 302 ```yaml 303 server: 304 ... 305 service: 306 - name: trpc.test.helloworld.Greeter1 307 ip: 127.0.0.1 308 # nic: eth0 309 port: 12345 310 network: tcp 311 protocol: trpc 312 timeout: 1000 313 - name: trpc.test.helloworld.Greeter2 314 ip: 127.0.0.1 315 # nic: eth0 316 port: 54321 317 network: tcp 318 protocol: restful 319 timeout: 1000 320 ``` 321 322 **Note: Each service in tRPC must be configured with a different port number.** 323 324 **4. starting the Service** 325 326 Starting the service is the same as other protocols: 327 328 ```go 329 package main 330 import ( 331 ... 332 pb "trpc.group/trpc-go/trpc-go/examples/restful/helloworld" 333 ) 334 func main() { 335 s := trpc.NewServer() 336 pb.RegisterGreeterService(s, &greeterServerImpl{}) 337 // Start 338 if err := s.Serve(); err != nil { 339 ... 340 } 341 } 342 ``` 343 344 **5. Calling** 345 346 Since you are building a RESTful service, please use any REST client to make calls. It is not supported to use the RPC 347 method of calling using NewXXXClientProxy. 348 349 ```go 350 package main 351 import "net/http" 352 func main() { 353 ... 354 // native HTTP invocation 355 req, err := http.NewRequest("POST", "http://127.0.0.1:8080/v1/foobar", bytes.Newbuffer([]byte(`{"name": "xyz"}`))) 356 if err != nil { 357 ... 358 } 359 cli := http.Client{} 360 resp, err := cli.Do(req) 361 if err != nil { 362 ... 363 } 364 ... 365 } 366 ``` 367 Of course, if you have configured a tRPC protocol service in step 3 [Configuration], you can still call the tRPC 368 protocol service using the RPC method of NewXXXClientProxy, but be sure to distinguish the port. 369 370 371 **6. Mapping Custom HTTP Headers to RPC Context** 372 373 HttpRule resolves the transcoding between tRPC message body and HTTP/JSON, but how can HTTP requests pass the RPC call 374 context? This requires defining the mapping of HTTP headers to RPC context. 375 376 The HeaderMatcher for RESTful service is defined as follows: 377 378 ```go 379 type HeaderMatcher func( 380 ctx context.Context, 381 w http.ResponseWriter, 382 r *http.Request, 383 serviceName, methodName string, 384 ) (context.Context, error) 385 ``` 386 387 The default handling of HeaderMatcher is as follows: 388 389 ```go 390 var defaultHeaderMatcher = func( 391 ctx context.Context, 392 w http.ResponseWriter, 393 req *http.Request, 394 serviceName, methodName string, 395 ) (context.Context, error) { 396 // It is recommended to customize and pass the codec.Msg in the ctx, and specify the target service and method name. 397 ctx, msg := codec.WithNewMessage(ctx) 398 msg.WithCalleeServiceName(service) 399 msg.WithServerRPCName(method) 400 msg.WithSerializationType(codec.SerializationTypePB) 401 return ctx, nil 402 } 403 ``` 404 405 You can set the HeaderMatcher through the `WithOptions` method: 406 407 ```go 408 service := server.New(server.WithRESTOptions(restful.WithHeaderMatcher(xxx))) 409 ``` 410 411 **7. Customize the Response Handling [Set the Return Code for Successful Request Handling]** 412 413 The "response_body" field in HttpRule specifies the RPC response, for example, in the above example, the "HelloReply" 414 needs to be serialized into the HTTP Response Body as a whole or for a specific field. However, users may want to 415 perform additional custom operations, such as setting the response code for successful requests. 416 417 The custom response handling function for RESTful services is defined as follows: 418 419 ```go 420 type CustomResponseHandler func( 421 ctx context.Context, 422 w http.ResponseWriter, 423 r *http.Request, 424 resp proto.Message, 425 body []byte, 426 ) error 427 ``` 428 429 The "trpc-go/restful" package provides a function that allows users to set the response code for successful request handling: 430 431 ```go 432 func SetStatusCodeOnSucceed(ctx context.Context, code int) {} 433 ``` 434 435 The default custom response handling function is as follows: 436 437 ```go 438 var defaultResponseHandler = func( 439 ctx context.Context, 440 w http.ResponseWriter, 441 r *http.Request, 442 resp proto.Message, 443 body []byte, 444 ) error { 445 // compress 446 var writer io.Writer = w 447 _, compressor := compressorForRequest(r) 448 if compressor != nil { 449 writeCloser, err := compressor.Compress(w) 450 if err != nil { 451 return fmt.Errorf("failed to compress resp body: %w", err) 452 } 453 defer writeCloser.Close() 454 w.Header().Set(headerContentEncoding, compressor.ContentEncoding()) 455 writer = writeCloser 456 } 457 // Set StatusCode 458 statusCode := GetStatusCodeOnSucceed(ctx) 459 w.WriteHeader(statusCode) 460 // Set body 461 if statusCode != http.StatusNoContent && statusCode != http.StatusNotModified { 462 writer.Write(body) 463 } 464 return nil 465 } 466 ``` 467 468 If using the default custom response handling function, users can set the return code in their own RPC handling functions 469 (if not set, it will return 200 for success): 470 471 ```go 472 func (s *greeterServerImpl) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { 473 ... 474 restful.SetStatusCodeOnSucceed(ctx, 200) // Set the return code for success. 475 return rsp, nil 476 } 477 ``` 478 479 You can set the HeaderMatcher through the `WithOptions` method: 480 481 ```go 482 var xxxResponseHandler = func( 483 ctx context.Context, 484 w http.ResponseWriter, 485 r *http.Request, 486 resp proto.Message, 487 body []byte, 488 ) error { 489 reply, ok := resp.(*pb.HelloReply) 490 if !ok { 491 return errors.New("xxx") 492 } 493 ... 494 w.Header().Set("x", "y") 495 expiration := time.Now() 496 expiration := expiration.AddDate(1, 0, 0) 497 cookie := http.Cookie{Name: "abc", Value: "def", Expires: expiration} 498 http.SetCookie(w, &cookie) 499 w.Write(body) 500 return nil 501 } 502 ... 503 service := server.New(server.WithRESTOptions(restful.WithResponseHandler(xxxResponseHandler))) 504 ``` 505 506 **8. Custom Error Handling [Error Code]** 507 508 The definition of the error handling function for RESTful services is as follows: 509 510 ```go 511 type ErrorHandler func(context.Context, http.ResponseWriter, *http.Request, error) 512 ``` 513 514 You can set the HeaderMatcher through the `WithOptions` method: 515 516 ```go 517 var xxxErrorHandler = func(ctx context.Context, w http.ResponseWriter, r *http.Request, err error) { 518 if err == errors.New("say hello failed") { 519 w.WriteHeader(500) 520 } 521 ... 522 } 523 service := server.New(server.WithRESTOptions(restful.WithErrorHandler(xxxErrorHandler))) 524 ``` 525 526 527 **Recommend using the default error handling function of the trpc-go/restful package or referring to the implementation 528 to create your own error handling function.** 529 530 Regarding **error codes:** 531 532 If an error of the type defined in the "trpc-go/errs" package is returned during RPC processing, the default error 533 handling function of "trpc-go/restful" will map tRPC's error codes to HTTP error codes. If users want to decide 534 what error code is used for a specific error, they can use `WithStatusCode` defined in the "trpc-go/restful" package. 535 536 537 ```go 538 type WithStatusCode struct { 539 StatusCode int 540 Err error 541 } 542 ``` 543 544 Wrap your own error in a function and return it, such as: 545 546 ```go 547 func (s *greeterServerImpl) SayHello(ctx context.Context, req *hpb.HelloRequest, rsp *hpb.HelloReply) (err error) { 548 if req.Name != "xyz" { 549 return &restful.WithStatusCode{ 550 StatusCode: 400, 551 Err: errors.New("test error"), 552 } 553 } 554 return nil 555 } 556 ``` 557 558 If the error type is not the `Error` type defined by "trpc-go/errs" and is not wrapped with `WithStatusCode` defined 559 in the "trpc-go/restful" package, the default error code 500 will be returned. 560 561 **9. Body Serialization and Compression** 562 563 Like normal REST requests, it's specified through HTTP headers and supports several popular formats. 564 565 > **Supported Content-Type (or Accept) for serialization: application/json, application/x-www-form-urlencoded, 566 > application/octet-stream. By default it is application/json.** 567 568 Serialization interface is defined as follows: 569 570 ```go 571 type Serializer interface { 572 // Marshal serializes the tRPC message or one of its fields into the HTTP body. 573 Marshal(v interface{}) ([]byte, error) 574 // Unmarshal deserializes the HTTP body into the tRPC message or one of its fields. 575 Unmarshal(data []byte, v interface{}) error 576 // Name Serializer Name 577 Name() string 578 // ContentType is set when returning the HTTP response. 579 ContentType() string 580 } 581 ``` 582 583 **Users can implement their own serializer and register it using the `restful.RegisterSerializer()` function.** 584 585 > **Compression is supported through Content-Encoding (or Accept-Encoding): gzip. By default, there is no compression.** 586 587 Compression interface is defined as follows: 588 589 ```go 590 type Compressor interface { 591 // Compress 592 Compress(w io.Writer) (io.WriteCloser, error) 593 // Decompress 594 Decompress(r io.Reader) (io.Reader, error) 595 // Name represents the name of the compressor. 596 Name() string 597 // ContentEncoding represents the Content-Encoding that is set when returning the HTTP response. 598 ContentEncoding() string 599 } 600 ``` 601 602 **Users can implement their own serializer and register it using the `restful.RegisterSerializer()` function.** 603 604 **10. Cross-Origin Requests** 605 606 RESTful also supports [trpc-filter/cors](https://github.com/trpc-ecosystem/go-filter/tree/main/cors) cross-origin requests 607 plugin. To use it, you need to add the HTTP OPTIONS method in pb by using `custom`, for example: 608 609 ```protobuf 610 service HelloTrpcGo { 611 rpc Hello(HelloReq) returns (HelloRsp) { 612 option (trpc.api.http) = { 613 post: "/hello" 614 body: "*" 615 additional_bindings: { 616 get: "/hello/{name}" 617 } 618 additional_bindings: { 619 custom: { // use custom verb 620 kind: "OPTIONS" 621 path: "/hello" 622 } 623 } 624 }; 625 } 626 } 627 ``` 628 629 Next, regenerate the stub code using the trpc-cmdline command-line tool. 630 Finally, add the CORS plugin to the service interceptors. 631 632 If you do not want to modify the protobuf file, RESTful also provides a code-based custom method for cross-origin requests. 633 634 The RESTful protocol plugin will generate a corresponding http.Handler for each Service. You can retrieve it before 635 starting to listen, and replace it with you own custom http.Handler: 636 637 638 ```go 639 func allowCORS(h http.Handler) http.Handler { 640 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 641 if origin := r.Header.Get("Origin"); origin != "" { 642 w.Header().Set("Access-Control-Allow-Origin", origin) 643 if r.Method == "OPTIONS" && r.Header.Get("Access-Control-Request-Method") != "" { 644 preflightHandler(w, r) 645 return 646 } 647 } 648 h.ServeHTTP(w, r) 649 }) 650 } 651 func main() { 652 // set custom header matcher 653 s := trpc.NewServer() 654 // register service implementation 655 pb.RegisterPingService(s, &pingServiceImpl{}) 656 // retrieve restful.Router 657 router := restful.GetRouter(pb.PingServer_ServiceDesc.ServiceName) 658 // wrap it up and re-register it again 659 restful.RegisterRouter(pb.PingServer_ServiceDesc.ServiceName, allowCORS(router)) 660 // start 661 if err := s.Serve(); err != nil { 662 log.Fatal(err) 663 } 664 } 665 ``` 666 667 # Performance 668 669 To improve performance, the RESTful protocol plugin also supports handling HTTP packets based on [fasthttp](https://github.com/valyala/fasthttp). 670 The performance of the RESTful protocol plugin is related to the complexity of the registered URL path and the method of 671 passing PB Message fields. Here is a comparison between the two modes in the simplest echo test scenario: 672 673 Test PB: 674 675 ```protobuf 676 service Greeter { 677 rpc SayHello(HelloRequest) returns (HelloReply) { 678 option (trpc.api.http) = { 679 get: "/v1/foobar/{name}" 680 }; 681 } 682 } 683 message HelloRequest { 684 string name = 1; 685 } 686 message HelloReply { 687 string message = 1; 688 } 689 ``` 690 691 Greeter implementation 692 693 ```go 694 type greeterServiceImpl struct{} 695 func (s *greeterServiceImpl) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { 696 return &pb.HelloReply{Message: Name}, nil 697 } 698 ``` 699 700 Test machine: 8 cores 701 702 | mode | QPS when P99 < 10ms | 703 | ----------------- | ------------------- | 704 | based on net/http | 16w | 705 | base on fasthttp | 25w | 706 707 - To enable fasthttp, add one line of code before `trpc.NewServer()` as follows: 708 709 ```go 710 package main 711 import ( 712 "trpc.group/trpc-go/trpc-go/transport" 713 thttp "trpc.group/trpc-go/trpc-go/http" 714 ) 715 func main() { 716 transport.RegisterServerTransport("restful", thttp.NewRESTServerTransport(true)) 717 s := trpc.NewServer() 718 ... 719 } 720 ```