1 Star 0 Fork 0

简单的机械键盘 / doio

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MulanPSL-2.0

doio

doio是运行于服务端的Web框架,是从titbit衍生的框架,一些集成的功能做了模块分离,并独立发布了扩展。通常doio会做一些新的尝试,不过仍然可以保证稳定。考虑使用的易用和功能完整度,你应该使用titbit,它既不是重型框架,也不简单过头,并且提供一个足够使用的功能集。

完全兼容titbit和titbit-loader的版本停留在了1.2.8。从2.0开始,完全进行了更新。

Node.js的Web开发框架,同时支持HTTP/1.1和HTTP/2协议, 提供了简洁却极具表现力的中间件机制。

核心功能:

  • 中间件模式
  • 路由命名和分组
  • 中间件分组和规则过滤
  • 开启守护进程:使用cluster模块
  • 自动解析body数据
  • 支持通过配置启用HTTP/1.1或是HTTP/2服务
  • 支持配置启用HTTPS服务(HTTP/2服务必须要开启HTTPS)
  • 限制请求数量
  • 限制一段时间内单个IP的最大访问次数
  • IP黑名单和IP白名单

!注意

请使用最新版本。

安装

npm i doio

最小示例

'use strict';

const doio = require('doio');

const app = new doio();

app.get('/', async c => {
  c.res.body = 'success';
});

app.run(2022);

//run接口和http模块的listen接口参数一致。

获取URL参数和表单数据

'use strict';

const doio = require('doio');

var app = new doio();

var {router} = app;

router.get('/q', async c => {
  //URL中?后面的查询字符串解析到query中。
  c.res.body = c.query; //返回JSON文本,主要区别在于header中content-type为text/json
});

router.post('/p', async c => {
  //POST、PUT提交的数据保存到body,如果是表单则会自动解析,否则只是保存原始文本值,
  //可以使用中间件处理各种数据。
  c.res.body = c.body;
});

app.run(2019);

路由参数

app.get('/:name/:id', async c => {
  //使用:表示路由参数,请求参数被解析到c.param
  let username = c.param.name;
  let uid = c.param.id;
  c.res.body = `${username} ${id}`;
});
app.run(8000);

任意路由


//使用 * 表示任意路径,/static后出现的任意路径都会被解析到c.param.startPath。

const doio = require('doio');

const app = new doio();

app.get('/static/*', async c => {
  let path = c.param.starPath;
  c.res.body = path;
});
app.run(2022);
请求/static/css/a.css
返回结果:
    css/a.css

通过c.param.starPath获取的路径并不带有static。

上传文件

默认会解析上传的文件,如果你真的需要自己来控制,或者是需要其他特殊的处理流程,可以通过在外面用一层中间件来改变。这在后面dataHandle部分有说明。

解析后的文件数据在c.files中存储,想知道具体结构请往下看。

'use strict';

const doio = require('doio');

var app = new doio();

//添加中间件过滤上传文件的大小,后面有中间件详细说明。
app.use(async (c, next) => {

  if (c.name !== 'upload-image') {
    return await next();
  }

  //解析后的文件在c.files中存储,通过getFile可以方便获取文件数据。
  let upf = c.getFile('image');
  if (!upf) {
    c.res.body = 'file not found';
    return ;
  } else if (upf.data.length > 2000000) {
    c.res.body = 'max file size: 2M';
    return ;
  }
  await next();

});

app.post('/upload', async c => {
  let f = c.getFile('image');
  try {
    
    //通过sha1生成不会冲突的文件名,extName是解析文件扩展名的助手函数。
    let fname = c.helper.sha1(`${Date.now()}${Math.random()}`) + c.helper.extName(f.filename);

    c.res.body = await c.moveFile(f, fname);
  } catch (err) {
    c.res.body = err.message;
  }
}, 'upload-image'); //给路由命名为upload-image,可以在c.name中获取。

app.run(2022);

c.files数据结构


{
  "image" : [
    {
      'content-type': CONTENT_TYPE,
      filename: ORIGIN_FILENAME,
      start : START,
      end   : END,
      length: LENGTH
    },
    ...
  ],

  "video" : [
    {
      'content-type': CONTENT_TYPE,  //文件类型
      filename: ORIGIN_FILENAME //原始文件名
      start : START, //ctx.rawBody开始的索引位置
      end   : END,   //ctx.rawBody结束的索引位置
      length: LENGTH,  //文件长度,字节数
    },
    ...
  ]
}

c.getFile就是通过名称索引,默认索引值是0,如果是一个小于0的数字,则会获取整个文件数组,没有返回null。

中间件

中间件是一个很有用的模式,不同语言实现起来多少还是有些区别的,这个框架采用了一个有些不同的设计,目前来说运行很好,而且它很快。

中间件图示:

此框架的中间件设计是从titbit的设计中演变过来的,但是做了更多的精简,简洁却不失灵活,举重若轻,就像蚂蚁的力量,猎豹的速度。在设计层面上,并不是根据中间件函数的数组取出来动态封装,递归调用,而是在服务运行之前,已经确定了执行链条。

