Swoft HTTP Middleware 中间件

什么是HTTP中间件呢?

中间件Middleware用于控制请求到达响应请求的整个流程,可对请求进行过滤验证,当需要对请求或响应做出修改或处理,或是想要调整请求处理流程时均可使用中间件来实现。简单来说,中间为过滤进入应用的HTTP请求提供了一套便利的机制。

HTTP 中间件

中间件作为请求和响应之间的中间人,本质上是HTTP的一种过滤机制。由于中间件可对路由进行拦截,因此在路由的基础上增加一层保护和过滤。

HTTP 中间件

理解中间件的最好方式就是将中间件看做HTTP请求达到目标动作之前,必须经过的“层”,每一层都会检查请求并且可以完全拒绝它。

HTTP中间件的应用场景有那些呢?

  • 鉴权中间件用来验证用户是否已经经过授权
    如果用户没有经过授权,中间件将用户重定向到登录页面,否则允许请求继续向前进入下一步操作。
  • CORS中间件可用于为离开站点的响应添加跨域的头信息
  • 日志中间件可以记录所有进入站点的请求
  • 权限验证
  • 开启会话
    ...

定义中间件

如何定义HTTP中间件呢?

定义中间件只需要实现了Swoft\Http\Message\Middleware\MiddlewareInterface接口,就会成为一个合法的中间件。

$ vim vendor/swoft/http-message/src/Middleware/MiddlewareInterface.php
<?php
namespace Swoft\Http\Message\Middleware;

use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

interface MiddlewareInterface extends \Psr\Http\Server\MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface;
}

MiddlewareInterface接口中process()方法是中间件逻辑处理的方法

例如:创建中间件用于为表单添加CSRF Token以及错误信息的输出。

注意CSRF Token与错误信息输入均使用Session的方式

使用命令方式创建中间件,进入项目根目录输入命令:

$ php bin/swoft gen:middleware ControllerMiddleware

关于生成命令的详情,可参见《Swoft 生成命令》

创建成功后,将在app/Middlewares文件夹下生成一个ControllerMiddleware的类文件。

编辑ControllerMiddleware.php文件,添加我们需要的业务逻辑代码。

$ vim app/Middlewares/ControllerMiddleware.php
<?php
mespace App\Middlewares;

use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Swoft\Bean\Annotation\Bean;
use Swoft\Http\Message\Middleware\MiddlewareInterface;

/**
 * @Bean()
 * @uses      ControllerMiddleware
 */
class ControllerMiddleware implements MiddlewareInterface
{
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        // 若请求会话中存在error则forget掉
        if(session()->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单个中间件注解的语法,可传入两个个参数classvalue,其中class为中间件的类名,注意这个类不会被自动引入需手工引入。那value表示什么意思呢,望文生义等于望梅止渴,不知道。

@Middleware(class=MiddlewareName::class)

例如:在登录接口中使用自定义的中间件ControllerMiddleware

<?php
namespace App\Controllers\Admin;

use Swoft\Http\Message\Bean\Annotation\Middleware;
use Swoft\Http\Message\Server\Request;
use Swoft\Http\Server\Bean\Annotation\Controller;
use Swoft\Http\Server\Bean\Annotation\RequestMapping;
use Swoft\Http\Server\Bean\Annotation\RequestMethod;
use Swoft\View\Bean\Annotation\View;
use App\Middlewares\ControllerMiddleware;

/**
 * 控制器 公用
 * @Controller(prefix="/admin")
 * @package App\Controllers\Admin
 */
class PublicController
{
    /**
     * @RequestMapping(route="login", method={RequestMethod::GET})
     * @Middleware(class=ControllerMiddleware::class)
     * @View(template="admin/public/login")
     */
    public function login(Request $request)
    {
        $data = [];
        $data["title"] = "管理后台登录";
        $data["remark"] = "管理后台登录";
        return $data;

    }
}

问题:在动作方法中加入单个中间件后,出现了错误{"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错误处理器捕获并处理,中间件内抛出也是如此。

中间件应用

  1. 提前拦截请求,注意拦截要在$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);
}
  1. 请求设置允许跨域
    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框架比较熟悉,那么对中间件的概念理解起来会比较容易,如果是第一次接触,或多或少会觉得概念有些陌生。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容