简介中间件

中间件的思想就是在应用程序周围包裹了一系列的层, 就像蛋糕,或者洋葱。 每个请求进入应用程序要穿过每个中间件层,然后每个响应也一层一层穿过中间件,最终到达最终用户。

中间件,应该与你应用程序的逻辑分开,通常被设计的理论上适用于任何应用程序。

中间件 可以基于接收的内容,对请求进行检查, 装饰,甚至是拒绝。这意味着,中间件很适合速率限制之类的东西: 它可以检查 IP 地址,检查最后一分钟访问资源的次数,如果超过阈值,则返回(429)请求过多状态。

因为中间件,还可以在响应离开应用程序时访问响应,所以中间件也很适合修饰响应。例如:Laravel 使用中间件将请求/响应周期中所有排队的cookie添加到响应中,然后再发送给最终用户。

中间件拥有强大多用途是因为, 它是请求/响应循环周期中的第一环和最后一环。 这使得它非常适合处理需要启用会话的事情——PHP需要你尽可能早的打开、尽可能晚的关闭 会话,中间件也一样。

创建自定义中间件

例子: 创建一个拒绝所有 DELETE HTTP 方法请求 同时 返回cookie 的中间件。

  1. 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 默认开箱即用的中间件组有两个 webapiweb 中间件组几乎作用于每一个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 可以使用代理的情况下 正确的处理 HTTPHTTPS, 并且正确解析这些代理请求的 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