使用方式:


app.use(async (c, next) => {
    console.log('before');
    await next();
    console.log('after');
});

app.use(async (c, next) => {
    
    let start_time = Date.now();
    await next();
    let end_time = Date.now();

    console.log('运行时间:', end_time - start_time, 'ms');

});

使用use添加的中间件按照添加顺序执行,这更方便编码。

中间件参数

你可以通过use的第二个参数控制过滤条件,传递的是一个函数对象,如果返回false,则表示检测失败,此时不会执行此中间件,直接跳转到下一层,任何非false的返回值都表示通过。

示例


let m1 = async (c, next) => {
  console.log('m1 in');
  await next();
  console.log('m1 out');
};

//只针对POST请求才执行
let m1filter = (ctx) => {
  if (ctx.method !== 'POST') {
    return false;
  }
  return true;
};

app.use(m1, mifilter);

pre:在处理data事件以前

使用use添加的中间件在处理完body数据之后,这时候已经可以获取到解析后的body数据。而使用pre添加的中间件,在body数据接收以前,这时候还没有接收数据,在此之前的处理逻辑要尽可能快,通常来说你可以进行更多的验证工作以及动态设定最大接收请求体的数据量。这可以通过ctx.maxBody属性设置,以字节为单位。默认它就是初始化配置的maxBody设置。


//根据路由分组设定最大接收的body数据量

app.pre(async (c, next) => {
  if (c.group === 'admin') {
    //~30M
    c.maxBody = 30000000;
  } else {
    //~10M
    c.maxBody = 10000000;
  }
});

pre的参数和use完全相同。在这一层,你可以挂载自己的数据请求处理函数,如果你设置了ctx.box.dataHandle为函数对象,则会挂载此函数在data事件上,而不是默认的处理过程,其函数参数就是chunk,是传递过来的buffer数据,具体参考Node.js文档。比如,你需要处理非常大量的数据,但是不能保存到内存再统一处理,而是使用流,直接把数据写入到一个创建的可写流,这时候就能够通过在这一层做处理。只要rawBody为空,或者是消息头content-type不是上传文件的类型则在下一层的bodyparser就不会进行body的文件解析和其他处理。如果你使用了rawBody,则要把它最后设置为空字符串。

配置选项

应用初始化,完整的配置选项如下

  {
    //此配置表示POST/PUT提交表单的最大字节数,也是上传文件的最大限制。
    maxBody   : 8000000,

    //最大解析的文件数量
    maxFiles      : 12,

    daemon        : false, //开启守护进程

    /*
      开启守护进程模式后,如果设置路径不为空字符串,则会把pid写入到此文件,可用于服务管理。
    */
    pidFile       : '',

    //开启HTTPS
    https       : false,

    http2   : false,

    //HTTPS密钥和证书的文件路径,如果设置了路径,则会自动设置https为true。
    key   : '',
    cert  : '',

    //服务器选项都写在server中,在初始化http服务时会传递,参考http2.createSecureServer、tls.createServer
    server : {
      handshakeTimeout: 7168, //TLS握手连接(HANDSHAKE)超时
      //sessionTimeout: 350,
    },

    //设置服务器超时,毫秒单位,在具体的请求中,可以再设置请求的超时。
    timeout   : 18000,

    debug     : false,

    //忽略路径末尾的 /
    ignoreSlash: true,

    //启用请求限制
    useLimit: false,

    // 请求处理的钩子函数,如果设定了,在请求开始,回调函数中会执行此函数,
    // 在这里你可以设定一些事件处理函数,最好仅做这些或者是其他执行非常快的任务,可以是异步的。
    //在http/1.1协议中,传递参数就是request,response以及protocol表示协议的字符串'http:' 或者 'https:'
    //在http/2协议中,传递stream参数。

    requestHandle : null,


    //404要返回的数据
    notFound: 'Not Found',
    
    //400要返回的数据
    badRequest : 'Bad Request'
  };
  // 对于HTTP状态码,在这里仅需要这两个,其他很多是可以不必完整支持,并且你可以在实现应用时自行处理。
  // 因为一旦能够开始执行,就可以通过运行状态返回对应的状态码。
  // 而在这之前,框架还在为开始执行洋葱模型做准备,不过此过程非常快。

请求上下文

