中间件 Middleware

中间件是用于控制 请求到达响应请求 的整个流程的,通常用于对请求进行过滤验证处理,当你需要对请求或响应作出对应的修改或处理,或想调整请求处理的流程时均可以使用中间件来实现。
中间件

定义中间件

只需要实现了 Swoft\Http\Message\Middleware\MiddlewareInterface 接口均为一个合法的中间件,其中 process() 方法为该中间件逻辑处理方法, 可以参考 Swoft 项目呢 app/Middlewares/ 目录下的文件, 比如 app/Middlewares/ActionTestMiddleware:

  1. <?php
  2. namespace App\Middlewares;
  3. use Psr\Http\Server\RequestHandlerInterface;
  4. use Psr\Http\Message\ResponseInterface;
  5. use Psr\Http\Message\ServerRequestInterface;
  6. use Swoft\Bean\Annotation\Bean;
  7. use Swoft\Http\Message\Middleware\MiddlewareInterface;
  8. /**
  9. * @Bean()
  10. */
  11. class ActionTestMiddleware implements MiddlewareInterface
  12. {
  13. /**
  14. * Process an incoming server request and return a response, optionally delegating
  15. * response creation to a handler.
  16. *
  17. * @param \Psr\Http\Message\ServerRequestInterface $request
  18. * @param \Psr\Http\Server\RequestHandlerInterface $handler
  19. * @return \Psr\Http\Message\ResponseInterface
  20. * @throws \InvalidArgumentException
  21. */
  22. public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
  23. {
  24. $response = $handler->handle($request);
  25. return $response->withAddedHeader('Middleware-Action-Test', 'success');
  26. }
  27. }

使用

配置全局中间件

当你的自定义中间件需要全局请求应用,则可以考虑将此中间件作为全局中间件去使用,只需在 Bean 配置文件内配置 serverDispatchermiddlewares 属性,在数组中加入你的自定义中间件的命名空间地址,相关配置通常在 app/config/beans/base.php

  1. // 全局中间件配置: app/config/beans/base.php
  2. return [
  3. ...
  4. 'serverDispatcher' => [
  5. 'middlewares' => [
  6. \Swoft\View\Middleware\ViewMiddleware::class,
  7. \Swoft\Session\Middleware\SessionMiddleware::class,
  8. ]
  9. ],
  10. ...
  11. ];

通过注解使用

通过 @Middleware@Middlewares, 可以很方便的配置中间件到当前的 ControllerAction

  • 当将此注解应用于 Controller 上,则作用域为整个 Controller
  • 将此注解应用于 Action 上,则作用域仅为当前的 Action
  • @Middleware 用于配置单个中间件
  • @Middlewares 显而易见的是用于配置一组 @Middleware,按照定义顺序依次执行, 使用参考 app/Controllers/MiddlewareController.php
  1. <?php
  2. namespace App\Controllers;
  3. use Swoft\Http\Server\Bean\Annotation\Controller;
  4. use Swoft\Http\Message\Bean\Annotation\Middleware;
  5. use Swoft\Http\Message\Bean\Annotation\Middlewares;
  6. use Swoft\Http\Server\Bean\Annotation\RequestMapping;
  7. use App\Middlewares\GroupTestMiddleware;
  8. use App\Middlewares\ActionTestMiddleware;
  9. use App\Middlewares\SubMiddleware;
  10. use App\Middlewares\ControlerSubMiddleware;
  11. use App\Middlewares\ControlerTestMiddleware;
  12. /**
  13. * @Controller("middleware")
  14. * @Middleware(class=ControlerTestMiddleware::class)
  15. * @Middlewares({
  16. * @Middleware(ControlerSubMiddleware::class)
  17. * })
  18. */
  19. class MiddlewareController
  20. {
  21. /**
  22. * @RequestMapping()
  23. * @Middlewares({
  24. * @Middleware(GroupTestMiddleware::class),
  25. * @Middleware(ActionTestMiddleware::class)
  26. * })
  27. * @Middleware(SubMiddleware::class)
  28. */
  29. public function action1(): array
  30. {
  31. return ['middleware'];
  32. }
  33. /**
  34. * @RequestMapping()
  35. * @Middleware(SubMiddleware::class)
  36. * @Middlewares({
  37. * @Middleware(GroupTestMiddleware::class),
  38. * @Middleware(ActionTestMiddleware::class)
  39. * })
  40. */
  41. public function action2(): array
  42. {
  43. return ['middleware2'];
  44. }
  45. /**
  46. * @RequestMapping()
  47. */
  48. public function action3(): array
  49. {
  50. return ['middleware3'];
  51. }
  52. }

