Client 与 Transport 中间件

使用场景

RequestMiddleware 只能在发起请求之前执行,ResponseMiddleware 只能在响应之后执行,而在某些场景需要在一个中间件函数里的逻辑,同时覆盖请求前与响应后,比如 tracing 场景,一般在函数开头创建 span,然后用 defer span.End() 确保 span 在函数结束时终止,以便统计函数执行时间。如果在 RequestMiddlewareResponseMiddleware 创建 span,也只能统计到中间件自身执行时间的耗时,无法统计到整个请求的耗时。这个时候我们就可以使用 Client 中间件或 Transport 中间件,在请求前创建 span,记录请求信息到 span,在响应后记录响应信息到 span,最后才结束 span 以便统计请求整体耗时。

另外如果是只用 Transport,不用 Client 的场景(通常是用 req 的 Transport 来替换现有项目的 http Transport),给 Client 设置的 RequestMiddlewareResponseMiddleware 是无意义的,这时候就可以用 Transport 中间件来实现为存量项目代码,增加为所有请求统一记录 log、metrics、tracing 等能力。

Client 中间件与 Transport 中间件的区别

它们很相似,都可以同时覆盖请求前与响应后,不同的是,Client 中间内部可以访问到 *req.Request*req.Response,而 Transport 中间件只能访问到 *http.Request*http.Response,后者只是前者的功能子集,所以 Client 中间件更强大,其中有一个比较关键的好处是,在 Client 中间件内部即使获取请求体和响应体也不会影响正常请求,而如果在 Transport 中间件将请求体或响应体读取走就会造成破坏性影响: 服务端收不到请求体,客户端上层调用方收不到响应体。

如何选择?肯定首选 Client 中间件。

那什么情况下用 Transport 中间件?如果是与其它现有的,可以自定义 Transport 的库进行集成,或者是有历史包袱的项目,不方便直接将底层 http 请求库替换成 req,但又想要利用 req 的 Transport 中间件能力,统一为所有请求记录 log、metrics、tracing 之类的信息,这种情况就适合用 Transport 中间件。

Client 中间件用法

Client 提供一个便捷的方法 WrapRoundTripFunc 直接将函数作为中间件传入:

client := req.C()
client.WrapRoundTripFunc(func(rt req.RoundTripper) req.RoundTripFunc {
	return func(req *req.Request) (resp *req.Response, err error) {
		// before request
		// ...
		resp, err = rt.RoundTrip(req)
		// after response
		// ...
		return
	}
})

如有需要,你也可以自己实现接口,用 WrapRoundTrip 传入中间件。

Transport 中间件用法

Transport 提供一个便捷的方法 WrapRoundTripFunc 直接将函数作为中间件传入:

transport := req.C().GetTransport()
transport.WrapRoundTripFunc(func(rt http.RoundTripper) req.HttpRoundTripFunc {
	return func(req *http.Request) (resp *http.Response, err error) {
		// before request
		// ...
		resp, err = rt.RoundTrip(req)
		// after response
		// ...
		return
	}
})

如有需要,你也可以自己实现接口,用 WrapRoundTrip 传入中间件。

示例