HTTP文件上传如何工作?
当我提交一个附带文件的简单表单时:
<form enctype="multipart/form-data" action="http://localhost:3000/upload?upload_progress_id=12344" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
Choose a file to upload: <input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
它如何在内部发送文件? 该文件是否作为数据的HTTP身体的一部分发送? 在这个请求的头文件中,我没有看到与文件名相关的任何内容。
我只想知道发送文件时HTTP的内部工作原理。
让我们来看看当你选择一个文件并提交你的表单时(为了简洁起见,我已经截断了标题):
POST /upload?upload_progress_id=12344 HTTP/1.1
Host: localhost:3000
Content-Length: 1325
Origin: http://localhost:3000
... other headers ...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryePkpFF7tjBAqx29L
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="MAX_FILE_SIZE"
100000
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="uploadedfile"; filename="hello.o"
Content-Type: application/x-object
... contents of file goes here ...
------WebKitFormBoundaryePkpFF7tjBAqx29L--
表单参数(包括文件数据)不是URL编码表单参数,而是作为请求正文中的多部分文档中的部分发送。
在上面的示例中,您可以看到输入MAX_FILE_SIZE
,其中包含表单中MAX_FILE_SIZE
的值以及包含文件数据的部分。 文件名称是Content-Disposition
标题的一部分。
完整的细节在这里。
它如何在内部发送文件?
这个格式被称为multipart/form-data
,问题在于:enctype ='multipart / form-data'是什么意思?
我要去:
HTML5参考
enctype
有三种可能性:
x-www-urlencoded
multipart/form-data
(spec指向RFC2388) text-plain
。 这是“不可靠的电脑解释”,所以它不应该用于生产,我们不会进一步考虑。 如何生成示例
一旦你看到每种方法的例子,就会明白它们是如何工作的以及何时应该使用每一种方法。
您可以使用以下示例生成示例
nc -l
或ECHO服务器 将表单保存为最小的.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 'axCFx89b' > binary
运行我们的小回声服务器:
while true; do printf '' | nc -l 8000 localhost; done
在浏览器中打开HTML,选择文件并点击提交并检查终端。
nc
打印收到的请求。
测试:Ubuntu 14.04.3, nc
BSD 1.105,Firefox 40。
多部分/格式数据
Firefox发送:
POST / HTTP/1.1
[[ Less interesting headers ... ]]
Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150
Content-Length: 834
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="text1"
text default
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="text2"
aωb
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain
Content of a.txt.
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html
<!DOCTYPE html><title>Content of a.html.</title>
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file3"; filename="binary"
Content-Type: application/octet-stream
aωb
-----------------------------735323031399963166993862150--
对于二进制文件和文本字段,字节61 CF 89 62
(UTF-8中的aωb
)以字面形式发送。 你可以使用nc -l localhost 8000 | hd
来验证 nc -l localhost 8000 | hd
,它说,字节:
61 CF 89 62
被发送( 61
=='a'和62
=='b')。
因此很明显:
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
将内容类型设置为multipart/form-data
并表示字段由给定的boundary
字符串。
每个字段在其数据之前都会获得一些子标题: Content-Disposition: form-data;
,字段name
, filename
,后面跟着数据。
服务器读取数据直到下一个边界字符串。 浏览器必须选择不会出现在任何字段中的边界,所以这就是为什么边界可能因请求而不同。
由于我们有独特的边界,因此不需要编码数据:二进制数据按原样发送。
TODO:什么是最佳边界大小( 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
由浏览器自动确定。
如何确定它的确切位置:如何通过浏览器确定上传文件的MIME类型?
应用程序/ x-WWW窗体-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
这样a
通常的可打印字符以一个字节发送,而不可打印的0xCF
如0xCF
和0x89
分别占用3个字节 : %CF%89
!
对照
文件上传通常包含很多不可打印的字符(例如图像),而文本表单几乎从不做。
从我们看到的例子来看:
multipart/form-data
:将几个字节的边界开销添加到消息中,并且必须花费一些时间计算它,但将每个字节发送一个字节。
application/x-www-form-urlencoded
:每个字段( &
)具有单个字节边界,但为每个不可打印字符添加3x的线性开销因子。
因此,即使我们可以发送带有application/x-www-form-urlencoded
,我们也不想这样做,因为它效率太低。
但是对于在文本字段中找到的可打印字符,它无关紧要并且生成的开销较小,因此我们只是使用它。
将文件作为二进制内容发送(无需Form或FormData即可上传)
在给定的答案/例子中,文件(最有可能)是用HTML表单或使用FormData API上传的。 该文件只是请求中发送的数据的一部分,因此是multipart/form-data
Content-Type
标头。
如果要将文件作为唯一内容发送,则可以直接将其添加为请求正文,并将Content-Type
标题设置为要发送的文件的MIME类型。 文件名可以添加到Content-Disposition
标题中。 你可以像这样上传:
var xmlHttpRequest = new XMLHttpRequest();
var file = ...file handle...
var fileName = ...file name...
var target = ...target...
var mimeType = ...mime type...
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.send(file);
如果您没有(想要)使用表单,并且您只想上传一个文件,这是将文件包含在请求中的最简单方法。
链接地址: http://www.djcxy.com/p/7091.html上一篇: How does HTTP file upload work?
下一篇: Mixing REST API plural and singular for different resources?