中间件(Middleware)作为Http请求(Request)和Http响应(Response)之间的中间人,本质是对HTTP过滤和完善的一种机制。可以完成对请求数据的拦截处理、数据校验,同时在进入逻辑处理后会判断是否允许进入下一个中间件。应用中通过多个中间件的层层过滤逐步完善以实现解耦。
在Laravel中Middleware起着过滤进入应用的HTTP请求对象(Request)并完善离开应用的HTTP响应对象(Response)的作用。因此中间件实际分为两大角色,过滤请求的称为前置中间件,完善响应的称之为后置中间件。
Middleware作用
- 使用Middleware可为系统提供HTTP过滤层,可让系统层次更为明确。
- Middleware在路由上拦截请求,为路由增加一层保护和过滤。
装饰器模式(Decorator Pattern)
Laravel是如何实现Middleware的呢?
Laravel中Middleware实现修饰器模式,通过捕获请求进而处理并将处理后的请求对象返回给下一个堆栈层。
编码中动态扩展类的功能经常会使用继承的方式来实现,随着动态扩展的功能越来越多,子类逐渐会越来越膨胀,系统也会变得越来越不够灵活。
装饰器模式允许向一个现有的对象中添加新的功能,同时又不改变其原来的结构。也就时说在类中动态扩展功能时依然保持类原本的灵活性。即实现在“开放封闭”原则下动态新增或删除功能。
开放封闭原则(OCP,Open Closed Principle)即对扩展开放对修改封闭。
比如在请求发送后且执行前,可能需进行Cookie加密、开启Session、CSRF保护等操作,但每个请求各有不同,而且执行请求后可能仍需执行某些操作。此时就需要根据请求的特性动态的新增操作。
应用场景
- 授权认证
- 加密解密
- cookie队列
- session读写
- 速率限制
- 请求解析
场景1:验证用户认证中间件
用户登录流程中,使用中间件来验证用户是否已认证,若认证失败则重定向至登录视图,若认证成功则登录请求会被认证中间件通过,并将请求传递给应用。
场景2:跨同源策略中间件
用于处理请求在被响应前添加正确的响应头
场景3:日志中间件
在应用被请求时优先记录下请求信息
[案例] 路由中使用SESSION
回话
app/Http/routes.php
//未用中间件
Route::get('/', function () {
session(['start_time'=>time()]);//存入session
return view('welcome');
});
Route::get('/test',function(){
return session('start_time');//获取session
});
//使用中间件
Route::group(['middleware'=>'web'],function(){
Route::get('/', function () {
session(['start_time'=>time()]);//存入session
return view('welcome');
});
Route::get('/test',function(){
return session('start_time');//获取session
});
});
[疑问] 为什么 session()
放在 Route::group(['middleware'=>'web'],function(){...})
才生效呢?
查看中间件注册文件 app/Http/Kernal.php
,发现在$middlewareGrouops
成员属性的web
中存在Session
和Cookie
相关的配置。
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
//关于Cookie的处理
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
//关于Session的处理
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
],
'api' => [
'throttle:60,1',
],
];
注册中间件
使用中间件之前必须创建并注册,Laravel中间件分为两种类型。
- 全局中间件:在应用的每个HTTP请求中运行
- 路由中间件:分配给特定路由使用的中间件
中间件的注册文件位于 app/Http/Kernal.php
,在Kernal
类中存在两个成员属性$middleware
和$routeMiddleware
。
class Kernal extends HttpKernal
{
//注册全局中间件
protected $middleware = [];
//注册路由中间件
protected $routeMiddleware = [];
}
创建中间件
可使用php artisan
命令查看所有的命令选项,使用artisan
命令创建中间件make:middleware
,创建的中间件将保存在 app/Http/Middleware
目录下。
[验证] 中间件具有拦截HTTP请求的作用
步骤1:在CLI命令行提示界面运行命令,创建登录中间件。
使用命令行生成文件:php artisan make:middleware AdminLogin
生成中间件文件路径:app/Http/Middleware/AdminLogin.php
在中间件文件中添加配置信息
public function handler($request, Closure $next){
echo 'stop'; //中间件具有拦截HTTP请求作用,此处应有输出。
exit;//后续控制器中方法中断执行
return $next($request);
}
步骤2:将AdminLogin
中间件注册到路由中间件中
中间件配置文件路径:app/Http/Kernal.php
protected $routeMiddleware = [
'admin.login'=>\App\Http\Middleware\AdminLogin::class,
];
步骤3:在路由配置文件中调用中间件
路由配置文件:app/Http/routes.php
Route::group(['middleware'=>['admin.login']], function(){
Route::get('admin/login', 'Admin\LoginController@index');
})
步骤4:使用浏览器访问本地域名并查看页面输出
http://blog.com/admin/login
使用中间件
[案例] 用户登录时根据SESSION判断登录状态
步骤1:使用artisan
命令创建后台登录中间件
php artisan make:middleware AdminLogin
步骤2:将中间件注册到路由中
app/Http/Kernal.php
protected $routeMiddleware = [
'admin.login'=>\App\Http\Middleware\AdminLogin::class,
];
步骤3:配置路由规则
(1)设置后台登录路由
app/Http/routes.php
Route::group(['middleware'=>'web'],function(){
//进入后台用户登录页面
Route::get('admin/login', 'Admin\IndexController.php@login');
});
(2)处理用户登录
app\Http\Controllers\Admin\IndexController.php
public function login(){
//用户登录成功后写入session
session(['login_user_id'=>999]);
return __FUNCTION__;
}
(3)设置后台首页路由
app/Http/routes.php
Route::group(['prefix'=>'admin', 'namespace'=>'Admin', 'middleware'=>['web','admin.login']], function(){
//后台首页获取session数据
Route('index', 'IndexController@index');//预设后台首页控制器及操作
});
(5)设置后台登录中间件
app/Http/Middleware/AdminLogin.php
public function handler($request, Closure $next){
//若session中不存在用户登录信息则跳转至登录页面
if(!session('login_user_id')){
return redirect('admin/index');
}
//否则进入预设控制器
return $next($request);
}
(4)预设后台首页
app/Http/Controllers/Admin/IndexController.php
public function index(){
echo 'admin index page';
}
小结
- 中间节位于路由和控制器之间
- 中间件对HTTP请求拦截并过滤
- 中间件自动创建
php artisan make:middleware MiddleName
- 中间件在路由配置的关键为
middleware
- 中间件针对路由分为全局
$middleware
和特定routeMiddleware
- 中间件注册和使用时应使用驼峰转小写和点号连接方式
- 自定义中间件在
handler()
内进行编写拦截过滤业务逻辑 -
session
和cookie
需在路由分组的middleware
参数配置web
值
中间节的使用场景如用户认证、权限验证、访问记录、重定向、日志、开启回话等,Laravel中可以把HTTP中间件看做“装饰器”, 在请求达到最终动作之前对请求进行过滤和处理。
Laravel的请求在进入逻辑处理之前会通过HTTP中间件进行处理,也就是说HTPT请求的逻辑是
- 创建中间件
- 注册中间件
- 使用中间件
创建中间件
# 在 app/Http/Middleware 目录下生成中间件
$ php artisan make:middleware MiddlewareName
# eg
$ php artisan make:middleware BuildMenu
app/Http/Middleware/BuildMenu
<?php
namespace App\Http\Middleware;
use Closure;
class BuildMenu
{
public function handle($request, Closure $next)
{
return $next($request);
}
}
中间件在HTTP请求阶段会调用自己的handle()
处理器方法,同时中间件也可在响应阶段使用,不过此时会调用自己的terminate()
方法。
- 中间件处理请求
handle($request, $next)
@param mixed request 请求参数,包含输入、URL、文件上传等信息。
@param mixed next 闭包函数,处理请求的业务逻辑
@return mixed
- 中间件处理响应
terminate($request, $response)
注册中间件
中间件可针对route
路由,也可针对所有HTTP请求。
- 针对所有HTTP请求的中间件
若中间在每个HTTP请求期间都被执行,仅需将中间件类设置到 app/Http/Kernal.php
的$middleware
数组属性中。
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\TrustProxies::class,
];
- 针对特定route路由的中间件
对于针对特定路由的中间件,app/Http/Kernel.php
类中$routeMiddleware
属性包含了Laravel内置的入口中间件,在其中追加并设置别名即可。
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'menu'=>\App\Http\Middleware\BuildMenu::class
];
'menu'=>\App\Http\Middleware\BuildMenu::class
使用中间件
注册完毕中间件后要开始绑定中间件到路由,绑定路由有两种方式。
- 通过数组分配
Route::get('/', ['middleware'=>['auth', 'menu']], function(){
});
Route::group(['namespace'=>'Admin','prefix'=>'admin','middleware'=>['web','menu']],function($router){
$router->any('/login', 'LoginController@login')->name('agent.login');
$router->any('/logout', 'LoginController@logout');
$router->any('/', 'IndexController@index');
});
- 通过方法连来分配
Route::get('/', function(){
})->middleware(['auth', 'menu']);
也可以在控制器的构造器中调用中间件
public function __construct()
{
$this->middleware('menu');
}