当前位置: 首页 > 后端技术 > Node.js

nodejs篇-手写koa中间件

时间:2023-04-04 00:11:00 Node.js

koa-statickoa-static可以处理静态资源。参数为静态资源文件夹路径。官方实现包括更多的参数配置。具体可以参考koajs/static实现分析:获取请求url路径,找到static文件夹下的路径是否匹配如果路径是文件夹,则寻找文件夹下的index.html文件设置响应头文件类型(mime)gzip压缩,设置压缩类型并返回可读流错误或文件不存在next继续下一个中间件constfs=require("fs")constpath=require("path")constmime=require("mime")constzlib=require("zlib")functionstatic(dir){returnasync(ctx,next)=>{try{letreqUrl=ctx.path让abspath=path.join(dir,reqUrl)letstatObj=fs.statSync(abspath)//如果是文件夹,拼接index.htmlif(statObj.isDirectory()){abspath=path.join(abspath,"index.html")}//判断是否是路径是准确的fs.accessSync(abspath);//设置文件类型ctx.set("Content-Type",mime.getType(abspath)+";charset=utf8")//客户端允许的编码格式,判断是否需要gzip压缩constencoding=ctx.get("accept-encoding")if(/\bgzip\b/.test(encoding)){ctx.设置(“内容编码”,“gzip”)ctx.body=fs.createReadStream(abspath).pipe(zlib.createGzip())}elseif(/\bdeflate\b/.test(encoding)){ctx.set("内容编码","bdeflate")ctx.body=fs.createReadStream(abspath).pipe(zlib.createDeflate())}else{ctx.body=fs.createReadStream(abspath)}}catch(error){awaitnext()}}}module.exports=statickoa-bodyparserkoa-bodyparser可以处理POST请求数据并将form-data数据解析成ctx.request.body,官方地址:koa-bodyparser实现分析:读取请求数据,设置响应头类型application/json,判断客户端请求头content-type类型,解析数据并绑定到ctx.request.bodyfunctionbodyParser(){returnasync(ctx,next)=>{awaitnewPromise((resolve,reject)=>{letdata=[]ctx.req.on("data",(chunk)=>{data.push(chunk)})ctx.req.on("end",()=>{letct=ctx.get("content-type")letbody={}ctx.set("Content-Type","application/json")如果(ct==="application/x-www-form-urlencoded"){body=require("querystring").parse(Buffer.concat(data).toString())}if(ct==="application/json"){body=JSON.parse(Buffer.concat(data).toString())}ctx.request.body=bodyresolve()})ctx.req.on("error",(error)=>{reject(error)})})awaitnext()}}module.exports=bodyParserkoa-routerkoa-router允许koa像express一样控制路由,源码koa-router实现是比较复杂,这里实现一个简单的版本。实现分析:先把request保存到middlewares数组,routes返回middleware函数,从middlewares数组中获取ctx和next过滤掉相同路径/方法的数据中间件处理,传入ctx和封装好的next,调用处理完成后真正的nextmethodclassRouter{constructor(){//保存中间件方法数组this.middlewares=[]}get(path,handler){//get,post,delete,putmust被处理,只在这里实现getthis.middlewares.push({path,handler})}compose(routes,ctx,next){//调度每个存储的中间件方法constdispatch=(index)=>{//执行上下文只有在next处理完成后if(routes.length===index)returnnext();//将中间件的next包裹到下一个执行dispath中,防止context的next方法多次执行routes[index].handler(ctx,()=>dispatch(++index))}dispatch(0)}routes(){returnasync(ctx,next)=>{//过滤掉相同的存储对象letroutes=this.middlewares.filter((item)=>item.path===ctx.url)this.compose(routes,ctx,next)}}}module.exports=Router;koa-better-body使用koa-better-body处理koa-better-body中的文件上传,这里需要保证表单包含multipart/form-data:下面是multipart/form-data通过表单enctype后台提交得到的数据:------WebKitFormBoundaryfCunWPksjjur83I5Content-Disposition:form-data;name="username"chenwl------WebKitFormBoundaryfCunWPksjjur83I5Content-Disposition:form-data;name="password"1234567-----WebKitFormBoundaryfCunWPksjjur83I5Content-Disposition:form-data;名字=“头像”;filename="test.txt"Content-Type:text/plain这里是文件内容------WebKitFormBoundaryfCunWPksjjur83I5--实现分析:获取请求信息,请求头需要有multipart/form-data切分请求信息,提取有用信息包括文件名作为File,写入对应路径提取其他信息保存到ctx.request.fieldsconstfs=require("fs");constpath=require("path");Buffer.prototype.split=函数(sep){让arr=[];让偏移量=0;让len=Buffer.from(sep).length;让current=this.indexOf(sep,offset);while(current!==-1){letdata=this.slice(offset,current)arr.push(data);偏移量=当前+长度;current=this.indexOf(sep,offset)}arr.push(this.slice(offset));returnarr;}module.exports=function({uploadDir}){returnasync(ctx,next)=>{//结果放至req.request.fieldsawaitnewPromise((resolve,reject)=>{letdata=[]ctx.req.on("data",(chunk)=>{data.push(chunk)})ctx.req.on("end",()=>{//multipart/form-data;boundary=----WebKitFormBoundaryvFyQ9QW1McYTqHkpconstcontentType=ctx.get("content-type")if(contentType.includes("multipart/form-data")){constboundary="--"+contentType.split("=")[1];constr=Buffer.concat(data);constarr=r.split(boundary).slice(1,-1);常量字段={};arr.forEach(line=>{let[head,body]=line.split("\r\n\r\n");body=body.slice(0,-2);//取出有效内容head=head.toString();if(head.includes("filename")){//处理文件//请求头长度=总内容长度-头长度-4个换行符长度constfilecontent=line.slice(head.length+4,-2);constfilenanme=head.match(/filename="(.*?)"/)[1]||uid();constuploadPath=path.join(uploadDir,filenanme)fs.writeFileSync(uploadPath,filecontent)}else{fields[head.match(/name="(.*?)"/)[1]]=body.toString();}})ctx.request.fields=fields}resolve()})ctx.req.on("error",(error)=>{reject(error)})})awaitnext()}}functionuid(){返回Math.random().toString(32).slice(2);}