node实现mock-plugin中间件的方法
写在前面
最近在使用Mockjs作为项目里面mock数据的工具,发现mockjs做的拦截部分是自己实现摸拟了一个XMLHttpRequest的方法做的拦截,使用Mockjs拦截请求后,在chrome的network上无法看到请求(具体mockjs使用方法可以查看他的api,mockjs-api这里我不多做阐述),但为了更加真实的像后台返回数据,我自己使用Node作为中间代理去实现了一个mock-plugin.
express中间件介绍
因为插件相当于是实现了一个express的中间件的方式,所以这里简单对express中间件的使用做一个说明:
express中间件通过app.use(也有app.get,app.post等方法)的方式注册到express的实例某个属性上,将执行函数存放在栈内部,然后在回调执行的时候调用next()方法将执行下一个存在栈内的方法。
这里列举一个示例:
constexpress=require('express');
constapp=express();
app.use(function(req,res,next){
console.log('firstalluse');
next()
});
app.use(function(req,res,next){
setTimeout(()=>{
console.log(`twoalluse`)
next()
},1000)
});
app.use(function(req,res,next){
console.log('endalluse')
next()
});
app.use('/',function(req,res,next){
res.end('hellouse')
});
app.listen(4000,function(){
console.log(`起动服务成功!`)
});
通过node执行以上代码后,在浏览器上通过访问http://locahost:4000可以看到控制台打印:
可以发现在执行的时候先执行了use注册的中间件,然后再执行到get路由的时候,又执行了app.use注册的中间件。
详细的express中间件可以在express官网查看
实现dev-server
devServer可以使用webpack-dev-server然后通过before的回调去做一层拦截,这样也能够实现在响应之前对后台的数据做一些处理。
我这儿选择自己实现一个devServer,在之前使用webpack-dev-server的服务大概需要配置,port,proxy,以及跨域https等。当然自己实现devServer就没必要实现那么多功能了,正常在开发场景下很多也不一定用得上,这里我主要使用了webpack-dev-middleware和webpack-hot-middleware达到自动编译和热更新的目的,以及可以自己在中间添加express中间件.
贴上代码:
onstpath=require('path');
constexpress=require('express');
constwebpack=require('webpack');
constwebpackConfig=require('./webpack.dev');
constdevMiddleware=require('webpack-dev-middleware');
consthotMiddleware=require('webpack-hot-middleware');
constapp=express();
constcompiler=webpack(webpackConfig);//webpack开发环境配置
constmockPlugin=require('./mock-plugin');
constconfig={
prd:8800
};
//注册webpack-dev-middleware中间件
app.use(
devMiddleware(compiler,{
publicPath:webpackConfig.output.publicPath
})
);
//注册webpack-hot-middleware中间件
app.use(
hotMiddleware(compiler)
);
//注册mockPlugin插件
app.use(
mockPlugin({
routes:{
'/app':'http://locahost:3002',//测试代理到服务器的地址
'/api':'http://localhost:3003'//测试代理到服务器的地址
},
root:path.resolve(__dirname)//项目根目录
})
);
app.listen(config.prd,function(){
console.log('访问地址:',`http://localhost:${config.prd}`);
});
具体的一些演示操作,这里也不多讲了(这不是实现mock-plugin的重点),网上也有很多如果通过webpack-dev-middleware和webpack-hot-middleware的教程,唯一的区别是代理部分,网上可能用的是http-proxy之类已现有的工具,因为我们这儿需要在请求代理中间还需要处理一层,所以这儿我们自己实现mockPlugin注册进去。
摸拟mock文件
因为mock工具包含了一个请求后台的结果自动写入到Mock目录下。所以这里将目录层级设置为与请求路径保持一致:
api接口:/app/home/baseInfo=>目录:mock\app\home\baseInfo.js
对应baseInfo.js模板:
//mock开关
exports.check=function(){
returntrue;
}
//mock数据
exports.mockData=function(){
return{
"success":true,
"errorMsg":"",
"data":{
name:'test'
}
}
}
当check为true时对就请求将会取mockData的数据。
主逻辑实现
mock-plugin主要暴露一个高阶函数,第一层为请求代理配置,返回的函数的参数与app.get('/')的回调参数一致,不描述细节,大概输出主要的逻辑。
//获取mock文件的mock数据
constsetMockData=(moduleName)=>{
const{mockData}=require(moduleName);
returnmockData();
};
//中间件暴露方法
module.exports=function(options){
const{routes,root}=options;
returnasync(req,res,next)=>{
let{isReq,host}=awaitvalid.isRequestPath(routes,req);
//不是请求地址直接return掉
if(!isReq){
next();
return;
}
//如果存在Mock对应的文件
letfilePath=awaitvalid.isMockFileName(root,req.path);
if(filePath){
//检验本地mock文件开关是否开启
letcheck=awaitvalid.inspectMockCheck(filePath);
if(check){
//发送本地mock数据
returnres.send(setMockData(filePath))
}else{
//请求结果
letbody=awaitrequest(host,req,res).catch(proxyRes=>{
res.status(proxyRes.statusCode);
});
//发送请求的结果信息
returnres.send(body);
}
}else{
//请求返回主体
letbody=awaitrequest(host,req,res).catch(proxyRes=>{
res.status(proxyRes.statusCode);
next();
});
if(body){
//定义需要写入文件路径
constfilePath=path.resolve(root,`mock${req.path}.js`);
//写入mock文件
writeMockFile(filePath,body);
//响应返回主体
returnres.send(body);
}
}
};
};
以下是一些校验方法,详细代码就不贴了,具体源码可查看:https://github.com/moxaIce/lo...
- isRequestPath校验是否为api接口请求,返回Promise包含isReq布尔值,host请求域名,route请求路由的对象
- isMockFileName是否存在对应的mock文件,返回Promise返回匹配路径或者空字符串
- inspectMockCheck校验模拟文件请求,开关是否开起,返回布尔值
至于request方法和writeMockFile方法看下面的小结。
以下是我自己画的一个逻辑图,有点丑见谅:
请求代理
代理的作用不用多说,都知道是解决了前端起的服务和直接请求后台的跨域问题。我这儿主要是在中间件内部通过http.request方法发起一个http请求,对于http.request方法的使用可以看这里,里面也有比较详细的示例,我这儿贴上我写的代码:
/**
*@description请求方法
*/
consturl=require('url');
consthttp=require('http');
module.exports=function(host,req,res){
letbody='';
returnnewPromise((resolve,reject)=>{
constparse=url.parse(host);
letproxy=http.request(
{
host:host.hostname,
port:parse.port,
method:req.method,
path:req.path,
headers:req.headers
},
(proxyRes)=>{
//非200字段内直接响应错误,在主逻辑里处理
if(proxyRes.statusCode<200||proxyRes.statusCode>300){
reject(proxyRes)
}
proxyRes.on('data',(chunk)=>{
body+=chunk.toString();
}).on('end',()=>{
try{
resolve(JSON.parse(body));
}catch(e){
//将响应结果返回,在主文件做异常回调
reject(proxyRes)
}
}).on('error',(err)=>{
console.log(`erroris`,err);
})
});
proxy.on('error',(e)=>{
console.error(`请求报错:${e.message}`)
});
proxy.end()
})
};
代理的实现比较简单,主要通过外层传入host和requset,response在内部用url解析得到ip然后配置request的options,通过监听data与end事件将得到的主体报文resolve出去,以及中间对非200段内的响应处理。
文件写入
通过中间传options传入的root,可以得到完整的mock路径path.resolve(__dirname,mock${req.path}.js)。传入到写入mock文件方法里
module.exports=asyncfunction(filePath,body){
awaitdirExists(path.dirname(filePath));
fs.writeFile(filePath,echoTpl(JSON.stringify(body)),function(err){
if(err){
console.log(`写入文件失败`)
}
});
}
定义mockjs模板
module.exports=asyncfunction(filePath,body){
awaitdirExists(path.dirname(filePath));
fs.writeFile(filePath,echoTpl(JSON.stringify(body)),function(err){
if(err){
console.log(`写入文件失败`)
}
});
}
dirExists通过递归的方式写入文件目录
module.exports=asyncfunction(filePath,body){
awaitdirExists(path.dirname(filePath));
fs.writeFile(filePath,echoTpl(JSON.stringify(body)),function(err){
if(err){
console.log(`写入文件失败`)
}
});
}
效果演示
使用koa起一个node服务并且暴露如下路由和其中的数据,具体代码可以看这儿,我这儿只贴上了关键代码
服务端代码:
router.get('/app/home/baseInfo',user_controller.baseInfo)
router.post('/app/login',user_controller.login)
constlogin=async(ctx,next)=>{
ctx.body={
success:true,
message:'',
code:0,
data:{
a:1,
b:'2'
}
}
};
constbaseInfo=async(ctx,next)=>{
ctx.body={
success:true,
errorMsg:'',
data:{
avatar:'http://aqvatarius.com/themes/taurus/html/img/example/user/dmitry_b.jpg',
total:333,
completed:30,
money:'500'
}
};
};
client代码
mounted(){
axios.get('/app/home/baseInfo',function(res){
console.log(`res23`,res)
});
axios({
url:'/app/login',
method:'post',
headers:{
//'Content-Type':'application/json;charset=UTF-8',
'a':'b'
}
})
}
具体效果可以看下图:
结语
感觉在写文章的过程中发现写入mock文件的时候可能使用stream会更好一点,年底了业务需求不太多,避免上班划水,随便想了写一写~觉得有用的可以点个收藏+赞~
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。