中介軟體 - 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 EnsureTokenIsValid
1php artisan make:middleware EnsureTokenIsValid
This command will place a new EnsureTokenIsValid
class within your app/Http/Middleware
directory. In this middleware, we will only allow access to the route if the supplied token
input matches a specified value. Otherwise, we will redirect the users back to the /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\Foundation\Http\Middleware\InvokeDeferredCallbacks::class,4 // \Illuminate\Http\Middleware\TrustHosts::class,5 \Illuminate\Http\Middleware\TrustProxies::class,6 \Illuminate\Http\Middleware\HandleCors::class,7 \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,8 \Illuminate\Http\Middleware\ValidatePostSize::class,9 \Illuminate\Foundation\Http\Middleware\TrimStrings::class,10 \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,11 ]);12})
1->withMiddleware(function (Middleware $middleware) {2 $middleware->use([3 \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class,4 // \Illuminate\Http\Middleware\TrustHosts::class,5 \Illuminate\Http\Middleware\TrustProxies::class,6 \Illuminate\Http\Middleware\HandleCors::class,7 \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance::class,8 \Illuminate\Http\Middleware\ValidatePostSize::class,9 \Illuminate\Foundation\Http\Middleware\TrimStrings::class,10 \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,11 ]);12})
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:
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 allow 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:
排序 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 :
:
1use App\Http\Middleware\EnsureUserHasRole;23Route::put('/post/{id}', function (string $id) {4 // ...5})->middleware(EnsureUserHasRole::class.':editor');
1use App\Http\Middleware\EnsureUserHasRole;23Route::put('/post/{id}', function (string $id) {4 // ...5})->middleware(EnsureUserHasRole::class.':editor');
Multiple parameters may be delimited by commas:
1Route::put('/post/{id}', function (string $id) {2 // ...3})->middleware(EnsureUserHasRole::class.':editor,publisher');
1Route::put('/post/{id}', function (string $id) {2 // ...3})->middleware(EnsureUserHasRole::class.':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}