什么是HTTP中间件呢?
中间件Middleware
用于控制请求到达和响应请求的整个流程,可对请求进行过滤验证,当需要对请求或响应做出修改或处理,或是想要调整请求处理流程时均可使用中间件来实现。简单来说,中间为过滤进入应用的HTTP请求提供了一套便利的机制。
中间件作为请求和响应之间的中间人,本质上是HTTP的一种过滤机制。由于中间件可对路由进行拦截,因此在路由的基础上增加一层保护和过滤。
理解中间件的最好方式就是将中间件看做HTTP请求达到目标动作之前,必须经过的“层”,每一层都会检查请求并且可以完全拒绝它。
HTTP中间件的应用场景有那些呢?
- 鉴权中间件用来验证用户是否已经经过授权
如果用户没有经过授权,中间件将用户重定向到登录页面,否则允许请求继续向前进入下一步操作。 - CORS中间件可用于为离开站点的响应添加跨域的头信息
- 日志中间件可以记录所有进入站点的请求
- 权限验证
- 开启会话
...
定义中间件
如何定义HTTP中间件呢?
定义中间件只需要实现了Swoft\Http\Message\Middleware\MiddlewareInterface
接口,就会成为一个合法的中间件。
$ vim vendor/swoft/http-message/src/Middleware/MiddlewareInterface.php
MiddlewareInterface
接口中process()
方法是中间件逻辑处理的方法
例如:创建中间件用于为表单添加CSRF Token以及错误信息的输出。
注意CSRF Token与错误信息输入均使用Session的方式
使用命令方式创建中间件,进入项目根目录输入命令:
$ php bin/swoft gen:middleware ControllerMiddleware
关于生成命令的详情,可参见《Swoft 生成命令》。
创建成功后,将在app/Middlewares
文件夹下生成一个ControllerMiddleware
的类文件。
编辑ControllerMiddleware.php
文件,添加我们需要的业务逻辑代码。
$ vim app/Middlewares/ControllerMiddleware.php
exists("error") && session()->has("error")){
session()->forget("error");
}
// 每次GET请求时将重新生成CSRF token
if(strtoupper($request->getMethod())==="GET"){
if(session()->exists("_token") && session()->has("_token")){
session()->put("_token", sha1(mt_rand()));//表单CSRF TOKEN
}
}
$response = $handler->handle($request);
return $response;
}
}
注意:定义的中间件类使用Bean()
注解,同样需提前引入use Swoft\Bean\Annotation\Bean;
,至于为什么要要使用,接着往下看。
创建中间件完成以后,如何使用中间件呢?使用中间件有两种方式:
第一种是全局范围内使用,也就是在每个请求都使用的中间件,这种中间件可以通过配置全局中间件的参数来实现。典型的比如视图中间件、会话中间件...
第二种是只针对需要的控制器或方法使用特定的中间件,可以通过中间件注解的方式来实现。
配置全局中间件
想要使用已经创建的中间件之前,需要提前配置中间件。当自定义的中间件需要在全局请求中应用时,可将中间件作为全局中间件使用。
只需要在全局Bean
配置文件base.php
内配置serviceDispatcher
服务分发器的middlewares
中间件属性,在数组中加入自己定义的中间件的命名空间地址即可。
$ vim config/beans/base.php
'serverDispatcher' => [
'middlewares' => [
\Swoft\View\Middleware\ViewMiddleware::class,
// \Swoft\Devtool\Middleware\DevToolMiddleware::class,
\Swoft\Session\Middleware\SessionMiddleware::class,
]
],
默认添加了两个全局中间件,它们分别有什么作用呢?
\Swoft\View\Middleware\ViewMiddleware::class
\Swoft\Session\Middleware\SessionMiddleware::class
问题:当配置全局中间件后,观察中间件中的代码被连续执行了两次,怎么办?怎么办?怎么办?
添加注解使用中间件
中间件配置完成后,应该如何使用呢?如果已经配置了全局中间件,那么在所有请求中就已经生效,不需要添加代码。如果定义的中间件只是针对部分控制器或动作方法,则可使用注解的方式进行使用。
注意,在控制器中使用注解的方式调用中间件时,首先必须引入对应的中间件注解类:
use Swoft\Http\Message\Bean\Annotation\Middleware;
use Swoft\Http\Message\Bean\Annotation\Middlewares;
可通过@Middleware
和@Middlewares
两个注解,方便的配置中间件到当前的控制器Controller
和动作方法Action
中。
需要注意的是:
- 当中间件注解应用于控制器上时,将作用域为整个控制器。
- 当中间件注解应用于动作方法时,作用域仅为当前的动作方法有效。
-
@Middleware
注解用于配置单个中间件 -
@Middlewares
注解用于配置一组中间件,按照定义的先后顺序依次执行。
单个中间件
@Middleware
单个中间件注解的语法,可传入两个个参数class
或value
,其中class
为中间件的类名,注意这个类不会被自动引入需手工引入。那value
表示什么意思呢,望文生义等于望梅止渴,不知道。
@Middleware(class=MiddlewareName::class)
例如:在登录接口中使用自定义的中间件ControllerMiddleware
问题:在动作方法中加入单个中间件后,出现了错误{"msg":"Bean ControllerMiddleware not exist","file":"\/var\/www\/swoft\/vendor\/swoft\/framework\/src\/Bean\/Container.php","line":77,"code":0}
提示中间件不存在,怎么办?
原因是使用@Middleware(class=ControllerMiddleware::class)
中间件注解所加载的类ControllerMiddleware::class
没有被引入use
,这厮是不会自动引入的需要手工引入。因此,你需要添加use App\Middlewares\ControllerMiddleware;
。官方的文档很简洁,很简洁,很简洁...
中间件组
@Middlewares({
@Middleware(GroupTestMiddleware::class),
@Middleware(ActionTestMiddleware::class)
})
中间件中断
当使用验证检查类的中间件时,需要中断当前请求并直接给出响应,可通过中断流程实现。
- 构造一个新的
Response
响应对象直接返回
在定义中间件时,在中间件的process
处理方法中构造一个Response
响应对象并返回。
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler):ResponseInterface
{
$auth = false;
//如果认证失败
if(!$auth){
//从RequestContext请求上下文对象中获取Response响应对象
return response()->withStatus(401);
}
//如果认证成功则委托给下一个中间件进行处理
return $handler->handle($request);
}
- 直接抛出异常返回
只要在请求的生命周期内抛出异常,则会被ErrorHandler
错误处理器捕获并处理,中间件内抛出也是如此。
中间件应用
- 提前拦截请求,注意拦截要在
$handler->handle($request)
之前进行实现。
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler):ResponseInterface
{
//拦截请求并处理
$path = $request->getUri()->getPath();
if($path === "/favicon.ico"){
return response()->withStatus(404);
}
//递交给下一个中间件处理请求
return $handler->handle($request);
}
- 请求设置允许跨域
CORS中间件可用于为离开站点的响应添加合适的头进行跨域
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler):ResponseInterface
{
//跨域CORS判断并处理
if($request->getMethod === "OPTIONS"){
return $response->withHeader("Access-Control-Allow-Origin", "http://sitename.com")
->withHeader("Access-Control-Allow-Header", "X-Request-With, Content-Type, Accept, Origin, Authorization")
->withHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
}
//递交给下一个中间件处理请求
return $handler->handle($request);
}
小结
如果对Laravel框架比较熟悉,那么对中间件的概念理解起来会比较容易,如果是第一次接触,或多或少会觉得概念有些陌生。