注意! 廣告警告! 廣告警告! 廣告警告!
在一個web應用的開發週期中, 通常前端與後端都是並行開發的, 各自完成本身的開發工做後進行聯調, 聯調經過再進行提測/發佈.html
開發過程當中, 前端都會之後端提供的 api 文檔做爲標準, mock 模擬 api 返回數據, 以確保在開發中就保證功能的完整性.前端
而關於如何更好的進行 mock, 業界/開源社區可謂有至關多質量上乘的解決方案, 如easy-mock, yapi等.webpack
可是越是大而全的工具不少時候功能會超越需求很是多, 要簡單實現 mock api 的需求其實也有很是多小而美工具庫可使用.git
而本文主要介紹 mock-server 這個工具的使用github
選用 mock-server 的主要緣由除了是我開發的使用比較簡單以外, 更多的是知足了下文提到的一些開發需求, 若是你也有一樣的需求而還沒找到解決方案的話, 不妨試用一下.web
可選全局安裝, 安裝完成事後, 就能夠經過mock
命令啓動來 mock servernpm
npm install -g mock-server-local mock -h # 即安裝成功 # 用 -d 指定mock數據配置目錄, 爲空時默認爲當前目錄 `.` mock -d ./mock # 用 -p 指定server的端口, 默認爲8888, 若是8888被佔用會端口號+1, 直至端口可用 # 注意若是指定了端口號, 可是端口號被佔用的話, 會拋出錯誤 mock -d ./mock -p 8080
我的比較習慣在項目中進行安裝, 並經過npm script
啓動, 而 mock 數據也存放在項目當中, 經過 git 等版本管理工具在項目成員當中共享, 假設項目目錄爲proj
json
// proj/package.json { // ... "script": { "mock": "mock -d ./mock -p 8080" } // ... }
# 本地安裝 npm install mock-server-local --save-dev # 啓動mock server npm run mock > mock@1.8.9 mock /path/to/proj > mock -d ./mock -p 8080 you can access mock server: http://127.0.0.1:8080 http://ww.xx.yy.zz:8080 you can access mock server view: http://127.0.0.1:8080/view http://ww.xx.yy.zz:8080/view
就這樣 mock server 就已經啓動了, 訪問127.0.0.1:8080/view
便可看到 mock server 的控制頁面後端
就下來就是調整代理, 把應用的請求轉發到 mock server 進行處理api
若是你使用webpack
來構建你的項目, 那你只須要改動一下webpack.devServer
的配置便可
假設咱們的業務域名爲target.mock.com
, 而接口基本都是target.mock.com/api/**
, 能夠這樣進行配置
devServer: { proxy: { '/api': { target: 'http://127.0.0.1:8080', // mock server headers: { host: 'target.mock.com' // 業務域名 }, onProxyReq: function(proxyReq, req, res) { proxyReq.setheader('host', 'target.mock.com'); // 業務域名 } } } }
接着在開發中, 啓動 webpack 以後, 發出的請求/api/**
都會被轉發到
而若是應用自己不使用 webpack 或其餘帶 server 功能的打包工具, 可使用代理工具進行請求轉發
若是是用 Chrome 瀏覽器調試應用, 能夠下載SwitchyOmega
一類可配置, 把特定域名的請求進行轉發
使用微信開發者工具的話, 可直接設置代理, 設置
-> 代理設置
-> 勾選手動設置代理
-> 填寫代理配置
推薦使用 whistle, fiddler 一類功能完整代理工具, 相似配置以下
target.mock.com/api 127.0.0.1:8080 # 接口請求轉發到mock server target.mock.com www.xx.yy.zz # 頁面從正常的開發測試機ip中獲取, 或本地調試服務器
mock server 的配置是根據 mock 目錄的目錄結構生成的, 假設須要進行 mock 的 api 接口完整的 url 爲target.mock.com/api/login
並且須要模擬如下三種狀況的數據返回
那麼目錄結構與數據配置文件應該以下所示
|- proj |- mock |- target.mock.com |- api |- login |- 登陸成功 |- 登陸失敗 |- 請求超時 // 登陸失敗 // proj/mock/target.mock.com/api/login/登陸失敗/data.js module.exports = { code: -1, msg: '登陸失敗!' }; // 登陸成功 // proj/mock/target.mock.com/api/login/登陸成功/data.js module.exports = { code: 0, msg: '登陸成功', username: 'ahui' }; // proj/mock/target.mock.com/api/login/登陸成功/http.js module.exports = { header: { 'Set-Cookie': 'token=123456789;' } }; // 請求超時 // proj/mock/target.mock.com/api/login/請求超時/data.js module.exports = {}; // proj/mock/target.mock.com/api/login/請求超時/http.js module.exports = { delay: 8 };
根據上面目錄配置, 訪問 mock server 頁面的 mock 面板127.0.0.1:8080/view/mocks
, 就能夠看到如下頁面
如今咱們只須要勾選其中一個狀態, 而後發出請求便可
mock完成以後就能夠愉快編寫業務邏輯了
看了上面的例子應該也就大體能夠了解到, data.js
裏面定義接口返回的數據. 而http.js
, 顧名思義就是定義 http 請求相關行爲的, 例如能夠定義響應頭, http 狀態碼, 請求耗時等.
data 同時也支持使用 json 文件, 非 js/json 文件的一概當 json 格式處理, 而 http 則只支持經過 js 文件定義
http.js
可選配置以下
module.exports = { delay: 8, // 耗時8秒 status: 200 // http狀態碼 header: { // ... http響應頭 } }
而data.js
除了能夠簡單定義返回數據以外, 還能夠直接返回模板, 如
module.exports = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> 這個是由mock server返回的模板 </body> </html> `;
而且能夠返回一個方法, 經過腳本邏輯來處理最終要返回的數據
// ctx: https://koajs.com/#context module.exports = function(ctx) { // 邏輯處理 return { data: {}, // 響應數據 header: {}, // 若有須要能夠配置, 同http.js#header delay: 0.1, // 若有須要能夠配置, 同http.js#delay status: 200 // 若有須要能夠配置, 同http.js#status }; }; // 若是當中有異步邏輯, 請返回promise, 或直接使用async方法 module.exports = async function(ctx) { // 異步邏輯處理 await asyncFn(); return { data: {}, // 響應數據 header: {}, // 若有須要能夠配置, 同http.js#header delay: 0.1, // 若有須要能夠配置, 同http.js#delay status: 200 // 若有須要能夠配置, 同http.js#status }; };
在開發過程可能出現這樣的場景, 一期項目已經開發完了, 如今進行二期迭代的開發工做, 這個時候因爲以前的接口後臺已經都實現, 二期開發中只想對新增的 api 進行 mock
這個時候能夠修改一下代理工具的配置, 把不一樣接口的請求轉發到不一樣的服務器
# 新增接口轉發至mock server target.mock.com/api/new1 127.0.0.1:8080 target.mock.com/api/new2 127.0.0.1:8080 # 其他接口直接使用線上/測試機數據 target.mock.com ww.xx.yy.zz
又或者能夠直接使用 mock server 提供的簡單的代理功能, 只須要在 mock 目錄個目錄下新建proxy.js
文件
|- proj |- mock |- proxy.js // proj/mock/proxy.js module.exports = { target.mock.com: 'https://ww.xx.yy.zz' // 這裏能夠指定ip也能夠指定域名, 可是須要注意協議類型是必需要帶上的 }
這樣配置以後, 在代理工具中就能夠直接把全部的target.mock.com
的請求都直接轉發到 mock server
當對應請求的 url 並無勾選任何一個返回狀態, 或根本沒有配置對應的 url 時, mock server 都會幫助咱們把請求轉發到目標 ip
假設沒有配置proxy.js
的話, 對於沒有命中的 url 請求, 會根據 host 直接請求線上的資源或接口
在非先後端分離的架構中, 很常會出現這樣的需求, 應用的入口便是後端接口, 後端會進行鑑權, 拼接模板內容和數據, 而後直接返回頁面給到前端進行展現.
這樣的場景 mock server 能夠很簡單經過data.js
中導出方法的方式來處理
const fs = require('fs'); module.exports = async ctx => { let html = ''; let template = fs.readFileSync('path/to/html/template/index.ejs'); // 舉例ejs, 實際能夠處理任何模板引擎 // 這裏處理模板的語法 // 1. 處理相似include的拼接模板的語法 // 2. 處理相似<%= =>插入變量/數據的語法 // 3. 等等等等.... html = processedHtml; return { data: html, header: { 'Set-Cookie': 'SESSIONID=123123123123;' }; }; };
這樣子咱們就能夠進行模板接口的調試了. 再回到咱們的上一個例子
咱們但願可使用線上已有接口和數據狀態(如開戶數據)
也但願使用後端的登陸態(這樣後續的接口調用也能經過鑑權), 但也同時但願能夠調試本地模板呢?
比較直觀的方式是, 本地修改模板而後把模板改動上傳到開發服務器, 而後直接請求開發服務器進行調試
可是改動比較多, 須要頻繁調試的話, 或許使用 mock server 也是一個不錯的選擇. 更進一步, 若是是微信 h5 且後端的登陸鑑權接入了微信登陸呢?
咱們來分析一下如何使用 mock server 知足這樣的調試述求, h5 微信登陸基本的流程以下
上面的流程中, 其實須要介入只有最後一步而已, 就是獲取到登陸態並返回須要調試的 html 模板內容便可
而前面的步驟, 徹底能夠經過在data.js
中實現簡單的代理完成
// 微信登陸/data.js const httpProxy = require("http-proxy"); const fs = require("fs"); const path = require("path"); proxy = httpProxy.createServer({ secure: false }); async function req({ req, res }) { proxy.web(req, res, { target: { protocol: "https:", host: "ww.xx.yy.zz", // 目標服務器 port: 443, pfx: fs.readFileSync(path.resolve(process.cwd(), "cert/cert.p12")), // 若是服務器是https須要生成證書 passphrase: "password" }, selfHandleResponse: true }); return new Promise((resolve, reject) => { proxy.once("proxyRes", function(proxyRes, req, res) { let body = []; let size = 0; function onData(chunk) { body.push(chunk); size += chunk.length; } proxyRes.on("data", onData).once("end", () => { proxyRes.off("data", onData); body = Buffer.concat(body, size); resolve({ header: proxyRes.headers, data: body, status: proxyRes.statusCode }); }); }); }); } module.exports = async function(ctx) { // 登陸態 const res = await req(ctx); const header = res.header; res.header = Object.keys(header).reduce((c, k) => { let nk = k .split("-") .map(v => v.charAt(0).toUpperCase() + v.slice(1)) .join("-"); c[nk] = header[k]; return c; }, {}); if (res.header["Set-Cookie"]) { // 若是有Set-Cookie header, 則要處理返回本地模板 // 這裏處理模板的語法 // 1. 處理相似include的拼接模板的語法 // 2. 處理相似<%= =>插入變量/數據的語法 // 3. 等等等等.... res.data = template; // 這裏須要注意, 目標服務器可能會返回gzip事後的數據 // 若是不對Content-Encoding和Content-Length進行處理的話 // 會致使響應中Content-Length和實際內榮長度不一致而出錯 res.header["Content-Encoding"] = "identity"; delete res.header["Content-Length"]; } return res; };
這樣咱們就能夠對具體的接口模板進行調試了
重複造輪子不易, 且造且珍惜
若是你們有mock api的需求的話, 不妨也試用一下 mock-server
若是以爲 mock-server 還不錯, 或者解決了mock的一些痛點, 不妨賞個star
最後, 用得不爽或發現bug, 懇請提issue!