共计 3643 个字符,预计需要花费 10 分钟才能阅读完成。
本文主要讲述基于 node 如何实现文件上传和下载,分成原生 node 实现版、中间件实现版。
1、文件上传为什么需要使用 multipart/form-data 编码类型?
The encoding type application/x-www-form-urlencoded is inefficient for sending large quantities of binary data or text containing non-ASCII characters. Thus, a new media type,multipart/form-data, is proposed as a way of efficiently sending the values associated with a filled-out form from client to server.
文档大概意思是说 application/x-www-form-urlencoded 不适合用于传输大型二进制数据和包含非 ASCII 字符的文本,因此提出了一个新的 MIME 类型 multipart/form-data,有效地将与填写的表单相关联的值从客户端发送到服务器。想了解很多 MIME 类型,点击查看
2、文件对象介绍
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<form method="POST" id="uploadForm" enctype="multipart/form-data">
<input type="file" id="file" name="file" />
</form>
<button id="submit">submit</button>
</body>
</html>
<script>
$("#submit").click(function() {console.log($("#file")[0].files[0])
});
</script>
文件(File)接口提供有关文件的信息,并允许网页中的 JavaScript 访问其内容。
File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。比如说,FileReader, URL.createObjectURL(), createImageBitmap(), 及 XMLHttpRequest.send() 都能处理 Blob 和 File。
另外还有一个 blob 对象,也附上一张 chrome 浏览器的截图。blob,二进制大文档存储
两个对象里面都有 size 和 type,按照官方的文档,file 集成了 blob,而且可以使用 slice 方法.
slice 方法可以在当前的 blob 数据中,取出一段数据,作为新的 blob。常用的就是文件断点上传。
3、node 中的 buffer、Stream、fs 模块介绍
JavaScript 语言没有用于读取或处理二进制数据流的机制。
但在处理像 TCP 流或文件流时,必须使用到二进制数据。因此在 Node.js 中,定义了一个 Buffer 类,该类用来创建一个专门存放二进制数据的缓存区。
在 Node.js 中,Buffer 类是随 Node 内核一起发布的核心库。Buffer 库为 Node.js 带来了一种存储原始数据的方法,可以让 Node.js 处理二进制数据,每当需要在 Node.js 中处理 I / O 操作中移动的数据时,就有可能使用 Buffer 库。原始数据存储在 Buffer 类的实例中。一个 Buffer 类似于一个整数数组,但 它对应于 V8 堆内存之外的一块原始内存(buffer 是 C++ 层面分配的,所得内存不在 V8 内)。
const Buffer = require('buffer').Buffer
var buf1 = Buffer.from('tést');
var buf2 = Buffer.from('tést', 'latin1');
var buf3 = Buffer.from([1, 2, 3]);
console.log(buf1, buf2, buf3);
输出的结果为:
<Buffer 74 c3 a9 73 74> <Buffer 74 e9 73 74> <Buffer 01 02 03>
不是说 node 引入 buffer 是来处理二进制数据流吗?怎么转换成 buffer 对象打印出来却不是二进制,而是十六进制呢?
在计算机内使用二进制表示数据,一个存储空间叫做一个 bit,只能存储 0 或是 1。通常,计算机把 8 个 bit 作为一个存储的单位,称为一个 Byte。
于是一个 Byte 可以出现 256 种不同的情况。一个 Buffer 是一段内存,比如大小为 2(Byte)的 buffer,一共有 16 bit,比如是
00000001 00100011
,可是这样显示太不方便。所以显示这段内存的数据的时候,用其对应的 16 进制就比较方便了,是01 23
,之所以用 16 进制是因为转换比较方便。内存仅仅存储的是二进制的数据,但是如何解释就是我们人类自己的事情了。。。。比如
A
在 内存中占用两个 Byte,对应的内存状态是0000000 01000001
,而uint16
(JS 不存在这个类型) 类型的65
对应的存储内存的状态也是这个。如果输出 Buffer 那么 nodejs 输出的是内存实际存储的值 (因为你没有给出如何解释这段内存中的数据),可是二进制显示起来不方便看,所以转换为 16 进制方便人类阅读。
如果转换为数组,那么意思就是把这个 buffer 的每一个字节解释为一个数字(其实是 10 进制数字,这是人类最方便的),所以是 0~255 的 10 进制数字。
总之,这样转化的目的是方便显示和查看。
Stream 是一个抽象接口,Node 中有很多对象实现了这个接口。例如,对 http 服务器发起请求的 request 对象和服务端响应对象 response 就是 Stream,还有 stdout(标准输出)。
你可以把流理解成一种传输的能力。通过流,可以以平缓的方式,无副作用的将数据传输到目的地。Stream 表示的是一种传输能力,Buffer 是传输内容的载体 (可以这样理解,Stream:外卖小哥哥,Buffer:你的外卖)。
在 node 中流无处不在:
流为什么这么好用还这么重要呢?
现在有个需求,我们要向客户端传输一个大文件。每次接收一个请求,就要把这个大文件读入内存,然后再传输给客户端。
const fs = require('fs');
const server = require('http').createServer();
server.on('request', (req, res) => {fs.readFile('./big.file', (err, data) => {if (err) throw err;
res.end(data);
});
});
server.listen(8000);
通过这种方式可能会产生以下三种后果:
- 内存耗尽
- 拖慢其他进程
- 增加垃圾回收器的负载
所以这种方式在传输大文件的情况下,不是一个好的方案。并发量一大,几百个请求过来很容易就将内存耗尽。
如果采用流呢?
const fs = require('fs');
const server = require('http').createServer();
server.on('request', (req, res) => {const src = fs.createReadStream('./big.file');
src.pipe(res);
});
server.listen(8000);
采用这种方式,不会占用太多内存,可以将文件一点一点读到内存中,再一点一点返回给用户,读一部分,写一部分。(利用了 HTTP 协议的 Transfer-Encoding: chunked 分段传输特性),用户体验得到优化,同时对内存的开销明显下降。如果想在传输的过程中,想对文件进行处理,比如压缩、加密等等,也很好扩展。
未完待续。。。
参考文献:
buffer 概述:
http://www.runoob.com/nodejs/nodejs-buffer.html
https://nodejs.org/api/buffer.html#buffer_buffer
buffer 数据显示:
https://segmentfault.com/q/1010000009002065
stream 运行机制:
https://www.php.cn/js-tutorial-412138.html
stream 的理解:
https://www.jianshu.com/p/4eb9077a8956