• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

titbit: titbit是node.js环境的Web后端框架,支持HTTP/HTTPS/HTTP2,并且支持配置切换 ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

titbit

开源软件地址:

https://gitee.com/daoio/titbit

开源软件介绍:

titbit

titbit是运行于服务端的Web框架,最开始是为了在教学中方便开发而设计,也用在一些业务系统上。它绝对算不上重型框架,但是也不简单过头。

有bug或是疑惑请提交issue或者发送私信。

它非常快,无论是路由查找还是中间件执行过程。

因为github无法正常显示图片,并且github服务访问太慢以及其他问题,建议使用gitee(码云)查看文档。

码云地址

Wiki中有相关主题的说明:Wiki

Node.js的Web开发框架,同时支持HTTP/1.1和HTTP/2协议, 提供了强大的中间件机制。

核心功能:

  • 请求上下文设计屏蔽接口差异。

  • 中间件模式。

  • 路由分组和命名。

  • 中间件按照路由分组执行。

  • 中间件匹配请求方法和路由来执行。

  • 开启守护进程:使用cluster模块。

  • 显示子进程负载情况。

  • 默认解析body数据。

  • 支持通过配置启用HTTP/1.1或是HTTP/2服务。

  • 支持配置启用HTTPS服务(HTTP/2服务必须要开启HTTPS)。

  • 限制请求数量。

  • 限制一段时间内单个IP的最大访问次数。

  • IP黑名单和IP白名单。

  • 在cluster模式,监控子进程超出最大内存限制则重启。

  • 可选择是否开启自动负载模式:根据负载创建新的子进程处理请求,并在空闲时恢复初始状态。

!注意

请尽可能使用最新版本。titbit会先查找路由再进行请求上下文对象的创建,如果没有发现路由,则不会创建请求上下文对象。 这是为了避免无意义的操作,也会有其他一些错误或恶意请求的检测处理,错误状态码涉及到404和400,因此若需要在这个过程中控制返回的错误信息,需要通过初始化选项中的notFound和badRequest进行设置即可,默认的它们只是一条简短的文本信息。

安装

npm i titbit

同样可以通过yarn安装:

yarn add titbit

兼容性

从最初发展到后来一段时间内,都尽可能保证大版本的兼容性。中间经历过多次比较大的演进,有时候次版本号演进也会有不兼容更新。从21.5+版本以后,只有大版本更新可能会有一些不兼容的更新,并给出不兼容项,请注意文档和Wiki。之后的两个小版本号更新都不会体现不兼容的更新。(在此之前,次要版本号仍然可以保证兼容性)

·重要版本改进

最小示例

'use strict'const titbit = require('titbit')const app = new titbit()app.run(1234)

当不填加路由时,titbit默认添加一个路由:

