ThinkJS Middleware(中間件)

2021-09-17 10:28 更新

Middleware

當(dāng)處理用戶的請(qǐng)求時(shí),需要經(jīng)過(guò)很多處理,如:解析參數(shù),判斷是否靜態(tài)資源訪問(wèn),路由解析,頁(yè)面靜態(tài)化判斷,執(zhí)行操作,查找模版,渲染模版等。項(xiàng)目里根據(jù)需要可能還會(huì)增加其他的一些處理,如:判斷 IP 是否在黑名單中,CSRF 檢測(cè)等。

ThinkJS 里通過(guò) middleware 來(lái)處理這些邏輯,每個(gè)邏輯都是一個(gè)獨(dú)立的 middleware。在請(qǐng)求處理中埋很多 hook,每個(gè) hook 串行執(zhí)行一系列的 middleware,最終完成一個(gè)請(qǐng)求的邏輯處理。

hook 列表

框架里包含的 hook 列表如下:

  • request_begin 請(qǐng)求開(kāi)始
  • payload_parse 解析提交上來(lái)的數(shù)據(jù)
  • payload_validate 驗(yàn)證提交的數(shù)據(jù)
  • resource 靜態(tài)資源請(qǐng)求處理
  • route_parse 路由解析
  • logic_before logic 處理之前
  • logic_after logic 處理之后
  • controller_before controller 處理之前
  • controller_after controller 處理之后
  • view_before 視圖處理之前
  • view_template 視圖文件處理
  • view_parse 視圖解析
  • view_after 視圖處理之后
  • response_end 請(qǐng)求響應(yīng)結(jié)束

每個(gè) hook 里調(diào)用多個(gè) middleware 來(lái)完成處理,具體包含的 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 默認(rèn)執(zhí)行的 middleware 往往不能滿足項(xiàng)目的需求,可以通過(guò)配置修改 hook 對(duì)應(yīng)要執(zhí)行的 middleware 來(lái)完成,hook 的配置文件為 src/common/config/hook.js。

export default {
  payload_parse: ["parse_xml"], //解析 xml
}

上面的配置會(huì)覆蓋掉默認(rèn)的配置值。如果在原有配置上增加的話,可以通過(guò)下面的方式:

在前面追加

export default {
  payload_parse: ["prepend", "parse_xml"], //在前面追加解析 xml
}
在后面追加
export default {
  payload_parse: ["append", "parse_xml"], //在后面追加解析 xml
}

注:建議使用追加的方式配置 middleware,系統(tǒng)的 middleware 名稱可能在后續(xù)的版本中有所修改。

執(zhí)行 hook

可以通過(guò) think.hook 方法執(zhí)行一個(gè)對(duì)應(yīng)的 hook,如:

await think.hook("payload_parse", http, data); //返回的是一個(gè) Promise

在含有 http 對(duì)象的類中可以直接使用 this.hook 來(lái)執(zhí)行 hook,如:

await this.hook("payload_parse", data);

創(chuàng)建 middleware

ThinkJS 支持 2 種方式的 middleware,即:class 方式和 function 方式??梢愿鶕?jù) middleware 復(fù)雜度決定使用哪種方式。

class 方式

如果 middleware 需要執(zhí)行的邏輯比較復(fù)雜,需要定義為 class 的方式??梢酝ㄟ^(guò) thinkjs 命令來(lái)創(chuàng)建 middleware,在項(xiàng)目目錄下執(zhí)行如下的命令:

thinkjs middleware xxx

執(zhí)行完成后,會(huì)看到對(duì)應(yīng)的文件 src/common/middleware/xxx.js。

ES6 方式

"use strict";
/**
 * middleware
 */
export default class extends think.middleware.base {
  /**
   * run
   * @return {} []
   */
  run(){

  }
}

動(dòng)態(tài)創(chuàng)建類的方式

"use strict";

/**
 * middleware
 */
module.exports = think.middleware({
  /**
   * run
   * @return {} []
   */
  run: function(){

  }
})

middleware 里會(huì)將 http 傳遞進(jìn)去,可以通過(guò) this.http 屬性來(lái)獲取。邏輯代碼放在 run 方法執(zhí)行,如果含有異步操作,需要返回一個(gè) Promise 或者使用 */yield。

function 方式

如果 middleware 要處理的邏輯比較簡(jiǎn)單,可以直接創(chuàng)建為函數(shù)的形式。這種 middleware 不建議創(chuàng)建成一個(gè)獨(dú)立的文件,而是放在一起統(tǒng)一處理。

可以建立文件 src/common/bootstrap/middleware.js,該文件在服務(wù)啟動(dòng)時(shí)會(huì)自動(dòng)被加載??梢栽谶@個(gè)文件添加多個(gè)函數(shù)式的 middleware。如:

think.middleware("parse_xml", http => {
  if (!http.payload) {
    return;
  }
  ...
});

函數(shù)式的 middleware 會(huì)將 http 對(duì)象作為一個(gè)參數(shù)傳遞進(jìn)去,如果 middleware 里含有異步操作,需要返回一個(gè) 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 可能會(huì)解析相關(guān)的數(shù)據(jù),然后希望重新賦值到 http 對(duì)象上,如:解析傳遞過(guò)來(lái)的 xml 數(shù)據(jù),但后續(xù)希望可以通過(guò) http.get 方法來(lái)獲取。

  • http._get 用來(lái)存放 GET 參數(shù)值,http.get(xxx) 從該對(duì)象獲取數(shù)據(jù)
  • http._post 用來(lái)存放 POST 參數(shù)值,http.post(xxx) 從該對(duì)象獲取數(shù)據(jù)
  • http._file 用來(lái)存放上傳的文件值,http.file(xxx) 從該對(duì)象獲取數(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ù)可以通過(guò) http.post 方法來(lái)獲取
  });
});

關(guān)于 http 對(duì)象更多信息請(qǐng)見(jiàn) API -> http。

阻止后續(xù)執(zhí)行

有些 middleware 執(zhí)行到一定條件時(shí),可能希望阻止后面的邏輯繼續(xù)執(zhí)行。如:IP 黑名單判斷,如果命中了黑名單,那么直接拒絕當(dāng)前請(qǐng)求,不再執(zhí)行后續(xù)的邏輯。

ThinkJS 提供了 think.prevent 方法用來(lái)阻止后續(xù)的邏輯執(zhí)行執(zhí)行,該方法是通過(guò)返回一個(gè)特定類型的 Reject Promise 來(lái)實(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)前請(qǐng)求
    return think.prevent(); //阻止后續(xù)的代碼繼續(xù)執(zhí)行
  }
});

除了使用 think.prevent 方法來(lái)阻止后續(xù)邏輯繼續(xù)執(zhí)行,也可以通過(guò) think.defer().promise 返回一個(gè) Pending Promise 來(lái)實(shí)現(xiàn)。

如果不想直接結(jié)束當(dāng)前請(qǐng)求,而是返回一個(gè)錯(cuò)誤頁(yè)面,ThinkJS 提供了 think.statusAction 方法來(lái)實(shí)現(xiàn),具體使用方式請(qǐng)見(jiàn) 擴(kuò)展功能 -> 錯(cuò)誤處理。

使用第三方 middleware

在項(xiàng)目里使用第三方 middleware 可以通過(guò) think.middleware 方法來(lái)實(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 倉(cāng)庫(kù)中,middleware 名稱推薦使用 think-xxx

第三方 middleware 列表

第三方 middleware 列表請(qǐng)見(jiàn) 插件 -> middleware。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)