热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

koa源码浏览[3]koasend与它的衍生(static)

koa源码浏览的第四篇,涉及到向接口请求方供应文件数据。第一篇:koa源码浏览-0第二篇:koa源码浏览-1-koa与koa-compose第三篇:koa源码浏览-2-koa-ro

koa源码浏览的第四篇,涉及到向接口请求方供应文件数据。

第一篇:
koa源码浏览-0

第二篇:
koa源码浏览-1-koa与koa-compose

第三篇:
koa源码浏览-2-koa-router

处置惩罚静态文件是一个烦琐的事变,由于静态文件都是来自于服务器上,肯定不能摊开一切权限让接口来读取。
种种途径的校验,权限的婚配,都是须要考虑到的处所。
koa-sendkoa-static就是协助我们处置惩罚这些烦琐事变的中间件。
koa-sendkoa-static的基本,可以在NPM的界面上看到,staticdependencies中包含了koa-send

《koa源码浏览[3]-koa-send与它的衍生(static)》

koa-send主假如用于更随意马虎的处置惩罚静态文件,与koa-router之类的中间件差别的是,它并非直接作为一个函数注入到app.use中的。
而是在某些中间件中举行挪用,传入当前请求的Context及文件对应的位置,然后完成功用。

koa-send的GitHub地点

原生的文件读取、传输体式格局

Node中,假如运用原生的fs模块举行文件数据传输,大抵是如许的操纵:

const fs = require('fs')
const Koa = require('koa')
const Router = require('koa-router')
const app = new Koa()
const router = new Router()
const file = './test.log'
const port = 12306
router.get('/log', ctx => {
const data = fs.readFileSync(file).toString()
ctx.body = data
})
app.use(router.routes())
app.listen(port, () => console.log(`Server run as http://127.0.0.1:${port}`))

或许用createReadStream替代readFileSync也是可行的,区分会在下边提到

这个简朴的示例仅针对一个文件举行操纵,而假如我们要读取的文件是有许多个,甚至于多是经由过程接口参数通报过来的。
所以很难保证这个文件肯定是实在存在的,而且我们可以还须要增添一些权限设置,防备一些敏感文件被接口返回。

router.get('/file', ctx => {
const { fileName } = ctx.query
const path = path.resolve('./XXX', fileName)
// 过滤隐蔽文件
if (path.startsWith('.')) {
ctx.status = 404
return
}
// 推断文件是不是存在
if (!fs.existsSync(path)) {
ctx.status = 404
return
}
// balabala
const rs = fs.createReadStream(path)
ctx.body = rs // koa做了针对stream范例的处置惩罚,概况可以看之前的koa篇
})

增添了种种逻辑推断今后,读取静态文件就变得平安不少,然则这也只是在一个router中做的处置惩罚。
假如有多个接口都邑举行静态文件的读取,势必会存在大批的反复逻辑,所以将其提炼为一个大众函数将是一个很好的挑选。

koa-send的体式格局

这就是koa-send做的事变了,供应了一个封装非常完美的处置惩罚静态文件的中间件。
这里是两个最基本的运用例子:

