Skip to content

中间件

中间件(Middleware)是现代 PHP 框架中处理 HTTP 请求/响应的核心机制。它提供了一种在请求到达控制器之前或响应返回客户端之前执行通用逻辑的方式。中间件采用"洋葱模型"(或称"责任链"模式),每个中间件可以执行前置操作(如认证、限流)、将请求传递给下一个处理器、执行后置操作(如日志记录、响应修改)。本节将深入讲解中间件的概念、PSR-15 标准和各种实际应用场景。

前置知识

阅读本节前,建议先了解:

基础概念

什么是中间件

中间件是一个位于请求处理流程中的可插拔组件,可以在请求到达目标处理器之前或响应返回客户端之后执行逻辑。

HTTP 请求

中间件 A(前置:认证)

中间件 B(前置:限流)

控制器/处理器(核心业务逻辑)

中间件 B(后置:响应头)

中间件 A(后置:日志)

HTTP 响应

洋葱模型

         中间件 A(前置)
        ┌─────────────────┐
        │   中间件 B(前置)  │
        │  ┌─────────────┐ │
        │  │   控制器       │ │
        │  └─────────────┘ │
        │   中间件 B(后置)  │
        └─────────────────┘
         中间件 A(后置)

详细说明

PSR-15 中间件接口

php
<?php
namespace Psr\Http\Server;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

interface MiddlewareInterface
{
    /**
     * 处理请求并返回响应
     */
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface;
}

interface RequestHandlerInterface
{
    /**
     * 处理请求并返回响应
     */
    public function handle(ServerRequestInterface $request): ResponseInterface;
}

基本中间件实现

php
<?php
declare(strict_types=1);

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class AuthMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        // 前置操作:检查认证
        $token = $request->getHeaderLine('Authorization');

        if (!$token || !str_starts_with($token, 'Bearer ')) {
            return new \Nyholm\Psr7\Response(401, [], json_encode([
                'error' => 'Unauthorized',
            ]));
        }

        $userId = $this->validateToken(substr($token, 7));
        if ($userId === null) {
            return new \Nyholm\Psr7\Response(401, [], json_encode([
                'error' => 'Invalid token',
            ]));
        }

        // 将用户信息附加到请求
        $request = $request->withAttribute('user_id', $userId);

        // 传递给下一个处理器
        $response = $handler->handle($request);

        // 后置操作:可以修改响应
        return $response->withHeader('X-Auth-User', (string) $userId);
    }

    private function validateToken(string $token): ?int
    {
        // 验证 token 并返回用户 ID
        return 1; // 简化示例
    }
}

中间件链实现

php
<?php
declare(strict_types=1);

namespace App\Http;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class MiddlewareStack implements RequestHandlerInterface
{
    private \SplStack $stack;

    public function __construct()
    {
        $this->stack = new \SplStack();
    }

    public function add(MiddlewareInterface $middleware): void
    {
        $this->stack->push($middleware);
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        if ($this->stack->isEmpty()) {
            throw new \RuntimeException('No middleware or handler in the stack');
        }

        $middleware = $this->stack->pop();

        return $middleware->process($request, $this);
    }
}
php
<?php
// 使用中间件链
$stack = new MiddlewareStack();
$stack->add(new CorsMiddleware());
$stack->add(new AuthMiddleware());
$stack->add(new RateLimitMiddleware());
$stack->add(new RequestHandler()); // 最终处理器

$response = $stack->handle($request);

常见中间件类型

1. CORS 中间件

php
<?php
declare(strict_types=1);

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class CorsMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly array $allowedOrigins = ['*'],
        private readonly array $allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
        private readonly array $allowedHeaders = ['Content-Type', 'Authorization'],
        private readonly int $maxAge = 86400,
    ) {}

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        // 处理预检请求
        if ($request->getMethod() === 'OPTIONS') {
            return $this->createPreflightResponse();
        }

        $response = $handler->handle($request);
        return $this->addCorsHeaders($response);
    }

    private function createPreflightResponse(): ResponseInterface
    {
        $response = new \Nyholm\Psr7\Response(204);
        return $this->addCorsHeaders($response)
            ->withHeader('Access-Control-Max-Age', (string) $this->maxAge);
    }

    private function addCorsHeaders(ResponseInterface $response): ResponseInterface
    {
        $origin = $this->allowedOrigins[0];
        return $response
            ->withHeader('Access-Control-Allow-Origin', $origin)
            ->withHeader('Access-Control-Allow-Methods', implode(', ', $this->allowedMethods))
            ->withHeader('Access-Control-Allow-Headers', implode(', ', $this->allowedHeaders));
    }
}

2. 限流中间件

php
<?php
declare(strict_types=1);

namespace App\Middleware;

use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class RateLimitMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly CacheItemPoolInterface $cache,
        private readonly int $maxRequests = 60,
        private readonly int $decaySeconds = 60,
    ) {}

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $key = 'rate_limit:' . $request->getClientIP();
        $item = $this->cache->getItem($key);

        $attempts = $item->isHit() ? (int) $item->get() : 0;
        $attempts++;

        if ($attempts > $this->maxRequests) {
            return new \Nyholm\Psr7\Response(429, [
                'Content-Type' => 'application/json',
                'Retry-After' => (string) $item->getExpirationTimestamp(),
                'X-RateLimit-Limit' => (string) $this->maxRequests,
                'X-RateLimit-Remaining' => '0',
            ], json_encode(['error' => 'Too Many Requests']));
        }

        // 保存计数
        $item->set($attempts);
        $item->expiresAfter($this->decaySeconds);
        $this->cache->save($item);

        $response = $handler->handle($request);

        return $response
            ->withHeader('X-RateLimit-Limit', (string) $this->maxRequests)
            ->withHeader('X-RateLimit-Remaining', (string) ($this->maxRequests - $attempts));
    }
}

