文件上传


概述

浏览器中文件上传,基本有两种方案,一种是表单上传,一种是通过ajax上传

一、表单上传

三要素

表单也就是form元素,如果要上传文件,必须满足上传三要素:

  1. method属性设置成post
  2. 文件上传的选项的type必须为file
  3. enctype属性必须为multipart/form-data

对于纯粹的表单上传,这三要素是必须的,原因看我下面的实验例子

例子

我的上传表单是这样:

我会对 name 为 info 的输入框填入test
而文件我选择了test.txt,该文件的内容是一段话:hello word

满足三要素

表单保持例子原状,当我点击上传后,浏览器会把这个表单整合成一个 HTTP 报文,然后发送给相应的 action路径,比如上述的会生成如下 HTTP 请求报文(我已经删除了多余的请求头信息,只保留了重点的一行Content-Type):

POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary51R26gaG9hSSCrHF

----WebKitFormBoundary51R26gaG9hSSCrHF
Content-Disposition: form-data; name="info"

test
----WebKitFormBoundary51R26gaG9hSSCrHF
Content-Disposition: form-data; name="upload"; filename="test.txt"
Content-Type: text/plain

hello word
----WebKitFormBoundary51R26gaG9hSSCrHF-

注意该报文是浏览器根据 from 的enctype 属性自动生成的,不需要人控制。
解析报文中的Content-Type

multipart/form-data是编码格式,boundary 是分割线格式,主要用途就是为了请求体的分割

所以为什么需要设置 POST 请求,原因也在此,只有表单设置为 POST ,浏览器才会将表单数据放入请求体。而设置了 enctype 为multipart/form-data,浏览器才会对 HTTP 请求报文进行相应的操作。

不满足三要素

接下来的阐述都只针对于 form 的 method 和 enctype 进行阐述。

method 为 GET 请求

将例子的表单,method 设置成 GET 请求。
那么浏览器会将表单数据存放在报文的请求行,而不是请求体。
所以不管表单的 enctype 设置成什么,是完全影响不到请求行的,所以 GET 请求的请求行始终不变,报文永远为下面的样子(同样删除多余的请求头信息):

GET /upload?info=test&upload=test.txt HTTP/1.1
Content-Type: application/x-www-form-urlencoded


POST 请求

将例子的表单属性 enctype 设置成其它属性,比如不设置(不设置那么 enctype 属性默认值为:"application/x-www-form-urlencoded"),则 POST 请求报文为:

POST /upload HTTP/1.1
Content-Type: application/x-www-form-urlencoded

info=test&upload=test.txt

会发现,upload 的值是个字符串,也就是test.txt,一个字符串,和 GET 请求一样的。

二、ajax上传

XHR2定义了全新的FormData对象,能够进行multipart/form-data编码的http请求。因此,这种方式上传文件,相当于使用表单上传文件。
这种请求方式现在很流行,一搜索基本都是教程,我就不讲了。

总结

表单上传,为什么要遵守三要素,因为浏览器默默帮你做了很多事情。除非 js 手段,否则必须准守这三个规则。

此外,并不是说 GET 方法不能上传,但是你得用js编码完成许多额外的工作,比如设置请求报文的请求头的Conten-Ttpe,自己完成对请求体的编码。
但是呢,用 js 操作,js 发送请求,这就属于ajax的范畴了。
而ajax上传,也有合适的解决方案,也就是通过FormData上传。
如果你很倔强,又或者有什么特殊的需求,也可以通过 GET 请求实现上传,也不是不可能,只是说约定如此,就像 RESTful 风格的请求。

对了,在stackoverflow上也早有对这个能否通过 GET 请求上传的探讨,链接在这:
File uploading using GET Method:As we all know, file uploading is most often accomplished using POST method. So, why can't the GET method be used for file uploads instead? Is there a specific prohibition against HTTP GET uploads?