trpc.group/trpc-go/trpc-go@v1.0.3/filter/README.md (about)

     1  English | [中文](README.zh_CN.md)
     2  
     3  # tRPC-Go Development of Filter
     4  
     5  
     6  ## Introduction
     7  
     8  This article introduces how to develop filter also known as interceptor, for the tRPC-Go framework. The tRPC framework uses the filter mechanism to modularize and make specific logic components of interface requests pluggable. This decouples specific business logic and promotes reusability. Examples of filters include monitoring filters, distributed tracing filters, logging filters, authentication filters, and more.
     9  
    10  ## Principles
    11  
    12  Understanding the principles of filters is crucial, focusing on the `trigger timing` and `sequencing` of filters.
    13  
    14  **Trigger Timing**: Filters can intercept interface requests and responses, and handle requests, responses, and contexts (in simpler terms, they can perform actions `before receiving a request` and `after processing a request`). Therefore, filters can be functionally divided into two parts: pre-processing (before business logic) and post-processing (after business logic).
    15  
    16  **Sequencing**: As shown in the diagram below, filters follow a clear sequence. They execute the pre-processing logic in the order of filter registration and then execute the post-processing logic in reverse order.
    17  
    18  ![The Order of Filters](/.resources/filter/filter.png)
    19  
    20  ## Examples
    21  
    22  Below is an example of how to develop a filter for reporting RPC call duration.
    23  
    24  **Step 1**: Define the filter functions:
    25  
    26  ```golang
    27  // ServerFilter: Server-side duration statistics from receiving the request to returning the response.
    28  func ServerFilter(ctx context.Context, req interface{}, next filter.ServerHandleFunc) (rsp interface{}, err error)
    29  
    30  // ClientFilter: Client-side duration statistics from initiating the request to receiving the response.
    31  func ClientFilter(ctx context.Context, req, rsp interface{}, next filter.ClientHandleFunc) (err error)
    32  ```
    33  
    34  **Step 2**: Implementation:
    35  
    36  ```golang
    37  func ServerFilter(ctx context.Context, req interface{}, next filter.ServerHandleFunc) (interface{}, error) {
    38      begin := time.Now()        // Timestamp before processing business logic
    39  
    40      rsp, err := next(ctx, req) // Note that here you must call the next filter unless there is a specific purpose for returning directly.
    41  
    42      // Calculate elapsed time after processing business logic
    43      cost := time.Since(begin)
    44  
    45      // Report the elapsed time to a specific monitoring platform
    46  
    47      return rsp, err // You must return the rsp and err from the next function, be careful not to override them with your own logic.
    48  }
    49  
    50  func ClientFilter(ctx context.Context, req, rsp interface{}, next filter.ClientHandleFunc) error {
    51      begin := time.Now() // Timestamp before sending the request
    52  
    53      err := next(ctx, req, rsp)
    54  
    55      // Calculate elapsed time after receiving the response
    56      cost := time.Since(begin)
    57  
    58      // Report the elapsed time to a specific monitoring platform
    59  
    60      return err
    61  }
    62  ```
    63  
    64  **Step 3**: Register the filters to the framework:
    65  
    66  ```golang
    67  filter.Register("name", ServerFilter, ClientFilter) // You can define the filter name as you like. It should be registered before trpc.NewServer().
    68  ```
    69  
    70  **Step 4**: Configuration:
    71  
    72  ```yaml
    73  server:
    74    filter: # Applies to all services
    75      - name1 # The name of the server filter registered in the previous step
    76    service:
    77      - name: trpc.app.server.service
    78        filter: # Applies only to the current service
    79          - name2
    80  
    81  client:
    82    filter:
    83      - name
    84  ```
    85  
    86  ## Stream Filters
    87  
    88  Due to the significant differences between streaming services and regular RPC calls, such as how a client initiates a streaming request and how a server handles streaming, tRPC-Go provides a different interface for stream filters.
    89  
    90  While the exposed interface is different, the underlying implementation is similar to regular RPC filters. The principles are the same as those explained for regular RPC filters.
    91  
    92  ### Client-side
    93  
    94  To configure a client-side stream filter, you need to implement `client.StreamFilter`:
    95  
    96  ```golang
    97  type StreamFilter func(context.Context, *client.ClientStreamDesc, client.Streamer) (client.ClientStream, error)
    98  ```
    99  
   100  Here's an example of a stream filter for monitoring the duration of streaming interactions:
   101  
   102  **Step 1**: Implement `client.StreamFilter`:
   103  
   104  ```golang
   105  func StreamClientFilter(ctx context.Context, desc *client.ClientStreamDesc, streamer client.Streamer) (client.ClientStream, error) {
   106      begin := time.Now() // Timestamp before creating the stream
   107  
   108      s, err := streamer(ctx, desc) // Note that here you must call streamer to execute the next filter unless there is a specific purpose for returning directly.
   109  
   110      cost := time.Since(begin) // Calculate elapsed time after creating the stream
   111  
   112      // Report the elapsed time to a specific monitoring platform
   113  
   114      return &wrappedStream{s}, err // The wrappedStream encapsulates client.ClientStream for intercepting methods like SendMsg, RecvMsg, etc. You must return the err from streamer.
   115  }
   116  ```
   117  
   118  **Step 2**: Wrap `client.ClientStream` and override the corresponding methods:
   119  
   120  Since streaming services involve methods like `SendMsg`, `RecvMsg`, and `CloseSend`, you need to introduce a new struct for intercepting these interactions. You should implement the `client.ClientStream` interface in this struct. When the tRPC framework calls the `client.ClientStream` interface methods, it will execute the corresponding methods in this struct, allowing interception.
   121  
   122  Since you may not want to intercept all `client.ClientStream` methods, you can embed `client.ClientStream` as an anonymous field in the struct. This way, methods that you don't want to intercept will pass through directly. You only need to override the methods you want to intercept.
   123  
   124  Here's an example:
   125  
   126  ```golang
   127  // wrappedStream encapsulates the original stream. Override the methods you want to intercept.
   128  type wrappedStream struct {
   129      client.ClientStream // You must embed client.ClientStream
   130  }
   131  
   132  // Override RecvMsg to intercept all RecvMsg calls on the stream.
   133  func (w *wrappedStream) RecvMsg(m interface{}) error {
   134      begin := time.Now() // Timestamp before receiving data
   135  
   136      err := w.ClientStream.RecvMsg(m) // Note that here you must call RecvMsg to let the underlying stream receive data unless there is a specific purpose for returning directly.
   137  
   138      cost := time.Since(begin) // Calculate elapsed time after receiving data
   139  
   140      // Report the elapsed time to a specific monitoring platform
   141  
   142      return err // You must return the err generated earlier.
   143  }
   144  
   145  // Override SendMsg to intercept all SendMsg calls on the stream.
   146  func (w *wrappedStream) SendMsg(m interface{}) error {
   147      begin := time.Now() // Timestamp before sending data
   148  
   149      err := w.ClientStream.SendMsg(m) // Note that here you must call SendMsg to let the underlying stream send data unless there is a specific purpose for returning directly.
   150  
   151      cost := time.Since(begin) // Calculate elapsed time after sending data
   152  
   153      // Report the elapsed time to a specific monitoring platform
   154  
   155      return err // You must return the err generated earlier.
   156  }
   157  
   158  // Override CloseSend to intercept all CloseSend calls on the stream.
   159  func (w *wrappedStream) CloseSend() error {
   160      begin := time.Now() // Timestamp before closing the local end
   161  
   162      err := w.ClientStream.CloseSend() // Note that here you must call CloseSend to let the underlying stream close the local end unless there is a specific purpose for returning directly.
   163  
   164      cost := time.Since(begin) // Calculate elapsed time after closing the local end
   165  
   166      // Report the elapsed time to a specific monitoring platform
   167  
   168      return err // You must return the err generated earlier.
   169  }
   170  ```
   171  
   172  **Step 3**: Configure the stream filter in the client, either through a configuration file or in code.
   173  
   174  Option 1: Configuration File
   175  
   176  Register the stream filter with the framework first:
   177  
   178  ```golang
   179  client.RegisterStreamFilter("name1", StreamClientFilter) // You can define the stream filter name as you like. It should be registered before trpc.NewServer().
   180  ```
   181  
   182  Then, configure it in the configuration file:
   183  
   184  ```yaml
   185  client:
   186    stream_filter: # Applies to all services
   187      - name1 # The name of the client stream filter registered in the previous step
   188    service:
   189      - name: trpc.app.server.service
   190        stream_filter: # Applies only to the current service
   191          - name2
   192  ```
   193  
   194  Option 2: Code Configuration
   195  
   196  ```golang
   197  // Add the stream filter using client.WithStreamFilters
   198  proxy := pb.NewGreeterClientProxy(client.WithStreamFilters(StreamClientFilter))
   199  
   200  // Create a stream
   201  cstream, err := proxy.ClientStreamSayHello(ctx)
   202  
   203  // Interact with the stream
   204  cstream.Send()
   205  cstream.Recv()
   206  ```
   207  
   208  ### Server-side
   209  
   210  To configure a server-side stream filter, you need to implement `server.StreamFilter`:
   211  
   212  ```golang
   213  type StreamFilter func(Stream, *StreamServerInfo, StreamHandler) error
   214  ```
   215  
   216  Here's an example of a server-side stream filter for monitoring the duration of streaming interactions:
   217  
   218  **Step 1**: Implement `server.StreamFilter`:
   219  
   220  ```golang
   221  func StreamServerFilter(ss server.Stream, si *server.StreamServerInfo, handler server.StreamHandler) error {
   222      begin := time.Now() // Timestamp before entering streaming processing
   223  
   224      // wrappedStream encapsulates server.Stream. Override SendMsg, RecvMsg, and other methods for interception.
   225      ws := &wrappedStream{ss}
   226  
   227      // Note that here you must call handler to execute the next filter unless there is a specific purpose for returning directly.
   228      err := handler(ws)
   229  
   230      cost := time.Since(begin) // Calculate elapsed time after the business process.
   231  
   232      // Report the elapsed time to a specific monitoring platform
   233  
   234      return err // You must return the err generated earlier from the handler.
   235  }
   236  
   237  // Override the methods you want to intercept in the wrappedStream struct.
   238  type wrappedStream struct {
   239      server.Stream // You must embed server.Stream
   240  }
   241  
   242  // Override RecvMsg to intercept all RecvMsg calls on the stream.
   243  func (w *wrappedStream) RecvMsg(m interface{}) error {
   244      begin := time.Now() // Timestamp before receiving data
   245  
   246      err := w.Stream.RecvMsg(m) // Note that here you must call RecvMsg to let the underlying stream receive data unless there is a specific purpose for returning directly.
   247  
   248      cost := time.Since(begin) // Calculate elapsed time after receiving data
   249  
   250      // Report the elapsed time to a specific monitoring platform
   251  
   252      return err // You must return the err generated earlier.
   253  }
   254  
   255  // Override SendMsg to intercept all SendMsg calls on the stream.
   256  func (w *wrappedStream) SendMsg(m interface{}) error {
   257      begin := time.Now() // Timestamp before sending data
   258  
   259      err := w.Stream.SendMsg(m) // Note that here you must call SendMsg to let the underlying stream send data unless there is a specific purpose for returning directly.
   260  
   261      cost := time.Since(begin) // Calculate elapsed time after sending data
   262  
   263      // Report the elapsed time to a specific monitoring platform
   264  
   265      return err // You must return the err generated earlier.
   266  }
   267  ```
   268  
   269  **Step 3**: Configure the stream filter on the server, either through a configuration file or in code.
   270  
   271  Option 1: Configuration File
   272  
   273  Register the stream filter with the framework first:
   274  
   275  ```golang
   276  server.RegisterStreamFilter("name1", StreamServerFilter) // You can define the stream filter name as you like. It should be registered before trpc.NewServer().
   277  ```
   278  
   279  Then, configure it in the configuration file:
   280  
   281  ```yaml
   282  server:
   283    stream_filter: # Applies to all services
   284      - name1 # The name of the server stream filter registered in the previous step
   285    service:
   286      - name: trpc.app.server.service
   287        stream_filter: # Applies only to the current service
   288          - name2
   289  ```
   290  
   291  Option 2: Code Configuration
   292  
   293  ```golang
   294  // Add the stream filter using server.WithStreamFilters
   295  s := trpc.NewServer(server.WithStreamFilters(StreamServerFilter))
   296  
   297  pb.RegisterGreeterService(s, &greeterServiceImpl{})
   298  if err := s.Serve(); err != nil {
   299      log.Fatal(err)
   300  }
   301  ```
   302  
   303  ## FAQ
   304  
   305  ### Q: Can binary data be obtained in the interceptor entry point?
   306  
   307  No, in the interceptor entry point, both `req` and `rsp` are already serialized data structures. You can directly use the data; there is no binary data available.
   308  
   309  ### Q: How are multiple interceptors executed in order?
   310  
   311  Multiple interceptors are executed in the order specified in the configuration file array. For example:
   312  
   313  ```yaml
   314  server:
   315    filter:
   316      - filter1
   317      - filter2
   318    service:
   319      - name: trpc.app.server.service
   320        filter:
   321          - filter3
   322  ```
   323  
   324  The execution order is as follows:
   325  
   326  ```
   327  Request received -> filter1 pre-processing logic -> filter2 pre-processing logic -> filter3 pre-processing logic -> User's business logic -> filter3 post-processing logic -> filter2 post-processing logic -> filter1 post-processing logic -> Response sent
   328  ```
   329  
   330  ### Q: Is it necessary to set both server and client for an interceptor?
   331  
   332  No, it's not necessary. If you only need a server-side interceptor, you can pass `nil` for the client-side interceptor, and vice versa. For example:
   333  
   334  ```golang
   335  filter.Register("name1", serverFilter, nil) // In this case, the "name1" interceptor can only be configured in the server's filter list. Configuring it in the client will result in an RPC error.
   336  
   337  filter.Register("name2", nil, clientFilter) // In this case, the "name2" interceptor can only be configured in the client's filter list. Configuring it in the server will cause the server to fail to start.
   338  ```