中间件 介绍 中间件提供了一种方便的机制来过滤进入应用程序的 HTTP 请求。例如,Laravel 包含一个验证用户身份的中间件。 如果用户未能通过认证,中间件会把用户重定向到登录页面。 反之,用户如果通过验证, 中间件将把请求进一步转发到应用程序中。 当然,除了验证身份外,还可以编写其他的中间件来执行各种任务。例如:CORS 中间件可以负责为所有的应用返回的 responses 添加合适的响应头。日志中间件可以记录所有传入应用的请求。 Laravel 自带了一些中间件,包括身份验证、CSRF 保护等。所有的这些中间件都位于 app/Http/Middleware 目录。 定义中间件 您可以使用 make:middleware 来创建一个中间件: ``` php artisan make:middleware CheckAge ``` 该命令会在 app/Http/Middleware 目录下放置新的 CheckAge 类。在这个中间件中,我们仅允许 age 参数大于 200 的请求对路由进行访问,否则将重定向到 home 页面: ``` <?php namespace App\Http\Middleware; use Closure; class CheckAge { /** * 处理传入的请求 * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if ($request->age <= 200) { return redirect('home'); } return $next($request); } } ``` 正如您所见,如果给定的 age 参数小于或等于 200, 这个中间件将返回一个 HTTP 重定向给客户端;否则这个请求将会通过,进一步传递到应用层中。要让请求继续传到到应用层中 (即允许 「通过」中间件验证), 只需要将 $request 作为参数来调用函数 $next 即可。 最好将中间件想象成一系列层次,HTTP 请求必须通过它们才能进入你的应用层。每一层都会检查请求(是否符合中间件要求),而后决定通过或拒绝访问应用。 技巧:所有的中间件都是通过 服务容器 解析的,因此,你可以在你的中间件构造函数中键入你需要的任何依赖。 前置 & 后置 中间件 中间件是在请求之前还是请求之后执行,取决于中间件本身。例如,下面的中间件将在应用处理请求之前执行一些任务: ``` <?php namespace App\Http\Middleware; use Closure; class BeforeMiddleware { public function handle($request, Closure $next) { // Perform action return $next($request); } } ``` 然而,下面中间件是在应用请求之后执行一些任务: ``` <?php namespace App\Http\Middleware; use Closure; class AfterMiddleware { public function handle($request, Closure $next) { $response = $next($request); // Perform action return $response; } } ``` 注册中间件 全局中间件 如果你希望中间件在应用处理每个 HTTP 请求期间运行, 只需要在 app/Http/Kernel.php 中的 $middleware 属性中列出这个中间件。 为路由分配中间件 假设你想为指定的路由分配中间件 , 首先应该在 app/Http/Kernel.php 文件内为该中间件分配一个键。默认情况下,该类中的 $routeMiddleware 属性下包含了 Laravel 内置的中间件。若要加入自定义的中间件,只需把它附加到列表后并为其分配一个自定义键。例如: ``` // Within App\Http\Kernel Class... protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, ]; 在 HTTP 内核中定义了中间件后,您可以使用 middleware 方法将中间件分配给路由: Route::get('admin/profile', function () { // })->middleware('auth'); 您还可以将多个中间件分配给路由: Route::get('/', function () { // })->middleware('first', 'second'); 分配中间件时,您还可以传递完全限定的类名: use App\Http\Middleware\CheckAge; Route::get('admin/profile', function () { // })->middleware(CheckAge::class); ``` 将中间件分配给一组路由时,有时可能需要阻止将中间件应用于该组中的单个路由。 您可以使用 withoutMiddleware 方法完成此操作: ``` use App\Http\Middleware\CheckAge; Route::middleware([CheckAge::class])->group(function () { Route::get('/', function () { // }); Route::get('admin/profile', function () { // })->withoutMiddleware([CheckAge::class]); }); ``` withoutMiddleware 方法只能删除路由中间件,不适用于 全局中间件。 中间件组 有时,您可能希望将多个中间件归为一个键,以使其更易于分配给路由。 您可以使用 HTTP 内核的 $middlewareGroups 属性来实现。 Laravel 开箱即用,带有 web 和 api 中间件组,其中包含您可能要应用于 Web UI 和 API 路由的通用中间件: ``` /** * 路由中间件组 * * @var array */ protected $middlewareGroups = [ 'web' => [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'api' => [ 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ]; ``` 中间件组可以使用与单个中间件相同的语法将自身分配给路由和控制器动作。同样,中间件组使得一次将多个中间件分配给一个路由更加方便: ``` Route::get('/', function () { // })->middleware('web'); Route::group(['middleware' => ['web']], function () { // }); Route::middleware(['web', 'subscribed'])->group(function () { // }); 技巧:RouteServiceProvider 默认将 web 中间件组自动应用到 routes/web.php。 ``` 中间件排序 很少情况下,你可能需要中间件以特定的顺序执行,但是当它们被分配到路由时,你无法控制它们的顺序。在这种情况下,可以使用 app/Http/Kernel.php 文件中的 $middlewarePriority 属性指定中间件的优先级: ``` /** * 中间件的优先级排序列表 * * 将会强制非全局中间件始终保持给定的顺序 * * @var array */ protected $middlewarePriority = [ \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class, \Illuminate\Routing\Middleware\ThrottleRequests::class, \Illuminate\Session\Middleware\AuthenticateSession::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, \Illuminate\Auth\Middleware\Authorize::class, ]; ``` 中间件参数 中间件还可以接收额外的参数。例如,如果你的应用程序需要在执行给定操作之前验证用户是否为给定的「角色」, 你可以创建一个 CheckRole 中间件,由它来接收「角色」名称作为附加参数。 附加的中间参数会在 $next 参数之后传递给中间件: ``` <?php namespace App\Http\Middleware; use Closure; class CheckRole { /** * 处理传入的请求 * * @param \Illuminate\Http\Request $request * @param \Closure $next * @param string $role * @return mixed */ public function handle($request, Closure $next, $role) { if (! $request->user()->hasRole($role)) { // 重定向... } return $next($request); } } ``` 定义路由时通过一个 : 来隔开中间件名称和参数来指定中间件参数。多个参数就使用逗号分隔: ``` Route::put('post/{id}', function ($id) { // })->middleware('role:editor'); ``` Terminable 中间件 有时可能需要在 HTTP 响应之后做一些工作。 如果你在中间件上定义了一个 terminate 方法,并且你使用的是 FastCGI,那么 terminate 方法会在响应发送到浏览器之后自动调用: ``` <?php namespace Illuminate\Session\Middleware; use Closure; class StartSession { public function handle($request, Closure $next) { return $next($request); } public function terminate($request, $response) { // 存储 session 数据 } } ``` terminate 方法应该同时接收请求和响应。定义了这个中间件之后,别忘了将它添加到路由列表或者 app/Http/Kernel.php 文件的全局中间件中。 当你在中间件上调用 terminate 方法的时候,Laravel 将从 服务容器 中解析出一个新的中间件实例。如果在调用 handle 和 terminate 方法的同时使用相同的中间件实例, 请使用容器的 singleton 方法注册中间件, 通常这应该在 AppServiceProvider.php 文件中的 register 方法中完成: ``` use App\Http\Middleware\TerminableMiddleware; /** * 注册任意应用服务 * * @return void */ public function register() { $this->app->singleton(TerminableMiddleware::class); } ```