中间件中断返回

当在实现验证检查类的中间件时,经常需要中断当前请求并直接给出响应,以下是中断流程的几种方式

构造一个新的 Response 对象直接返回

  1. public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
  2. {
  3. $auth = false;
  4. // 如果验证不通过
  5. if (!$auth) {
  6. // response() 函数可以快速从 RequestContext 获得 Response 对象
  7. return response()->withStatus(401);
  8. }
  9. // 委托给下一个中间件处理
  10. $response = $handler->handle($request);
  11. return $response;
  12. }

抛出异常返回

只要在请求生命周期内抛出的异常会被 ErrorHandler 捕获并处理,中间件内抛出也是如此,这部分不属于中间件的内容,顾在此不多做阐述。

示例:提前拦截请求

注意: 拦截要在 $handler->handle($request) 之前

  1. <?php
  2. namespace App\Middlewares;
  3. use Psr\Http\Server\RequestHandlerInterface;
  4. use Psr\Http\Message\ResponseInterface;
  5. use Psr\Http\Message\ServerRequestInterface;
  6. use Swoft\Bean\Annotation\Bean;
  7. use Swoft\Http\Message\Middleware\MiddlewareInterface;
  8. /**
  9. * @Bean()
  10. */
  11. class SomeMiddleware implements MiddlewareInterface
  12. {
  13. /**
  14. * Process an incoming server request and return a response, optionally delegating
  15. * response creation to a handler.
  16. *
  17. * @param \Psr\Http\Message\ServerRequestInterface $request
  18. * @param \Psr\Http\Server\RequestHandlerInterface $handler
  19. * @return \Psr\Http\Message\ResponseInterface
  20. * @throws \InvalidArgumentException
  21. */
  22. public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
  23. {
  24. $path = $request->getUri()->getPath();
  25. if ($path === '/favicon.ico') {
  26. return \response()->withStatus(404);
  27. }
  28. return $handler->handle($request);
  29. }

示例:允许跨域

  1. <?php
  2. namespace App\Middlewares;
  3. use Psr\Http\Server\RequestHandlerInterface;
  4. use Psr\Http\Message\ResponseInterface;
  5. use Psr\Http\Message\ServerRequestInterface;
  6. use Swoft\Bean\Annotation\Bean;
  7. use Swoft\Http\Message\Middleware\MiddlewareInterface;
  8. /**
  9. * @Bean()
  10. */
  11. class CorsMiddleware implements MiddlewareInterface
  12. {
  13. /**
  14. * Process an incoming server request and return a response, optionally delegating
  15. * response creation to a handler.
  16. *
  17. * @param \Psr\Http\Message\ServerRequestInterface $request
  18. * @param \Psr\Http\Server\RequestHandlerInterface $handler
  19. * @return \Psr\Http\Message\ResponseInterface
  20. * @throws \InvalidArgumentException
  21. */
  22. public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
  23. {
  24. if ('OPTIONS' === $request->getMethod()) {
  25. return $this->configResponse(\response());
  26. }
  27. $response = $handler->handle($request);
  28. return $this->configResponse($response);
  29. }
  30. private function configResponse(ResponseInterface $response)
  31. {
  32. return $response
  33. ->withHeader('Access-Control-Allow-Origin', 'http://mysite')
  34. ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
  35. ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
  36. }
  37. }