很差意思,離開博客園4年多了,一回來就是爲本身打廣告,真是害羞啊。。。javascript
http-mock-middleware 是我最近完成的一個前端數據 mock 庫。它是我彙總近3年工做經驗而誕生的一個工具,使用很方便。廢話很少說,我粘貼一下部分 README,歡迎你們去 star。html
一個強大、方便的 http mock 庫。前端
http-mock-middleware 是一個 http mock 庫,或者說 ajax/websocket mock 庫,它接收來自 web 前端頁面的 ajax/websocket 請求,將請求映射到本地 mock 文件並通過一系列插件處理後返回給 web 前端頁面。http-mock-middleware 內建了多個插件以實現各類各樣的功能,好比:根據 query 參數等的不一樣響應不一樣的數據,按需將請求轉發給後端服務器,延遲響應,設置 cookie,主動向 websocket 客戶端發送數據等。vue
什麼是本地 mock 文件?就是用於存放對應請求的假數據文件,好比要將請求 /login
映射爲本地假數據文件 .data/login.json
,就稱 .data/login.json
爲 mock 文件。java
http-mock-middleware 自己導出爲一個兼容 express middleware 的函數,所以你能夠很方便的集成到 webpack-dev-server, vue-cli-service, express 等現有服務器中。 webpack
npm i -D hm-middleware
或者ios
yarn add -D hm-middleware
http-mock-middleware 暴露了一個簡單的服務器命令: http-mock-server
,讓你能夠無需任何配置便可快速的獲得一個 mock server,因此,若是你以爲方便的話可使用全局安裝的方式:git
npm i -g hm-middleware
middleware(options)
返回:兼容 express middleware 的函數,它接收 (request, response, next)
3 個參數。github
options
初始化選項
.mockRules
mock 規則,若是指定了此選項,則忽略 mockrc.json,寫法參考 mockrc.json.cors
是否跨域,默認爲 true。也能夠是一個 cors middleware 接受的配置對象.parseBody
是否解析請求 body,默認爲 true。也能夠是一個 body-parser 接受的配置對象.parseCookie
是否解析請求 cookie,默認爲 true。也能夠是一個 cookie-parser 接受的配置對象.websocket
用於 websocket 消息處理的選項,若是啓用了 websocket , 這個選項是必須的。
.server
http.Server
對象,當須要啓用 websocket 時,這個是必選項。.serverOptions
WebSocketServer 初始化選項,參考 ws api.setupSocket(socket: WebSocket)
當有新的 websocket 鏈接時執行的鉤子函數。.decodeMessage
函數。收到 websocket 消息後,須要將消息對象先映射爲 url,再映射爲本地 mock 文件。這個函數用於將消息對象解析爲 url,這個函數也能夠返回一個對象:{url: string: args: any}
,args 表示要傳遞給插件上下文 args 的數據。.encodeMessage
函數。處理完本地 mock 文件後,須要將生成的內容轉換爲 websocket 客戶端能夠理解的消息格式。它接受三個參數:(error, data, decodedMsg)
。若是在處理本地 mock 文件的過程當中發生任何錯誤,error 被設置爲該錯誤,此時 data 爲空;若是處理過程成功,則 data 對象被設置爲最終的生成數據,此時 error 爲空。注意:若是映射的本地 mock 文件是 json,則 data 對象爲 json 對象,若是映射的是非 json 對象,則 data 對象爲包含文件內容的 Buffer 對象;因爲 websocket.send()
方法僅僅接受 String
, Buffer
, TypedArray
等對象,所以你有必要返回正確的數據。第三個參數表示收到本次消息事件後 decodeMessage() 返回的數據。.proxy
當收到的請求包含 X-Mock-Proxy 頭時,請求將被轉發到該頭所指向的服務器 url
.autoSave
是否自動將代理的內容保存爲本地 mock 文件,默認爲 false.saveDirectory
若是須要自動保存,這個選項指定保存的目錄,通常使用 mockRules 配置的 dir 就能夠了。.overrideSameFile
當自動保存時,若是發現文件已經存在,此選項指定如何處理。rename
現有文件被重命名,override
現有文件被覆蓋。使用方法:web
// webpack.config.js const middleware = require("hm-middleware"); module.exports = { devServer: { after: function(app, server){ // 若是僅僅使用 http mock,這樣寫就能夠了 app.use(middleware({ mockRules: { "/": ".data", "/ws/app1": { type: "websocket", dir: ".data/app1" } }, // 若是須要支持 websocket,須要提供下面的選項 websocket: { server: server || this, encodeMessage: function(){}, decodeMessage: function(){} } })); } } };
http-mock-middleware 按照以下順序工做:
注意:若是在初始化時指定了 mockRules
參數,則 http-mock-middleware 忽略查找 mockrc.json。
mockrc.json 指定了 url前綴 和 本地 mock 目錄的對應關係,如:
{ "/oa": ".data/oa-app", "/auth": ".data/auth-app", "/ws/app1": { "type": "websocket", "dir": ".data/websocket-app1" } }
上面的配置說明:
全部 url 前綴爲 /oa/
的 http 請求在 .data/oa-app
目錄查找 mock 文件,如:請求 GET /oa/version
優先映射爲 .data/oa-app/oa/get-version
全部 url 前綴爲 /auth/
的 http 請求在 .data/auth-app
目錄查找 mock 文件,如:請求 POST /auth/login
優先映射爲 .data/auth-app/auth/post-login
在 url /ws/app1
上監聽 websocket 請求,並在收到 onmessage
事件後將收到的數據映射爲 url, 而後在 .data/websocket-app1
目錄查找 mock 文件。
上面提到了優先映射,你能夠在下一章節找到優先映射的含義。
注意:url 前綴在匹配時,默認認爲它們是一個目錄,而不是文件的一部分。如:/oa
表示 mock 目錄下的 oa 目錄,而不能匹配 /oa-old
。
http-mock-middleware 初始化時會在當前目錄查找 mockrc.json
文件,若是找不到則讀取 package.json
的 mock
字段,若是還沒找到,就默認爲:
{ "/": ".data" }
即:全部的 http 請求都在 .data
目錄中查找 mock 文件。
當 http-mock-middleware 收到 http 請求時,首先將請求 url 分割爲兩部分:目錄 + 文件。下面是一個例子:
// 收到 GET /groups/23/user/11/score // 分割爲 目錄: /groups/23/user/11 文件: score
而後,以 .data 爲根目錄,逐級驗證 groups/23/user/11 是否存在:
.data/groups .data/groups/23 .data/groups/23/user .data/groups/23/user/11
若是上面每個目錄都是存在的,則進行下一步,若是某個目錄不存在,則查找失敗,前端頁面將收到 404。
一般來講,url 中的某些部分沒有必要硬編碼爲目錄名,好比 .data/groups/23
, 23 僅僅表明數據庫裏面的 id,它能夠是任何整數,若是咱們寫死爲 23 那麼就只能匹配 23 這個 group,若是咱們要 mock 這個 url 路徑,這個作法顯然是很愚蠢的。
http-mock-middleware 容許對整數作特殊處理,像這樣:[number]
。若是目錄名是 [number]
就表示這個目錄名能夠匹配任何整數,這樣,上面的匹配過程將變成這樣:
.data/groups .data/groups/23 => .data/groups/[number] .data/groups/23/user .data/groups/23/user/11 => .data/groups/23/user/[number]
=>
表示若是左邊的 url 路徑匹配失敗,則嘗試右邊的 url 路徑。能夠看到,這種方式經過對 url 中的某些部分模糊化,達到了通用匹配的目的。
http-mock-middleware 支持下面的模糊匹配:
模式 | 示例 |
---|---|
[number] |
1, 32, 3232 |
[date] |
2019-03-10 |
[time] |
11:29:11 |
[ip] |
127.0.0.1, 192.168.1.134 |
[email] |
xx@yy.com |
[uuid] |
45745c60-7b1a-11e8-9c9c-2d42b21b1a3e |
一個 url 裏面能夠有任意多個模糊匹配,若是請求 url 裏面的目錄部分所有匹配成功,則開始匹配文件名部分。匹配文件名時首先將目錄下面全部的文件都列出來,而後使用下面的格式進行匹配:
<method>-<filename><.ext>
匹配的結果若是多餘 1 個,優先使用以請求方法爲前綴的文件,如:
GET /groups/23/user/11/score 優先匹配的文件名是:get-score
文件名後綴並不做爲判斷依據,若是一個 url 同時匹配了幾個文件,除了請求方法爲前綴的文件外,其它文件的優先級是同樣的,誰是第一個優先使用誰,不過這種狀況應該不多,不須要考慮。
上面就是收到 http 請求時的匹配過程, websocket 的匹配過程基本一致,但與 http 不一樣的是,websocket 並不存在 url 一說,當咱們收到 onmessage 事件時,咱們收到的多是任意格式的數據,它們不是 url,所以在 http-mock-middleware 初始化時提供了將收到的數據轉換爲 url 的選項:
const middleware = require("hm-middleware"); middleware({ server: currentServer, websocket: { // 收到的數據爲 json,將其中的字段組合爲 url // 具體如何組合取決於你的業務邏輯實現 decodeMessage: function(msg){ msg = JSON.parse(msg); return `/${msg.type}/${msg.method}`; } } });
websocket 收到 onmessage 事件並將收到的數據解析爲 url 後,剩下的過程就和 http 一致了。
查找到本地 mock 文件後,文件內容和一些請求參數會丟給插件處理。
插件是一段有特殊功能的代碼,如多是設置 http 頭,多是解析 json 內特殊標記等。插件被設計爲是可插拔的,所以新增插件是很容易的。插件運行時接受一個共享的上下文環境對象。
注意:插件僅僅對 json 或 json5 文件生效。
http-mock-middleware 將多個核心功能丟給插件來完成。好比須要爲 response 設置 http 頭時,headers 插件就會在 json 文件內容裏面查找 #headers#
指令,並將指令的內容設置到 http 頭。
指令是插件可識別的特殊 json 鍵名,指令默認使用 #<name>#
格式命名,這是爲了不和 json 鍵名衝突,指令的值就是對應的鍵值。
http-mock-middleware 支持的插件和指令以下:
支持的指令: #cookies#
cookies 插件的主要功能是爲 response 設置 cookie http 頭。
#cookies#
的值爲對象或者對象數組,若是你但願對 cookie 作更爲精細的控制,則須要使用對象數組的形式。
假設 http 請求 GET /x
匹配的本地 mock 文件爲 .data/x.json
,當使用了 #cookies#
指令後:
// file: .data/x.json { // 對象形式 "#cookies#": {a: 3, b: 4} // 也能夠是對象數組的形式 // "#cookies#": [{name: a, value: 3, options: {path: "/"}}] }
響應的 http 頭包括:
Set-Cookie: a=3 Set-Cookie: b=4
若是但願使用對象數組的格式,請參考 express response.cookie()
支持的指令: #headers#
headers 插件的主要功能是爲 response 設置自定義 http 頭,除此以外,它爲每一個 response 添加了一個 X-Mock-File
頭用以指向當前請求匹配到的本地 mock 文件,若是本地 mock 文件是 json ,則主動添加 Content-Type: application/json
。
#headers#
的值爲對象。
假設 http 請求 GET /x
匹配的本地 mock 文件爲 .data/x.json
,當使用了 #headers#
指令後:
// file: .data/x.json { "#headers#": {'my-header1': 3, 'my-header2': 4} }
響應的 http 頭包括:
my-header1: 3 my-header2: 4
支持的指令: #if#
, #default#
, #args#
if 插件的主要功能是根據請求參數條件響應,請求參數以下:
query 請求 url 中的查詢字符串構成的對象,即 request.query body 請求體,如過請求體是 json,則 body 爲 json 對象,即 request.body headers 請求頭對象,包含了當前請求的全部 http 頭,即 request.headers cookies 當前請求所附帶的 cookie 信息,是一個對象,即 request.cookies signedCookies 當前請求所附帶的加密 cookie 信息,是一個對象,即 request.signedCookies args #args# 指令的值 env 當前環境變量對象,即 process.env
#if#
指令的使用形式爲:#if:<code>#
,code 是一段任意的 javascript 代碼,它運行在一個以請求參數爲全局對象的沙盒裏,當這段代碼求值結果爲真值,則表示使用它的值做爲 response 內容,若是全部的 #if#
求值結果均爲假值,則使用 #default#
指令的值,若是多個 #if#
指令求值結果爲真值,默認取第一個 #if#
指令的值,看下面的例子:
假設 http 請求 GET /x?x=b
匹配的本地 mock 文件爲 .data/x.json
,當使用了 #if#
指令後:
// file: .data/x.json { "#if:query.x == 'a'#": { "result": "a" }, "#if:query.x == 'b'#": { "result": "b" }, "#default#": { "result": "none" } }
response 的內容爲:
{"result": "b"}
支持的指令:#args#
變量替換插件主要的功能是遍歷輸出內容對象的值,將包含有變量的部分替換爲變量對應的值,變量的聲明格式爲 #$<var>#
,其中 var 就是變量名,變量名是一個或多個變量的引用鏈,如:query
, query.x
, body.user.name
。
變量可引用的全局變量也是請求參數,同 #if#
指令同樣。
默認狀況,變量聲明若是是字符串的一部分,則替換後的結果也是字符串的一部分,若是變量聲明是一個字符串的所有,則使用替換後的值覆蓋字符串值,看下面的例子:
假設有 http 請求 POST /x?x=b
,其請求體爲:
{ "x": "b", "y": [1, 2, 3] }
匹配的本地 mock 文件爲 .data/x.json
:
{ "result": { "x": "#$body.x#", "y": "#$body.y#", "xy": "#$body.x##$body.y#" } }
當通過變量替換後,內容以下:
{ "result": { "x": "b", "y": [1, 2, 3], "xy": "b1,2,3" } }
支持的指令: #delay#
delay 插件的主要功能是延遲 response 結束的時間。
支持的指令: #code#
, #kill#
status code 插件的主要功能是設置 response 狀態碼。
#code#
的值是一個有效的 http 狀態碼整數。
#kill#
的值是一個布爾值,它表示是否殺掉請求,殺掉請求後,後續的插件不會被調用。
支持的指令: #notify#
注意:該插件僅對 websocket 生效
ws-notify 插件的主要功能是通過固定延遲時間後主動發起一個服務器端 websocket 消息
#notify#
的值 value 若是是字符串,等同於 [{url: value, delay: 0}]
#notify#
的值 value 若是是對象,等同於 [value]
#notify#
的值 value 若是是對象數組,則爲數組中的每一項建立一個 delay 毫秒後解析併發送 url 指向的本地 mock 文件的服務器端 websocket 消息任務
mockjs 插件的主要功能是爲 json 內容提供數據模擬支持,mockjs 的語法請參考這裏
若是你但願在 url 上使用 websocket ,務必要在 mock 規則裏面爲 url 添加 "type": "websocket"
,不然沒法生效。
因爲 websocket 收發消息沒有統一的標準,能夠是二進制,也能夠是字符串,http-mock-middleware 沒法準確的知道消息格式,所以你有必要告訴 http-mock-middleware 如何解析、封裝你的 websocket 消息。參考 API 獲取有關配置的詳細信息。
在前端開發階段,有 mock 數據支持就夠了,在先後端聯調過程當中,後端服務器的數據更真實,mock 數據反而不那麼重要了。所以在必要的時候將數據代理到後端服務器就顯得頗有必要了。
http-mock-middleware 主要經過 X-Mock-Proxy
頭來判斷是否須要代理,下面使用 axios 庫演示如何使用 localStorage 控制動態代理:
const axios = require("axios"); axios.interceptors.request.use(function(config){ if(process.env.NODE_ENV === "development") { let mockHeader = localStorage.proxyUrl; if(mockHeader) { config.headers = config.headers || {}; config.headers["X-Mock-Proxy"] = mockHeader; } } // other code return config; });
使用上面的代碼,開發時不設置 localStorage.proxyUrl
使用假數據,聯調時設置 localStorage.proxyUrl
指向後端服務器使用真數據。
注意:X-Mock-Proxy
的值是一個 url, 由於 http-mock-middleware 沒法肯定你的服務器是否是 https。一般你須要只設置爲 http://host:port/
就能夠了
http-mock-middleware 提供了幾種數據遷移的方式:
http-mock-import -f har -d .data some.har