當(dāng)處理用戶的請求時,需要經(jīng)過很多處理,如:解析參數(shù),判斷是否靜態(tài)資源訪問,路由解析,頁面靜態(tài)化判斷,執(zhí)行操作,查找模版,渲染模版等。項(xiàng)目里根據(jù)需要可能還會增加其他的一些處理,如:判斷 IP 是否在黑名單中,CSRF 檢測等。
ThinkJS 里通過 middleware 來處理這些邏輯,每個邏輯都是一個獨(dú)立的 middleware。在請求處理中埋很多 hook,每個 hook 串行執(zhí)行一系列的 middleware,最終完成一個請求的邏輯處理。
框架里包含的 hook 列表如下:
request_begin
請求開始payload_parse
解析提交上來的數(shù)據(jù)payload_validate
驗(yàn)證提交的數(shù)據(jù)resource
靜態(tài)資源請求處理route_parse
路由解析logic_before
logic 處理之前logic_after
logic 處理之后controller_before
controller 處理之前controller_after
controller 處理之后view_before
視圖處理之前view_template
視圖文件處理view_parse
視圖解析view_after
視圖處理之后response_end
請求響應(yīng)結(jié)束每個 hook 里調(diào)用多個 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 默認(rèn)執(zhí)行的 middleware 往往不能滿足項(xiàng)目的需求,可以通過配置修改 hook 對應(yīng)要執(zhí)行的 middleware 來完成,hook 的配置文件為 src/common/config/hook.js
。
export default {
payload_parse: ["parse_xml"], //解析 xml
}
上面的配置會覆蓋掉默認(rèn)的配置值。如果在原有配置上增加的話,可以通過下面的方式:
export default {
payload_parse: ["prepend", "parse_xml"], //在前面追加解析 xml
}
export default {
payload_parse: ["append", "parse_xml"], //在后面追加解析 xml
}
注:
建議使用追加的方式配置 middleware,系統(tǒng)的 middleware 名稱可能在后續(xù)的版本中有所修改。
可以通過 think.hook
方法執(zhí)行一個對應(yīng)的 hook,如:
await think.hook("payload_parse", http, data); //返回的是一個 Promise
在含有 http
對象的類中可以直接使用 this.hook
來執(zhí)行 hook,如:
await this.hook("payload_parse", data);
ThinkJS 支持 2 種方式的 middleware,即:class 方式和 function 方式??梢愿鶕?jù) middleware 復(fù)雜度決定使用哪種方式。
如果 middleware 需要執(zhí)行的邏輯比較復(fù)雜,需要定義為 class 的方式??梢酝ㄟ^ thinkjs
命令來創(chuàng)建 middleware,在項(xiàng)目目錄下執(zhí)行如下的命令:
thinkjs middleware xxx
執(zhí)行完成后,會看到對應(yīng)的文件 src/common/middleware/xxx.js
。
"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
傳遞進(jìn)去,可以通過 this.http
屬性來獲取。邏輯代碼放在 run
方法執(zhí)行,如果含有異步操作,需要返回一個 Promise
或者使用 */yield
。
如果 middleware 要處理的邏輯比較簡單,可以直接創(chuàng)建為函數(shù)的形式。這種 middleware 不建議創(chuàng)建成一個獨(dú)立的文件,而是放在一起統(tǒng)一處理。
可以建立文件 src/common/bootstrap/middleware.js
,該文件在服務(wù)啟動時會自動被加載。可以在這個文件添加多個函數(shù)式的 middleware。如:
think.middleware("parse_xml", http => {
if (!http.payload) {
return;
}
...
});
函數(shù)式的 middleware 會將 http
對象作為一個參數(shù)傳遞進(jìn)去,如果 middleware 里含有異步操作,需要返回一個 Promise
或者使用 Generator Function。
以下是框架里解析 json payload 的實(shí)現(xiàn):
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 可能會解析相關(guān)的數(shù)據(jù),然后希望重新賦值到 http
對象上,如:解析傳遞過來的 xml 數(shù)據(jù),但后續(xù)希望可以通過 http.get
方法來獲取。
http._get
用來存放 GET 參數(shù)值,http.get(xxx) 從該對象獲取數(shù)據(jù)http._post
用來存放 POST 參數(shù)值,http.post(xxx) 從該對象獲取數(shù)據(jù)http._file
用來存放上傳的文件值,http.file(xxx) 從該對象獲取數(shù)據(jù)think.middleware("parse_xml", http => {
if (!http.payload) {
return;
}
return parseXML(http.payload).then(data => {
http._post = data; //將解析后的數(shù)據(jù)賦值給 http._post,后續(xù)可以通過 http.post 方法來獲取
});
});
關(guān)于 http
對象更多信息請見 API -> http。
有些 middleware 執(zhí)行到一定條件時,可能希望阻止后面的邏輯繼續(xù)執(zhí)行。如:IP 黑名單判斷,如果命中了黑名單,那么直接拒絕當(dāng)前請求,不再執(zhí)行后續(xù)的邏輯。
ThinkJS 提供了 think.prevent
方法用來阻止后續(xù)的邏輯執(zhí)行執(zhí)行,該方法是通過返回一個特定類型的 Reject Promise 來實(shí)現(xiàn)的。
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();//直接結(jié)束當(dāng)前請求
return think.prevent(); //阻止后續(xù)的代碼繼續(xù)執(zhí)行
}
});
除了使用 think.prevent
方法來阻止后續(xù)邏輯繼續(xù)執(zhí)行,也可以通過 think.defer().promise
返回一個 Pending Promise 來實(shí)現(xiàn)。
如果不想直接結(jié)束當(dāng)前請求,而是返回一個錯誤頁面,ThinkJS 提供了 think.statusAction
方法來實(shí)現(xiàn),具體使用方式請見 擴(kuò)展功能 -> 錯誤處理。
在項(xiàng)目里使用第三方 middleware 可以通過 think.middleware
方法來實(shí)現(xiàn),相關(guān)代碼存放在src/common/bootstrap/middleware.js
里。如:
var parseXML = require("think-parsexml");
think.middleware("parseXML", parseXML);
然后將 parseXML
配置到 hook 里即可。
項(xiàng)目里的一些通用 middleware 也推薦發(fā)布到 npm 倉庫中,middleware 名稱推薦使用 think-xxx
。
第三方 middleware 列表請見 插件 -> middleware。
更多建議: