# Middleware
[TOC=2,3]
当处理用户的请求时,需要经过很多处理,如:解析参数,判断是否静态资源访问,路由解析,页面静态化判断,执行操作,查找模版,渲染模版等。项目里根据需要可能还会增加其他的一些处理,如:判断 IP 是否在黑名单中,CSRF 检测等。
ThinkJS 里通过 middleware 来处理这些逻辑,每个逻辑都是一个独立的 middleware。在请求处理中埋很多 hook,每个 hook 串行执行一系列的 middleware,最终完成一个请求的逻辑处理。
## hook 列表
框架里包含的 hook 列表如下:
* `request_begin` 请求开始
* `payload_parse` 解析提交上来的数据
* `payload_validate` 验证提交的数据
* `resource` 静态资源请求处理
* `route_parse` 路由解析
* `logic_before` logic 处理之前
* `logic_after` logic 处理之后
* `controller_before` controller 处理之前
* `controller_after` controller 处理之后
* `view_before` 视图处理之前
* `view_template` 视图文件处理
* `view_parse` 视图解析
* `view_after` 视图处理之后
* `response_end` 请求响应结束
每个 hook 里调用多个 middleware 来完成处理,具体包含的 middleware 如下:
~~~
export default {
request_begin: [],
payload_parse: ["parse_form_payload", "parse_single_file_payload", "parse_json_payload", "parse_querystring_payload"],
payload_validate: ["validate_payload"],
resource: ["check_resource", "output_resource"],
route_parse: ["rewrite_pathname", "subdomain_deploy", "route"],
logic_before: ["check_csrf"],
logic_after: [],
controller_before: [],
controller_after: [],
view_before: [],
view_template: ["locate_template"],
view_parse: ["parse_template"],
view_after: [],
response_end: []
};
~~~
## 配置 hook
hook 默认执行的 middleware 往往不能满足项目的需求,可以通过配置修改 hook 对应要执行的 middleware 来完成,hook 的配置文件为 `src/common/config/hook.js`。
~~~
export default {
payload_parse: ["parse_xml"], //解析 xml
}
~~~
上面的配置会覆盖掉默认的配置值。如果在原有配置上增加的话,可以通过下面的方式:
#### 在前面追加
~~~
export default {
payload_parse: ["prepend", "parse_xml"], //在前面追加解析 xml
}
~~~
##### 在后面追加
~~~
export default {
payload_parse: ["append", "parse_xml"], //在后面追加解析 xml
}
~~~
`注:`建议使用追加的方式配置 middleware,系统的 middleware 名称可能在后续的版本中有所修改。
## 执行 hook
可以通过 `think.hook` 方法执行一个对应的 hook,如:
~~~
await think.hook("payload_parse", http, data); //返回的是一个 Promise
~~~
在含有 `http` 对象的类中可以直接使用 `this.hook` 来执行 hook,如:
~~~
await this.hook("payload_parse", data);
~~~
## 创建 middleware
ThinkJS 支持 2 种方式的 middleware,即:class 方式和 function 方式。可以根据 middleware 复杂度决定使用哪种方式。
### class 方式
如果 middleware 需要执行的逻辑比较复杂,需要定义为 class 的方式。可以通过 `thinkjs` 命令来创建 middleware,在项目目录下执行如下的命令:
~~~
thinkjs middleware xxx
~~~
执行完成后,会看到对应的文件 `src/common/middleware/xxx.js`。
#### ES6 方式
~~~
"use strict";
/**
* middleware
*/
export default class extends think.middleware.base {
/**
* run
* @return {} []
*/
run(){
}
}
~~~
#### 动态创建类的方式
~~~
"use strict";
/**
* middleware
*/
module.exports = think.middleware({
/**
* run
* @return {} []
*/
run: function(){
}
})
~~~
middleware 里会将 `http` 传递进去,可以通过 `this.http` 属性来获取。逻辑代码放在 `run` 方法执行,如果含有异步操作,需要返回一个 `Promise` 或者使用 `*/yield`。
### function 方式
如果 middleware 要处理的逻辑比较简单,可以直接创建为函数的形式。这种 middleware 不建议创建成一个独立的文件,而是放在一起统一处理。
可以建立文件 `src/common/bootstrap/middleware.js`,该文件在服务启动时会自动被加载。可以在这个文件添加多个函数式的 middleware。如:
~~~
think.middleware("parse_xml", http => {
if (!http.payload) {
return;
}
...
});
~~~
函数式的 middleware 会将 `http` 对象作为一个参数传递进去,如果 middleware 里含有异步操作,需要返回一个 `Promise` 或者使用 Generator Function。
以下是框架里解析 json payload 的实现:
~~~
think.middleware("parse_json_payload", http => {
let types = http.config("post.json_content_type");
if (types.indexOf(http.type()) === -1) {
return;
}
return http.getPayload().then(payload => {
try{
http._post = JSON.parse(payload);
}catch(e){}
});
});
~~~
## 解析后赋值
有些 middleware 可能会解析相关的数据,然后希望重新赋值到 `http` 对象上,如:解析传递过来的 xml 数据,但后续希望可以通过 `http.get` 方法来获取。
* `http._get` 用来存放 GET 参数值,http.get(xxx) 从该对象获取数据
* `http._post` 用来存放 POST 参数值,http.post(xxx) 从该对象获取数据
* `http._file` 用来存放上传的文件值,http.file(xxx) 从该对象获取数据
~~~
think.middleware("parse_xml", http => {
if (!http.payload) {
return;
}
return parseXML(http.payload).then(data => {
http._post = data; //将解析后的数据赋值给 http._post,后续可以通过 http.post 方法来获取
});
});
~~~
关于 `http` 对象更多信息请见 [API -> http](https://thinkjs.org/zh-CN/doc/2.0/api_http.html)。
## 阻止后续执行
有些 middleware 执行到一定条件时,可能希望阻止后面的逻辑继续执行。如:IP 黑名单判断,如果命中了黑名单,那么直接拒绝当前请求,不再执行后续的逻辑。
ThinkJS 提供了 `think.prevent` 方法用来阻止后续的逻辑执行执行,该方法是通过返回一个特定类型的 Reject Promise 来实现的。
~~~
think.middleware("parse_xml", http => {
if (!http.payload) {
return;
}
var ip = http.ip();
var blackIPs = ["123.456.789.100", ...];
if(blackIPs.indexOf(ip) > -1){
http.end();//直接结束当前请求
return think.prevent(); //阻止后续的代码继续执行
}
});
~~~
除了使用 `think.prevent` 方法来阻止后续逻辑继续执行,也可以通过 `think.defer().promise` 返回一个 Pending Promise 来实现。
如果不想直接结束当前请求,而是返回一个错误页面,ThinkJS 提供了 `think.statusAction` 方法来实现,具体使用方式请见 [扩展功能 -> 错误处理](https://thinkjs.org/zh-CN/doc/2.0/error_handle.html)。
## 使用第三方 middleware
在项目里使用第三方 middleware 可以通过 `think.middleware` 方法来实现,相关代码存放在`src/common/bootstrap/middleware.js` 里。如:
~~~
var parseXML = require("think-parsexml");
think.middleware("parseXML", parseXML);
~~~
然后将 `parseXML` 配置到 hook 里即可。
* * *
项目里的一些通用 middleware 也推荐发布到 npm 仓库中,middleware 名称推荐使用 `think-xxx`。
## 第三方 middleware 列表
第三方 middleware 列表请见 [插件 -> middleware](https://thinkjs.org/zh-CN/doc/2.0/plugin.html#middleware)。
- 快速入门
- 介绍
- 创建项目
- 项目结构
- 代码规范
- 升级指南
- 进阶应用
- 模块
- 控制器
- 视图
- 配置
- 路由
- 模型
- 介绍
- 事务
- 关联模型
- Mysql
- MongoDB
- SQLite
- Adapter
- 介绍
- Cache
- Session
- WebSocket
- Template
- 扩展功能
- thinkjs 命令
- 静态资源访问
- Middleware
- Service
- Cookie
- 错误处理
- 错误信息
- 数据校验
- 国际化
- 路径常量
- REST API
- 定时任务
- 线上部署
- 推荐模块
- API
- think
- think.base
- think.http.base
- http
- controller
- rest controller
- model
- model.mongo
- middleware