中间件对象使您能够以可重用、可组合的层来“包装”应用程序,这些层可以处理请求,或构建响应逻辑。从视觉上看,您的应用程序最终位于中心,中间件像洋葱一样包裹在应用程序周围。在这里,我们可以看到一个用路由、资产、异常处理和 CORS 标头中间件包裹的应用程序。
当应用程序处理请求时,它从最外面的中间件进入。每个中间件都可以将请求/响应委托给下一层,或者返回一个响应。返回响应会阻止更低层看到请求。例如,在开发过程中,AssetMiddleware 处理对插件图像的请求。
如果没有中间件采取行动来处理请求,则会找到一个控制器并调用其操作,或者会引发异常,从而生成一个错误页面。
中间件是 CakePHP 中新的 HTTP 堆栈的一部分,该堆栈利用 PSR-7 请求和响应接口。CakePHP 还支持 PSR-15 标准的服务器请求处理程序,因此您可以使用 Packagist 上提供的任何 PSR-15 兼容中间件。
CakePHP 提供了一些中间件来处理 Web 应用程序中的常见任务。
Cake\Error\Middleware\ErrorHandlerMiddleware
会捕获来自包装中间件的异常,并使用 错误和异常处理 异常处理程序呈现错误页面。
Cake\Routing\AssetMiddleware
检查请求是否引用主题或插件资产文件,例如存储在插件的 webroot 文件夹或主题的相应文件夹中的 CSS、JavaScript 或图像文件。
Cake\Routing\Middleware\RoutingMiddleware
使用 Router
解析传入的 URL 并将路由参数分配给请求。
Cake\I18n\Middleware\LocaleSelectorMiddleware
使能从浏览器发送的 Accept-Language
标头自动切换语言。
Cake\Http\Middleware\EncryptedCookieMiddleware
使您能够在需要操作包含混淆数据的 Cookie 时,操作加密的 Cookie。
Cake\Http\Middleware\BodyParserMiddleware
允许您根据 Content-Type
标头解码 JSON、XML 和其他编码的请求正文。
CakeHttpMiddlewareHttpsEnforcerMiddleware 要求使用 HTTPS。
CakeHttpMiddlewareCsrfProtectionMiddleware 为您的应用程序添加基于双重提交 Cookie 的 CSRF 保护。
CakeHttpMiddlewareSessionCsrfProtectionMiddleware 为您的应用程序添加基于会话的 CSRF 保护。
CakeHttpMiddlewareCspMiddleware 简化了将内容安全策略标头添加到应用程序的过程。
CakeHttpMiddlewareSecurityHeadersMiddleware 使您能够将与安全相关的标头(如 X-Frame-Options
)添加到响应。
可以将中间件全局应用于您的应用程序,应用于各个路由范围,或者应用于特定的控制器。
要将中间件应用于所有请求,请使用 middleware
方法,该方法位于 App\Application
类中。应用程序的 middleware
挂钩方法将在请求处理开始时被调用,您可以使用 MiddlewareQueue
对象来附加中间件。
namespace App;
use Cake\Http\BaseApplication;
use Cake\Http\MiddlewareQueue;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
class Application extends BaseApplication
{
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
// Bind the error handler into the middleware queue.
$middlewareQueue->add(new ErrorHandlerMiddleware());
// Add middleware by classname.
// As of 4.5.0 classname middleware are optionally resolved
// using the DI container. If the class is not found in the
// container then an instance is created by the middleware queue.
$middlewareQueue->add(UserRateLimiting::class);
return $middlewareQueue;
}
}
除了添加到 MiddlewareQueue
的末尾之外,您还可以执行各种操作。
$layer = new \App\Middleware\CustomMiddleware;
// Added middleware will be last in line.
$middlewareQueue->add($layer);
// Prepended middleware will be first in line.
$middlewareQueue->prepend($layer);
// Insert in a specific slot. If the slot is out of
// bounds, it will be added to the end.
$middlewareQueue->insertAt(2, $layer);
// Insert before another middleware.
// If the named class cannot be found,
// an exception will be raised.
$middlewareQueue->insertBefore(
'Cake\Error\Middleware\ErrorHandlerMiddleware',
$layer
);
// Insert after another middleware.
// If the named class cannot be found, the
// middleware will added to the end.
$middlewareQueue->insertAfter(
'Cake\Error\Middleware\ErrorHandlerMiddleware',
$layer
);
如果您的中间件仅适用于部分路由或单个控制器,您可以使用 路由范围中间件 或 控制器中间件。
插件可以使用它们的 middleware
挂钩方法将它们拥有的任何中间件应用于应用程序的中间件队列。
// in plugins/ContactManager/src/Plugin.php
namespace ContactManager;
use Cake\Core\BasePlugin;
use Cake\Http\MiddlewareQueue;
use ContactManager\Middleware\ContactManagerContextMiddleware;
class Plugin extends BasePlugin
{
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$middlewareQueue->add(new ContactManagerContextMiddleware());
return $middlewareQueue;
}
}
中间件可以实现为匿名函数(闭包),也可以实现为扩展 Psr\Http\Server\MiddlewareInterface
的类。虽然闭包适用于较小的任务,但它们使测试变得更加困难,并且会创建复杂的 Application
类。CakePHP 中的中间件类具有一些约定。
中间件类文件应放在 src/Middleware 中。例如:src/Middleware/CorsMiddleware.php
中间件类应以 Middleware
结尾。例如:LinkMiddleware
。
中间件必须实现 Psr\Http\Server\MiddlewareInterface
。
中间件可以通过调用 $handler->handle()
或创建自己的响应来返回响应。我们可以在简单的中间件中看到这两种选项。
// In src/Middleware/TrackingCookieMiddleware.php
namespace App\Middleware;
use Cake\Http\Cookie\Cookie;
use Cake\I18n\Time;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Server\MiddlewareInterface;
class TrackingCookieMiddleware implements MiddlewareInterface
{
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
{
// Calling $handler->handle() delegates control to the *next* middleware
// In your application's queue.
$response = $handler->handle($request);
if (!$request->getCookie('landing_page')) {
$expiry = new Time('+ 1 year');
$response = $response->withCookie(new Cookie(
'landing_page',
$request->getRequestTarget(),
$expiry
));
}
return $response;
}
}
现在我们已经创建了一个非常简单的中间件,让我们将它附加到我们的应用程序。
// In src/Application.php
namespace App;
use App\Middleware\TrackingCookieMiddleware;
use Cake\Http\MiddlewareQueue;
class Application
{
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
// Add your simple middleware onto the queue
$middlewareQueue->add(new TrackingCookieMiddleware());
// Add some more middleware onto the queue
return $middlewareQueue;
}
}
路由中间件负责应用应用程序的路由并解析请求将要访问的插件、控制器和操作。
// In Application.php
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
// ...
$middlewareQueue->add(new RoutingMiddleware($this));
}
如果您的应用程序接受 JSON、XML 或其他编码的请求正文,BodyParserMiddleware
将使您能够将这些请求解码为可通过 $request->getParsedData()
和 $request->getData()
获取的数组。默认情况下,只有 json
正文会被解析,但可以通过选项启用 XML 解析。您还可以定义自己的解析器。
use Cake\Http\Middleware\BodyParserMiddleware;
// only JSON will be parsed.
$bodies = new BodyParserMiddleware();
// Enable XML parsing
$bodies = new BodyParserMiddleware(['xml' => true]);
// Disable JSON parsing
$bodies = new BodyParserMiddleware(['json' => false]);
// Add your own parser matching content-type header values
// to the callable that can parse them.
$bodies = new BodyParserMiddleware();
$bodies->addParser(['text/csv'], function ($body, $request) {
// Use a CSV parsing library.
return Csv::parse($body);
});