fis3提供了强大的本地数据模拟的能力,与业务解耦的接口mock,在本地开发时候可谓非常方便。mock能力依赖基础的server.conf
及数据文件,首先下面我们来看看基本用法。
目录结构
- server.conf支持的目录名称:config mock
- test数据支持的目录:test mock
统一用mock目录作为数据模拟目录:
└── mock
├── sample.json
└── server.conf
server.conf 配置语法
指令名称 正则规则 目标文件
指令名称
支持 rewrite 、 redirect 和 proxy。正则规则
用来命中需要作假的请求路径目标文件
设置转发的目标地址,需要配置一个可请求的 url 地址。
mock 静态假数据
rewrite ^\/api\/user$ /mock/sample.json
// sample.json内容
{
"error": 0,
"message": "ok",
"data": {
"username": "younth",
"uid": 1,
"age": 25,
"company": "waimai"
}
}
mock 动态假数据
node 服务器可以通过 js 的方式提供动态假数据。,动态数据本质是 express 的 route.
rewrite ^\/api\/dynamic\/time$ /mock/dynamic.js
// dynamic.js内容
module.exports = function(req, res, next) {
res.write('Hello world ');
// set custom header.
// res.setHeader('xxxx', 'xxx');
res.end('The time is ' + Date.now());
};
// 更复杂的,可以直接引用其他模块,发送请求
var http = require('http');
var url = require('url');
var util = require('util');
var querystring = require('querystring');
// 通过nodejs来抓取线上的结果。这样就完成了动态获取线上数据的功能
module.exports = function(request, response, next) {
var method = request.method;
...
};
proxy 到其他服务的 api 地址
// 支持正则分组,不支持post请求转发,后面有解决方案
proxy ^\/wmall\/privilege\/(.*)$ http://10.19.161.92:8059/wmall/privilege/$1
以上就是mock
的基础用法。fis3的mock
能力实现的并不复杂,依赖fis3-server-node
模块。该模块的详细讲解放在后面。下面讲下post
请求转发问题。
如何proxy 在处理post请求时候的bug
proxy能力在联调阶段非常方便,但是目前fis3的proxy能力只能处理get请求。参见proxy bug issues。看起来可能是http-proxy
模块的问题,但目前fis团队还未修复,下面提供两种方法办法。
1. 利用动态数据能力
rewrite ^\/wmall.*$ /mock/data.js
data.js
在是一个expreses的路由,我们可以自己处理所有的请求。目前我自己项目中动态数据数据请求的代码,供参考:
// 注意这里面的.js,不是一般的.js 文件,而是相当于 express 的 route. var http = require('http'); var url = require('url'); var util = require('util'); var querystring = require('querystring'); var host = 'http://10.19.161.92:8059'; // 通过nodejs来抓取线上的结果。这样就完成了动态获取线上数据的功能 module.exports = function(request, response, next) { // // 最简单的测试 // response.writeHead(200, { // 'Content-type': 'text/html' // }); // response.end('Date.now() ' + Date.now()); // 获取 GET/POST var method = request.method; // 获取 post 时的参数 var postParams = request.body; // 解析参数 postParams = querystring.stringify(postParams) // 获取匹配到的 var originalUrl = request.originalUrl; // API 符合 /wmall... 格式 if (/^\/wmall.*$/.test(originalUrl) === false) { // API不是 /news?tn=... 格式,给出提示 response.writeHead(200, { 'Content-type': 'text/html' }); response.end('url format is not /wmall....'); return; } var apiUrl = host + originalUrl; var parsedUrl = url.parse(apiUrl, true); var options = { host: parsedUrl.hostname, port: parsedUrl.port || 80, path: parsedUrl.pathname, method: method, headers: {} }; // 携带cookie options.headers.cookie = request.headers.cookie; if (parsedUrl.search) { options.path += parsedUrl.search; } if (method.toLowerCase() === 'post') { // 增加 header参数 options.headers['Content-Type'] = 'application/x-www-form-urlencoded'; options.headers['Content-Length'] = postParams.length; } // 发送线上的数据请求,此时请求没有携带cookie var req = http.request(options, function (res) { res.setEncoding('utf8'); var str = ''; res.on('data', function (d) { // data 响应事件中先一步一步拼接数据 str += d; }); res.on('end', function () { // end 响应事件中,返回数据 var data; try { data = JSON.parse(str); } catch (ex) { data = str; } response.json(data); }); res.on('error',function (err) { // error 处理 response.end('reponse error: ' + err.message); }) }); req.on('error', function (err) { // error 处理 response.end('request error' + err.message); }); // 写入post参数 req.write(postParams); // 请求结束,告诉 response 可以返回了 req.end(); };
利用fis server配置文件
// fis3-server-node index.js var script = path.join(opt.root, 'server.js'); // www目录下不存在server.js 则走当前模块的app.js作为入口 if (!fis.util.exists(script)) { script = path.join(__dirname, 'app.js'); }
可以看出,server start的时候支持使用目录下的server.js, 在发布的目录下创建一个server.js, 就能自己实现server,使用http-proxy-middleware代理。我们完全可以用自己的server.js。
var express = require('express');
var path = require('path');
var url = require('url');
var fs = require('fs');
var bodyParser = require('body-parser')
var proxy = require('http-proxy-middleware');
var args = process
.argv
.join('|');
var program = require('commander');
var port = /\-\-port\|(\d+)(?:\||$)/.test(args)
? ~~ RegExp.$1
: 8080;
var https = /\-\-https\|(true)(?:\||$)/.test(args)
? !!RegExp.$1
: false;
var DOCUMENT_ROOT = path.resolve(/\-\-root\|(.*?)(?:\||$)/.test(args)
? RegExp.$1
: process.cwd());
var app = express();
// logger
app.use(require('morgan')('short'));
console.log(args)
// add proxy support
var apiAddress = 'http://api.test.com';
app.use('/api', proxy({
target: apiAddress,
changeOrigin: true,
pathRewrite: {
'^/api': '/nakedhub'
},
}));
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({extended: false}));
// parse application/json
app.use(bodyParser.json());
// 静态文件输出
app.use(express.static(DOCUMENT_ROOT, {
index: [
'index.html', 'index.htm', 'default.html', 'default.htm'
],
extensions: ['html', 'htm']
}));
// 静态文件列表。
app.use((function() {
return function(req, res, next) {
var pathname = url
.parse(req.url)
.pathname;
var fullpath = path.join(DOCUMENT_ROOT, pathname);
if (/\/$/.test(pathname) && fs.existsSync(fullpath)) {
var stat = fs.statSync(fullpath);
if (stat.isDirectory()) {
var html = '';
var files = fs.readdirSync(fullpath);
html = '<!doctype html>';
html += '<html>';
html += '<head>';
html += '<title>' + pathname + '</title>';
html += '</head>';
html += '<body>';
html += '<h1> - ' + pathname + '</h1>';
html += '<div id="file-list">';
html += '<ul>';
if (pathname != '/') {
html += '<li><a href="' + pathname + '..">..</a></li>';
}
files
.forEach(function(item) {
var s_url = path.join(pathname, item);
html += '<li><a href="' + s_url + '">' + item + '</a></li>';
});
html += '</ul>';
html += '</div>';
html += '</body>';
html += '</html>';
res.send(html);
return;
}
}
next();
};
})());
// utf8 support
app.use(function(req, res, next) {
// attach utf-8 encoding header to text files.
if (/\.(?:js|json|text|css)$/i.test(req.path)) {
res.charset = 'utf-8';
}
next();
});
// 错误捕获。
app.use(function(err, req, res, next) {
console.log(err);
});
// Bind to a port
var server;
if (https) {
server = require('https').createServer({
key: fs.readFileSync(path.join(__dirname, 'key.pem'), 'utf8'),
cert: fs.readFileSync(path.join(__dirname, 'cert.pem'), 'utf8')
}, app);
} else {
server = require('http').createServer(app);
}
server
.listen(port, '0.0.0.0', function() {
console.log(' Listening on ' + (https
? 'https'
: 'http') + '://127.0.0.1:%d', port);
});
// 在接收到关闭信号的时候,关闭所有的 socket 连接。
(function() {
var sockets = [];
server.on('connection', function(socket) {
sockets.push(socket);
socket.on('close', function() {
var idx = sockets.indexOf(socket);
~idx && sockets.splice(idx, 1);
});
});
var finalize = function() {
// Disconnect from cluster master
process.disconnect && process.disconnect();
process.exit(0);
}
// 关掉服务。
process.on('SIGTERM', function() {
console.log(' Recive quit signal in worker %s.', process.pid);
sockets.length
? sockets.forEach(function(socket) {
socket.destroy();
finalize();
})
: server.close(finalize);
});
})(server);
fis3 mock server 源码解读
fis3/node_modules/fis3-server-node