中间件

你可以在你的 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 客户端。下图清楚的说明了中间件的工作流程:

Middleware architecture

如何编写中间件?

中间件是一个接收三个参数的可回调(callable)对象,三个参数是:请求对象、响应对象以及下一层中间件。中间件必须返回一个 \Psr\Http\Message\ResponseInterface 的实例。

闭包中间件示例。

这个示例中间件是一个闭包。

  1. <?php
  2. /**
  3. * Example middleware closure
  4. *
  5. * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request
  6. * @param \Psr\Http\Message\ResponseInterface $response PSR7 response
  7. * @param callable $next Next middleware
  8. *
  9. * @return \Psr\Http\Message\ResponseInterface
  10. */
  11. function ($request, $response, $next) {
  12. $response->getBody()->write('BEFORE');
  13. $response = $next($request, $response);
  14. $response->getBody()->write('AFTER');
  15. return $response;
  16. };

可调用类的中间件示例。

这个示例中间件是一个实现了 __invoke() 魔术方法的可调用类。

  1. <?php
  2. class ExampleMiddleware
  3. {
  4. /**
  5. * Example middleware invokable class
  6. *
  7. * @param \Psr\Http\Message\ServerRequestInterface $request PSR7 request
  8. * @param \Psr\Http\Message\ResponseInterface $response PSR7 response
  9. * @param callable $next Next middleware
  10. *
  11. * @return \Psr\Http\Message\ResponseInterface
  12. */
  13. public function __invoke($request, $response, $next)
  14. {
  15. $response->getBody()->write('BEFORE');
  16. $response = $next($request, $response);
  17. $response->getBody()->write('AFTER');
  18. return $response;
  19. }
  20. }

要使用这个类作为中间件,你可以使用 -&gt;add( new ExampleMiddleware() ); 函数连接在 $app, Route, 或 group() 之后,在下面的代码中,它们中的任意一个都可以表示 $subject。

  1. $subject->add( new ExampleMiddleware() );

如何添加中间件?

你可以在 Slim 应用中添加中间件,或者添加到一个单独的 Slim 应用路由,或者是路由组。所有场景都能接受相同的中间件和实现相同的中间件接口。

应用程序中间件

应用程序中间件被每个传入(incoming) HTTP 请求调用。应用中间件可以通过 Slim 应用实例的 add() 方法来添加。下面这是一个添加上面那个闭包中间件的例子:

  1. <?php
  2. $app = new \Slim\App();
  3. $app->add(function ($request, $response, $next) {
  4. $response->getBody()->write('BEFORE');
  5. $response = $next($request, $response);
  6. $response->getBody()->write('AFTER');
  7. return $response;
  8. });
  9. $app->get('/', function ($req, $res, $args) {
  10. echo ' Hello ';
  11. });
  12. $app->run();

输出的 HTTP 响应主体为:

  1. BEFORE Hello AFTER

路由中间件

路由中间件只有在当前 HTTP 请求的方法和 URI 都与中间件所在路由相匹配时才会被调用。路由中间件会在你调用了任意 Slim 应用的路由方法(e.g., get()post())后被立即指定。 每个路由方法返回一个 \Slim\Route 的实例,这个类提供了相同的中间件接口作为 Slim 应用的实例。使用路由实例的 add() 方法将中间件添加到路由中。下面这是一个添加上面那个闭包中间件的例子:

  1. <?php
  2. $app = new \Slim\App();
  3. $mw = function ($request, $response, $next) {
  4. $response->getBody()->write('BEFORE');
  5. $response = $next($request, $response);
  6. $response->getBody()->write('AFTER');
  7. return $response;
  8. };
  9. $app->get('/', function ($req, $res, $args) {
  10. echo ' Hello ';
  11. })->add($mw);
  12. $app->run();

输出的 HTTP 响应主体为:

  1. BEFORE Hello AFTER

Group Middleware

除了整个应用,以及标准的路由可用接收中间件,还有 group() 多路由定义功能同样允许在其内部存在独立的路由。路由组中间件只有在其路由与已定义的 HTTP 请求和 URI 中的一个相匹配时才会被调用。要在回调中添加中间件,以及整组中间件,通过在 group() 方法后面连接 add() 来设置。

下面的示例程序,在一组 URL 处理程序(url-handlers)中使用了回调中间件。

  1. <?php
  2. require_once __DIR__.'/vendor/autoload.php';
  3. $app = new \Slim\App();
  4. $app->get('/', function ($request, $response) {
  5. return $response->getBody()->write('Hello World');
  6. });
  7. $app->group('/utils', function () use ($app) {
  8. $app->get('/date', function ($request, $response) {
  9. return $response->getBody()->write(date('Y-m-d H:i:s'));
  10. });
  11. $app->get('/time', function ($request, $response) {
  12. return $response->getBody()->write(time());
  13. });
  14. })->add(function ($request, $response, $next) {
  15. $response->getBody()->write('It is now ');
  16. $response = $next($request, $response);
  17. $response->getBody()->write('. Enjoy!');
  18. return $response;
  19. });

在调用 /utils/date 方法时,将输出类似下面这样子的字符串:

  1. It is now 2015-07-06 03:11:01\. Enjoy!

访问 /utils/time 将会输出类似下面这样子的字符串:

  1. It is now 1436148762\. Enjoy!

但访问 / 域名根目录(domain-root)时,将会有如下输出,因为并没有分配中间件。

  1. Hello World