请求上下文就是一个封装了各种请求数据的对象。通过这样的设计,把HTTP/1.1 和 HTTP/2协议的一些差异以及Node.js版本演进带来的一些不兼容做了处理,出于设计和性能上的考虑,对于HTTP2模块,封装请求对象是stream,而不是http模块的IncomingMessage和ServerResponse(封装对象是request和response)。


    var ctx = {

      version : '1.1', //协议版本
      
      maxBody : 0, //最大body请求数据量

      method    : '', //请求类型

      ip      : '', //客户端IP

      host    : '', 
      
      port    : 0,
      
      protocol: '', //协议

      //实际的访问路径
      path    : '',

      name    : '', //对路由和请求的命名
      
      headers   : {},

      //实际执行请求的路径,是添加到路由模块的路径
      routepath   : '', 

      //路由参数
      param     : {},

      //url的querystring参数,就是url 的 ? 后面的参数
      query     : {},

      //请求体解析后的数据
      body    : {},

      //是否是上传文件的操作
      isUpload  : false,

      //路由分组
      group     : '',
      
      //原始body数据
      rawBody   : '',

      //body数据接收到的总大小
      bodyLength  : 0,

      //解析后的文件信息,实际的文件数据还在rawBody中,这里只记录信息。
      files     : {},

      // 指向实际请求的回调函数,就是通过app.get等接口添加的回调函数。
      // 你甚至可以在执行请求过程中,让它指向一个新的函数,这称为请求函数重定向。
      exec : null,

      //助手函数,包括aes加解密、sha1、sha256、sha512、格式化时间字符串、生成随机字符串等处理。
      helper    : helper,

      //要返回数据和编码的记录
      res : {
        body : '',
        encoding : 'utf8',
      },
  
      //http模块请求回调函数传递的参数被封装到此。
      //在http2协议中,没有这两项。
      request   : null,
      response  : null,

      //只有在http2模块才有此项。
      stream : null,
  
      //中间件执行时挂载到此处的值可以传递到下一层。
      box : {},

      //app运行时,最开始通过addService添加的服务会被此处的service引用。
      //这称为依赖注入,不必每次在代码里引入。
      service:null,
    };

    ctx.send = (d) => {
      ctx.res.body = d;
    };

    ctx.getFile = (name, ind = 0) => {
      if (ind < 0) {return ctx.files[name] || [];}
  
      if (ctx.files[name] === undefined) {return null;}
      
      if (ind >= ctx.files[name].length) {return null;}
  
      return ctx.files[name][ind];
    };
  
    ctx.setHeader = (name, val) => {
      ctx.response.setHeader(name, val);
    };
 
    ctx.status = (stcode = null) => {
      if (stcode === null) { return ctx.response.statusCode; }
      if(ctx.response) { ctx.response.statusCode = stcode; }
    };

    //上传文件时,写入数据到文件的助手函数。
    ctx.moveFile = async (upf, target) => {
      let fd = await new Promise((rv, rj) => {
        fs.open(target, 'w+', 0o644, (err, fd) => {
          if (err) { rj(err); }
          else { rv(fd); }
        });
      });

      return new Promise((rv, rj) => {
        fs.write(fd, ctx.rawBody, upf.start, upf.length, 
          (err,bytesWritten,buffer) => {
            if (err) { rj(err); }
            else { rv(bytesWritten); }
          });
      })
      .then(d => {
        return d;
      }, e => { throw e; })
      .finally(() => {
        fs.close(fd, (err) => {});
      });
    };

注意:send函数只是设置ctx.res.body属性的值,在最后才会返回数据。和直接进行ctx.res.body赋值没有区别,只是因为函数调用如果出错会更快发现问题,而设置属性值写错了就是添加了一个新的属性,不会报错但是请求不会返回正确的数据。

最后

  • 关于中间件

要改造成可以在运行时能够重新生成执行过程的方式也不困难,实际上,由于本身就是利用栈来生成的,只需要清理掉并重新运行加载函数生成即可。无论哪种实现方式,要做到运行时修改该都是可以很快解决的,但是这很危险,除非真的有必要,否则你还是在修改逻辑后,重新运行服务,或者你应该在中间件内部来解决一些动态调整的问题。

木兰宽松许可证, 第2版 木兰宽松许可证, 第2版 2020年1月 http://license.coscl.org.cn/MulanPSL2 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 0. 定义 “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 “法人实体”是指提交贡献的机构及其“关联实体”。 “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 1. 授予版权许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 2. 授予专利许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 3. 无商标许可 “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 4. 分发限制 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 5. 免责声明与责任限制 “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 6. 语言 “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 条款结束 如何将木兰宽松许可证,第2版,应用到您的软件 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 3, 请将如下声明文本放入每个源文件的头部注释中。 Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. Mulan Permissive Software License,Version 2 Mulan Permissive Software License,Version 2 (Mulan PSL v2) January 2020 http://license.coscl.org.cn/MulanPSL2 Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 0. Definition Software means the program and related documents which are licensed under this License and comprise all Contribution(s). Contribution means the copyrightable work licensed by a particular Contributor under this License. Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. Legal Entity means the entity making a Contribution and all its Affiliates. Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 1. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 2. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 3. No Trademark License No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 4. Distribution Restriction You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 5. Disclaimer of Warranty and Limitation of Liability THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 6. Language THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. END OF THE TERMS AND CONDITIONS How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. Copyright (c) [Year] [name of copyright holder] [Software Name] is licensed under Mulan PSL v2. You can use this software according to the terms and conditions of the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details.

简介

暂无描述 展开 收起
JavaScript
MulanPSL-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
JavaScript
1
https://gitee.com/daoio/doio.git
git@gitee.com:daoio/doio.git
daoio
doio
doio
master

搜索帮助