简介
中间件为过滤进入应用的 HTTP 请求提供了一套便利的机制。例如,Laravel 内置了一个中间件来验证用户是否经过认证(如登录),如果用户没有经过认证,中间件会将用户重定向到登录页面,而如果用户已经经过认证,中间件就会允许请求继续往前进入下一步操作。
当然,除了认证之外,中间件还可以被用来处理很多其它任务。比如:CORS 中间件可以用于为离开站点的响应添加合适的头(跨域);日志中间件可以记录所有进入站点的请求,从而方便我们构建系统日志系统。
Laravel 框架自带了一些中间件,包括认证、CSRF 保护中间件等等。所有的中间件都位于 app/Http/Middleware
目录下。
定义中间件
要创建一个新的中间件,可以通过 Artisan 命令 make:middleware
:
php artisan make:middleware CheckAge
这个命令会在 app/Http/Middleware
目录下创建一个新的中间件类 CheckAge
,在这个中间件中,我们只允许提供的 age
大于 200 的请求才能访问应用该中间件的路由,否则,我们会将用户重定向到 /
URI:
<?php
namespace App\Http\Middleware;
use Closure;
class CheckAge
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($request->age <= 200) {
return redirect('/');
}
return $next($request);
}
}
正如你所看到的,如果请求参数中的 age
小于等于 200,中间件会返回一个 HTTP 重定向给客户端;否则,请求会被传递下去。将请求往下传递可以通过调用回调函数 $next
并传入当前 $request
。
注:此时只是定义好了中间件的逻辑,要让这个中间件生效,还要将其注册到指定路由中,我们很快就会在下面的注册中间件部分教你怎么做。
理解中间件的最好方式就是将中间件看做 HTTP 请求到达目标动作之前必须经过的“层”,每一层都会检查请求并且可以完全拒绝它。
注:所有的中间都是在服务容器中解析,所以你可以在中间件的构造函数中类型提示任何依赖。
请求之前/之后的中间件
一个中间件是请求前还是请求后执行取决于中间件本身。比如,以下中间件会在请求处理前执行一些任务:
<?php
namespace App\Http\Middleware;
use Closure;
class BeforeMiddleware{
public function handle($request, Closure $next) {
// Perform action
return $next($request);
}
}
而下面这个中间件则会在请求处理后执行其任务:
<?php
namespace App\Http\Middleware;
use Closure;
class AfterMiddleware{
public function handle($request, Closure $next) {
$response = $next($request);
// Perform action
return $response;
}
}
可以看到,决定中间件逻辑在请求之前只是请求之后执行的分水岭是中间件逻辑位于 $next($request)
语句之前还是之后。
注册中间件
中间件分三类,分别是全局中间件、中间件组和指定路由中间件:
全局中间件
如果你想要定义的中间件在每一个 HTTP 请求时都被执行,只需要将相应的中间件类添加到 app/Http/Kernel.php
的数组属性 $middleware
中即可:
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
...
\App\Http\Middleware\CheckAge::class,
];
...
但除非真的需要,否则我们一般不会把业务级别的中间件放到全局中间件中。
分配中间件到指定路由
如果你想要分配中间件到指定路由,首先应该在 app/Http/Kernel.php
文件中分配给该中间件一个 key
,默认情况下,该类的 $routeMiddleware
属性包含了 Laravel 自带的中间件,要添加你自己的中间件,只需要将其追加到后面并为其分配一个 key
,例如:
// 在 App\Http\Kernel 类中...
/**
* 应用的路由中间件列表
* 这些中间件可以分配给路由组或者单个路由
* @var array
*/
protected $routeMiddleware = [
...
'age' => \App\Http\Middleware\CheckAge::class,
];
这样,当我们在浏览器中访问 http://blog.test/hello
时就会跳到 http://blog.test
,而访问 http://blog.test?age=300
时则可以正常访问
还可以使用数组分配多个中间件到路由:
Route::get('/hello', function () {
//
})->middleware('age', 'auth');
分配中间件到路由群组时,你可能偶尔需要阻止中间件被应用到群组中的单个路由,这可以通过使用 withoutMiddleware
方法来实现:
use App\Http\Middleware\CheckAge;
Route::middleware([CheckAge::class])->group(function () {
Route::get('/', function () {
});
// 该路由不会应用 CheckAge 中间件
Route::get('admin/profile', function () {
//
})->withoutMiddleware([CheckAge::class]);
});
withoutMiddleware
方法只能移除路由中间件,不能移除全局中间件。