3. 日志中间件

php
<?php
declare(strict_types=1);

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;

class RequestLoggingMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly LoggerInterface $logger
    ) {}

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $start = microtime(true);

        $this->logger->info('Incoming request', [
            'method' => $request->getMethod(),
            'uri' => (string) $request->getUri(),
            'ip' => $request->getClientIP() ?? 'unknown',
        ]);

        $response = $handler->handle($request);

        $duration = round((microtime(true) - $start) * 1000, 2);

        $this->logger->info('Request completed', [
            'method' => $request->getMethod(),
            'uri' => (string) $request->getUri(),
            'status' => $response->getStatusCode(),
            'duration_ms' => $duration,
        ]);

        return $response;
    }
}

实战示例

场景一:构建中间件管道

php
<?php
declare(strict_types=1);

namespace App\Http;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class Pipeline implements RequestHandlerInterface
{
    private int $offset = 0;

    public function __construct(
        private readonly array $middlewares,
        private readonly RequestHandlerInterface $finalHandler
    ) {}

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        if (!isset($this->middlewares[$this->offset])) {
            return $this->finalHandler->handle($request);
        }

        $middleware = $this->middlewares[$this->offset];
        $this->offset++;

        return $middleware->process($request, $this);
    }
}

// 使用
$pipeline = new Pipeline(
    middlewares: [
        new CorsMiddleware(),
        new AuthMiddleware(),
        new RateLimitMiddleware(),
    ],
    finalHandler: new AppHandler()
);

$response = $pipeline->handle($request);

场景二:可终止的中间件

php
<?php
declare(strict_types=1);

namespace App\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\EventDispatcher\StoppableEventInterface;

class EventDispatchingMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly \Psr\EventDispatcher\EventDispatcherInterface $dispatcher
    ) {}

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $event = new RequestEvent($request);
        $this->dispatcher->dispatch($event);

        if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
            return $event->getResponse();
        }

        return $handler->handle($request);
    }
}

注意事项

1. 中间件顺序

中间件的顺序非常重要。认证中间件应该在限流中间件之后(先认证后限流),CORS 中间件通常放在最前面。

php
// 推荐的中间件顺序
$middlewares = [
    CorsMiddleware::class,        // 1. CORS(最先)
    TrustProxiesMiddleware::class, // 2. 代理信任
    RateLimitMiddleware::class,    // 3. 限流
    AuthMiddleware::class,         // 4. 认证
    ThrottleRequests::class,       // 5. 请求节流
    VerifyCsrfToken::class,        // 6. CSRF 保护
];

2. 中间件的性能影响

每个中间件都会增加请求处理时间。生产环境应只启用必要的中间件,并确保中间件逻辑高效。

最佳实践

1. 单一职责

每个中间件只做一件事:

php
// ✅ 推荐:每个中间件单一职责
class CorsMiddleware { /* 仅处理 CORS */ }
class AuthMiddleware { /* 仅处理认证 */ }
class LoggingMiddleware { /* 仅处理日志 */ }

// ❌ 不推荐:一个中间件做太多事
class CombinedMiddleware { /* CORS + 认证 + 日志 */ }

2. 可配置

php
<?php
declare(strict_types=1);

class RateLimitMiddleware implements MiddlewareInterface
{
    public function __construct(
        private readonly int $maxRequests = 60,
        private readonly int $decaySeconds = 60,
        private readonly ?string $keyPrefix = null,
    ) {}

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $key = ($this->keyPrefix ?? 'rate:') . $request->getClientIP();
        // ...
    }
}

3. 测试中间件

php
<?php
declare(strict_types=1);

namespace Tests\Middleware;

use App\Middleware\AuthMiddleware;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7\Response;
use PHPUnit\Framework\TestCase;

class AuthMiddlewareTest extends TestCase
{
    public function testValidTokenPasses(): void
    {
        $factory = new Psr17Factory();
        $request = $factory->createServerRequest('GET', '/api/users')
            ->withHeader('Authorization', 'Bearer valid-token');

        $middleware = new AuthMiddleware();
        $handler = new class ($factory) implements \Psr\Http\Server\RequestHandlerInterface {
            public function __construct(private readonly $factory) {}
            public function handle(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface
            {
                return new Response(200);
            }
        };

        $response = $middleware->process($request, $handler);
        $this->assertSame(200, $response->getStatusCode());
    }

    public function testMissingTokenReturns401(): void
    {
        $factory = new Psr17Factory();
        $request = $factory->createServerRequest('GET', '/api/users');

        $middleware = new AuthMiddleware();
        $handler = new class implements \Psr\Http\Server\RequestHandlerInterface {
            public function handle(\Psr\Http\Message\ServerRequestInterface $request): \Psr\Http\Message\ResponseInterface
            {
                return new Response(200);
            }
        };

        $response = $middleware->process($request, $handler);
        $this->assertSame(401, $response->getStatusCode());
    }
}

下一节

继续学习:依赖注入 — 了解 IoC 容器和依赖注入的核心原理。

参考链接