1、簡介
HTTP 中間件爲過濾進入應用的 HTTP 請求提供了一套便利的機制。例如,Laravel 內置了一箇中間件來驗證用戶是否經過認證,如果用戶沒有經過認證,中間件會將用戶重定向到登錄頁面,否則如果用戶經過認證,中間件就會允許請求繼續往前進入下一步操作。
當然,除了認證之外,中間件還可以被用來處理更多其它任務。比如:CORS 中間件可以用於爲離開站點的響應添加合適的頭(跨域);日誌中間件可以記錄所有進入站點的請求。
Laravel框架自帶了一些中間件,包括認證、CSRF 保護中間件等等。所有的中間件都位於 app/Http/Middleware 目錄。
2、定義中間件
要創建一個新的中間件,可以通過 Artisan 命令 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->input('age') <= 200) {
return redirect('home');
}
return $next($request);
}
}
正如你所看到的,如果 age <= 200,中間件會返回一個 HTTP 重定向到客戶端;否則,請求會被傳遞下去。將請求往下傳遞可以通過調用回調函數
理解中間件的最好方式就是將中間件看做 HTTP 請求到達目標動作之前必須經過的“層”,每一層都會檢查請求並且可以完全拒絕它。
中間件之前/之後
一箇中間件是請求前還是請求後執行取決於中間件本身。比如,以下中間件會在請求處理前執行一些任務:
<?php
namespace App\Http\Middleware;
use Closure;
class BeforeMiddleware
{
public function handle($request, Closure $next)
{
// 執行動作
return $next($request);
}
}
而下面這個中間件則會在請求處理後執行其任務:
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware
{
public function handle($request, Closure $next)
{
$response = $next($request);
// 執行動作
return $response;
}
}
3、註冊中間件
全局中間件
如果你想要中間件在每一個 HTTP 請求期間被執行,只需要將相應的中間件類設置到 app/Http/Kernel.php 的數組屬性 $middleware 中即可。
分配中間件到路由
如果你想要分配中間件到指定路由,首先應該在 app/Http/Kernel.php 文件中分配給該中間件一個key,默認情況下,該類的 $routeMiddleware 屬性包含了 Laravel 自帶的中間件,要添加你自己的中間件,只需要將其追加到後面併爲其分配一個 key,例如:
// 在 App\Http\Kernel 類中...
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
中間件在 HTTP Kernel 中被定義後,可以使用 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);
中間件組
有時候你可能想要通過指定一個鍵名的方式將相關中間件分到同一個組裏面,從而更方便將其分配到路由中,這可以通過使用 HTTP Kernel 的 $middlewareGroups 屬性實現。
Laravel 自帶了開箱即用的 web 和 api 兩個中間件組以分別包含可以應用到 Web UI 和 API 路由的通用中間件:
/**
* The application's route middleware groups.
*
* @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:60,1',
'auth:api',
],
];
中間件組可以被分配給路由和控制器動作,使用和單箇中間件分配同樣的語法。再次申明,中間件組的目的只是讓一次分配給路由多箇中間件的實現更加方便:
Route::get('/', function () {
//
})->middleware('web');
Route::group(['middleware' => ['web']], function () {
//
});
注:默認情況下, RouteServiceProvider 自動將中間件組 web 應用到 routes/web.php 文件。
4、中間件參數
中間件還可以接收額外的自定義參數,例如,如果應用需要在執行給定動作之前驗證認證用戶是否擁有指定的角色,可以創建一個 CheckRole 來接收角色名作爲額外參數。
額外的中間件參數會在 $next 參數之後傳入中間件:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckRole
{
/**
* 運行請求過濾器
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string $role
* @return mixed
* translator http://laravelacademy.org
*/
public function handle($request, Closure $next, $role)
{
if (! $request->user()->hasRole($role)) {
// Redirect...
}
return $next($request);
}
}
中間件參數可以在定義路由時通過 : 分隔中間件名和參數名來指定,多箇中間件參數可以通過逗號分隔:
Route::put('post/{id}', function ($id) {
//
})->middleware('role:editor');
5、終止中間件
有時候中間件可能需要在 HTTP 響應發送到瀏覽器之後做一些工作。比如,Laravel 內置的“session”中間件會在響應發送到瀏覽器之後將 Session 數據寫到存儲器中,爲了實現這個功能,需要定義一個終止中間件並添加 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 方法將會接收請求和響應作爲參數。定義了一個終止中間件之後,還需要將其加入到 HTTP kernel 的全局中間件列表中。
當調用中間件上的 terminate 方法時,Laravel 將會從服務容器中取出該中間件的新的實例,如果你想要在調用 handle 和 terminate 方法時使用同一個中間件實例,則需要使用容器的 singleton 方法將該中間件註冊到容器中。