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  ```