/*

浏览器访问会看到一个非常简单的页面,这仅仅是为了方便最开始的了解和访问文档,它不会对实际开发有任何影响。

添加一个路由

'use strict'const titbit = require('titibit')const app = new titbit()app.get('/', async c => {  //data类型为string|Buffer。可以设置c.res.encoding为返回数据的编码格式,默认为'utf8'。  c.res.body = 'success'})//默认监听0.0.0.0,参数和原生接口listen一致。app.run(1234)

路由和请求类型

HTTP的起始行给出了请求类型,也被称为:请求方法。目前的请求方法:

GET POST PUT DELETE OPTIONS  TRACE HEAD PATCH

最常用的是前面5个。对于每个请求类型,router中都有同名但是小写的函数进行路由挂载。为了方便调用,在初始化app后,可以使用app上同名的快捷调用。(框架层面仅支持这些。)

示例:

'use strict';const titbit = require('titibit');const app = new titbit({  debug: true});app.get('/', async c => {  c.res.body = 'success';});app.get('/p', async c => {  c.res.body = `${c.method} ${c.routepath}`;});app.post('/', async c => {  //返回上传的数据  c.res.body = c.body;});app.put('/p', async c => {  c.res.body = {    method : c.method,    body : c.body,    query : c.query  };});//默认监听0.0.0.0,参数和原生接口listen一致。app.run(8080);

获取URL参数和表单数据

  • URL中的查询字符串(?后面a=1&b=2形式的参数)解析到c.query中。

  • 表单提交的数据解析到c.body中。

表单对应的content-type为application/x-www-form-urlencoded

'use strict';const titbit = require('titbit');var app = new titbit();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);

关于content-type

application/x-www-form-urlencoded

基本的表单类型会解析到c.body,是一个JS对象。

text/*

若content-type是text/*,就是text/开头的类型,比如text/json,框架层面不做解析处理,仅仅是把上传数据以utf8编码的格式转换成字符串赋值给c.body。后续的处理开发者自行决定。

multipart/form-data;boundary=xxx

若content-type是上传文件类型则默认会解析。

其他类型

若content-type是其他类型,则默认只是让c.body指向c.rawBody,即为最原始的Buffer数据。

框架层面提供基本的核心的支持,其他类型需要开发处理或者是使用扩展,比如titbit-toolkit中的parsebody扩展。

要比较容易使用,也要留出足够的空间给开发者处理,你可以完全抛弃框架默认的body解析,通过parseBody选项为false关闭它。也可以在这基础上,进行扩展处理。

send函数

send函数就是对c.res.body的包装,其实就是设置了c.res.body的值。并且支持第二个参数,作为状态码,默认为0,表示采用模块自身的默认状态码,Node.js中http和http2默认状态码为200。

app.get('/', async c => {  c.send('success')})app.get('/randerr', async c => {  let n = parseInt(Math.random() * 10)  if (n >= 5) {    c.send('success')  } else {    //返回404状态码    /*      等效于:        c.status(404)        c.res.body = 'not found'    */   //你可以在v22.4.6以上的版本使用链式调用。    c.status(404).send('not found')  }})app.run(1234)

链式调用

在v22.4.6版本开始,可以对setHeader、status、sendHeader使用链式调用。

app.get('/', async c => {  c.setHeader('content-type', 'text/plain; charset=utf-8')    .setHeader('x-server', 'nodejs server')    .status(200)    .send(`${Date.now()} Math.random()}`)})

路由参数

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);

任意路径参数

* 表示任意路径,但是必须出现在路由最后。

