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