koa源码阅读第四部分涉及向接口请求方提供文件数据。第一篇:koa源码阅读-0第二篇:koa源码阅读-1-koa和koa-compose是从服务端来的,一定不能放过接口阅读的所有权限。各种路径的验证,权限的匹配,都是需要考虑的事情。而koa-send和koa-static就是帮助我们处理这些繁琐事情的中间件。koa-send是koa-static的基础,在npm界面可以看到,koa-send包含在静态依赖中。koa-send主要用来更方便的处理静态文件。与koa-router等中间件不同,它不是直接作为函数注入到app.use中。而是在一些中间件中调用,传入当前请求的Context和文件对应的位置,然后实现功能。koa-send原生文件读取和传输方式的GitHub地址在Node.js中。如果使用原生的fs模块进行文件数据传输,操作大致是这样的:constfs=require('fs')constkoa=require('koa')constRouter=require('koa-router')constapp=newKoa()constrouter=newRouter()constfile='./test.log'constport=12306router.get('/log',ctx=>{constdata=fs.readFileSync(file).toString()ctx.body=data})app.use(router.routes())app.listen(port,()=>console.log(`服务器运行为http://127.0.0.1:${port}`))或者使用createReadStream而不是readFileSync也是可行的。下面会讲到区别。这个简单的例子只对一个文件进行操作,如果我们要读取的文件有很多,甚至可能通过接口参数传递。所以很难保证这个文件一定真的存在,我们可能还需要添加一些权限设置,防止一些敏感文件被接口返回。router.get('/file',ctx=>{const{fileName}=ctx.queryconstpath=path.resolve('./XXX',fileName)//过滤隐藏文件if(path.startsWith('.')){ctx.status=404return}//判断文件是否存在if(!fs.existsSync(path)){ctx.status=404return}//balabalaconstrs=fs.createReadStream(path)ctx.body=rs//koa已经对stream类型做了处理,详见之前的koa文章})加入各种逻辑判断后,读取静态文件就安全多了,但这只是在router中做的处理。如果有多个读取静态文件的接口,必然会有很多重复的逻辑,将其提炼成一个公共函数会是一个不错的选择。koa-send的方式koa-send就是这样做的,提供了一个处理静态文件的中间件,封装的非常完整。下面是两个最基本的用法示例:constpath=require('path')constsend=require('koa-send')//getrouter.get('/file',asyncfor某个路径下的文件ctx=>{awaitsend(ctx,ctx.query.path,{root:path.resolve(__dirname,'./public')})})//Getrouter.get('/index'forafile,asyncctx=>{awaitsend(ctx,'./public/index.log')})假设我们的目录结构是这样的,simple-send.js就是可执行文件:.├──public│├──a.log│├──b.log│└──index.log└──simple-send.js可以通过/file?path=XXX轻松访问public下的文件。并且你可以通过访问/index来获取/public/index.log文件的内容。koa-send提供的函数koa-send提供了很多方便的选项,除了常用的root,大概有十个选项可以使用:optionstypedefaultdescmaxageNumber0设置浏览器可以缓存的毫秒数
对应的Header:Cache-Control:max-age=XXXimmutableBooleanfalse通知浏览器该URL对应的资源是不可变的,可以无限缓存
对应的Header:Cache-Control:max-age=XXX,immutablehiddenBooleanfalse支持隐藏文件Files从
开始。称为隐藏文件。rootString-设置静态文件路径的根目录,禁止访问该目录以外的任何文件。indexString-设置一个默认文件名,在访问目录时生效,自动拼接在路径后面(这里是一个小彩蛋)gzipBooleantrue如果访问接口的客户端支持gzip,和有一个.gz后缀的同名文件下面将把.gz文件brotliBooleantrue传给上面一样的逻辑。如果支持brotli并且有.br后缀的同名文件formatBooleantrue,开启后不会强制在路径末尾添加/。/path和/path/代表一个路径(仅当path是目录时)extensionsArrayfalse如果传入一个数组,它会尝试匹配数组中的所有项作为文件的后缀,并读取它匹配的文件。setHeadersFunction——用来手动指定一些headers,无意义的参数有些参数的具体表现可以达到一些神奇的效果,有些参数会影响Header,有些参数用来优化性能,类似于gzip和brotli选项。koa-send的主要逻辑可以分为这几个部分:路径的有效性检查,gzip等压缩逻辑的应用文件后缀,默认入口文件的匹配。读取文件数据在函数开头有这样的逻辑:constresolvePath=require('resolve-path')const{parse}=require('path')asyncfunctionsend(ctx,path.opts={}){consttrailingSlash=path[path.length-1]==='/'constindex=opts.index//这里省略各种参数的初始值设置path=path.substr(parse(path).root.length)//...//规范化路径path=decode(path)//内部调用的是`decodeURIComponent`//也就是说传入转义路径也可以正常使用if(index&&trailingSlash)path+=indexpath=resolvePath(root,path)//隐藏文件支持,忽略if(!hidden&&isHidden(root,path))return}functionisHidden(root,path){path=path.substr(root.length).split(sep)for(leti=0;i
