拦截器是一种强大的机制,可以监视、重写和重试调用。
拦截器可以被链接起来。假设您同时拥有压缩拦截器和校验和拦截器:您需要决定是压缩数据然后进行校验和,还是进行校验和然后压缩。OkHttp使用列表来跟踪拦截器,拦截器按顺序调用。
面向对象
用 Python 编写了一个简洁明了的接口示例:
@dataclass class Request: msg: str
@dataclass class Response: msg: str
class Chain(Protocol): def proceed(self, request: Request) -> Response: ... request: Request
class Interceptor(Protocol): def intercept(self, chain: Chain) -> Response: ...
|
仅凭这两个接口(链Chain 和拦截器Interceptor),我们所能编译的有用行为只能受限于我们的想象力!
使用这两个接口案例:
class PrintRequestInterceptor(Interceptor): def intercept(self, chain: Chain) -> Response: print("Intercepted outgoing request: [", chain.request, "]") return chain.proceed(chain.request)
class BaseInterceptor(Interceptor): def intercept(self, chain: Chain) -> Response: return Response("pong")
request = Request("ping") interceptors = [ PrintRequestInterceptor(), BaseInterceptor(), ] chain = RealInterceptorChain(request, interceptors) response = chain.proceed(request)
print(response)
# Yields this output: # # Intercepted outgoing request: [ Request(msg='ping') ] # Response(msg='pong')
|
函数式编程
OkHttp 是一个用 Kotlin 编写的 HTTP 网络库。这是一个很好的出发点,因为在过去的几个月里,我一直在(重新)编写一个同样用 Kotlin 编写的 NFC 网络库。因此,我将首先把上述接口转换为一种用 Kotlin 编写的函数式方法
data class Request(private val msg: String) data class Response(private val msg: String)
fun interface Interceptor { fun invoke(request: Request, proceed: (Request) -> Response): Response }
|
等等,"链Chain "怎么了?嗯,它被并入拦截器Interceptor 接口了。
让我们看看这些接口的最小演示也是如何变化的:
fun printRequestInterceptor() = Interceptor { request, proceed -> println("Intercepted outgoing request: [ $request ]") proceed(request) }
fun baseInterceptor() = Interceptor { _, _ -> Response("pong") }
val interceptors = listOf( printRequestInterceptor(), baseInterceptor(), )
val request = Request("ping") val response = chain(request, interceptors.iterator())
println(response)
// Yields this output: // // Intercepted outgoing request: [ Request(msg=ping) ] // Response(msg=pong)
|
在我看来,这两个接口及其用法几乎完全相同!
真正的区别在于迄今为止被忽略的 RealInterceptorChain / proceed 实现。
现在让我们来深入研究一下。
实现比较:
面向对象
为了清晰和公平起见,我再次调整和简化了的实现方法:
@dataclass class RealInterceptorChain(Chain): request: Request interceptors: typing.List[Interceptor] index: int = 0
def proceed(self, request: Request) -> Response: next_chain = RealInterceptorChain(request, self.interceptors, self.index + 1) interceptor = self.interceptors[self.index] return interceptor.intercept(next_chain)
函数式: fun proceed(request: Request, iterator: Iterator<Interceptor>): Response = iterator.next().invoke(request) { proceed(it, iterator) }
|
实施的关键是相同的:
关键区别在于我们从两个函数式编程中获得的提升:
- 我们依靠内部可变性传递一个迭代器,而不是手动索引一个 List
- 我们向一个函数(继续)递归,而不是创建一个新的 RealInterceptorChain 并调用它
注意Python 有一个迭代器协议;调整面向对象的示例来使用它是很简单的。其他:
Rust 中的 tower 库编译拦截器的方式与这里提到的纯函数式编译类似--tower::Layer<S> 类似于 (Effect) -> Effect,通过将传入的 "Effect"(tower 术语中的 "service")封装到另一个 "effect"中来运行。最终结果是一个函数。
actix 最初似乎受到了 "你的服务器是一个函数"(Your Server as a Function)的影响,在 0.2.0 版之前,它一直使用 andThen 组合器来处理过滤器。Transform 特质(与 tower::Layer 几乎相同)很快就被引入了,甚至在 2.0.2 版中也依然存在。