中介軟體 - Middleware
簡介
Middleware 提供了一個機制,可檢驗與過濾進入應用程式的 HTTP Request。舉例來說,Laravel 中包含了一個可以認證使用者是否已登入的 Middleware。若使用者未登入,該 Middleware 會將使用者重新導向回登入畫面。不過,若使用者已登入,這個 Middleware 就會讓 Request 進一步進入程式中處理。
Additional middleware can be written to perform a variety of tasks besides authentication. For example, a logging middleware might log all incoming requests to your application. A variety of middleware are included in Laravel, including middleware for authentication and CSRF protection; however, all user-defined middleware are typically located in your application's app/Http/Middleware directory.
定義 Middleware
若要建立新的 Middleware,請使用 make:middleware Artisan 指令:
1php artisan make:middleware EnsureTokenIsValid1php artisan make:middleware EnsureTokenIsValid
該指令會在 app/Http/Middleware 目錄中放置一個新的 EnsureTokenIsValid 類別。在這個 Middleware 中,我們要只在提供的 token 符合特定的值時才允許存取該 Route。token 不符合時,會將使用者重新導向回到 home URI:
1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Http\Request;7use Symfony\Component\HttpFoundation\Response;89class EnsureTokenIsValid10{11 /**12 * Handle an incoming request.13 *14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next15 */16 public function handle(Request $request, Closure $next): Response17 {18 if ($request->input('token') !== 'my-secret-token') {19 return redirect('home');20 }2122 return $next($request);23 }24}1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Http\Request;7use Symfony\Component\HttpFoundation\Response;89class EnsureTokenIsValid10{11 /**12 * Handle an incoming request.13 *14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next15 */16 public function handle(Request $request, Closure $next): Response17 {18 if ($request->input('token') !== 'my-secret-token') {19 return redirect('home');20 }2122 return $next($request);23 }24}
就像我們可以看到的一樣,若給定的 token 不符合我們的私密權杖 (Secret Token),則這個 Middleware 會回傳一個 HTTP Redirect 給用戶端。token 符合時,這個 Request 就會進一步地傳給我們的程式。若要將 Request 進一步傳進我們的應用程式中 (即,讓 Middleware「通過 - Pass」),應以 $request 呼叫 $next 回呼。
最好想像成我們有「一層又一層」的 Middleware。HTTP Request 必須通過每一層的 Middleware,最後才能進入你的應用程式中。每一層 Middleware 都可以檢查 Request 的內容,甚至還能完全拒絕 Request。
所有的 Middleware 都會經過 [Service Container] 解析,因此我們可以在 Middleware 的 Constructor (建構函式) 上型別提示 (Type-Hint) 任何需要的相依性。
Middleware and Responses
當然,Middleware 可以在將 Request 傳入應用程式的前後執行。舉例來說,下列 Middleware 會在 Request 被程式處理 之後 進行一些任務:
1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Http\Request;7use Symfony\Component\HttpFoundation\Response;89class BeforeMiddleware10{11 public function handle(Request $request, Closure $next): Response12 {13 // Perform action1415 return $next($request);16 }17}1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Http\Request;7use Symfony\Component\HttpFoundation\Response;89class BeforeMiddleware10{11 public function handle(Request $request, Closure $next): Response12 {13 // Perform action1415 return $next($request);16 }17}
不過,這個 Middleware 會在 Request 被程式處理 之後 才進行其任務:
1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Http\Request;7use Symfony\Component\HttpFoundation\Response;89class AfterMiddleware10{11 public function handle(Request $request, Closure $next): Response12 {13 $response = $next($request);1415 // Perform action1617 return $response;18 }19}1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Http\Request;7use Symfony\Component\HttpFoundation\Response;89class AfterMiddleware10{11 public function handle(Request $request, Closure $next): Response12 {13 $response = $next($request);1415 // Perform action1617 return $response;18 }19}
註冊 Middleware
全域 Middleware
If you want a middleware to run during every HTTP request to your application, you may append it to the global middleware stack in your application's bootstrap/app.php file:
1use App\Http\Middleware\EnsureTokenIsValid;23->withMiddleware(function (Middleware $middleware) {4 $middleware->append(EnsureTokenIsValid::class);5})1use App\Http\Middleware\EnsureTokenIsValid;23->withMiddleware(function (Middleware $middleware) {4 $middleware->append(EnsureTokenIsValid::class);5})
The $middleware object provided to the withMiddleware closure is an instance of Illuminate\Foundation\Configuration\Middleware and is responsible for managing the middleware assigned to your application's routes. The append method adds the middleware to the end of the list of global middleware. If you would like to add a middleware to the beginning of the list, you should use the prepend method.
Manually Managing Laravel's Default Global Middleware
If you would like to manage Laravel's global middleware stack manually, you may provide Laravel's default stack of global middleware to the use method. Then, you may adjust the default middleware stack as necessary:
1->withMiddleware(function (Middleware $middleware) {2 $middleware->use([3 // \Illuminate\Http\Middleware\TrustHosts::class,4 \Illuminate\Http\Middleware\TrustProxies::class,5 \Illuminate\Http\Middleware\HandleCors::class,6 \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,7 \Illuminate\Http\Middleware\ValidatePostSize::class,8 \Illuminate\Foundation\Http\Middleware\TrimStrings::class,9 \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,10 ]);11})1->withMiddleware(function (Middleware $middleware) {2 $middleware->use([3 // \Illuminate\Http\Middleware\TrustHosts::class,4 \Illuminate\Http\Middleware\TrustProxies::class,5 \Illuminate\Http\Middleware\HandleCors::class,6 \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,7 \Illuminate\Http\Middleware\ValidatePostSize::class,8 \Illuminate\Foundation\Http\Middleware\TrimStrings::class,9 \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,10 ]);11})
Assigning Middleware to Routes
若想將 Middleware 指定到特定的 Route 中,可以在定義 Route 時呼叫 middleware 方法:
1use App\Http\Middleware\EnsureTokenIsValid;23Route::get('/profile', function () {4 // ...5})->middleware(EnsureTokenIsValid::class);1use App\Http\Middleware\EnsureTokenIsValid;23Route::get('/profile', function () {4 // ...5})->middleware(EnsureTokenIsValid::class);
也可以傳入一組 Middleware 陣列給 middleware 方法來指派多個 Middleware 給 Route:
1Route::get('/', function () {2 // ...3})->middleware([First::class, Second::class]);1Route::get('/', function () {2 // ...3})->middleware([First::class, Second::class]);
排除 Middleware
當我們將 Middleware 指派給 Route 群組時,我們有時候會需要讓某個 Middleware 不要被套用到群組中的個別 Route 上。我們可以使用 withoutMiddleware 方法來完成:
1use App\Http\Middleware\EnsureTokenIsValid;23Route::middleware([EnsureTokenIsValid::class])->group(function () {4 Route::get('/', function () {5 // ...6 });78 Route::get('/profile', function () {9 // ...10 })->withoutMiddleware([EnsureTokenIsValid::class]);11});1use App\Http\Middleware\EnsureTokenIsValid;23Route::middleware([EnsureTokenIsValid::class])->group(function () {4 Route::get('/', function () {5 // ...6 });78 Route::get('/profile', function () {9 // ...10 })->withoutMiddleware([EnsureTokenIsValid::class]);11});
也可以將一組 Middleware 從整個 Route 群組定義中排除:
1use App\Http\Middleware\EnsureTokenIsValid;23Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {4 Route::get('/profile', function () {5 // ...6 });7});1use App\Http\Middleware\EnsureTokenIsValid;23Route::withoutMiddleware([EnsureTokenIsValid::class])->group(function () {4 Route::get('/profile', function () {5 // ...6 });7});
withoutMiddleware 方法只能移除 Route Middleware,不能移除全域 Middleware。
Middleware 群組
Sometimes you may want to group several middleware under a single key to make them easier to assign to routes. You may accomplish this using the appendToGroup method within your application's bootstrap/app.php file:
1use App\Http\Middleware\First;2use App\Http\Middleware\Second;34->withMiddleware(function (Middleware $middleware) {5 $middleware->appendToGroup('group-name', [6 First::class,7 Second::class,8 ]);910 $middleware->prependToGroup('group-name', [11 First::class,12 Second::class,13 ]);14})1use App\Http\Middleware\First;2use App\Http\Middleware\Second;34->withMiddleware(function (Middleware $middleware) {5 $middleware->appendToGroup('group-name', [6 First::class,7 Second::class,8 ]);910 $middleware->prependToGroup('group-name', [11 First::class,12 Second::class,13 ]);14})
Middleware groups may be assigned to routes and controller actions using the same syntax as individual middleware:
1Route::get('/', function () {2 // ...3})->middleware('group-name');45Route::middleware(['group-name'])->group(function () {6 // ...7});1Route::get('/', function () {2 // ...3})->middleware('group-name');45Route::middleware(['group-name'])->group(function () {6 // ...7});
Laravel's Default Middleware Groups
Laravel includes predefined web and api middleware groups that contain common middleware you may want to apply to your web and API routes. Remember, Laravel automatically applies these middleware groups to the corresponding routes/web.php and routes/api.php files:
The web Middleware Group |
|---|
Illuminate\Cookie\Middleware\EncryptCookies |
Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse |
Illuminate\Session\Middleware\StartSession |
Illuminate\View\Middleware\ShareErrorsFromSession |
Illuminate\Foundation\Http\Middleware\ValidateCsrfToken |
Illuminate\Routing\Middleware\SubstituteBindings |
The api Middleware Group |
|---|
Illuminate\Routing\Middleware\SubstituteBindings |
If you would like to append or prepend middleware to these groups, you may use the web and api methods within your application's bootstrap/app.php file. The web and api methods are convenient alternatives to the appendToGroup method:
1use App\Http\Middleware\EnsureTokenIsValid;2use App\Http\Middleware\EnsureUserIsSubscribed;34->withMiddleware(function (Middleware $middleware) {5 $middleware->web(append: [6 EnsureUserIsSubscribed::class,7 ]);89 $middleware->api(prepend: [10 EnsureTokenIsValid::class,11 ]);12})1use App\Http\Middleware\EnsureTokenIsValid;2use App\Http\Middleware\EnsureUserIsSubscribed;34->withMiddleware(function (Middleware $middleware) {5 $middleware->web(append: [6 EnsureUserIsSubscribed::class,7 ]);89 $middleware->api(prepend: [10 EnsureTokenIsValid::class,11 ]);12})
You may even replace one of Laravel's default middleware group entries with a custom middleware of your own:
1use App\Http\Middleware\StartCustomSession;2use Illuminate\Session\Middleware\StartSession;34$middleware->web(replace: [5 StartSession::class => StartCustomSession::class,6]);1use App\Http\Middleware\StartCustomSession;2use Illuminate\Session\Middleware\StartSession;34$middleware->web(replace: [5 StartSession::class => StartCustomSession::class,6]);
Or, you may remove a middleware entirely:
1$middleware->web(remove: [2 StartSession::class,3]);1$middleware->web(remove: [2 StartSession::class,3]);
Manually Managing Laravel's Default Middleware Groups
If you would like to manually manage all of the middleware within Laravel's default web and api middleware groups, you may redefine the groups entirely. The example below will define the web and api middleware groups with their default middleware, allowing you to customize them as necessary:
1->withMiddleware(function (Middleware $middleware) {2 $middleware->group('web', [3 \Illuminate\Cookie\Middleware\EncryptCookies::class,4 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,5 \Illuminate\Session\Middleware\StartSession::class,6 \Illuminate\View\Middleware\ShareErrorsFromSession::class,7 \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,8 \Illuminate\Routing\Middleware\SubstituteBindings::class,9 // \Illuminate\Session\Middleware\AuthenticateSession::class,10 ]);1112 $middleware->group('api', [13 // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,14 // 'throttle:api',15 \Illuminate\Routing\Middleware\SubstituteBindings::class,16 ]);17})1->withMiddleware(function (Middleware $middleware) {2 $middleware->group('web', [3 \Illuminate\Cookie\Middleware\EncryptCookies::class,4 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,5 \Illuminate\Session\Middleware\StartSession::class,6 \Illuminate\View\Middleware\ShareErrorsFromSession::class,7 \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,8 \Illuminate\Routing\Middleware\SubstituteBindings::class,9 // \Illuminate\Session\Middleware\AuthenticateSession::class,10 ]);1112 $middleware->group('api', [13 // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,14 // 'throttle:api',15 \Illuminate\Routing\Middleware\SubstituteBindings::class,16 ]);17})
By default, the web and api middleware groups are automatically applied to your application's corresponding routes/web.php and routes/api.php files by the bootstrap/app.php file.
Middleware Aliases
You may assign aliases to middleware in your application's bootstrap/app.php file. Middleware aliases allows you to define a short alias for a given middleware class, which can be especially useful for middleware with long class names:
1use App\Http\Middleware\EnsureUserIsSubscribed;23->withMiddleware(function (Middleware $middleware) {4 $middleware->alias([5 'subscribed' => EnsureUserIsSubscribed::class6 ]);7})1use App\Http\Middleware\EnsureUserIsSubscribed;23->withMiddleware(function (Middleware $middleware) {4 $middleware->alias([5 'subscribed' => EnsureUserIsSubscribed::class6 ]);7})
Once the middleware alias has been defined in your application's bootstrap/app.php file, you may use the alias when assigning the middleware to routes:
1Route::get('/profile', function () {2 // ...3})->middleware('subscribed');1Route::get('/profile', function () {2 // ...3})->middleware('subscribed');
For convenience, some of Laravel's built-in middleware are aliased by default. For example, the auth middleware is an alias for the Illuminate\Auth\Middleware\Authenticate middleware. Below is a list of the default middleware aliases:
| Alias | 中介軟體 - Middleware |
|---|---|
auth |
Illuminate\Auth\Middleware\Authenticate |
auth.basic |
Illuminate\Auth\Middleware\AuthenticateWithBasicAuth |
auth.session |
Illuminate\Session\Middleware\AuthenticateSession |
cache.headers |
Illuminate\Http\Middleware\SetCacheHeaders |
can |
Illuminate\Auth\Middleware\Authorize |
guest |
Illuminate\Auth\Middleware\RedirectIfAuthenticated |
password.confirm |
Illuminate\Auth\Middleware\RequirePassword |
precognitive |
Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests |
signed |
Illuminate\Routing\Middleware\ValidateSignature |
subscribed |
\Spark\Http\Middleware\VerifyBillableIsSubscribed |
throttle |
Illuminate\Routing\Middleware\ThrottleRequests or Illuminate\Routing\Middleware\ThrottleRequestsWithRedis |
verified |
Illuminate\Auth\Middleware\EnsureEmailIsVerified |
排序 Middleware
Rarely, you may need your middleware to execute in a specific order but not have control over their order when they are assigned to the route. In these situations, you may specify your middleware priority using the priority method in your application's bootstrap/app.php file:
1->withMiddleware(function (Middleware $middleware) {2 $middleware->priority([3 \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,4 \Illuminate\Cookie\Middleware\EncryptCookies::class,5 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,6 \Illuminate\Session\Middleware\StartSession::class,7 \Illuminate\View\Middleware\ShareErrorsFromSession::class,8 \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,9 \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,10 \Illuminate\Routing\Middleware\ThrottleRequests::class,11 \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,12 \Illuminate\Routing\Middleware\SubstituteBindings::class,13 \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,14 \Illuminate\Auth\Middleware\Authorize::class,15 ]);16})1->withMiddleware(function (Middleware $middleware) {2 $middleware->priority([3 \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,4 \Illuminate\Cookie\Middleware\EncryptCookies::class,5 \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,6 \Illuminate\Session\Middleware\StartSession::class,7 \Illuminate\View\Middleware\ShareErrorsFromSession::class,8 \Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,9 \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,10 \Illuminate\Routing\Middleware\ThrottleRequests::class,11 \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,12 \Illuminate\Routing\Middleware\SubstituteBindings::class,13 \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,14 \Illuminate\Auth\Middleware\Authorize::class,15 ]);16})
Middleware 參數
Middleware 也可以接收額外的參數。舉例來說,若你的程式需要在執行給定動作前認證登入的使用者是否有給定的「職位 (Role)」,則我們可以先建立一個 EnsureUserHasRole Middleware,讓該 Middleware 接收一個職位名稱來作為其額外的引數。
額外的 Middleware 引數會被放在 $next 引數之後傳遞給 Middleware:
1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Http\Request;7use Symfony\Component\HttpFoundation\Response;89class EnsureUserHasRole10{11 /**12 * Handle an incoming request.13 *14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next15 */16 public function handle(Request $request, Closure $next, string $role): Response17 {18 if (! $request->user()->hasRole($role)) {19 // Redirect...20 }2122 return $next($request);23 }2425}1<?php23namespace App\Http\Middleware;45use Closure;6use Illuminate\Http\Request;7use Symfony\Component\HttpFoundation\Response;89class EnsureUserHasRole10{11 /**12 * Handle an incoming request.13 *14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next15 */16 public function handle(Request $request, Closure $next, string $role): Response17 {18 if (! $request->user()->hasRole($role)) {19 // Redirect...20 }2122 return $next($request);23 }2425}
Middleware parameters may be specified when defining the route by separating the middleware name and parameters with a ::
1Route::put('/post/{id}', function (string $id) {2 // ...3})->middleware('role:editor');1Route::put('/post/{id}', function (string $id) {2 // ...3})->middleware('role:editor');
Multiple parameters may be delimited by commas:
1Route::put('/post/{id}', function (string $id) {2 // ...3})->middleware('role:editor,publisher');1Route::put('/post/{id}', function (string $id) {2 // ...3})->middleware('role:editor,publisher');
可終止的 Middleware
有時候,某個 Middleware 可能需要在 HTTP Response 被傳送到瀏覽器後才進行某些動作。若我們在 Middleware 上定義一個 terminate 方法,且網頁伺服器 (Web Server) 使用 FastCGI,則會在 Response 傳送給瀏覽器後會自動呼叫 terminate 方法:
1<?php23namespace Illuminate\Session\Middleware;45use Closure;6use Illuminate\Http\Request;7use Symfony\Component\HttpFoundation\Response;89class TerminatingMiddleware10{11 /**12 * Handle an incoming request.13 *14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next15 */16 public function handle(Request $request, Closure $next): Response17 {18 return $next($request);19 }2021 /**22 * Handle tasks after the response has been sent to the browser.23 */24 public function terminate(Request $request, Response $response): void25 {26 // ...27 }28}1<?php23namespace Illuminate\Session\Middleware;45use Closure;6use Illuminate\Http\Request;7use Symfony\Component\HttpFoundation\Response;89class TerminatingMiddleware10{11 /**12 * Handle an incoming request.13 *14 * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next15 */16 public function handle(Request $request, Closure $next): Response17 {18 return $next($request);19 }2021 /**22 * Handle tasks after the response has been sent to the browser.23 */24 public function terminate(Request $request, Response $response): void25 {26 // ...27 }28}
The terminate method should receive both the request and the response. Once you have defined a terminable middleware, you should add it to the list of routes or global middleware in your application's bootstrap/app.php file.
呼叫 Middleware 上的 terminate 方法時,Laravel 會從 [Service Container] 中解析出這個 Middleware 的新實體。若想讓 handle 與 terminate 都在同一個 Middleware 實體上呼叫的話,請使用 Container 的 singleton 方法來想 Container 註冊這個 Middleware。一般來說,這個註冊應在 AppServiceProvider 的 register 方法中進行:
1use App\Http\Middleware\TerminatingMiddleware;23/**4 * Register any application services.5 */6public function register(): void7{8 $this->app->singleton(TerminatingMiddleware::class);9}1use App\Http\Middleware\TerminatingMiddleware;23/**4 * Register any application services.5 */6public function register(): void7{8 $this->app->singleton(TerminatingMiddleware::class);9}