你可以在你的 Slim 应用之前(before) 和 之后(after) 运行代码来处理你认为合适的请求和响应对象。这就叫做中间件。为什么要这么做呢?比如你想保护你的应用不遭受跨站请求伪造。也许你想在应用程序运行前验证请求。中间件对这些场景的处理简直完美。
什么是中间件?
从技术上来讲,中间件是一个接收三个参数的可回调(callable)对象:
\Psr\Http\Message\ServerRequestInterface
- PSR7 请求对象
\Psr\Http\Message\ResponseInterface
- PSR7 响应对象
callable
- 下一层中间件的回调对象
它可以做任何与这些对象相应的事情。唯一硬性要求就是中间件必须返回一个 \Psr\Http\Message\ResponseInterface
的实例。
每个中间件都 应当调用下一层中间件,并讲请求和响应对象作为参数传递给它(下一层中间件)。
中间件是如何工作的?
不同的框架使用中间件的方式不同。在 Slim 框架中,中间件层以同心圆的方式包裹着核心应用。由于新增的中间件层总会包裹所有已经存在的中间件层。当添加更多的中间件层时同心圆结构会不断的向外扩展。
当 Slim 应用运行时,请求对象和响应对象从外到内穿过中间件结构。它们首先进入最外层的中间件,然后然后进入下一层,(以此类推)。直到最后它们到达了 Slim 应用程序自身。在 Slim 应用分派了对应的路由后,作为结果的响应对象离开 Slim 应用,然后从内到外穿过中间件结构。最终,最后出来的响应对象离开了最外层的中间件,被序列化为一个原始的 HTTP 响应消息,并返回给 HTTP 客户端。下图清楚的说明了中间件的工作流程:
一起看下Slim是如何处理中间件的,在应用主体类App中
class App
{
use MiddlewareAwareTrait;
......
继续研读该特制的实现过程
/**
* Middleware
* 这是一个启用同心中间件的内部类。
* This is an internal class that enables concentric middleware layers. This
* class is an implementation detail and is used only inside of the Slim
* application; it is not visible to—and should not be used by—end users.
*/
trait MiddlewareAwareTrait
{
/**
* 中间件调用堆栈的栈顶
* Tip of the middleware call stack
*
* @var callable
*/
protected $tip;
/**
* 中间件栈锁
* Middleware stack lock
*
* @var bool
*/
protected $middlewareLock = false;
/**
* 增加中间件
* Add middleware
* 这个方法增加一个新的中间件到应用中间件栈
* This method prepends new middleware to the application middleware stack.
* 中间件以回调形式存在:包括:1.一个请求对象 2.一个响应对象 3.next中间件回调函数
* @param callable $callable Any callable that accepts three arguments:
* 1. A Request object
* 2. A Response object
* 3. A "next" middleware callable
* @return static
*
* @throws RuntimeException If middleware is added while the stack is dequeuing
* @throws UnexpectedValueException If the middleware doesn't return a Psr\Http\Message\ResponseInterface
*/
protected function addMiddleware(callable $callable)
{
// 若中间件锁住,抛出异常
if ($this->middlewareLock) {
throw new RuntimeException('Middleware can’t be added once the stack is dequeuing');
}
// 栈顶元素为空,则初始化栈(实质先存储一个元素),该步骤之后
// $this->tip保存当前应用主体类$app实例
if (is_null($this->tip)) {
$this->seedMiddlewareStack();
}
// 这一步是关键,不断从栈中取出元素(中间件)
// call_user_func的$next参数为上一中间件(闭包)的返回值
$next = $this->tip;
$this->tip = function (
ServerRequestInterface $request,
ResponseInterface $response
) use (
$callable,
$next
) {
$result = call_user_func($callable, $request, $response, $next);
if ($result instanceof ResponseInterface === false) {
throw new UnexpectedValueException(
'Middleware must return instance of \Psr\Http\Message\ResponseInterface'
);
}
return $result;
};
return $this;
}
/**
* 使用一个回调填充中间件栈
* Seed middleware stack with first callable
*
* @param callable $kernel The last item to run as middleware
*
* @throws RuntimeException if the stack is seeded more than once
*/
protected function seedMiddlewareStack(callable $kernel = null)
{ // 填充前提是栈顶元素为空
if (!is_null($this->tip)) {
throw new RuntimeException('MiddlewareStack can only be seeded once.');
}
// 将当前类(App)实例赋值给$kernel
if ($kernel === null) {
$kernel = $this;
}
// $this->tip元素即为当前应用主体app实例
$this->tip = $kernel;
}
/**
* 调用中间件
* Call middleware stack
*
* @param ServerRequestInterface $request A request object
* @param ResponseInterface $response A response object
*
* @return ResponseInterface
*/
public function callMiddlewareStack(ServerRequestInterface $request, ResponseInterface $response)
{
if (is_null($this->tip)) {
$this->seedMiddlewareStack();
}
/** @var callable $start */
// 取出栈顶元素(闭包),取的过程要锁栈,取出之后再打开
// 锁栈的过程是不可增加中间件的
$start = $this->tip;
$this->middlewareLock = true;
$response = $start($request, $response);
$this->middlewareLock = false;
return $response;
}
}