一个 HTML 表单中的 enctype 有三种类型
- application/x-www-urlencoded
- multipart/form-data
- text-plain
默认情况下是 application/x-www-urlencoded
,当表单使用 POST 请求时,数据会被以 x-www-urlencoded 方式编码到 Body 中来传送,
而如果 GET 请求,则是附在 url 链接后面来发送。
GET 请求只支持 ASCII 字符集,因此,如果我们要发送更大字符集的内容,我们应使用 POST 请求。
注意
"application/x-www-form-urlencoded"
编码的格式是 ASCII,如果 form 中传递的是二进制等 Media Type 类型的数据,那么 application/x-www-form-urlencoded
会把其编码转换成 ASCII 类型。对于 1 个 non-ASCII 字符,它需要用 3 个 ASCII字符来表示,如果要发送大量的二进制数据(non-ASCII),"application/x-www-form-urlencoded"
显然是低效的。因此,这种情况下,应该使用 "multipart/form-data"
格式。
The content type "application/x-www-form-urlencoded" is inefficient for sending large quantities of binary data or text containing non-ASCII characters. The content type "multipart/form-data" should be used for submitting forms that contain files, non-ASCII data, and binary data.
application/x-www-urlencoded
我们在通过 HTTP 向服务器发送 POST 请求提交数据,都是通过 form 表单形式提交的,代码如下:
<FORM method="post" action="http://w.sohu.com" >
<INPUT type="text" name="txt1">
<INPUT type="text" name="txt2">
</FORM>
提交时会向服务器端发出这样的数据(已经去除部分不相关的头信息),数据如下:
POST / HTTP/1.1
Content-Type:application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Host: w.sohu.com
Content-Length: 21
Connection: Keep-Alive
Cache-Control: no-cache
txt1=hello&txt2=world
对于普通的 HTML Form POST请求,它会在头信息里使用 Content-Length
注明内容长度。
请求头信息每行一条,空行之后便是 Body,即“内容”(entity)。内容的格式是在头信息中的 Content-Type 指定的,如上是 application/x-www-form-urlencoded
,这意味着消息内容会经过 URL 格式编码,就像在 GET请 求时 URL 里的 QueryString 那样。txt1=hello&txt2=world
multipart/form-data
multipart/form-data
定义在 rfc2388 中,最早的 HTTP POST 是不支持文件上传的,给编程开发带来很多问题。但是在1995年,ietf 出台了 rfc1867,也就是《RFC 1867 -Form-based File Upload in HTML》,用以支持文件上传。所以 Content-Type 的类型扩充了multipart/form-data 用以支持向服务器发送二进制数据。因此,发送 POST 请求时候,表单 <form> 属性 enctype 共有二个值可选,这个属性管理的是表单的 MIME 编码:
① application/x-www-form-urlencoded (默认值)
② multipart/form-data
注:form 表单中 enctype 的默认值是 enctype="application/x- www-form-urlencoded"
.
通过 form 表单提交文件操作如下:
<FORM method="POST" action="http://w.sohu.com/t2/upload.do" enctype="multipart/form-data">
<INPUT type="text" name="city" value="Santa colo">
<INPUT type="text" name="desc">
<INPUT type="file" name="pic">
</FORM>
浏览器将会发送以下数据:
POST /t2/upload.do HTTP/1.1
User-Agent: SOHUWapRebot
Accept-Language: zh-cn,zh;q=0.5
Accept-Charset: GBK,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Content-Length: 60408
Content-Type:multipart/form-data; boundary=ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Host: w.sohu.com
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data; name="city"
Santa colo
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data;name="desc"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
...
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC
Content-Disposition: form-data;name="pic"; filename="photo.jpg"
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
... binary data of the jpg ...
--ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC--
从上面的 multipart/form-data
格式发送的请求的样式来看,它包含了多个 Parts,每个 Part 都包含头信息部分,
Part 头信息中必须包含一个 Content-Disposition
头,其他的头信息则为可选项, 比如 Content-Type
等。
Content-Disposition
包含了 type 和 一个名字为 name 的 parameter,type 是 form-data,name 参数的值则为表单控件(也即 field)的名字,如果是文件,那么还有一个 filename 参数,值就是文件名。
比如:
Content-Disposition: form-data; name="user"; filename="hello.txt"
上面的 "user" 就是表单中的控件的名字,后面的参数 filename 则是点选的文件名。
对于可选的 Content-Type(如果没有的话),默认就是 text/plain
。
注意:
如果文件内容是通过填充表单来获得,那么上传的时候,Content-Type 会被自动设置(识别)成相应的格式,如果没法识别,那么就会被设置成 "application/octet-stream"
如果多个文件被填充成单个表单项,那么它们的请求格式则会是 multipart/mixed。
如果 Part 的内容跟默认的 encoding 方式不同,那么会有一个 "content-transfer-encoding"
头信息来指定。
下面,我们填充两个文件到一个表单项中,行程的请求信息如下:
Content-Type: multipart/form-data; boundary=AaB03x
--AaB03x
Content-Disposition: form-data; name="submit-name"
Larry
--AaB03x
Content-Disposition: form-data; name="files"
Content-Type: multipart/mixed; boundary=BbC04y
--BbC04y
Content-Disposition: file; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...
--BbC04y
Content-Disposition: file; filename="file2.gif"
Content-Type: image/gif
Content-Transfer-Encoding: binary
...contents of file2.gif...
--BbC04y--
--AaB03x--
Boundary 分隔符
每个部分使用 --boundary
分割开来,最后一行使用 --boundary--
结尾。
实验
To see exactly what is happening, use nc -l and an user agent like a browser or cURL.
Save the form to an .html file:
<FORM action="http://localhost:8000" method="post" enctype="multipart/form-data">
<p><INPUT type="text" name="text" value="text default">
<p><INPUT type="file" name="file1">
<p><INPUT type="file" name="file2">
<p><BUTTON type="submit">Submit</BUTTON>
</FORM>
Create files to upload:
echo 'Content of a.txt.' > a.txt
echo '<!DOCTYPE html><title>Content of a.html.</title>' > a.html
Run:
nc -l localhost 8000
Open the HTML on your browser, select the files and click on submit and check the terminal.
nc prints the request received. Firefox sent:
POST / HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:29.0) Gecko/20100101 Firefox/29.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Cookie: __atuvc=34%7C7; permanent=0; _gitlab_session=226ad8a0be43681acf38c2fab9497240; __profilin=p%3Dt; request_method=GET
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
Content-Length: 554
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="text"
text default
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain
Content of a.txt.
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html
<!DOCTYPE html><title>Content of a.html.</title>
-----------------------------9051914041544843365972754266--
Aternativelly, cURL should send the same POST request as your a browser form:
nc -l localhost 8000
curl -F "text=default" -F "file1=@a.html" -F "file1=@a.txt" localhost:8000
You can do multiple tests with:
while true; do printf '' | nc -l localhost 8000; done
参考:
https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4
https://stackoverflow.com/questions/4526273/what-does-enctype-multipart-form-data-mean/28380690#28380690
https://tools.ietf.org/html/rfc2388
https://stackoverflow.com/questions/913626/what-should-a-multipart-http-request-with-multiple-files-look-like