它是如何在内部发送文件的?
该格式称为
multipart/form-data
,如所问:
enctype='multipart/form-data'的含义是什么?
我将:
- 添加更多HTML5参考
- 解释为什么他使用表单提交示例是正确的
HTML5参考
enctype
有 三种可能性:
如何生成示例
一旦您看到每种方法的示例,就会明显它们的工作原理以及何时应该使用每一种方法。
您可以使用以下方式生成示例:
将表单保存为一个简化的.html
文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>upload</title>
</head>
<body>
<form action="http://localhost:8000" method="post" enctype="multipart/form-data">
<p><input type="text" name="text1" value="text default">
<p><input type="text" name="text2" value="aωb">
<p><input type="file" name="file1">
<p><input type="file" name="file2">
<p><input type="file" name="file3">
<p><button type="submit">Submit</button>
</form>
</body>
</html>
我们将默认文本值设置为
aωb
,这意味着
aωb
,因为
ω
是
U+03C9
,在UTF-8中是
61 CF 89 62
字节。
创建要上传的文件:
echo 'Content of a.txt.' > a.txt
echo '<!DOCTYPE html><title>Content of a.html.</title>' > a.html
# Binary file containing 4 bytes: 'a', 1, 2 and 'b'.
printf 'a\xCF\x89b' > binary
运行我们的小回声服务器:
while true; do printf '' | nc -l localhost 8000; done
在浏览器中打开HTML,选择文件并单击提交,然后检查终端。
nc
打印接收到的请求。
测试环境:Ubuntu 14.04.3,nc
BSD 1.105,Firefox 40。
multipart/form-data
Firefox 发送:
POST / HTTP/1.1
[[ Less interesting headers ... ]]
Content-Type: multipart/form-data
Content-Length: 834
-----------------------------735323031399963166993862150
Content-Disposition: form-data
text default
-----------------------------735323031399963166993862150
Content-Disposition: form-data
aωb
-----------------------------735323031399963166993862150
Content-Disposition: form-data
Content-Type: text/plain
Content of a.txt.
-----------------------------735323031399963166993862150
Content-Disposition: form-data
Content-Type: text/html
<!DOCTYPE html><title>Content of a.html.</title>
-----------------------------735323031399963166993862150
Content-Disposition: form-data
Content-Type: application/octet-stream
aωb
-----------------------------735323031399963166993862150--
对于二进制文件和文本字段,字节61 CF 89 62
(在UTF-8中为aωb
)会被直接发送。您可以使用nc -l localhost 8000 | hd
进行验证,该命令会显示以下字节:
61 CF 89 62
被发送的内容为 (61
== 'a' and 62
== 'b')。
因此很明显:
Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150
将内容类型设置为 multipart/form-data
并指定字段由给定的 boundary
字符串分隔。
但请注意:
boundary=---------------------------735323031399963166993862150
比实际障碍物少两个破折号 --
-----------------------------735323031399963166993862150
这是因为标准要求边界以两个破折号 --
开始。其他破折号似乎只是 Firefox 选择实现任意边界的方式。RFC 7578 明确提到需要这两个前导破折号 --
:
4.1. multipart/form-data的"Boundary"参数
与其他multipart类型一样,使用CRLF、"--"和"boundary"参数的值构造边界分隔符来分隔各个部分。
每个字段在其数据之前都会有一些子标题:
Content-Disposition: form-data;
,字段
name
,
filename
,然后是数据。
服务器读取数据直到下一个边界字符串。浏览器必须选择一个不会出现在任何字段中的边界,因此边界可能因请求而异。
由于我们有唯一的边界,因此不需要对数据进行编码:二进制数据按原样发送。
待办事项:最佳边界大小(我打赌是
log(N)
),以及找到它的算法的名称/运行时间? 在
https://cs.stackexchange.com/questions/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences上提问。
Content-Type
由浏览器自动确定。
如何确定确切地被问到了:
How is mime type of an uploaded file determined by browser?。
应用程序/x-www-form-urlencoded
现在将enctype
更改为application/x-www-form-urlencoded
,重新加载浏览器并重新提交。
Firefox发送了:
POST / HTTP/1.1
[[ Less interesting headers ... ]]
Content-Type: application/x-www-form-urlencoded
Content-Length: 51
text1=text+default&text2=a%CF%89b&file1=a.txt&file2=a.html&file3=binary
很明显,文件数据没有被发送,只有基本名称。因此,这不能用于文件。
至于文本字段,我们可以看到通常的可打印字符如a
和b
在一个字节中发送,而不可打印的字符如0xCF
和0x89
每个字符占用了3个字节:%CF%89
!
比较
文件上传通常包含大量的非打印字符(例如图像),而文本表单几乎从不包含。
从我们所见的例子中,我们可以得出以下结论:
因此,即使我们可以使用
application/x-www-form-urlencoded
发送文件,我们也不希望这样做,因为这样会非常低效。
但对于在文本字段中找到的可打印字符,它并不重要且生成的开销较小,因此我们只需使用它。