中间件

中间件(Middleware)采用洋葱模型,在路由处理器前后插入横切逻辑(鉴权、限流、日志、跨域等)。Viswoole 的中间件分为三个层级:全局中间件服务级中间件路由级中间件,请求按「全局 → 服务级 → 路由级」的顺序依次穿过。

注册中间件

通过 Viswoole\Core\Facade\Middleware 门面的 register() 方法注册:

php
Middleware::register(callable|string|array $handler, ?string $server = null): void
参数类型默认值说明
handlercallable|string|array中间件处理器,支持下文三种形式
serverstring|nullnull服务名称;null 时注册为全局中间件,否则注册为对应服务的服务级中间件
php
use Viswoole\Core\Facade\Middleware;

// 全局中间件:作用于所有服务
Middleware::register(LogMiddleware::class);

// 服务级中间件:仅作用于名为 http 的服务
Middleware::register(AuthMiddleware::class, 'http');

INFO

路由在服务启动时装配,请求分发阶段才会执行中间件,因此注册代码需早于首次请求处理——推荐放在服务提供者的 boot()config/listens.php 注册的事件监听中。中间件注册属于进程级配置,修改后需重启服务。

中间件的三种形式

1. 闭包

闭包中间件适合轻量逻辑,参数由容器按类型注入,其中名为 $handler 的参数接收「下一个中间件 + 处理器」组成的闭包,必须调用 $handler() 才能继续执行后续链路

php
use Closure;
use Viswoole\Core\Facade\Middleware;
use Viswoole\HttpServer\Contract\{RequestInterface, ResponseInterface};

Middleware::register(function (RequestInterface $request, ResponseInterface $response, Closure $handler) {
  // 前置逻辑
  $response->setHeader('X-Request-Time', (string) time());
  $result = $handler();  // 不调用则请求在此终止
  // 后置逻辑
  return $result;
}, 'http');

2. 类中间件(推荐)

类中间件必须实现 Viswoole\Core\Contract\MiddlewareInterface,核心逻辑写在 process() 方法:

php
namespace App\Middleware;

use Closure;
use Override;
use Viswoole\Core\Contract\MiddlewareInterface;

class AuthMiddleware implements MiddlewareInterface
{
  /**
   * @param RequestInterface $request 请求实例(构造参数由容器注入)
   * @param ResponseInterface $response 响应实例
   */
  public function __construct(
    protected RequestInterface  $request,
    protected ResponseInterface $response
  )
  {
  }

  /**
   * @param Closure $handler 下一个中间件的处理闭包
   */
  #[Override] public function process(Closure $handler): mixed
  {
    // 未携带令牌则直接短路返回,不再调用 $handler
    if (empty($this->request->getHeader('authorization'))) {
      return ['code' => 401, 'message' => '请先登录'];
    }
    return $handler();
  }
}

构造参数由容器自动解析;类中间件实例每个请求新建,可在构造器中注入请求级依赖:

php
use Viswoole\Core\Facade\Middleware;

Middleware::register(AuthMiddleware::class, 'http');

3. 类中间件 + 显式构造参数

需要向构造函数传入参数时使用 [类名, ['构造参数名' => 值]] 格式。显式传入的参数与容器自动解析的依赖按名称合并,未传入的仍由容器注入;类若定义了 public static factory() 则按容器 make 语义注入 factory 方法:

php
use Viswoole\Core\Facade\Middleware;

// 实例化 RateLimitMiddleware(['limit' => 10]),其余构造参数仍由容器解析
Middleware::register([RateLimitMiddleware::class, ['limit' => 10]], 'http');

WARNING

[对象实例, ['参数' => 值]] 不受支持——实例已创建无法再传构造参数,注册时会直接抛出异常。可调用形式仅支持 [类或实例, '方法名'] 与全局函数名等标准 callable。

执行顺序

请求分发时中间件按以下顺序合并成一条管道:

  1. 全局中间件Middleware::register($handler) 注册
  2. 服务级中间件Middleware::register($handler, $serverName) 注册
  3. 路由级中间件:路由实例的 setMiddlewares(),或注解路由的 middlewares 参数(组级中间件会被子路由继承)
text
请求 → 全局中间件 → 服务级中间件 → 路由级中间件 → 路由处理器
响应 ← 全局中间件 ← 服务级中间件 ← 路由级中间件 ← 处理器返回

同层级内按注册/声明顺序执行。

路由级中间件

php
// 编程式:链式追加(可多次调用,追加合并)
Router::get('/user/{id}', [UserController::class, 'detail'])
  ->setMiddlewares([AuthMiddleware::class]);

// 注解式:Controller 类级 + RouteMapping 方法级,方法级追加在类级之后
#[Controller(prefix: 'user', middlewares: [LogMiddleware::class])]
class UserController
{
  #[RouteMapping(method: 'GET', middlewares: [AuthMiddleware::class])]
  public function detail(): array {}
  // 执行顺序:LogMiddleware → AuthMiddleware
}

分组中间件详见编程式路由

内置跨域中间件

框架内置 Viswoole\Core\Middlewares\AllowCrossDomain:为所有响应添加 CORS 头,并对 OPTIONS 预检请求短路直接返回响应(不再进入路由处理器):

  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Headers: *
  • Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
  • Access-Control-Max-Age: 86400(预检结果缓存一天)

需要跨域支持时注册即可:

php
use Viswoole\Core\Middlewares\AllowCrossDomain;
use Viswoole\Core\Facade\Middleware;

Middleware::register(AllowCrossDomain::class, 'http');

INFO

即使未注册跨域中间件,框架对 OPTIONS 预检请求也会跳过「请求方法不允许」校验并放行进入中间件管道,由跨域中间件负责短路响应,避免预检被 404 拦截。

下一步

  • 编程式路由setMiddlewares() 与分组中间件
  • 注解路由:注解的 middlewares 参数(不支持闭包)
  • 容器:理解中间件构造参数的解析规则