fooler-core
v1.2.6
Published
node http web api framework. Project process manager.
Downloads
36
Maintainers
Readme
fooler-core
- fooler-core 是一个http服务框架;
- 它可以作为 API 接口服务;
- 也可以作为 前端 页面的渲染服务;
- 关键词:node http web api framework
一、框架优势
- 安装简单
- 引包即用,包占资源小,没有三方依赖。
- 概念少
- 不像其他框架那样,Router、middleware、pipline、controller、Event、Stack 每个概念都有独立规则与用法。
- 本框架只有 Router(路由)、control(过程) 2个概念,变通control的开发与顺序可以做到框架的增强。
- 易于拓展
- Router 可以设置无限个 control
- 根据 control 的设置顺序与作用自行分组,可以起到 middleware、pipline、controller 的效果,并且规则用法一致。
- Router 可以设置子集 Router,向树杈一样,一层层向子集调用,并一层层向外抛出结果。
- 对接框架处理
- 热更新、重启、全局错误接收,可自定制系统维护系统。
二、安装使用
- 安装
- npm install fooler-core
- 编写代码 test2.js
const { Fooler } = require('fooler-core'); const app = new Fooler({ p: 8080 }); app.route.GET('/hello').then(({ ctx }) => { ctx.sendHTML('hello world'); }); app.route.GET(/^\/hello\/(\w+)$/).then(({ ctx, match }) => { ctx.sendHTML(`hello ${match[0]}`); }); app.run();
- 执行
node test2.js
- 浏览器访问
http://127.0.0.1:8080/hello http://127.0.0.1:8080/hello/xiaoming
- 联系作者
- 邮箱:[email protected]
- 微信:NBye_oO
三、配置详解
app = new Fooler({p: 8080});
//或者
app = Fooler.create({
p: 8080, //http 服务端口(默认80)
route: __dirname + '/app-routes', //定义路由规则的js文件(推荐此方式,可以分开定义路由并且支持热重载)
event: __dirname + '/app-events', //定义系统事件的js文件(推荐此方式,支持热重载)
processes: 1, //服务进程数,缺省或0时=cpu核数,在expand定义可以热更新动态调整进程数
log: { //日志定输出向文件设置
level: ['error', 'log', 'warn'], //捕获的级别
//自定义格式化日志格式
// error: (...args) => { return JSON.stringify(args) },
// log: (...args) => { return JSON.stringify(args) },
// warn: (...args) => { return JSON.stringify(args) },
//日志目录配置,一旦配置,console.log,error,warn 将会写入
path: '/test.{{yyyy-mm-dd}}.log',
},
expand: `${__dirname}/conf.json`, //配置拓展,可以在热更新时进行重载,并覆盖以上配置
});
四、使用路由
//app-routes.js
module.exports = async function (roue) {
roue.then(async ({ ctx }) => {
//请求开始时调用
}).catch(async ({ err, ctx }) => {
//异常未被其他路由接收时,会被再次接收
}).finally(async ({ ctx }) => {
//请求执行完最后会执行
});
//1. 字符串路由,当URL 参数之前 == /string/roue 时
roue.when('/string/roue').then(async ({ ctx }) => {
//命中时调用
}).catch(async ({ err, ctx }) => {
//异常时调用
}).finally(async ({ ctx }) => {
//命中结束后调用
});
//2. 这则路由,当URL 参数之前 满足当前正则表达式 时
roue.when(/^user\/(\d+)$/).then(async ({ ctx,match }) => {
//命中时调用
//收集匹配参数
let [user_id] = match;
}).catch(async ({ err, ctx }) => {
//异常时调用
}).finally(async ({ ctx }) => {
//命中结束后调用
});
//3. 字符串子路由
roue.when('/string').childens((r) => {
//当URL 参数之前 == /string/roue 时
r.when('/roue').then(async ({ ctx }) => {
//命中时调用
}).catch(async ({ err, ctx }) => {
//异常时调用
}).finally(async ({ ctx }) => {
//命中结束后调用
});
});
//4. 正则子路由
roue.when(/^user/).then(async ({ ctx,match }) => {
//注意:正则路由,不能继承父级表达式,必须每次正则都全等
roue.when(/^user\/(\d+)$/).then(async ({ ctx,match }) => {
//命中时调用
//收集匹配参数
let [user_id] = match;
});
//异常不设置,会被复层级接收
}).catch(async ({ err, ctx }) => {
//异常时调用
}).finally(async ({ ctx }) => {
//命中结束后调用
});
//5. 路由方法 when 的其他用法
roue.when('/url',['GET'])
//等价于
roue.GET('/url')
roue.when('/url',['POST'])
roue.POST('/url')
roue.when('/url',['PUT'])
roue.PUT('/url')
roue.when('/url',['OPTIONS'])
roue.OPTIONS('/url')
roue.when('/url',['DELETE'])
roue.DELETE('/url')
roue.when('/url',['HEAD'])
roue.HEAD('/url')
//6. 路由的 并行 串行
//按顺序执行 func1 到 func5
roue.when('/url',['GET']).then(func1,func2,func3).then(func4).then(func5);
//并发执行 func1~func3
roue.when('/url',['GET']).then([func1,func2,func3]);
//catch、finally 同样支持
}
五、使用说明书
- 事件 “event”
- 事件是系统提供捕获:错误、热重启、进程重启、事件路由的重载
- 请看:/test-events.js
- 路由 “route”
- 是配置根据uri地址与处理程序关系的对象
- 请看:/test-routes.js
- 2.1 子路由
- 任何路由都可以无限制创建子路由,向树枝一样,可以无限向下分
- roue.when(path, ["GET", "POST", "PUT"],parser).childens((r)=>{});
- roue.when(路由表达式, 请求方法, 请求解析器).childens((r)=>{});
- 请求解析器请看test2.js
- 2.2 命中
- 路由会根据配置的正则或者url路径进行匹配,匹配成功则命中,同时只能命中靠前定义的那1个
- 2.2.1 正则路由
- 路由的表达式为正则,可以通过正则的(\w+)捕获参数
- roue.when(/^/user/(info)/(\d+)/ , ["GET"].then(({ctx,match})=>ctx.sendJSON(match))
- 2.2.2 字符串路由
- 路由的表达式为字符串,满足表达式全等与uri则会命中,字符串路由会将父级路由与子集路由表达式组合匹配。
- roue.when('/user/info' , ["GET"].then(({ctx})=>ctx.sendJSON([ctx.GET(),ctx.POST()]))
- 2.2.3 混合路由
- 路由与子路由,多个子路由之间,随意混合。正则不会与父级别路合并匹配,但字符串路由会。
- roue.when('/user').childens(r=>r.when('/info')) 会命中 /user/info
- 2.3 过程 “control”
- 过程是路由命中后的处理函数
- 2.3.1 顺序过程
- 过程被按先后设置顺序执行
- roue.then(contr1,contr2,contr3).then(contr4).then(contr5)
- 2.3.2 并行过程
- 过程被并行执行
- roue.then([contrA1,contrA2,contrA3],contrB)
- 路由的方法
- roue.when(rule,["GET","协议方法"]) //路由命中规则,返回子Router
- roue.GET(rule) //路由命中规则,返回子Router
- roue.POST(rule) //路由命中规则,返回子Router
- roue.PUT(rule) //路由命中规则,返回子Router
- roue.OPTIONS(rule) //路由命中规则,返回子Router
- roue.DELETE(rule) //路由命中规则,返回子Router
- roue.HEAD (rule) //路由命中规则,返回子Router
- roue.childens //路由添加子路由,返回自己
- roue.then(contr1,contr2,contr3) //命中路由执行过程设置,返回自己
- roue.catch(contr4,contr5,contr6) //命中路由执行异常接收过程设置,返回自己
- roue.finally(contr7,contr8,contr9) //命中路最后如果为发生send过程设置,返回自己
六、完整使用示例
- 编写入口文件 app.js
const {Fooler} = require('fooler-core') const app = new Fooler({ //web服务端口(默认80) p: 8080, //服务进程数,缺省或0时=cpu核数,在expand定义可以热更新动态调整进程数 processes: 1, //初始化路由模块,写在该属性上可以支持热更新(可以不设置、也可以设置到当前文件内) route: __dirname + '/app-routes', //初始化事件模块,写在该属性上可以支持热更新(可以不设置、也可以设置到当前文件内) event: __dirname + '/app-events', //该项不设置,或log.path不设置 则不会将console输出到log.path文件上 log: { //捕获的级别 level: ['error', 'log', 'warn'], //自定义格式化日志格式 // error: (...args) => { return JSON.stringify(args) }, // log: (...args) => { return JSON.stringify(args) }, // warn: (...args) => { return JSON.stringify(args) }, //日志目录配置,一旦配置,console.log,error,warn 将会写入 path: '/test.{{yyyy-mm-dd}}.log', }, //expand: `${__dirname}/conf.json`, //配置拓展,可以在热更新时进行重载,并覆盖代码中的设置 }); app.run();
- 编写路由文件 app-routes.js
module.exports = async function (roue) { roue.then(async ({ ctx }) => { ctx.stime = Date.now(); }).catch(async ({ err, ctx }) => {//异常被执行的过程 ctx.sendHTML(`<pre>${err.stack}</pre>`) }).finally(async ({ ctx }) => {//总会被执行 let timer = parseInt((Date.now() - ctx.stime) / 1000); console.log(`[${ctx.req.method},${ctx.res.statusCode}] ${ctx.req.url} use:${timer}ms`); }); roue.when('/api/v2') .childens((r) => { //请求 http GET /api/v2/fn1 r.GET('/fn1').then(({ctx})=>ctx.sendHTML("fn1")) //请求 http GET /api/v2/fn2 r.GET('/fn2').then(({ctx})=>ctx.sendHTML("fn2")) //请求 http GET /api/v2/www r.GET(/^\/api\/v2\/(\w+)$/).then(({ctx,match})=>ctx.sendHTML("fn2: " + match.join(','))) }).catch(async (err) => { //捕获异常,但不返回让父级的finally处理输出 console.log(err); }); //更多请看 test-routes.js }
- 编写路事件文件 app-events.js
const { loadlogRouter, loadlogEvents } = require('./index'); module.exports = function (app) { app.on('error', async function (err) { console.error('on error:', err); }, '服务错误回调事件'); app.on('restart', async function () { console.log('on restart'); }, '服务被重启回调事件'); app.on('reload', async function () { console.log('on reload'); }, '服务被重载回调事件'); app.on('load-events', async function (service) { loadlogEvents(service); }, '加载事件完成时'); app.on('load-router', async function (service) { loadlogRouter(service); }, '加载路由完成时'); }
七、过程定义使用详解
/**
* 这是一个处理过程示例
* 本框架不区分 中间件、控制器、管道 等,一切均为过程,过程可以并行、串行,按路由定义顺序执行。
* {
* ctx : 请求上下文
* options : app初始化的参数对象
* match : 正则路由匹配成功的列表
* err : 上一个路由命中时 抛出错误
* }
*/
const control = async function ({ ctx, options, router, match = null, err = null }) {
return ctx.send({
text: require('fs').readFileSync(__filename),
status: 200,
headers: { 'Content-Type': 'text/javascript;charset=UTF-8' }
});
let [m1, m2, m3] = match || ['', '', '']; //正则路由通过match拿到
ctx.req; //原生对象:Request
ctx.res; //原生对象:Reponse
ctx.service; //框架服务对象
ctx.options; //框架app启动配置,参数中的 options 为该对象中的引用
ctx.router; //命中的rooter对象
ctx.data; //请求上下文中的全局data存储对象
ctx.data.set(key, val); //设置临时变量 key支持'key.key1.key2'
ctx.data.get(key); //获取临时变量 key支持'key.key1.key2'
ctx.cookie; //请求上下文cookie对象
ctx.cookie.set(key, value, options = { path: '/' }) //设置cookie
ctx.cookie.get(key) //获取cookie
/* 如何想使用 session ,可以自定义中间件,给ctx设置session对象。可参考中间件: /middleware/plug/Session.js */
ctx.completed //一次请求中,是否已发送结束(发送结束不代表执行结束),completed一旦设置为true,后续的 ctx.send????() 将失效
ctx.FILES(key); //获取提交上来的图片{key:{data:Buffer,filename:'文件名',type:'文件IME类型'}}
ctx.GET(key); //获取GET参数
ctx.POST(key); //获取POST参数
ctx.setdHeader(key, val); //设置header
ctx.send({ text, status, headers }) //发送数据 并设置 completed=true
ctx.sendJSON(data, status) //发送JSON数据 并设置 completed=true
ctx.sendHTML(html, status) //发送HTML数据 并设置 completed=true
};
八、热重载 与 重启
//app-routes.js
module.exports = async function (roue) {
const process = require('process');
roue.GET('/reload').then(({app})=>{
//通知服务 热重载
process.send({ event:'reload' })
});
roue.GET('/restart').then(({app})=>{
//通知服务 重启
process.send({ event:'restart' })
});
//无论 热重载还是重启 都会重新订阅路由,重新加载配置
//可以定义路由暴露接口
}
九、设置自定义request 解析器
自定义解析价值,为了方便与部分接口对于数据流的特殊处理; 可以针对不同的应用场景使用不同的解析方式,提升性能;
//app-routes.js
module.exports = async function (roue) {
//roue.when(路由规则,请求协议,解析器)
//roue.POST(路由规则,解析器)
//解析器 === false 时,强制不解析,缺省则 为自动默认解析,设置函数为,自定义解析
roue.when('/custom/parse/request',["POST"],async (req) => {
//这里写解析过程
req._query_data = {}; //可自定义解析结果 GET
req._post_data = {}; //可自定义解析结果 POST
req._file_data = {}; //可自定义解析结果 FILE
});
roue.POST('/custom/parse/request',async (req) => {
//这里写解析过程
return new Promise((resolve, reject) => {
let buff = Buffer.from('');
req.on('data', (chunk) => {
buff = Buffer.concat([buff, chunk]);
});
req.on('end', () => {
req._query_data = {}; //可自定义解析结果 GET
req._post_data = {}; //可自定义解析结果 POST
req._file_data = {}; //可自定义解析结果 FILE
resolve();
});
req.on('error', () => {
reject(err);
});
});
});
}
十、进阶
请查看 https://github.com/NBye 中更多的fooler 组件