Middleware 扩展

介绍

Middleware 是扩展 Kitex 框架的一个主要的方法,大部分基于 Kitex 的扩展和二次开发的功能都是基于 middleware 来实现的。

在扩展过程中,要记得两点原则:

  1. 中间件和套件都只允许在初始化 Server、Client 的时候设置,不允许动态修改。
  2. Middleware 是按照添加的先后顺序执行的。

Kitex 的中间件定义在 pkg/endpoint/endpoint.go 中,其中最主要的是两个类型:

  1. Endpoint 是一个函数,接受 ctx、req、resp,返回 err,可参考下方示例;
  2. Middleware(下称 MW)也是一个函数,接收同时返回一个 Endpoint

实际上一个中间件就是一个输入是 Endpoint,输出也是 Endpoint 的函数,这样保证了对应用的透明性,应用本身并不会知道是否被中间件装饰的。由于这个特性,中间件可以嵌套使用。

中间件是串连使用的,通过调用传入的 next,可以得到后一个中间件返回的 response(如果有)和 err,据此作出相应处理后,向前一个中间件返回 err(务必判断 next err 返回,勿吞了 err)或者设置 response。

客户端中间件

有两种方法可以添加客户端中间件:

  1. client.WithMiddleware 对当前 client 增加一个中间件,在 Service 熔断和超时中间件之后执行;
  2. client.WithInstanceMW 对当前 client 增加一个中间件,在服务发现、负载均衡之后执行,如果有实例熔断器,会在实例熔断器后执行(如果使用了 Proxy 则不会调用到,如 Mesh 模式下)。

注意,上述函数都应该在创建 client 时作为传入的 Option

客户端中间件调用顺序 :

  1. client.WithMiddleware 设置的中间件
  2. ACLMiddleware
  3. (ResolveMW + client.WithInstanceMW + PoolMW / DialerMW) / ProxyMW
  4. IOErrorHandleMW

调用返回的顺序则相反。

客户端所有中间件的调用顺序可以看 client/client.go

Context 中间件

Context 中间件本质上也是一种客户端中间件,但是区别是,其由 ctx 来控制是否注入以及注入哪些中间件。

Context 中间件的引入是为了提供一种能够全局或者动态注入 Client 中间件的方法,典型的使用场景比如统计某个接口调用了哪些下游。但是这种全局性设置只会在 ctx 调用链中存在,可以规避第三方库注入不可控的中间件引起的问题。

可以通过 ctx = client.WithContextMiddlewares(ctx, mw) 来向 ctx 注入中间件。

注意:Context 中间件会在 Client 中间件之前执行。

服务端中间件

服务端的中间件和客户端有一定的区别。

可以通过 server.WithMiddleware 来增加 server 端的中间件,使用方式和 client 一致,在创建 server 时通过 Option 传入。

总的服务端中间件的调用顺序可以看 server/server.go

示例

我们可以通过以下这个例子来看一下如何使用中间件。

假如我们现在有需求,需要在请求前打印出 request 内容,再请求后打印出 response 内容,可以编写如下的 MW:

func PrintRequestResponseMW(next endpoint.Endpoint) endpoint.Endpoint {
    return func(ctx context.Context, request, response interface{}) error {
        fmt.Printf("request: %v\n", request)
        err := next(ctx, request, response)
        fmt.Printf("response: %v", response)
        return err
    }
}

假设我们是 Server 端,就可以使用 server.WithMiddleware(PrintRequestResponseMW) 来使用这个 MW 了。

以上方案仅为示例,不可用于生产,会有性能问题。

注意事项

如果自定义 middleware 中用到了 RPCInfo,要注意 RPCInfo 在 rpc 结束之后会被回收,所以如果在 middleware 中起了 goroutine 操作 RPCInfo 会出问题,不能这么做。


最后修改 July 3, 2023 : Update response.md (0a36fb2)