拦截器责任链也是函数式编程


拦截器是一种强大的机制,可以监视、重写和重试调用。
拦截器可以被链接起来。假设您同时拥有压缩拦截器和校验和拦截器:您需要决定是压缩数据然后进行校验和,还是进行校验和然后压缩。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)
  }


实施的关键是相同的:

  • 它们都遍历拦截器
  • 它们都递归调用 proceed

关键区别在于我们从两个函数式编程中获得的提升:
  • 我们依靠内部可变性传递一个迭代器,而不是手动索引一个 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 版中也依然存在。