const path = require('path')
const send = require('koa-send')
// 针对某个途径下的文件猎取
router.get('/file', async ctx => {
await send(ctx, ctx.query.path, {
root: path.resolve(__dirname, './public')
})
})
// 针对某个文件的猎取
router.get('/index', async ctx => {
await send(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以外,另有也许小十个的选项可供运用:

optionstypedefaultdesc
maxageNumber0设置浏览器可以缓存的毫秒数
对应的Header: Cache-Control: max-age=XXX
immutableBooleanfalse关照浏览器该URL对应的资本不可变,可以无限期的缓存
对应的Header: Cache-Control: max-age=XXX, immutable
hiddenBooleanfalse是不是支撑隐蔽文件的读取
.开首的文件被称为隐蔽文件
rootString–设置静态文件途径的根目次,任何该目次以外的文件都是制止接见的。
indexString–设置一个默许的文件名,在接见目次的时刻见效,会自动拼接到途径后边 (此处有一个小彩蛋)
gzipBooleantrue假如接见接口的客户端支撑gzip,而且存在.gz后缀的同名文件的情况下会通报.gz文件
brotliBooleantrue逻辑同上,假如支撑brotli且存在.br后缀的同名文件
formatBooleantrue开启今后不会强请求途径末端的//path/path/示意的是一个途径 (仅在path是一个目次的情况下见效)
extensionsArrayfalse假如通报了一个数组,会尝试将数组中的一切item作为文件的后缀举行婚配,婚配到哪一个就读取哪一个文件
setHeadersFunction–用来手动指定一些Headers,意义不大

参数们的具体表现

有些参数的搭配可以完成一些奇异的效果,有一些参数会影响到Header,也有一些参数是用来优化机能的,相似gzipbrotli的选项。

koa-send的重要逻辑可以分为这几块:

  1. path途径有用性的搜检
  2. gzip等紧缩逻辑的运用
  3. 文件后缀、默许进口文件的婚配
  4. 读取文件数据

在函数的开首部份有如许的逻辑:

const resolvePath = require('resolve-path')
const {
parse
} = require('path')
async function send (ctx, path. opts = {}) {
const trailingSlash = path[path.length - 1] === '/'
const index = opts.index
// 此处省略种种参数的初始值设置
path = path.substr(parse(path).root.length)
// ...
// normalize path
path = decode(path) // 内部挪用的是`decodeURIComponent`
// 也就是说传入一个转义的途径也是可以一般运用的
if (index && trailingSlash) path += index
path = resolvePath(root, path)
// hidden file support, ignore
if (!hidden && isHidden(root, path)) return
}
function isHidden (root, path) {
path = path.substr(root.length).split(sep)
for (let i = 0; i if (path[i][0] === '.') return true
}
return false
}

途径搜检

起首是推断传入的path是不是为一个目次,_(末端为/会被以为是一个目次)_。
假如是目次,而且存在一个有用的index参数,则会将index拼接到path后边。
也就是也许如许的操纵:

send(ctx, './public/', {
index: 'index.js'
})
// ./public/index.js

resolve-path 是一个用来处置惩罚途径的包,用来协助过滤一些非常的途径,相似path//file/etc/XXX 如许的歹意途径,而且会返回处置惩罚后绝对途径。

isHidden用来推断是不是须要过滤隐蔽文件。
由于但通常.开首的文件都邑被以为隐蔽文件,同理目次运用.开首也会被以为是隐蔽的,所以就有了isHidden函数的完成。

实在我个人以为这个运用一个正则就可以处理的题目。。为何还要分割为数组呢?

function isHidden (root, path) {
path = path.substr(root.length)
return new RegExp(`${sep}\\.`).test(path)
}

已给社区提交了PR

紧缩的开启与文件夹的处置惩罚

在上边的这一坨代码实行完今后,我们就获得了一个有用的途径,_(假如是无效途径,resolvePath会直接抛出非常)_
接下来做的事变就是搜检是不是有可用的紧缩文件运用,此处没有什么逻辑,就是简朴的exists操纵,以及Content-Encoding的修正 _(用于开启紧缩)_。

后缀的婚配:

if (extensions && !/\.[^/]*$/.exec(path)) {
const list = [].concat(extensions)
for (let i = 0; i let ext = list[i]
if (typeof ext !== 'string') {
throw new TypeError('option extensions must be array of strings or false')
}
if (!/^\./.exec(ext)) ext = '.' + ext
if (await fs.exists(path + ext)) {
path = path + ext
break
}
}
}

可以看到这里的遍历是完整根据我们挪用send是传入的递次来走的,而且还做了.标记的兼容。
也就是说如许的挪用都是有用的:

await send(ctx, 'path', {
extensions: ['.js', 'ts', '.tsx']
})

假如在增添了后缀今后可以婚配到实在的文件,那末就以为这是一个有用的途径,然后举行了break的操纵,也就是文档中所说的:First found is served.

在终了这部份操纵今后会举行目次的检测,推断当前途径是不是为一个目次:

let stats
try {
stats = await fs.stat(path)
if (stats.isDirectory()) {
if (format && index) {
path += '/' + index
stats = await fs.stat(path)
} else {
return
}
}
} catch (err) {
const notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR']
if (notfound.includes(err.code)) {
throw createError(404, err)
}
err.status = 500
throw err
}

一个小彩蛋

可以发明一个很有意义的事变,假如发明当前途径是一个目次今后,而且明白指定了format,那末还会再尝试拼接一次index
这就是上边所说的谁人彩蛋了,当我们的public途径构造长得像如许的时刻:

└── public
   └── index
      └── index # 现实的文件 hello

我们可以经由过程一个简朴的体式格局猎取到最底层的文件数据:

router.get('/surprises', async ctx => {
await send(ctx, '/', {
root: './public',
index: 'index'
})
})
// > curl http://127.0.0.1:12306/surprises
// hello

这里就用到了上边的几个逻辑处置惩罚,起首是trailingSlash的推断,假如以/末端会拼接index,以及假如当前path婚配为是一个目次今后,又会拼接一次index
所以一个简朴的/加上index的参数就可以直接猎取到/index/index
一个小小的彩蛋,现实开辟中应当很少会这么玩

终究的读取文件操纵

末了终究来到了文件读取的逻辑处置惩罚,起首就是挪用setHeaders的操纵。

由于经由上边的层层挑选,这里拿到的path和你挪用send时传入的path不是同一个途径。
不过倒也没有必要必须在setHeaders函数中举行处置惩罚,由于可以看到在函数终了时,将现实的path返回了出来。
我们完整可以在send实行终了后再举行设置,至于官方readme中所写的and doing it after is too late because the headers are already sent.
这个不须要忧郁,由于koa的返回数据都是放到ctx.body中的,而body的剖析是在一切的中间件悉数实行完今后才会举行处置惩罚。
也就是说一切的中间件都实行完今后才会最先发送http请求体,在此之前设置Header都是有用的。

if (setHeaders) setHeaders(ctx.res, path, stats)
// stream
ctx.set('Content-Length', stats.size)
if (!ctx.response.get('Last-Modified')) ctx.set('Last-Modified', stats.mtime.toUTCString())
if (!ctx.response.get('Cache-Control')) {
const directives = ['max-age=' + (maxage / 1000 | 0)]
if (immutable) {
directives.push('immutable')
}
ctx.set('Cache-Control', directives.join(','))
}
if (!ctx.type) ctx.type = type(path, encodingExt) // 接口返回的数据范例,默许会掏出文件后缀
ctx.body = fs.createReadStream(path)
return path

以及包含上边的maxageimmutable都是在这里见效的,然则要注意的是,假如Cache-Control已存在值了,koa-send是不会去掩盖的。

运用Stream与运用readFile的区分

在末了给body赋值的位置可以看到,是运用的Stream而并非是readFile,运用Stream举行传输能带来最少两个优点:

  1. 第一种体式格局,假如是大文件,在读取完成后会暂时存放到内存中,而且toString是有长度限定的,假如是一个庞大的文件,toString挪用会抛出非常的。
  2. 采纳第一种体式格局举行读取文件,是要在悉数的数据都读取完成后再返回给接口挪用方,在读取数据的时期,接口都是处于Wait的状况,没有任何数据返回。

可以做一个相似如许的Demo:

const http = require('http')
const fs = require('fs')
const filePath = './test.log'
http.createServer((req, res) => {
if (req.url === '/') {
res.end('')
} else if (req.url === '/sync') {
const data = fs.readFileSync(filePath).toString()
res.end(data)
} else if (req.url === '/pipe') {
const rs = fs.createReadStream(filePath)
rs.pipe(res)
} else {
res.end('404')
}
}).listen(12306, () => console.log('server run as http://127.0.0.1:12306'))

起首接见首页http://127.0.0.1:12306/进入一个空的页面 _(主假如懒得搞CORS了)_,然后在控制台挪用两个fetch就可以获得如许的对照效果了:

《koa源码浏览[3]-koa-send与它的衍生(static)》
《koa源码浏览[3]-koa-send与它的衍生(static)》

可以看出在下行传输的时候相差无几的同时,运用readFileSync的体式格局会增添肯定时候的Waiting,而这个时候就是服务器在举行文件的读取,时候是非取决于读取的文件大小,以及机械的机能。

koa-static

koa-static是一个基于koa-send的浅封装。
由于经由过程上边的实例也可以看到,send要领须要自身在中间件中挪用才行。
手动指定send对应的path之类的参数,这些也是属于反复性的操纵,所以koa-static将这些逻辑举行了一次封装。
让我们可以经由过程直接注册一个中间件来完成静态文件的处置惩罚,而不再须要体贴参数的读取之类的题目:

const Koa = require('koa')
const app = new Koa()
app.use(require('koa-static')(root, opts))

opts是透传到koa-send中的,只不过会运用第一个参数root来掩盖opts中的root
而且增添了一些细节化的操纵:

  • 默许增添一个index.html

if (opts.index !== false) opts.index = opts.index || 'index.html'

  • 默许只针对HEADGET两种METHOD

if (ctx.method === 'HEAD' || ctx.method === 'GET') {
// ...
}

  • 增添一个defer选项来决议是不是先实行其他中间件。

假如deferfalse,则会先实行send,优先婚配静态文件。
不然则会比及其他中间件先实行,肯定其他中间件没有处置惩罚该请求才会去寻觅对应的静态资本。
只需指定root,剩下的事情交给koa-static,我们就无需体贴静态资本应当怎样处置惩罚了。

小结

koa-sendkoa-static算是两个非常轻量级的中间件了。
自身没有太庞杂的逻辑,就是一些反复的逻辑被提炼成的中间件。
不过确切可以削减许多一样平常开辟中的任务量,可以让人更专注的关注营业,而非这些边边角角的功用。


推荐阅读
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
author-avatar
奶爸集丶训营_502
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有