在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:titbit开源软件地址:https://gitee.com/daoio/titbit开源软件介绍:titbit
因为github无法正常显示图片,并且github服务访问太慢以及其他问题,建议使用gitee(码云)查看文档。 Wiki中有相关主题的说明:Wiki Node.js的Web开发框架,同时支持HTTP/1.1和HTTP/2协议, 提供了强大的中间件机制。 核心功能:
!注意请尽可能使用最新版本。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参数和表单数据
'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-typeapplication/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默认情况只返回第一个文件,因为多数情况只是一个上传名对应一个文件。
{ "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完整的流程图示
中间件参数使用use或者pre接口添加中间件,还支持第二个参数,可以进行精确的控制,传递选项属性:
示例: 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格式中,限制单个表单项的最大长度。< |
请发表评论