[ Laravel 5.2 文档 ] 基础 —— HTTP 中间件

1、简介

HTTP 中间件提供了为过滤进入应用的 HTTP 请求提供了一套便利的机制。例如,Laravel 内置了一个中间件来验证用户是否经过授权,如果用户没有经过授权,中间件会将用户重定向到登录页面,否则如果用户经过授权,中间件就会允许请求继续往前进入下一步操作。

当然,除了认证之外,中间件还可以被用来处理更多其它任务。比如:CORS 中间件可以用于为离开站点的响应添加合适的头(跨域);日志中间件可以记录所有进入站点的请求。

Laravel框架自带了一些中间件,包括维护模式、认证、CSRF 保护中间件等等。所有的中间件都位于 app/Http/Middleware 目录。

2、定义中间件

要创建一个新的中间件,可以通过 Artisan 命令 make:middleware

php artisan make:middleware OldMiddleware

这个命令会在 app/Http/Middleware 目录下创建一个新的中间件类 OldMiddleware,在这个中间件中,我们只允许提供的 age 大于 200 的访问路由,否则,我们将用户重定向到主页:

<?php

namespace App\Http\Middleware;

use Closure;

class OldMiddleware
{
    /**
     * 返回请求过滤器
     *
     * @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 重定向到客户端;否则,请求会被传递下去。将请求往下传递可以通过调用回调函数 $next 并传入  $request

理解中间件的最好方式就是将中间件看做 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' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];

中间件在 HTTP Kernel 中被定义后,可以在路由选项数组中使用 $middleware 键来指定该中间件:

Route::get('admin/profile', ['middleware' => 'auth', function () {
    //
}]);

使用数组分配多个中间件到路由:

Route::get('/', ['middleware' => ['first', 'second'], function () {
    //
}]);

除了使用数组外,还可以使用 middleware 方法链的方式定义路由:

Route::get('/', function () {
    //
})->middleware(['first', 'second']);

中间件组

有时候你可能想要通过指定一个键名的方式将相关中间件分到一个组里面,从而更方便将其分配到路由中,这可以通过使用 HTTP Kernel 的 $middlewareGroups  实现。

Laravel 自带了开箱即用的 webapi 两个中间件组以包含可以应用到 Web UI 和 API 路由的通用中间件:

/**
 * 应用的路由中间件组
 *
 * @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,
    ],

    'api' => [
        'throttle:60,1',
        'auth:api',
    ],
];

中间件组可以被分配给路由和控制器动作,使用和单个中间件分配同样的语法。再次申明,中间件组的目的只是让一次分配给路由多个中间件的实现更加简单:

Route::group(['middleware' => ['web']], function () {
    //
});

4、中间件参数

中间件还可以接收额外的自定义参数,例如,如果应用需要在执行给定动作之前验证认证用户是否拥有指定的角色,可以创建一个 RoleMiddleware 来接收角色名作为额外参数。

额外的中间件参数会在 $next 参数之后传入中间件:

<?php

namespace App\Http\Middleware;

use Closure;

class RoleMiddleware
{
    /**
     * 运行请求过滤器
     *
     * @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}', ['middleware' => 'role:editor', function ($id) {
    //
}]);

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 将会从服务容器中取出该中间件的新的实例,如果你想要在调用 handleterminate 方法时使用同一个中间件实例,则需要使用容器的 singleton 方法将该中间件注册到容器中。

学院君 has written 976 articles

Laravel学院院长,终身学习者

积分:111867 等级:P12 职业:手艺人 城市:杭州

23 条回复

  1. 陆志洁 陆志洁 says:
    学院君,请问一下 当我在路由中更添加了web中间件,with()方法和flash()方法就不能传递一次性数据了 请问是怎么回事 我是laravel5.2的
  2. 竹翁_Jerry 竹翁_Jerry says:
    你好,学院君,我在路由群组里面,使用web中间件,Route::group(['middleware' => ['web']],function (){ Route::get('/', function () { return view('welcome'); }); Route::get('test','IndexController@index');});然后控制器里面去获取数据,class IndexController extends Controller{ public function index() { $user = DB::table('users')->get(); dd($user); //echo 123; echo env('DB_DATABASE'); }}为什么页面会报错呢?Whoops, looks like something went wrong.1/1FatalErrorException in AliasLoader.php line 63:Maximum function nesting level of '100' reached, aborting!in AliasLoader.php line 63at FatalErrorException->__construct() in HandleExceptions.php line 133at HandleExceptions->fatalExceptionFromError() in HandleExceptions.php line 118at HandleExceptions->handleShutdown() in HandleExceptions.php line 0然后我把 Route::get('test','IndexController@index');这条路由丢到路由群组外面,就没有这个错误了,不知道要怎么才能放进群组里面调用呢
  3. 刘帅才 刘帅才 says:
    你好,学院君,我在用web中间件时报错,用web中间件还需要配置吗???Whoops, looks like something went wrong.1/1ReflectionException in Container.php line 741:Class web does not existin Container.php line 741at ReflectionClass->__construct('web') in Container.php line 741at Container->build('web', array()) in Container.php line 631at Container->make('web', array()) in Application.php line 674at Application->make('web') in Pipeline.php line 123at Pipeline->IlluminatePipeline{closure}(object(Request))at call_user_func(object(Closure), object(Request)) in Pipeline.php line 103at Pipeline->then(object(Closure)) in Router.php line 710at Router->runRouteWithinStack(object(Route), object(Request)) in Router.php line 675at Router->dispatchToRoute(object(Request)) in Router.php line 635at Router->dispatch(object(Request)) in Kernel.php line 236at Kernel->IlluminateFoundationHttp{closure}(object(Request))at call_user_func(object(Closure), object(Request)) in Pipeline.php line 139at Pipeline->IlluminatePipeline{closure}(object(Request)) in CheckForMaintenanceMode.php line 44at CheckForMaintenanceMode->handle(object(Request), object(Closure))at call_user_func_array(array(object(CheckForMaintenanceMode), 'handle'), array(object(Request), object(Closure))) in Pipeline.php line 124at Pipeline->IlluminatePipeline{closure}(object(Request))at call_user_func(object(Closure), object(Request)) in Pipeline.php line 103at Pipeline->then(object(Closure)) in Kernel.php line 122at Kernel->sendRequestThroughRouter(object(Request)) in Kernel.php line 87at Kernel->handle(object(Request)) in index.php line 54
  4. 霜之哀伤 霜之哀伤 says:
    @ 学院君 我的理解是这样的哈:$request->user()返回一个UserModel的实例(对象?),hasRoles是该实例的一个方法。应该是这样的,我在UserModel新加了一个方法并做了测试。不知道大神们觉得是否如此。另:middleware中间件没有任何继承,这个$request是怎么来的。。。。是框架自动加载的吗?
  5. ghc的春天 ghc的春天 says:
    @ 史志策 www.demo.com/user?age=20
  6. 李云龙 李云龙 says:
    @ 史志策 www.demo.com/user?age=20这样做才可以取得age;www.demo.com/user这一部分是用来定义路由规则的?age=20这一部分是用的定义请求参数的
  7. Thank Thank says:
    public function handle($request, Closure $next) { if ($request->input('age') <= 200) { return redirect('home'); } return $next($request); } 在测试示例中的中间件时,发现$request->input('age'),得不到值,不知原因出在哪儿?访问url类似:www.demo.com/user/age/20
  8. 海洪 海洪 says:
    有使用日志中间件的项目吗?

登录后才能进行评论,立即登录?