trpc.group/trpc-go/trpc-go@v1.0.3/filter/README.zh_CN.md (about) 1 [English](README.md) | 中文 2 3 # tRPC-Go 开发拦截器插件 4 5 6 ## 前言 7 8 本文介绍如何开发 tRPC-Go 框架的拦截器(也称之为过滤器)。tRPC 框架利用拦截器的机制,将接口请求相关的特定逻辑组件化,插件化,从而同具体的业务逻辑解除耦合,达到复用的目的。例如监控拦截器,分布式追踪拦截器,日志拦截器,鉴权拦截器等。 9 10 ## 原理 11 12 理解拦截器的原理关键点在于理解拦截器的`触发时机` 以及 `顺序性`。 13 14 触发时机:拦截器可以拦截到接口的请求和响应,并对请求,响应,上下文进行处理(用通俗的语言阐述也就是 可以在`请求接受前`做一些事情,`请求处理后`做一些事情),因此,拦截器从功能上说是分为两个部分的 前置(业务逻辑处理前) 和 后置(业务逻辑处理后)。 15 16 顺序性:如下图所示,拦截器是有明确的顺序性,根据拦截器的注册顺序依次执行前置部分逻辑,并逆序执行拦截器的后置部分。 17 18 ![The Order of Filters](/.resources/filter/filter.png) 19 20 ## 示例 21 22 下面以一个 rpc 耗时统计上报拦截器进行举例说明如何开发拦截器。 23 24 第一步:如下为实现拦截器的函数原型 25 26 ```golang 27 // ServerFilter server 耗时统计,从收到请求到返回响应的处理时间 28 func ServerFilter(ctx context.Context, req interface{}, next filter.ServerHandleFunc) (rsp interface{}, err error) 29 ``` 30 31 ```golang 32 // ClientFilter client 耗时统计,从发起请求到接收响应的调用时间 33 func ClientFilter(ctx context.Context, req, rsp interface{}, next filter.ClientHandleFunc) (err error) 34 ``` 35 36 第二步:实现 37 38 ```golang 39 func ServerFilter(ctx context.Context, req interface{}, next filter.ServerHandleFunc) (interface{}, error) { 40 begin := time.Now() // 业务逻辑处理前打点记录时间戳 41 42 rsp, err := next(ctx, req) // 注意这里必须用户自己调用下一个拦截器,除非有特定目的需要直接返回 43 44 cost := time.Since(begin) // 业务逻辑处理后计算耗时 45 46 // 上报耗时到具体监控平台 47 48 return rsp, err // 必须返回 next 的 rsp 和 err,要格外注意不要被自己的逻辑的 rsp 和 err 覆盖 49 } 50 51 func ClientFilter(ctx context.Context, req, rsp interface{}, next filter.ClientHandleFunc) error { 52 begin := time.Now() // 发起请求前打点记录时间戳 53 54 err := next(ctx, req, rsp) 55 56 cost := time.Since(begin) // 接受响应后计算耗时 57 58 // 上报耗时到具体监控平台 59 60 return err 61 } 62 ``` 63 64 第三步:将拦截器注册到框架中 65 66 ```golang 67 filter.Register("name", ServerFilter, ClientFilter) // 拦截器名字自己随便定义,供后续配置文件使用,必须放在 trpc.NewServer() 之前 68 ``` 69 70 第四步:配置使用 71 72 ```yaml 73 server: 74 filter: # 对所有 service 全部生效 75 - name1 # 上面第三步注册到框架中的 server 拦截器名字 76 service: 77 - name: trpc.app.server.service 78 filter: # 只对当前 service 生效 79 - name2 80 81 client: 82 filter: 83 - name 84 ``` 85 86 ## 流式拦截器 87 88 因为流式服务和普通 RPC 调用接口差异较大,例如普通 RPC 的客户端通过 `proxy.SayHello`发起一次 RPC 调用,但是流式客户端通过`proxy.ClientStreamSayHello`创建一个流。流创建后,再调用`SendMsg` `RecvMsg` `CloseSend`来进行流的交互,所以针对流式服务,提供了不一样的拦截器接口。 89 90 虽然暴露的接口不同,但是底层的实现方式和普通 RPC 类似,原理参考普通 RPC 拦截器的原理 91 92 ### 客户端 93 94 在客户端配置流式拦截器,需要实现`client.StreamFilter` 95 96 ```golang 97 type StreamFilter func(context.Context, *client.ClientStreamDesc, client.Streamer) (client.ClientStream, error) 98 ``` 99 100 以流式交互过程中的耗时统计上报拦截器进行举例说明如何开发流式拦截器 101 102 **第一步**:实现`client.streamFilter` 103 104 ```golang 105 func StreamClientFilter(ctx context.Context, desc *client.ClientStreamDesc, streamer client.Streamer) (client.ClientStream, error) { 106 begin := time.Now() // 创建流之前,打点记录时间戳 107 108 s, err := streamer(ctx, desc) // 注意这里必须用户自己调用 streamer 执行下一个拦截器,除非有特定目的需要直接返回 109 110 cost := time.Since(begin) // 流创建完成后,计算耗时 111 112 // 上报耗时到具体监控平台 113 114 return &wrappedStream{s}, err // wrappedStream 封装了 client.ClientStream,用于后续拦截 SendMsg、RecvMsg 等方法。注意这里必须返回 streamer 的 err 115 } 116 ``` 117 118 **第二步**:封装 `client.ClientStream`,重写对应方法方法 119 120 因为流式服务的交互过程中客户端有`SendMsg`、`RecvMsg`、`CloseSend`这些方法,为了拦截这些交互过程,需要引入一个新的结构体。用户需要为这个结构体重写`client.ClientStream`接口,框架调用`client.ClientStream`接口时,会执行这个结构体的对应方法,这样就实现了拦截。 121 122 因为用户可能不需要拦截`client.ClientStream`的所有方法,所以可以将`client.ClientStream`设置为结构体的匿名字段,这样,不需要拦截的方法,会直接走原始的路径。用户需要拦截哪些方法,就在这个结构体中重写那些方法。 123 124 例如我只想拦截发送数据的过程,那么只需要重写`SendMsg`方法,至于`client.ClientStream`其他的方法都不需要重新实现。这里是为了演示,所以实现了`client.ClientStream`的所有方法。 125 126 ```golang 127 // wrappedStream 封装原始流,需要拦截哪些方法,就重写哪些方法 128 type wrappedStream struct { 129 client.ClientStream // 必须内嵌 client.ClientStream 130 } 131 132 // 重写 RecvMsg,用来拦截流的所有 RecvMsg 调用 133 func (w *wrappedStream) RecvMsg(m interface{}) error { 134 begin := time.Now() // 接收数据之前,打点记录时间戳 135 136 err := w.ClientStream.RecvMsg(m) // 注意这里必须用户自己调用 RecvMsg 让底层流接收数据,除非有特定目的需要直接返回 137 138 cost := time.Since(begin) // 接收到数据后,计算耗时 139 140 // 上报耗时到具体监控平台 141 142 return err // 注意这里必须返回前面产生的 err 143 } 144 145 // 重写 SendMsg,用来拦截流的所有 SendMsg 调用 146 func (w *wrappedStream) SendMsg(m interface{}) error { 147 begin := time.Now() // 发送数据之前,打点记录时间戳 148 149 err := w.ClientStream.SendMsg(m) // 注意这里必须用户自己调用 SendMsg 让底层流接收数据,除非有特定目的需要直接返回 150 151 cost := time.Since(begin) // 发送数据后,计算耗时 152 153 // 上报耗时到具体监控平台 154 155 return err // 注意这里必须返回前面产生的 err 156 } 157 158 // 重写 CloseSend,用来拦截流的所有 CloseSend 调用 159 func (w *wrappedStream) CloseSend() error { 160 begin := time.Now() // 关闭本端之前,打点记录时间戳 161 162 err := w.ClientStream.CloseSend() // 注意这里必须用户自己调用 CloseSend 让底层流关闭本端,除非有特定目的需要直接返回 163 164 cost := time.Since(begin) // 关闭本端后,计算耗时 165 166 // 上报耗时到具体监控平台 167 168 return err // 注意这里必须返回前面产生的 err 169 } 170 ``` 171 172 **第三步**:将拦截器配置到 client,可以通过配置文件配置或者在代码中配置 173 174 方式 1: 在配置文件配置 175 176 先将拦截器注册到框架中 177 178 ```golang 179 client.RegisterStreamFilter("name1", StreamClientFilter) // 拦截器名字自己随便定义,供后续配置文件使用,必须放在 trpc.NewServer() 之前 180 ``` 181 182 再在配置文件中配置 183 184 ```yaml 185 client: 186 stream_filter: # 对所有 service 全部生效 187 - name1 # 上面注册到框架中 client 流式拦截器的名字 188 service: 189 - name: trpc.app.server.service 190 stream_filter: # 只对当前 service 生效 191 - name2 192 ``` 193 194 方式 2: 在代码中配置 195 196 ```golang 197 // 通过 client.WithStreamFilters 将拦截器添加进去 198 proxy := pb.NewGreeterClientProxy(client.WithStreamFilters(StreamClientFilter)) 199 200 // 创建流 201 cstream,err := proxy.ClientStreamSayHello(ctx) 202 203 // 流的交互过程 204 cstream.Send() 205 cstream.Recv() 206 ``` 207 208 ### 服务端 209 210 在服务端配置流式拦截器,需要实现`server.StreamFilter` 211 212 ```golang 213 type StreamFilter func(Stream, *StreamServerInfo, StreamHandler) error 214 ``` 215 216 以流式交互过程中的耗时统计上报拦截器进行举例说明如何开发流式拦截器 217 218 **第一步**:实现`server.StreamFilter` 219 220 ```golang 221 func StreamServerFilter(ss server.Stream, si *server.StreamServerInfo, handler server.StreamHandler) error { 222 begin := time.Now() // 进入流式处理之前,打点记录时间戳 223 224 // wrappedStream 封装了 server.Stream,用于后续拦截 SendMsg、RecvMsg 等方法 225 ws := &wrappedStream{ss} 226 227 // 注意这里必须用户自己调用 handler 执行下一个拦截器,除非有特定目的需要直接返回。 228 err := handler(ws) 229 230 cost := time.Since(begin) // 处理函数退出后,计算耗时 231 232 // 上报耗时到具体监控平台 233 234 return err // 注意这里必须返回 handler 的 err 235 } 236 ``` 237 238 **第二步**:封装 `server.Stream`,重写对应方法 239 240 因为流式服务的交互过程中服务端端有`SendMsg`、`RecvMsg`这些方法,为了拦截这些交互过程,需要引入一个新结构体。用户需要为这个结构体重写`server.Stream`接口,框架调用`server.Stream`接口时,会执行这个结构体的对应方法,这样就实现了拦截。 241 242 因为用户可能不需要拦截`server.Stream`的所有方法,所以可以将`server.Stream`设置为结构体的匿名字段,这样,不需要拦截的方法,会直接走原始的路径。用户需要拦截哪些方法,就在这个结构体中重写那些方法。 243 244 例如我只想拦截发送数据的过程,那么只需要重写`SendMsg`方法,至于`server.Stream`其他的方法都不需要实现。这里是为了演示,所以实现了`server.Stream`的所有方法。 245 246 ```golang 247 // wrappedStream 封装原始流,需要拦截哪些方法,就重写哪些方法 248 type wrappedStream struct { 249 server.Stream // 必须内嵌 server.Stream 250 } 251 252 // 重写 RecvMsg,用来拦截流的所有 RecvMsg 调用 253 func (w *wrappedStream) RecvMsg(m interface{}) error { 254 begin := time.Now() // 接收数据之前,打点记录时间戳 255 256 err := w.Stream.RecvMsg(m) // 注意这里必须用户自己调用 RecvMsg 让底层流接收数据,除非有特定目的需要直接返回 257 258 cost := time.Since(begin) // 接收到数据后,计算耗时 259 260 // 上报耗时到具体监控平台 261 262 return err // 注意这里必须返回前面产生的 err 263 } 264 265 // 重写 SendMsg,用来拦截流的所有 SendMsg 调用 266 func (w *wrappedStream) SendMsg(m interface{}) error { 267 begin := time.Now() // 发送数据之前,打点记录时间戳 268 269 err := w.Stream.SendMsg(m) // 注意这里必须用户自己调用 SendMsg 让底层流接收数据,除非有特定目的需要直接返回 270 271 cost := time.Since(begin) // 发送数据后,计算耗时 272 273 // 上报耗时到具体监控平台 274 275 return err // 注意这里必须返回前面产生的 err 276 } 277 ``` 278 279 **第三步**:将拦截器配置到 server,可以通过配置文件配置或者在代码中配置 280 281 方式 1: 在配置文件配置 282 283 先将拦截器注册到框架中 284 285 ```golang 286 server.RegisterStreamFilter("name1", StreamServerFilter) // 拦截器名字自己随便定义,供后续配置文件使用,必须放在 trpc.NewServer() 之前 287 ``` 288 289 再在配置文件中配置 290 291 ```yaml 292 server: 293 stream_filter: # 对所有 service 全部生效 294 - name1 # 上面注册到框架中的 server 流式拦截器名字 295 service: 296 - name: trpc.app.server.service 297 stream_filter: # 只对当前 service 生效 298 - name2 299 ``` 300 301 方式 2: 在代码中配置 302 303 ```golang 304 // 通过 server.WithStreamFilters 将拦截器添加进去 305 s := trpc.NewServer(server.WithStreamFilters(StreamServerFilter)) 306 307 pb.RegisterGreeterService(s, &greeterServiceImpl{}) 308 if err := s.Serve(); err != nil { 309 log.Fatal(err) 310 } 311 ``` 312 313 ## FAQ 314 315 ### Q:拦截器入口这里能否拿到二进制数据 316 317 不可以,拦截器入口这里的 req rsp 都是已经经过序列化过的结构体了,可以直接使用数据,没有二进制。 318 319 ### Q:多个拦截器执行顺序如何 320 321 多个拦截器的执行顺序按配置文件中的数组顺序执行,如 322 323 ```yaml 324 server: 325 filter: 326 - filter1 327 - filter2 328 service: 329 - name: trpc.app.server.service 330 filter: 331 - filter3 332 ``` 333 334 则执行顺序如下: 335 336 ``` 337 接收到请求 -> filter1 前置逻辑 -> filter2 前置逻辑 -> filter3 前置逻辑 -> 用户的业务处理逻辑 -> filter3 后置逻辑 -> filter2 后置逻辑 -> filter1 后置逻辑 -> 回包 338 ``` 339 340 ### Q:一个拦截器必须同时设置 server 和 client 吗 341 342 不需要,只需要 server 时,client 传入 nil,同理只需要 client 时,server 传入 nil,如 343 344 ```golang 345 filter.Register("name1", serverFilter, nil) // 注意,此时的 name1 拦截器只能配置在 server 的 filter 列表里面,配置到 client 里面,rpc 请求会报错 346 filter.Register("name2", nil, clientFilter) // 注意,此时的 name2 拦截器只能配置在 client 的 filter 列表里面,配置到 server 里面会启动失败 347 ```