监控系统实践第13天:5天上手Laravel8.5之中间件篇(2/5)
Posted 家铭didphp
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了监控系统实践第13天:5天上手Laravel8.5之中间件篇(2/5)相关的知识,希望对你有一定的参考价值。
监控系统实践第13天:5天上手Laravel8.5之中间件篇(2/5)
前言
很多语言的框架中都有中间件
,只是名称、叫法、存在形式不同罢了。
中间件
真的非常好用,用得好省时又省力。
昨天介绍了Laravel8.5
的路由,今天来介绍一下它的中间件
,重点内容会放在注册中间件
上面。
Laravel基础功能:中间件(Middleware)
中间件是什么?
中间件
会在路由
被调用之前
或之后
执行,它的作用,有点点像过滤器、拦截器之类的吧。
你可以在里面埋入过滤参数的代码,同时也可以埋入并执行一些自己自定义的逻辑代码。
如何运用?
中间件
在实际运用中,可以有什么作用呢?比如可以进行一些HTTP请求的参数过滤,检查用户是否登录、用户的输入是否非法等。
可以使用中间件
的地方有很多,很多地方可以用到一个中间件
,也可以用到多个不同的中间件
。
在使用中间件
之前,要确定一下是什么场景,什么情况下使用它,是否有必要使用到它。
实际运用
中间件
相对路由
更容易理解,更容易上手。
下面我们通过5
个步骤,结合实例,来编写并使用一个真实可用的中间件
。
1. 确定中间件场景
现在我用一个实际的例子,来介绍一下Laravel 中间件
的写法与用法。
假设现在有一个场景如下:
- 现有的站点提供了很多API接口供外部调用;
- 每次访客都会传递字符串与数字给对应的接口;
- 有三个指定的参数:
id
、uid
、order_id
,必须为数字,否则会对网站存在一定的安全与性能隐患。
2. 分析场景情况
从刚才提供的场景可以得知,不论有多少接口,有多少数据要传递,最终要防范的,是参数:id
、uid
、order_id
对应的值。
3. 编写中间件
明确了要处理防范的内容,编写起来就很简单了。从上面得知,我们只需要处理三个参数:id
、uid
、order_id
即可。
3.1 创建中间件
所有的中间件
文件,默认放在文件夹/app/Http/Middleware
下,这里我也建议不要到处放,以免造成各个文件夹的职责混乱
。
中间件
可以通过:命令
与手工
的方式创建。
想要创建中间件
,除了原始的手工
方式外,还有一种方式就是通过命令
来创建,官方文档是使用命令
创建,这里推荐使用命令
。
下面开始创建需要过滤那三个参数的中间件
,我命名为:FilterParams
,到Laravel
的根目录下执行:
php artisan make:middleware FilterParams
执行之后,会出现以中间件名称命令的PHP文件FilterParams.php
打开刚才创建的中间件:/app/Http/Middleware/FilterParams.php
,简单的看一下代码:
上图中可以看到,有一个公共方法:handle()
,这个方法可以说是中间件
的核心方法了,如果启用了中间件
,框架就会调用被启用
的中间件
的handle()
方法。
如果要增加自己的业务逻辑,要在这个方法的return
之前添加处理。
3.2 注册中间件(重点章节)
注册相当于是启用,启用后,中间件就开始生效了。
刚创建,不写业务逻辑就启用?
是的,还没开始写代码, 不启用,怎么调试?
现在我们来启用刚才创建的中间件FilterParams
。
直接打开/app/Http/Kernel.php
,可以看到里面定义了3
个protected
受保护的变量,分别为:$middleware
、$middlewareGroups
、$routeMiddleware
,且都是数组:
/app/Http/Kernel.php
是什么文件,起什么作用?
是存放需要调用的中间件
信息的,需要调用哪些中间件
,全部都要在这个文件里面注册登记。
文件里面的这3
个受保护的变量都是些什么?
这3
个受保护变量,分别有不同的含义:
$middleware
为全局中间件
,只要加入进来的中间件
,不需要到别的地方做任何配置,每次请求都会被执行一次;$middlewareGroups
为中间件组
,哪个地方需要就在哪个地方用;$routeMiddleware
为路由中间件
,有点像中间件组
,哪个路由需要,就在哪个路由上面用。
中间件组
和路由中间件
是不是有点懵?到底我要在哪个变量里面注册开启我那个FilterParams
的中间件?
说实在的,我要是不懂的话,现在就懵了。不过不用着急,现在来结合例子一一介绍。
3.2.1 注册全局中间件(核心小节)
假设现在我要把中间件FiterParams
设置为全局中间件,使用它来处理三个参数:id
、uid
、order_id
的值。
打开/app/Http/Kernel.php
,里面定义了3
个protected
受保护的变量,其它的不管,只管负责全局中间件的变量:$middleware
。
避免出现其它问题,原来的内容我们不动,只在最下面增加`FilterParams:
\\App\\Http\\Middleware\\FilterParams::class,
增加后,原先的整个$middleware
就变为:
protected $middleware = [
// \\App\\Http\\Middleware\\TrustHosts::class,
\\App\\Http\\Middleware\\TrustProxies::class,
\\Fruitcake\\Cors\\HandleCors::class,
\\App\\Http\\Middleware\\PreventRequestsDuringMaintenance::class,
\\Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize::class,
\\App\\Http\\Middleware\\TrimStrings::class,
\\Illuminate\\Foundation\\Http\\Middleware\\ConvertEmptyStringsToNull::class,
\\App\\Http\\Middleware\\FilterParams::class,
];
这个时候可以直接写中间件
的代码来看效果了。
打开之前创建的中间件:/app/Http/Middleware/FilterParams.php
,会看到handle()
方法会传递一个Request $request
进来,现在需要进行处理的,就是它了。
这里简单的说一下Request
:Request
是Laravel
自有的获取用户输入的类,可以获取浏览器传递的GET
、POST
、JSON
等数据,后面我会抽一天介绍。
现在知道要处理的参数,我们接收参数来做判断即可,只是不知道参数是通过什么方式传递。
从表面上看,可以排除很少人用的JSON
方式,剩下GET
和POST
,现在只管这两个方式。
因此,如果只考虑GET
方式传递那三个参数过来,我们可以这样编写中间件
的代码:
- 如果三个指定的参数,没有传值,就给一个默认值:0,代表没有传值;
$id = $request->query('id', 0);
$uid = $request->query('uid', 0);
$order_id = $request->query('order_id', 0);
- 如果三个参数都传值了,就不会为
0
,被强制转成数字后,如果跟以前不相等,就为不合法的值;
//如果三个参数都传值了,就不会为`0`,被强制转成数字后,如果跟以前不相等,就为不合法的值
if ($id != '0' && $id != intval($id)) {
echo '参数 id 的值不合法';
exit;
}
if ($uid != '0' && $uid != intval($uid)) {
echo '参数 uid 的值不合法';
exit;
}
if ($order_id != '0' && $order_id != intval($order_id)) {
echo '参数 order_id 的值不合法';
exit;
}
- 整个流程的代码一定要写在
return $next($request)
的那行之前,且不能动那一行代码,现在只要发现三个参数中有一个不是数字,整个请求就中断。
如果是POST
呢?这里不占篇幅浪费时间了,逻辑跟上面一样,只是把$request->query
更换成$request->post
。
那在不确定对方是GET
还是POST
的情况下,有没有更方便的方法?
有,使用$request->input()
方法,将存在的$request->query
或者$request->post
,替换为$request->input
,它既可以获取GET
传参的值,也可以获取到POST
传参的值。
全局中间件
是全局的,因此启用后,根本不需要配置路由来测试路径,直接在浏览器里执行当前站点的URL访问即可查看效果。
3.2.2 注册中间件组
中间件组
与全局中间件
一样,都是打开/app/Http/Kernel.php
修改变量,只是它的变量为里面定义了3
个protected
受保护的变量,其它的不管,只管负责全局中间件的变量:$middlewareGroups
。
需要注意的是,中间件组
默认存在两个下标:web
、api
。
下标名称,就是下次调用的时候需要填写的名称,需要增加的中间件
就填在其中一个下标之中,也可以自己增加下标,每个下标都对应一个数组。
可以继续使用刚才编写好的FilterParams
,我现在把它从全局中间件
的调用中去除,运用到中间件组
中,放到下标web
的末尾:
这个地方的调用,就需要自己去路由中定义了,路由中多调用一个方法middleware
来指定中间件组
,我这里选择web
:
Route::get('/abc', function (Request $request) {
return "Hello World " . $request->input('id');exit;
})->middleware('web');
这个地方正常情况下,只要传递的id
是数字,就会正常输出:
如果id
为非数字,就会触发中间件
里的中断操作:
如果不太记得路由
了,可以看这篇:监控系统实践第12天:5天上手Laravel8.5之路由篇(1/5)
3.2.3 注册路由中间件
路由中间件
与中间件组
类似,只是下标没有那么深。
同样是找到/app/Http/Kernel.php
,里面的路由中间件
组变量为:$routeMiddleware
。
路由中间件
是一个下标对应一个中间件
,中间件组
是一个下标对应多个中间件
。
刚才的FilterParams
再次利用,注意中间件组
与路由中间件
的不要存在重复的下标,可以为它定义一个下标:filter_params
:
'filter_params' => \\App\\Http\\Middleware\\FilterParams::class,
默认完整的路由中间件代码为:
protected $routeMiddleware = [
'auth' => \\App\\Http\\Middleware\\Authenticate::class,
'auth.basic' => \\Illuminate\\Auth\\Middleware\\AuthenticateWithBasicAuth::class,
'cache.headers' => \\Illuminate\\Http\\Middleware\\SetCacheHeaders::class,
'can' => \\Illuminate\\Auth\\Middleware\\Authorize::class,
'guest' => \\App\\Http\\Middleware\\RedirectIfAuthenticated::class,
'password.confirm' => \\Illuminate\\Auth\\Middleware\\RequirePassword::class,
'signed' => \\Illuminate\\Routing\\Middleware\\ValidateSignature::class,
'throttle' => \\Illuminate\\Routing\\Middleware\\ThrottleRequests::class,
'verified' => \\Illuminate\\Auth\\Middleware\\EnsureEmailIsVerified::class,
'filter_params' => \\App\\Http\\Middleware\\FilterParams::class,
];
效果如下:
调用也是需要自己去路由中定义,和中间件组
的方法一样,我这里选择就成了filter_params
:
Route::get('/abc', function (Request $request) {
return "Hello World " . $request->input('id');exit;
})->middleware('filter_params');
路由文件中效果如图:
与中间件组
一样,正常情况下,只要传递的id
是数字,就会正常输出:
如果id
为非数字:
3.3 后置中间件
一开始说过,中间件
会在路由
被调用之前
或之后
执行,即分为:前置中间件
与后置中间件
。
前面全局中间件
、中间件组
、路由中间件
的写法,全部属于前置中间件
,前置中间件
主要处理请求前的参数,后置中间件
主要处理请求后的结果。
现在来介绍一下后置中间件
的写法。
继续使用之前定义的中间件FiterParams
,继续来处理它的handle()
方法。
中间件是属于前置
还是后置
,最好的判断方法就是,看handle()
方法的最后一行return $next($request);
,看是否有代码存在于$next($request)
之后,就是后置中间件
,反之为前置中间件
。
简单的回顾一下前置中间件
的写法:
/**
* 逻辑代码开始
**/
if ($request->post('id') > 0) {
exit;
}
/**
* 逻辑代码结束
**/
return $next($request);
简单的后置中间件
代码是这么写,代码写在 $next()
之后,最后 return $next()
的内容:
$response = $next($request);
/**
* 逻辑代码开始
**/
\\Illuminate\\Support\\Facades\\Storage::disk('public')->append("abc.log", 'request finished');
/**
* 逻辑代码结束
**/
return $response;
请求结束后,在文件abc.log
中写入执行的内容,一般日志的做法。
3.4 中间件的其它调用方法
中间件组
和路由中间件
,除了传递传递下标之外,还有其它的调用方法。
以下,列举几个常用的中间件组
与路由中间件
的调用方法:
- 传递下标;
Route::get('admin/profile', function () {
})->middleware('check_age');
- 传递类名;
Route::get('admin/profile', function () {
})->middleware(\\App\\Http\\Middleware\\CheckAge::class);
- 传递多个中间件,用逗号
,
隔开,如下为类名与下标;
Route::get('admin/profile', function () {
})->middleware(\\App\\Http\\Middleware\\CheckAge::class, 'auth');
- 路由分组使用中间件;
# 方法 1
Route::group(['middleware' => ['check_age']], function () {});
# 方法 2
Route::middleware(['check_age', 'auth'])->group(function () {});
- 路由分组中,某个路由,排除其中一个中间件
withoutMiddleware()
;
Route::middleware([CheckAge::class])->group(function () {
Route::get('/', function () {
});
Route::get('admin/profile', function () {
})->withoutMiddleware([CheckAge::class]);
});
3.5 中间件的排序
中间件排序是框架本身默认的,定义在/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
中。
如果想自己的中间件优先执行,必须修改这个文件中的变量$middlewarePriority
来达到覆盖原有顺序的目的:
protected $middlewarePriority = [
\\Illuminate\\Cookie\\Middleware\\EncryptCookies::class,
\\Illuminate\\Session\\Middleware\\StartSession::class,
\\Illuminate\\View\\Middleware\\ShareErrorsFromSession::class,
\\Illuminate\\Contracts\\Auth\\Middleware\\AuthenticatesRequests::class,
\\Illuminate\\Routing\\Middleware\\ThrottleRequests::class,
\\Illuminate\\Session\\Middleware\\AuthenticateSession::class,
\\Illuminate\\Routing\\Middleware\\SubstituteBindings::class,
\\Illuminate\\Auth\\Middleware\\Authorize::class,
];
这里建议不要在有原始顺序的文件上直接修改顺序,应在文件:/app/Http/Kernel.php
上重新进行覆盖定义。
需要注意的是,排序只会影响中间件组
与路由中间件
的执行顺序,不会影响到全局中间件
。
3.6 中间件参数
中间件也是可以接收额外的参数的,比如外部可以传递用户的角色信息。
再次就FilterParams
说事,这个文件刚创建好的时候handle()
方法是默认存在两个参数的:
public function handle(Request $request, Closure $next)
实际我们可以定义更多变量,传递更多数据进去:
上图中,我多定义了一个$role
代表用户的权限,一个$gender
代表用户性别。
假如现在有个中间件
下标为role
,对应的中间件类名为CheckRole
,需要传递指定的值admin
给它,调用方法为:
Route::get('admin/profile', function () {})->middleware('role:admin');
如果我要同时传递$gender
为male
进去,,
逗号隔开即可:
Route::get('admin/profile', function () {})->middleware('role:admin,male');
最后就是$role
等于admin
,$gender
等于male
。
3.7 Terminable 中间件
前置中间件
是在请求执行之前执行,后置中间件
是在请求执行之后执行,而Terminable 中间件
则跟它们大不相同,主要是指中间件中的一个方法terminate()
。
这个方法可以获得请求参数
与请求返回
的内容,它在请求执行完毕之后,在整个HTTP
响应结束后执行。
每个中间件
都可以定义terminate()
方法,比如整个HTTP
响应完毕后,需要刷新某些数据的操作,需要在 中间件
类的handle()
方法后面,就定义terminate()
方法:
public function terminate($request, $response) {
//刷新数据
}
值得注意的是,编写完terminate()
方法的代码后,需要到/app/Providers/AppServiceProvider.php
文件中去注册对应的中间件类。
比如我现在创建了一个中间件,名称为:FitlerParams
,找到/app/Providers/AppServiceProvider.php
的register()
方法,添加中间件FitlerParams
的注册代码:
public function register()
{
# 这里添加 FitlerParams的注册代码
$this->app->singleton(\\App\\Http\\Middleware\\FitlerParams::class);
}
注册完毕后,Terminable 中间件
开始生效,它具体有哪些用途,产生什么效果,这里就不多啰嗦了。
最后的补充
我的观点是,中间件
很好用,但是绝对不是必须出现在项目中的,如果不太理解,先跳过这个章节,慢慢的根据自己学习的进度,熟悉了整个框架后,再回过来看,会更容易理解。
以上的阐述只对有基础的朋友有作用,如果对以上的知识点有质疑或者不懂的可以在下方留言。
以上是关于监控系统实践第13天:5天上手Laravel8.5之中间件篇(2/5)的主要内容,如果未能解决你的问题,请参考以下文章
监控系统实践第15天:5天上手Laravel8.5之HTTP请求篇(4/5)
监控系统实践第15天:5天上手Laravel8.5之HTTP请求篇(4/5)
监控系统实践第14天:5天上手Laravel8.5之控制器篇(3/5)
监控系统实践第10天:用宝塔部署Laravel8.5框架的站点