# titbit-toolkit **Repository Path**: daoio/titbit-toolkit ## Basic Information - **Project Name**: titbit-toolkit - **Description**: Node.js环境的Web服务端框架titbit扩展集合。包括跨域(cors)、反向代理、负载均衡、请求参数检测、toFile、请求计时统计、cookie、session、realip、referer、sni、静态资源处理、jwt等。 - **Primary Language**: JavaScript - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 1 - **Created**: 2020-10-26 - **Last Updated**: 2025-07-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Titbit框架工具集 Titbit Web框架的扩展,主要用于提升开发效率,避免重复工作。 主要包括:跨域、静态资源处理、反向代理、解析代理模式真实IP、计时统计测试、cookie、session、参数自动检测、http2限流、SSE等。 目前所有扩展组件都是中间件形式,初始化后运行mid()返回中间件,所以通用的使用形式如下: ``` JavaScript const {Timing} = require('titbit-toolkit') let t = new Timing() app.use( t.mid() ) ``` **从titbit v22.2.1开始,支持加载具有mid属性或者middleware属性作为中间件,要求:** - mid是一个普通函数,运行此函数要返回一个真正的中间件函数。 - middleware则应该是一个完整的中间件函数,会自动进行this绑定(箭头函数无法绑定this)。 **titbit会先检测mid属性,不满足条件才会检测middleware,但是如果mid的返回值不满足条件会抛出错误。** 所以从titbit v22.2.1之后,可以直接使用以下方式加载: ```javascript const titbit = require('titbit') const {ToFile} = require('titbit-toolkit') const app = new titbit() app.use( new ToFile() ) ``` 一些扩展因为要做的工作比较多,会提供init方法,这时候,通常是以这样的方式启用扩展: ```javascript //t是扩展模块的实例。 t.init(app) ``` 这种方式要看具体扩展的文档描述。 ## 导出示例 ``` JavaScript const {timing,resource,tofile} = require('titbit-toolkit') ``` ## timing(耗时统计) 计时中间件,默认会计算GET、POST、PUT、DELETE请求类型的耗时,并在test选项为true时,输出每个请求的平均耗时和当前时间消耗。主要用于测试和统计。 ```javascript 'use strict' const { timing } = require('titbit-toolkit') const Titbit = require('titbit') const app = new Titbit({ debug: true }) let tlog = new timing({ //控制台输出测试统计情况 test: true, //记录到日志文件 logfile: '/tmp/timing.log' }) app.pre(tlog) /* 其他添加路由处理的代码。 */ app.run(1234) ``` ## cookie和session 这两个扩展是为了测试和教学使用而设计的,cookie组件运行后会在请求上下文中添加cookie属性是一个对象保存了cookie值。session基于cookie实现,利用文件保存数据,**但是这两个扩展不建议用在生产环境**,你应该使用更好的方案来进行会话处理,比如自己生成token或者是利用jwt。 使用: ``` JavaScript const Titbit = require('titbit') const {Cookie, Session} = require('titbit-toolkit') const app = new Titbit({ debug: true }) let sess = new Session() //初始化:给请求上下文添加原型方法 sess.init(app) app.use( new Cookie() ).use( sess ) app.get('/', async ctx => { console.log(ctx.cookie) }) //测试添加session的操作 app.get('/:key/:data', async ctx => { ctx.setSession(ctx.param.key, ctx.param.data) ctx.send( ctx.getSession() ) }) app.run(1234) ``` 启用cookie扩展之后,在请求上下文的ctx对象上,可以通过ctx.cookie拿到解析后cookie对象。 启用session后,在ctx上可以调用: - ctx.setSession(key, data) 设置session。 - ctx.getSession(key = null) 获取session数据,key默认为null,此时获取所有session数据。 - ctx.delSession(key) 删除对应key值的session。 - ctx.clearSession() 清理所有session数据。 ## resource(静态资源处理) 静态资源处理,主要用于js、css、图片、音频、短视频的处理,最主要的还是用于站点的js、css、图片等数据量不大的静态资源。 对于大视频来说,需要分片处理,会比较麻烦。 使用: ``` JavaScript let st = new resource({ //设定静态资源所在目录 staticPath: './public' }) //只对分组为static执行中间件。 app.use(st, {group: 'static'}) //添加静态资源路由 app.get('/static/*', async c => { //请求分组为static }, '@static') /** * 比如目录public存在css/a.css * 在之前的示例中,请求/static/css/a.css即可获取资源。 * / ``` 快速示例: ``` JavaScript let st = new resource({ //设定静态资源所在目录 staticPath: './public', //默认就是/static/*,添加的路由,前端必须以/static/ 开头,后面是./public目录下的文件路径。 routePath : '/static/*', routeGroup: '_static', //默认不会把路径进行base64解码,所以要支持对中文路径的识别,需要开启此选项。 decodePath: true, // 前缀路径,默认为空字符串。 // 如果设置为xyz,会自动修正为/xyz。 //prepath : '', //最大缓存文件大小,超过此大小则不会缓存 maxFileSize: 12_000_000, //设置消息头cache-control的值,默认为null表示不发送消息头cache-control cacheControl: 'max-age=3600' }) st.init(app) //添加到_static分组,之后,在public目录中的存在favicon.ico文件, //通过此请求既可以获取到。 //浏览器会自动发起此请求获取tab标签的图标。 app.get('/favicon.ico', async c => {}, {group: '_static'}) ``` #### 其他选项 - maxCacheSize 默认值 120_000_000,用于设置最大的缓存,单位是字节。 - failedLimit 默认值50,用于设置最大失败缓存的次数,超过这个次数,则会决定是否释放缓存。 - prob 默认值6,表示当超过failedLimit设置的次数,则会以这个概率决定是否释放缓存,取值范围是1~100闭区间。 #### 相关接口 - addType(obj) 添加扩展名到content-type的映射关系,扩展已经内置了常用的静态资源类型的映射表。 - clearCache() 清理缓存。 #### 关于prepath选项 有时候你会面临这样的需求,静态资源目录为public,但是里面不是所有的资源都是静态资源,而且是以目录独立存放的。比如,对于前端来说,只需要引入/static/css/a.css即可,但是后台默认会读取/public/x/css/a.css文件返回数据,此时的prepath为x,某一时刻,配置更改prepath为y,这时候前端引入资源的路径不变,而服务端,Node.js无需重启,各个对象无需重新实例化,只需要更改prepath配置即可实现资源所在目录的更改。 此功能有一个很重要的应用,就是为自动切换前端应用主题资源提供支持。 ## tofile(上传文件) 按照面向对象的风格保存上传的文件: ``` JavaScript let {tofile} = require('titbit-toolkit') app.use( new tofile() ) app.post('/upload', async c => { let f = c.getFile('image') if (!f) { return c.status(400).send('image file not found') } //把文件移动到images目录,此目录可能需要手动创建。 //可以使用第二个参数指定文件名,默认会根据时间戳和随机数生成唯一文件名。 let fname = await f.toFile('./images') //返回上传的文件名 c.res.body = fname }) ``` ## cors(跨域) 跨域支持: ``` JavaScript let {cors} = require('titbit-toolkit') let cr = new cors({ //默认为*表示全部开启跨域, //若要指定要支持的域名则需要传递一个数组。 //注意:这里指定的是用于访问接口的应用页面所在域。 allow : [ 'https://a.com', 'https://www.a.com', //... ], //默认只有content-type allowHeaders : 'authorization,content-type', //OPTIONS请求缓存60秒,此期间浏览器请求会先去读取缓存。 optionsCache: 60, //默认为'*',可以设置允许的请求方法。 //requestHeaders : '*' //默认为'',表示哪些消息头可以暴露给请求端,多个消息头使用 , 分隔。 //exposeHeaders: 'x-test-key,x-type' }) app.pre(cr) //支持OPTIONS请求,因为浏览器在处理一些POST和PUT请求以及DELETE请求时会先发送OPTIONS预检请求。 //如果没有OPTIONS请求,则跨域支持不完整。 //在titbit-loader 22.0.1+版本开始,支持默认自动添加OPTIONS路由。 app.options('/*', async c => {}) ``` ### 关于跨域的其他选项和说明 在真实开发测试中,面对的问题比较复杂,有的应用在跨域过程中没有提交origin消息头,比如小程序就是。有的referer和origin都没有,还有很多工具寄希望设置referer为空绕过检测。而且直接浏览器访问就是没有referer的。 cors扩展尽管是对跨域的场景,但是在具备origin消息头的跨域通信和referer资源引入场景都做了支持,并且你可以灵活选择和配置。 **完整配置示例** ```javascript let cr = new cors({ //不允许referer为空 allowEmptyReferer: false, //允许referer为空的路由分组, //分组名称是自定义的,参考titbit框架的路由分组功能。 //此功能主要用于在API开发中还要兼顾页面的服务上。 emptyRefererGroup: [ '@webapp', '@pages' ], allow: [ //默认的字符串形式表示origin跨域和referer外链接引用都被允许 'https://w.a.com', 'https://w.x.cn', {url: 'https://servicewechat.com/wx234hiohr23', referer: true}, //不应用于referer检测,就是说如果有请求提交了referer为self-define不会通过检测 {url: 'self-define', referer: false} ], }) ``` ## realip(解析真实IP) 在代理模式下,获取真实的客户端IP地址,常见的比如使用nginx作为反向代理,或者使用node自身作为代理,这时候后端的服务是代理服务请求并转发的,获取的IP地址永远都是代理服务的,而不是真实的客户端IP地址。 这时候,代理服务为了能够让后端的服务知道真实的IP地址,会在请求转发时,设置消息头,常见的两个消息头: ``` x-real-ip x-forwarded-for ``` Nginx通常使用x-real-ip,x-forwarded-for最早在Squid软件中使用,你可以在自己的应用中使用其他消息头,这并不是什么标准,只是使用的多而已。 > 因为使用的多了,x-forwarded-for被写入了RFC7239标准。 realip默认会检测两个消息头,顺序为:x-real-ip,x-forwarded-for。只要有一个消息头被发现,就不再继续检测,而是直接使用发现的值。 如果两个消息头都没有设置则会跳过。 使用: ``` JavaScript app.use( (new realip()).mid() ) app.get('/*', async c => { c.send(c.ip) }) app.run(1234) ``` 多级代理,如果使用了多级代理,这时候根据配置不同,有可能,会从第一层代理开始,每个后续的代理都把自己的看到的客户端IP地址追加到消息头的后面,并使用 , 分隔。 此时,扩展检测到消息头信息中存在 , 则会切分成数组,第一个元素则为最开始用户请求的IP地址,并把此值赋值给请求上下文的ip属性。整个数组会挂载到c.box.realip。 ``` JavaScript app.use( (new realip()).mid() ) app.get('/*', async c => { let rip = { ip : c.ip, realip : c.box.realip || 'null' } c.send(rip) }) app.run(1234) ``` ## proxy(反向代理) 反向代理服务,支持负载均衡。因为是基于titbit框架的一个扩展组件,还可以配合其他中间件完成任何你需要的复杂功能。 此扩展优势在于可控度高,和Node集成更好,可以使用titbit加上其他各种扩展完成更复杂的功能。 如果你希望在代理服务之前还需要其他验证和检测处理,可以在开启代理之前,再使用自己编写的中间件处理,这是非常灵活的,或者你仅仅是想有一个简单的代理服务。 ---- 关于性能:其性能比Nginx略有差异,但是也足够快,能够满足大部分需求。实际测试上,更改调度策略不使用rr,而是采用none,则并发处理能力会大大提升。因为默认的,node.js的cluster模块使用master进程转发套接字的方式,而设置为none则让每个子进程去抢占。 如果你需要配合更加复杂的业务逻辑,而且后台的web服务都是基于Node.js,那么整体优势反而更大。 ---- **此扩展仅支持HTTP/1.1协议,如果需要HTTP/2协议的反向代理,可使用gohttp扩展提供的模块http2proxy,具体参考gohttp文档。** ``` JavaScript 'use strict' const titbit = require('titbit') const {proxy} = require('titbit-toolkit') let hostcfg = { //会自动转换为数组的形式,默认path为 / 'a.com' : 'http://localhost:8001', //会自动转换为数组的形式 'b.com' : { path : '/xyz', url : 'http://localhost:8002' }, //标准形式 'c.com' : [ { path : '/name', url : 'http://localhost:8003' }, { path : '/', url : 'http://localhost:8004', rewrite: (ctx, path) => { //自定义重写路由规则 if (path.indexOf('/xyz') === 0) { return path.replace('/xyz', '/w') } } } ] }; const app = new titbit({ debug: true }) const pxy = new proxy({ timeout: 10000, config: hostcfg, connectOptions: { //连接选项,参考http.request文档 } }) pxy.init(app) //在Linux上,必须以root身份运行才能监听80端口。 app.daemon(80, 2) ``` 这个时候,要访问真正运行在8001端口的后端服务,可以按照以下形式: ``` http://a.com/ ``` 访问运行在8002端口的服务: ``` http://b.com/xyz/ ``` ### 负载均衡处理 首先,使用代理作为负载均衡,对代理服务的配置要求是比较高的,对带宽的要求也很高,这样才可以支撑业务,属于应用层负载均衡。 实际场景下,负载均衡往往是综合利用多种方案实现的。无论如何,这个扩展也实现了一个比较完整的负载均衡支持: - 多域名、多路径负载均衡。 - 定时检测后端服务是否存活。 - 通过权重值控制不同后端的服务器配置获取不同的流量。 使用proxy扩展开启负载均衡非常简单,只需要配置多个相同host和路径作为一个数组即可: ``` JavaScript let load_balance_cfg = { 'a.com' : [ { path : '/', url : 'http://localhost:1234', //每3秒钟检测服务是否运行 aliveCheckInterval : 3, //检测服务运行的路径,根据请求状况和返回状态码来动态设定此服务是否运行。 //实际上,此路径在后端服务有没有都可以,即使返回404也说明服务是存在的。 aliveCheckPath : '/alive-check', //表示权重,必须是整数,数字越大权重越高。 //默认为1。 weight: 3 }, { path : '/', url : 'http://localhost:1235', aliveCheckInterval : 3, aliveCheckPath : '/ok', weight: 2 }, { path : '/', url : 'http://localhost:1236', aliveCheckInterval : 2, //此超时会覆盖整体配置的超时 timeout : 5000 }, ] } const app = new titbit({ debug: true }) const pxy = new proxy({ //针对每一项设置的超时会覆盖此值,这样每一项都可以有自己的超时,默认则使用全局超时设定。 timeout: 10000, //如果开启此选项,会把代理路径后面的字符串作为路径转发,否则会转发整个路径 starPath : true, config: load_balance_cfg }) pxy.init(app) app.daemon(1234, 2) ``` **注意:不同的路径是不作为负载均衡项的。** **关于weight** 这个值用于控制不同配置的机器获取对应的请求量,用于平衡不同配置的差异,高配置的可以使用高一些的权重值,默认都为1,一旦检测到有大于1的值,则表示开启权重计算功能,而默认的,则仅仅是简单的轮转。 weight无法做到十分精确的控制,可以维持一个大概的比例,请求越多越密集,分配比例越准确,而在并发不高时,任何一个后端服务处理都可以。 **path属性和顺序** 如果在数组中,同一个host下的多个路径,/开头是会覆盖其他路径的,这时候如果要同时启用 /xyz 和 / ,则需要把path为 /xyz的配置放在前面。 ## http2反向代理:Http2Proxy ```javascript 'use strict' const Titbit = require('titbit') const {Http2Proxy} = require('titbit-toolkit') const app = new Titbit({ debug: true, http2: true, key : './rsa/localhost.key', cert : './rsa/localhost.cert', timeout: 30000, logType: 'stdio', //globalLog: true }) let hxy = new Http2Proxy({ config: { 'a.com' : [ { url: 'http://localhost:3001', weight: 10, path : '/', reconnDelay: 200, max: 2, headers: { 'x-test-key': `${Date.now()}-${Math.random()}` }, connectTimeout: 2000 }, { url: 'http://localhost:3002', weight: 4, path : '/', max: 2, reconnDelay: 100, headers: { 'x-test-key2': `${Date.now()}-${Math.random()}` } } ] }, debug: true }) hxy.init(app) app.run(args.port) ``` http2的反向代理参数基本和proxy扩展一致,只是保活检测是利用http2的连接超时之后的自动重连机制,不必进行定时器请求检测。 ## mixlogger(混合日志) 混合日志支持,此扩展可以自定义日志处理函数,并在返回false时,直接返回,这可以实现对日志记录的过滤操作。这个扩展对日志的处理是和titbit默认的日志处理共存的,先处理此扩展设定的函数。 > 全局日志的设计是经过大量实践验证的,不要认为中间件的方式更好,在Node.js中,如果你要启用cluster,则很多问题凸显,titbit框架已经解决了这个问题,无论是否使用cluster,全局日志都能让你更轻松的记录请求。并且,在框架层面,你仍然可以使用中间件做更精细的日志处理。 ### 使用 ```javascript const titbit = require('titbit') const {mixlogger} = require('titbit-toolkit') const app = new titbit({ debug: true, //日志输出到终端 logType: 'stdio', globalLog: true, }) let mlog = new mixlogger({ logHandle: (w, msg, handle) => { //不记录204状态码的请求 if (msg.status == 204) { return false } return true } }) mlog.init(app) //使用一个worker处理请求 app.daemon(1234, 1) ``` ## sendtype 针对html、css、js、text、xml、json、javascript几种文本类型做了快速返回处理,你不需要再通过c.setHeader设置消息头content-type。 ### 使用 ```javascript const {sendtype} = require('titbit-toolkit') const app = new titbit() app.use( (new sendtype).mid() ) app.get('/', async c => { c.sendhtml(`