Skip to content

上传文件 #109

@coconilu

Description

@coconilu

上传文件

获取文件句柄的方式:

  1. type为file的input表单,<input type="file" id="input">,这个元素可以通过files属性获取文件句柄,如果需要上传多个文件需要添加属性multiple
  2. 拖拽文件上传,可以通过event.dataTransfer.files

上传文件方式:

  1. 通过传统的方式,读取文件内容然后附加在http请求体里
  2. 借助第三方库上传文件,比如LeanCloud提供的JS插件

上传文件步骤

1. 浏览器端的处理

有两种方式,一种是格式化表单(form),另一种是二进制流。

发送表单的请求有两种常用表头:

  1. Content-Type: application/x-www-form-urlencoded
  2. Content-Type: multipart/form-data; boundary=--------------------------XXXXXXXXXXXXXXXXX

格式化表单也有几种方式,常见的有formElement.submit(),还有使用新的对象FormData。所以获取FormData对象的方法大致有:

  1. new FormData()formData.append(key, value)
  2. new FormData(formElement)
  3. 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/jpegContent-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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions