fooler-core

1.2.6 • Public • Published

fooler-core

  1. fooler-core 是一个http服务框架;
  2. 它可以作为 API 接口服务;
  3. 也可以作为 前端 页面的渲染服务;
  • 关键词:node http web api framework

一、框架优势

  1. 安装简单
    • 引包即用,包占资源小,没有三方依赖。
  2. 概念少
    • 不像其他框架那样,Router、middleware、pipline、controller、Event、Stack 每个概念都有独立规则与用法。
    • 本框架只有 Router(路由)、control(过程) 2个概念,变通control的开发与顺序可以做到框架的增强。
  3. 易于拓展
    • Router 可以设置无限个 control
    • 根据 control 的设置顺序与作用自行分组,可以起到 middleware、pipline、controller 的效果,并且规则用法一致。
    • Router 可以设置子集 Router,向树杈一样,一层层向子集调用,并一层层向外抛出结果。
  4. 对接框架处理
    • 热更新、重启、全局错误接收,可自定制系统维护系统。

二、安装使用

  1. 安装
    • npm install fooler-core
  2. 编写代码 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();
  3. 执行
    node test2.js
    
  4. 浏览器访问
    http://127.0.0.1:8080/hello
    http://127.0.0.1:8080/hello/xiaoming
    
  5. 联系作者

三、配置详解

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 同样支持
}

五、使用说明书

  1. 事件 “event”
    • 事件是系统提供捕获:错误、热重启、进程重启、事件路由的重载
    • 请看:/test-events.js
  2. 路由 “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)
  3. 路由的方法
    • 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过程设置,返回自己

六、完整使用示例

  1. 编写入口文件 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();
  2. 编写路由文件 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
    }
  3. 编写路事件文件 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);
            });
        });
    });
}

十、进阶

  1. session 的使用
  2. Gzip 的开启
  3. 跨域的开启
  4. Rpc模式使用
  5. Vue组件化开发
  6. 静态文件服务

请查看 https://github.com/NBye 中更多的fooler 组件

Package Sidebar

Install

npm i fooler-core

Weekly Downloads

4

Version

1.2.6

License

ISC

Unpacked Size

59.1 kB

Total Files

18

Last publish

Collaborators

  • nbye