简介中间件
中间件的思想就是在应用程序周围包裹了一系列的层, 就像蛋糕,或者洋葱。 每个请求进入应用程序要穿过每个中间件层,然后每个响应也一层一层穿过中间件,最终到达最终用户。
中间件,应该与你应用程序的逻辑分开,通常被设计的理论上适用于任何应用程序。
中间件 可以基于接收的内容,对请求进行检查, 装饰,甚至是拒绝。这意味着,中间件很适合速率限制之类的东西: 它可以检查 IP 地址,检查最后一分钟访问资源的次数,如果超过阈值,则返回(429)请求过多状态。
因为中间件,还可以在响应离开应用程序时访问响应,所以中间件也很适合修饰响应。例如:Laravel 使用中间件将请求/响应周期中所有排队的cookie添加到响应中,然后再发送给最终用户。
中间件拥有强大多用途是因为, 它是请求/响应循环周期中的第一环和最后一环。 这使得它非常适合处理需要启用会话的事情——PHP需要你尽可能早的打开、尽可能晚的关闭 会话,中间件也一样。
创建自定义中间件
例子: 创建一个拒绝所有 DELETE HTTP 方法请求 同时 返回cookie 的中间件。
php artisan make:middleware BanDeleteMethod
打开 app/Http/Middleware/BanDeleteMethod.php , 这是默认内容
...
class BanDeleteMethod
{
public function handle($request, Closure $next){
return $next($request);
}
}
理解中间件的 handle()方法
首先, 中间件是一层一层嵌套的,最终嵌套的 app 上, 在请求进入时, 注册的第一个中间件,会首先获取访问权限, 然后请求按顺序穿过其他中间件,最终达到 app , 然后响应向外穿过一层一层的中间件, 最先获得的请求的中间件最后获取请求.
假如,我们定义了一个名叫BanDeleteMethod 的中间件,作为第一个中间件. 这意味着,当请求进入时,请求没有被任何其余的中间件修饰,是一个原始的请求.
把请求传递给 $next() 意味着,传递请求给其余的中间件. $next() 闭包仅接受 $request 并将其传递到中间件堆栈中的下一个中间件的 handle() 方法。然后继续将其往下传递,直到没有其他中间件可以持有(捕获)请求,最终它到达了应用程序。
接着, 响应时怎么出来的呢? 首先,应用程序返回响应,响应将回传到中间件链中 — 因为每个中间件都返回自己的响应. 所以, 还是在handle() 中, 中间件可以修饰 $request并且转递请求到 next() 闭包, 然后在最终返回给用户之前,可以根据接受情况决定做一些事情.
伪代码 解释中间件
class BanDeleteMethod
{
public function handle($request, Closure $next){
// 此时, $reques 是原始的
// 校验ip
if($request->ip() === '192.168.1.1'){
return response('Banned Ip Address', 403);
}
// 现在我们决定接受它. 传递它到下一个中间件(堆中的).
// 我们传递它给 `$next()`. `$next()`返回的是一个响应,这个响应是请求穿过中间件堆栈向下抵达应用程序,
// 最终应用程序返回响应,这个响应向上穿过中间件堆栈, 最终获得
$response = $next($request);
// 这里, 在响应到达最终用户之前, 我们再一次与响应交互
$response->cookie('visited-our-site'. true);
// 最终, 我返回响应给用户
return $response;
}
}
实现拒绝所有 detele Http 的中间件
...
class BanDeleteMethod
{
public function handle($request, Closure $next)
{
// Test for the DELETE method
if ($request->method() === 'DELETE') {
return response(
"Get out of here with that delete method",
405
);
}
$response = $next($request);
// Assign cookie
$response->cookie('visited-our-site', true);
// Return response
return $response;
}
}
绑定中间件
接下来,我们要注册中间件,有两种方式:全局注册,或者注册到特定的路由。
全局中间件作用于每一个路由;路由中间件作用于一个路由或者一个基础路由。
绑定全局中间件
绑定都发生在app/Http/Kernel.php 文件中,所以添加全局中间件,只需要增加 $middleware 属性即可。
// app/Http/Kernel.php
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\BanDeleteMethod::class,
];
绑定路由中间件
作用于特定路由的中间件,可以被添加为路由中间件,或者只是作为路由组的一部分。
绑定路由中间件,就是添加 app\Http\Kernel 文件中的 $routeMiddleware 数组。和 $middleware类似,但是我们要指定一个 key 值,这个 key 值将被用于路由使用中间件过滤的时候。
// app/Http/kernel.php
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'ban-delete' => \App\Http\Middleware\BanDeleteMethod::class,
//...
]
现在, 我们使用中间件(在路由定义的时候)
Route::get('contacts', 'ContactsController@index')->middleware('ban-delete');
Route::prefix('api')->middleware('ban-delete')->group(function(){
// 所有和api有关的路由
})
使用中间件组
中间件组本质上就是一组预先打包的中间件。
laravel 默认开箱即用的中间件组有两个 web 和 api 。web 中间件组几乎作用于每一个laravel 请求, 它包含 cookie 中间件,session 中间件,CSRF保护中间件。api 这些都没有, 它包含 throttling middleware 可用于速率限制,和一个路由模型绑定中间件。他们都在 app/Http/Kernel.php 文件中定义。
你可以像使用路由中间件那样使用中间件组:
Route::get('/', 'Homecontroller@index')->middleware('web');
你也可以创建自己的中间件组,或者增加或删除现有的中间件组。然后需要把他们添加到 $middlewareGroups 数组中(在 app/Http/Kernel.php 文件中)。
默认路由组 web路由组匹配route/web.php 文件 ,api路由组匹配 route/api.php 文件。route/* 文件在 RouteServiceProvider 中被加载。
// App\Providers\RouteServiceProvider
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
}
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namesace)
->group(base_path('routes/web'));
}
传递参数给中间件
有时,你需要传递参数给路由中间件。例如认证中间件,会根据用户类型(普通会员或者拥有者)做出不同的反应。
Route::get('company', function(){
return view('company.admin');
})->middleware('auth:owner');
为了让以上代码工作。我需要在 handle() 中添加参数。
public function handle($request, $next, $role){
if(auth()->check() && auth()->user()->hasRole($role)){
return $next($request);
}
return redirect('login');
}
你也可以添加多个参数给 handle(),记得把参数逗号分隔传递给中间键即可。
Route::get('company', function(){
return view('company.admin');
})->middleware('auth:owner,view');
Trusted Proxies(信任代理) 中间件
当你使用laravel 工具生成 URLs 时, laravel会自动决定使用 HTTP或者 HTTPS。
但是,当你使用代理(负载均衡或者其余网络代理)时,他就失效了。有些代理会发送类似 X_FORWARDED_PORT 或者 X_FORWARDED_PROTO 的非标准的 header , 希望你信任他们,解析他们,并使用这些作为解析请求的一部分。为了使 laravel 可以使用代理的情况下 正确的处理 HTTP 和 HTTPS, 并且正确解析这些代理请求的 header 参数, 你需要自定义怎么处理。
信任所有代理,显然不合适,你需要锁定你的应用程序只信任某些代理,设置只信任这些代理中的部分转发表头(forwarded headers)。
Laravel 5.6 之后 TrustedProxy 包被包含在 laravel 中。之前的版本也可以安装。TrustedProxy 可以设置信任的流量源的白名单,并且可以标记那些转发表头是可以信任的,设置非标准的转发表头怎么映射到正常表头。
// App\Http\Middleware\TrustProxies.php
// 信任的代理 ip 数组
protected $proxies = [
'192.168.1.1',
'192.168.1.1'
];
// 代理用于被检测的标头
protected $header = Request::HEASER_X_FORWARDED_ALL; // 默认信任所有转发 Header