app.get('/static/*', async c => {  //*表示的任意路径解析到c.param.starPath  let spath = c.param.starPath  c.send(spath)})

上传文件

默认会解析上传的文件,你可以在初始化服务的时候,传递parseBody选项关闭它,关于选项后面有详细的说明。解析后的文件数据在c.files中存储,具体结构在后面有展示。

'use strict'const titbit = require('titbit')const app = new titbit()router.post('/upload', async c => {    let f = c.getFile('image')  //此函数是助手函数,makeName默认会按照时间戳生成名字,extName解析文件的扩展名。  //let fname = `${c.helper.makeName()}${c.helper.extName(f.filename)}`  //根据原始文件名解析扩展名并生成时间戳加随机数的唯一文件名。  let fname = c.helper.makeName(f.filename)  try {    c.res.body = await c.moveFile(f, fname)  } catch (err) {    c.res.body = err.message  }  }, 'upload-image'); //给路由命名为upload-image,可以在c.name中获取。app.run(1234)

c.files数据结构

这种结构是根据HTTP协议上传文件时的数据构造设计的,HTTP协议允许同一个上传名有多个文件,所以要解析成一个数组。而使用getFile默认情况只返回第一个文件,因为多数情况只是一个上传名对应一个文件。

对于前端来说,上传名就是你在HTML中表单的name属性:<input type="file" name="image">image是上传名,不要把上传名和文件名混淆。

{  "image" : [    {      'content-type': CONTENT_TYPE,      filename: ORIGIN_FILENAME,      start : START,      end   : END,      length: LENGTH,      rawHeader: HEADER_DATA    },    ...  ],  "video" : [    {      'content-type': CONTENT_TYPE,  //文件类型。      filename: ORIGIN_FILENAME //原始文件名。      start : START, //ctx.rawBody开始的索引位置。      end   : END,   //ctx.rawBody结束的索引位置。      length: LENGTH,  //文件长度,字节数。      rawHeader: HEADER_DATA //原始消息头文本,是multipart/form-data的消息头。    },    ...  ]}

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

body最大数据量限制

'use strict'const titbit = require('titbit')const app = new titbit({  //允许POST或PUT请求提交的数据量最大值为将近20M。  //单位为字节。  maxBody: 20000000})//...app.run(1234)

中间件

中间件是一个很有用的模式,不同语言实现起来多少还是有些区别的,但是本质上没有区别。中间件的运行机制允许开发者更好的组织代码,方便实现复杂的逻辑需求。事实上,整个框架的运行机制都是中间件模式。

中间件图示:

此框架的中间件在设计层面上,按照路由分组区分,也可以识别不同请求类型,确定是否执行还是跳过到下一层,所以速度非常快,而且多个路由和分组都具备自己的中间件,相互不冲突,也不会有无意义的调用。参考形式如下:

/*  第二个参数可以不填写,表示全局开启中间件。  现在第二个参数表示:只对POST请求方法才会执行,并且路由分组必须是/api。  基于这样的设计,可以保证按需执行,不做太多无意义的操作。*/app.add(async (c, next) => {    console.log('before');    await next();    console.log('after');}, {method: 'POST', group: '/api'});

使用add添加的中间件是按照添加顺序逆序执行,这是标准的洋葱模型。为了提供容易理解的逻辑,提供use接口添加中间件,使用use添加的中间件按照添加顺序执行。不同的框架对实现顺序的逻辑往往会不同,但是顺序执行更符合开发者习惯。

建议只使用use来添加中间件:

//先执行app.use(async (c, next) => {  let start_time = Date.now()  await next()  let end_time = Date.now()  console.log(end_time - start_time)})//后执行app.use(async (c, next) => {  console.log(c.method, c.path)  await next()})//use可以级联: app.use(m1).use(m2)//在21.5.4版本以后,不过这个功能其实根本不重要//因为有titbit-loader扩展,实现的功能要强大的多。

titbit完整的流程图示

需要知道的是,其实在内部,body数据接收和解析也都是中间件,只是刻意安排了顺序,分出了pre和use接口。

中间件参数

使用use或者pre接口添加中间件,还支持第二个参数,可以进行精确的控制,传递选项属性:

  • group 路由分组,表示针对哪个分组执行。

  • method 请求方法,可以是字符串或数组,必须大写。

  • name 请求名称,表示只针对此请求执行。

示例:

app.get('/xyz', async c => {  //...  //路由分组命名为proxy}, {group: 'proxy'})app.use(proxy, {  method : ['PUT', 'POST', 'GET', 'DELETE', 'OPTIONS'],  //针对路由分组proxy的请求执行。  group : 'proxy'})

pre 在接收body数据之前

使用pre接口添加的中间件和use添加的主要区别就是会在接收body数据之前执行。可用于在接收数据之前的权限过滤操作。其参数和use一致。

为了一致的开发体验,你可以直接使用use接口,只需要在选项中通过pre指定:

let setbodysize = async (c, next) => {    //设定body最大接收数据为~10k。    c.maxBody = 10000;    await next();};//等效于app.pre(setbodysize);app.use(setbodysize, {pre: true});

使用pre可以进行更复杂的处理,并且可以拦截并不执行下一层,比如titbit-toolkit扩展的proxy模块利用这个特性直接实现了高性能的代理服务,但是仅仅作为框架的一个中间件。其主要操作就是在这一层,直接设置了request的data事件来接收数据,并作其他处理,之后直接返回。

根据不同的请求类型动态限制请求体大小

这个需求可以通过pre添加中间件解决:

const app = new titbit({  //默认最大请求体 ~10M 限制。  maxBody: 10000000})app.pre(async (c, next) => {  let ctype = c.headers['content-type'] || ''  if (ctype.indexOf('text/') === 0) {    //50K    c.maxBody = 50000  } else if (ctype.indexOf('application/') === 0) {    //100K    c.maxBody = 100000  } else if (ctype.indexOf('multipart/form-data') < 0) {    //10K    c.maxBody = 10000  }  await next()}, {method: ['POST', 'PUT']})

这些参数若同时出现在文件里会显得很复杂,维护也不方便,但是功能很强,所以若要交给程序自动完成则可以大大简化编码的工作。

完整的项目结构搭建,请配合使用titbit-loader,此扩展完成了路由、模型的自动加载和中间件自动编排。titbit-loader

配置选项

应用初始化,完整的配置选项如下,请仔细阅读注释说明。

  {    //此配置表示POST/PUT提交表单的最大字节数,也是上传文件的最大限制。    maxBody   : 8000000,    //最大解析的文件数量    maxFiles      : 12,    daemon        : false, //开启守护进程    /*      开启守护进程模式后,如果设置路径不为空字符串,则会把pid写入到此文件,可用于服务管理。    */    pidFile       : '',    //是否开启全局日志,true表示开启,这时候会把请求信息输出或者写入到文件    globalLog: false,    //日志输出方式:stdio表示输出到终端,file表示输出到文件    logType : 'stdio',    //正确请求日志输出的文件路径    logFile : '',    //错误请求日志输出的文件路径    errorLogFile : '',    //日志文件最大条数    logMaxLines: 50000,    //最大历史日志文件数量    logHistory: 50,    //自定义日志处理函数    logHandle: null,    //开启HTTPS    https       : false,    http2   : false,    //HTTPS密钥和证书的文件路径,如果设置了路径,则会自动设置https为true。    key   : '',    cert  : '',    //服务器选项都写在server中,在初始化http服务时会传递,参考http2.createSecureServer、tls.createServer    server : {      handshakeTimeout: 8192, //TLS握手连接(HANDSHAKE)超时      //sessionTimeout: 350,    },    //设置服务器超时,毫秒单位,在具体的请求中,可以再设置请求的超时。    timeout   : 15000,    debug     : false,    //忽略路径末尾的 /    ignoreSlash: true,    //启用请求限制    useLimit: false,    //最大连接数,0表示不限制    maxConn : 1024,    //单个IP单位时间内的最大连接数,0表示不限制    maxIPRequest: 0,    //单位时间,默认为1秒    unitTime : 1,        //展示负载信息,需要通过daemon接口开启cluster集群模式    loadMonitor : true,    //负载信息的类型,text 、json、--null    //json类型是给程序通信使用的,方便接口开发    loadInfoType : 'text',    //负载信息的文件路径,如果不设置则输出到终端,否则保存到文件    loadInfoFile : '',    //404要返回的数据    notFound: 'Not Found',        //400要返回的数据    badRequest : 'Bad Request',    //控制子进程最大内存使用量的百分比参数,范围从-0.42 ~ 0.36。基础数值是0.52,所以默认值百分比为80%。    memFactor: 0.28,    //url最大长度    maxUrlLength: 2048,    //请求上下文缓存池最大数量。    maxpool: 4096,    //子进程汇报资源信息的定时器毫秒数。    monitorTimeSlice: 640,    //在globalLog为true时,全局日志是否记录真实的IP地址,主要用在反向代理模式下。    realIP: false,    //允许的最大querystring参数个数。    maxQuery: 12,    //是否启用strong模式,启用后会使用process处理rejectionHandled 和 uncaughtException事件,    //并捕获一些错误:TypeError,ReferenceError,RangeError,AssertionError,URIError,Error。    strong: false,    //快速解析querystring,多个同名的值会仅设置第一个,不会解析成数组。    fastParseQuery: false,        //是否自动解码Query参数,会调用decodeURIComponent函数。    autoDecodeQuery: true,    //在multipart格式中,限制单个表单项的最大长度。< 

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap