文件上传相关知识储备

279 阅读4分钟

POST提交数据方式

POST请求必须要设置请求头,如Content-type: x-www-form-urlencoded

1. x-www-form-urlencoded

enctype默认属性值,将数据键值对化,如username=123&password=123

使用js中URLencode转码方法。包括将name、value中的空格替换为加号;将非ascii字符做百分号编码;将input的name、value用‘=’连接,不同的input之间用‘&’连接。

百分号编码什么意思呢?比如汉字‘丁’,他的utf8编码在十六进制下是0xE4B881,占3个字节,把它转成字符串‘E4B881’,变成了六个字节,每两个字节前加上百分号前缀,得到字符串“%E4%B8%81”,变成九个ascii字符,占九个字节(十六进制下是0x244534254238253831)。把这九个字节拼接到数据包里,这样就可以传输“非ascii字符的  utf8编码的 十六进制表示的 字符串的

2. multipart/form-data

对于一段utf8编码的字节,用application/x-www-form-urlencoded传输其中的ascii字符没有问题,但对于非ascii字符传输效率就很低了(汉字‘丁’从三字节变成了九字节),因此在传很长的字节时应用multipart/form-data格式。

如果是文件,使用 x-www-form-urlencoded 格式,只提交了文件名,找不到文本数据只能找标识,不可能上传成功

所以文件不能用文本的形式上传,只能用二进制

<form action="..." method="post" enctype="multipart/form-data">
    <input type="text" name="filename" />
    <input type="file" name="file" /> // 成功
    <input type="submit" value="上传" />
</form>

点击上传即可提交表单

3. application/json

上传JSON格式,比较友好,便于提交复杂的结构化数据

4. text/xml

POST http://www.example.com HTTP/1.1
Content-Type: text/xml
<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value><i4>41</i4></value>
</param>
</params>
</methodCall>

文件上传

因为浏览器本身的限制,浏览器是不能直接操作文件系统的,需要通过浏览器所暴露出来的统一接口,由用户主动授权发起来访问文件动作,然后读取文件内容进指定内存里,最后执行提交请求操作,将内存里的文件内容数据上传到服务端

FormData

FormData可以看做是一个应用数据的场景,使用FormData我们可以异步上传一个二进制文件,即Blob对象

XMLHttpRequest Level2添加了一个新的接口FormData. 利用FormData对象,我们可以通过JavaScript用一些键值对来模拟一系列表单控件,我们还可以使用XMLHttpRequest的send()方法来异步的提交这个"表单".比起普通的ajax,使用FormData的最大优点就是我们可以异步上传一个二进制文件.

上面利用了form表单的能力来上传本地文件,但是由于form表单提交操作网页会造成整体刷新,所以一般比较少用,而是利用熟悉的异步请求操作AJAX来完成上传动作

例:Vue中的二次确认上传实例,需要使用FormData进行数据传递,并且content-type要为multipart/form-data

uploadFile() {
    let file = new FormData();
    file.append("file", this.fileList[0]);
    if (this.uploadType === "report") {
      file.append("the_month", formatTimeToStr(this.importDate, "yyyy-MM"));
    }

    axios({
      method: "post",
      url: this.uploadUrl,
      headers: {
        "content-type": "multipart/form-data;charset=UTF-8",
        "x-token": this.token
      },
      data: file
    }).then(res => {

    });
}

请求头

Content-Type: multipart/form-data; boundary=------WebKitFormBoundary7YGEQ1Wf4VuKd0cE	

请求体中

  • 内容用WebKitFormBoundary码进行分割
  • 第二行的ContentDisposition,该行包含一些文件基本信息,
  • 还有第三行文件内容类型,所以后端如果要获取到正确的文件内容则需要自己去除由浏览器在上传时所添加的进来的几行内容,而保留有效文件内容后进行写文件操作,完成上传目的。
------WebKitFormBoundary7YGEQ1Wf4VuKd0cE	
Content-Disposition: form-data; name="file"; filename="index.html"	
Content-Type: text/html	
 
<文件内容省略>

------WebKitFormBoundary7YGEQ1Wf4VuKd0cE--

可以看出一下几点:

  1. 前端文件上传实际是文件内容的传递,是数据的传递,并非我们最常使用的文件拷贝与复制操作。

  2. 传递过程中要使用编码来制定发送的文件数据规则,以便于后端能够实现一套对应的解析规则。

  3. 传递的数据规则里包含所传递文件的基本信息 ,如文件名与文件类型,以便后端写出正确格式的文件。

参考文章