-
Notifications
You must be signed in to change notification settings - Fork 20
Description
上传文件
获取文件句柄的方式:
- type为file的input表单,
<input type="file" id="input">,这个元素可以通过files属性获取文件句柄,如果需要上传多个文件需要添加属性multiple - 拖拽文件上传,可以通过
event.dataTransfer.files
上传文件方式:
- 通过传统的方式,读取文件内容然后附加在http请求体里
- 借助第三方库上传文件,比如LeanCloud提供的JS插件
上传文件步骤
1. 浏览器端的处理
有两种方式,一种是格式化表单(form),另一种是二进制流。
发送表单的请求有两种常用表头:
Content-Type: application/x-www-form-urlencodedContent-Type: multipart/form-data; boundary=--------------------------XXXXXXXXXXXXXXXXX
格式化表单也有几种方式,常见的有formElement.submit(),还有使用新的对象FormData。所以获取FormData对象的方法大致有:
new FormData()和formData.append(key, value)new FormData(formElement)formElement.getFormData()
而我们从type="file"的input获取的file对象,可以读取到的信息有:name、size、type。
然后把这个FormData对象放到Ajax(axios)或者Fetch里传送到服务器。
用multipart方式传递数据到服务器会有多余的数据,比如:
----------------------------484347809064590577699464
Content-Disposition: form-data; name="myfile"; filename="hello.txt"
Content-Type: text/plain
hello world!
hello world!
----------------------------484347809064590577699464
Content-Disposition: form-data; name="oneKey"
1
----------------------------484347809064590577699464
Content-Disposition: form-data; name="twoKey"
2
----------------------------484347809064590577699464--
可以看到,在MIME类型为multipart/form-data的报文中,为了区分各字段,会有boundary作为边界,如果我们仅仅是为了上传一个文件到服务器,那么使用multipart会有一些劣势。这时候使用二进制方式上传文件是比较妥当的。而且现在浏览器提供了特别方便的接口,一般上传一个文件的过程如下:
var inputElement = document.getElementById("input");
inputElement.addEventListener("change", handleFiles, false);
function handleFiles() {
var fileList = this.files; /* now you can work with the file list */
var xhr = new XMLHttpRequest();
// deal with xhr
xhr.send(fileList[0])
}监听type="file"的input元素的change事件,并获取文件句柄(fileList[0]),最后传递给XMLHttpRequest对象的send方法,就可以把二进制流传递给服务器了。接下来就到服务器处理了。
2. 服务器端的处理
如果请求头是 Content-Type: application/x-www-form-urlencoded,那么只需要使用NodeJS的querystring解析就好。
如果请求头是 Content-Type: multipart/form-data; boundary=--------------------------XXXXXXXXXXXXXXXXX。那么我们就需要借助Buffer接口来获取数据字节了。
常用方式:
const bufferArr = [];
let bufferLength = 0;
req.on("data", function(chunk) {
bufferLength += chunk.length;
bufferArr.push(chunk);
});
req.on("end", function() {
const datagramBuffer = Buffer.concat(bufferArr, bufferLength);
// do with datagramBuffer
});multipart的数据包还需要进一步解析才能方便使用,有很多开源工具已经帮我们处理,比如Express的bodyParser中间件。
如果发送过来的是二进制流,比如Content-Type: image/jpeg、Content-Type: video/mp4,我们可以用上面的方式获取datagramBuffer,它就是二进制流在NodeJS的表示,我们使用内置库fs和buffer接口就可以完成持久化工作。
上传大文件
前面提到文件句柄有三个属性,其中一个是size,我们可以通过它来限制上传文件的大小。
当然,有时候我们也有上传大文件的需求。
说一个行业潜规则,就是无论是什么情况,都要对请求体的大小做限制,一来是节省系统资源,二来防止黑客攻击。比如nginx就提供了限制请求体大小的参数:client_max_body_size。
那我们就没有办法上传大文件了么?别忘了我们算法中经常提到分而治之。
当然了这个方案需要前后端一起合作完成,比如前端先把大文件的一部分传递给服务器,并要求服务器在持久化到磁盘的时候做个标记——一般是文件名,再次传递另一部分数据到服务器,服务器可以数据添加到之前文件的末尾,NodeJS就提供了一个API:fs.appendFile(path, data[, options], callback)。
当然了这里只是提供了一个大致的思路,实现过程还是会有一些细节需要完善的。
参考
Using files from web applications
LeanCloud上传文件
Node.js HTTP服务器中不依赖第三方模块的文件、图片上传
FileReader
fs.appendFile