https://github.com/younth/fis3-analysis/issues/3
fis3重度用户,最近在看fis3源码,与大家分享下。fis3源码注释写的很好,学到了不少黑魔法~~
- fis3 server start --qrcode 启动本地server的时候生成一个二维码
- fis.requre方法用于加载内置插件 …
近期遇到了fis3 proxy 只支持get请求,无法Post,于是研究了一番。
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();
};
2. 利用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
下周给大家分享下,fis3-server 如何做到daemon
与热加载。