很差意思,离开博客园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