HTTP文件上传如何工作?

0xS*_*ina 484 file-upload 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>
Run Code Online (Sandbox Code Playgroud)

它是如何在内部发送文件的?该文件是作为数据发送的HTTP主体的一部分吗?在此请求的标头中,我没有看到与文件名相关的任何内容.

我只是想知道发送文件时HTTP的内部工作原理.

tod*_*ted 290

让我们来看看当你选择一个文件并提交你的表单时会发生什么(我为了简洁而截断了标题):

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--
Run Code Online (Sandbox Code Playgroud)

表单参数(包括文件数据)不是URL编码表单参数,而是作为请求正文中的多部分文档中的部分发送.

在上面的示例中,您可以看到MAX_FILE_SIZE表单中设置值的输入,以及包含文件数据的部分.文件名是Content-Disposition标题的一部分.

详细信息在这里.

  • 请注意,作为Content-Type标题字段的一部分传递的边界字符串比下面各个部分的边界字符串短2个字符.我花了一个小时试图弄清楚为什么我的上传器不起作用,因为很难注意到第一个边界字符串中实际上只有4个破折号而在其他边界字符串中只有6个破折号.换句话说:当使用边界字符串来分隔单个表单数据时,它必须以两个破折号为前缀: - 当然在RFC1867中有描述,但我认为这里也应该指出 (29认同)
  • 线程解释有点不正确,因为有高性能服务器设计为单线程,并使用状态机快速轮流从连接下载数据包.相反,在TCP/IP中,端口80是侦听端口,而不是传输数据的端口. (9认同)
  • 当IP侦听套接字(端口80)接收到连接时,在另一个端口上创建另一个套接字,通常使用大于1000的随机数.然后将此套接字连接到远程套接字,使端口80可以自由侦听新连接. (9认同)
  • @slebetman首先,这是关于HTTP的.FTP活动模式不适用于此处.其次,每个连接都不会阻止侦听套接字.您可以拥有与一个端口一样多的连接,因为其他端口具有将其自己的端绑定到的端口. (9认同)
  • @ source.rar:没有.Webservers(几乎?)总是线程化,以便它们可以处理并发连接.本质上,正在侦听端口80的守护进程立即将服务任务交给另一个线程/进程,以便它可以返回监听另一个连接; 即使两个传入连接在完全相同的时刻到达,它们也只是坐在网络缓冲区中,直到守护进程准备好读取它们为止. (6认同)
  • @Slotos:我说的是HTTP.不是FTP活动模式.我也不是在谈论端口,我在谈论套接字. (3认同)
  • @Andreas 评论非常非常重要。我花了几个小时才弄清楚如何从 FoxPro 上传文件 :P 直到我发现这是问题所在。也许这个答案可以更新? (3认同)

Cir*_*四事件 254

它是如何在内部发送文件的?

调用格式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&#x03C9;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>
Run Code Online (Sandbox Code Playgroud)

我们将默认文本值设置为a&#x03C9;b,这意味着a?b因为?是UTF-8 U+03C9中的字节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
Run Code Online (Sandbox Code Playgroud)

运行我们的小型echo服务器:

while true; do printf '' | nc -l 8000 localhost; done
Run Code Online (Sandbox Code Playgroud)

在浏览器上打开HTML,选择文件,然后单击"提交"并检查终端.

nc 打印收到的请求.

测试:Ubuntu 14.04.3,ncBSD 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--
Run Code Online (Sandbox Code Playgroud)

对于二进制文件和文本字段,字面61 CF 89 62(a?b以UTF-8为单位)按字面发送.您可以验证,nc -l localhost 8000 | hd表示字节:

61 CF 89 62
Run Code Online (Sandbox Code Playgroud)

被发送(61=='a'和62=='b').

因此很明显:

  • Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266将内容类型设置为multipart/form-data并表示字段由给定boundary字符串分隔.

  • 每场得到它的数据之前,一些子头:Content-Disposition: form-data;,外地namefilename,其次是数据.

    服务器读取数据直到下一个边界字符串.浏览器必须选择不会出现在任何字段中的边界,因此这就是边界可能因请求而异的原因.

    因为我们有唯一的边界,所以不需要对数据进行编码:二进制数据按原样发送.

    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

现在更改enctypeapplication/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
Run Code Online (Sandbox Code Playgroud)

显然,文件数据没有发送,只有基本名称.所以这不能用于文件.

至于文本字段中,我们可以看到,平时打印的字符像ab一个字节被送往,而不可打印的像0xCF,并0x89讨论了3个字节的每个:%CF%89!

对照

文件上传通常包含许多不可打印的字符(例如图像),而文本形式几乎从不这样做.

从我们看到的例子中可以看到:

  • multipart/form-data:向消息添加几个字节的边界开销,并且必须花一些时间计算它,但是在一个字节中发送每个字节.

  • application/x-www-form-urlencoded:每个字段(&)具有单字节边界,但为每个不可打印字符添加3倍线性开销因子.

因此,即使我们可以发送文件application/x-www-form-urlencoded,我们也不愿意,因为它是如此低效.

但是对于在文本字段中找到的可打印字符,它无关紧要并且产生的开销较少,因此我们只使用它.

  • @ianbeks浏览器在发送请求之前自动执行此操作.我不知道它使用了哪种启发式方法,但很可能文件扩展名在其中.这可能会回答这个问题:http://stackoverflow.com/questions/1201945/how-is-mime-type-of-an-uploaded-file-determined-by-browser (3认同)
  • @CiroSantilli六四事件法轮功纳米比亚威视我认为这个答案比选择的要好得多.但请从您的个人资料中删除不相关的内容.这违背了SO的精神. (3认同)
  • 您将如何添加二进制附件?(即小图像) - 我可以看到更改“Content-Disposition”和“Content-Type”属性的值,但如何处理“内容”? (2认同)
  • @smwikipedia 感谢您的 rfc 报价和喜欢这个答案!关于用户名:对我来说,SO 的精神是每个人都应该始终拥有最好的信息。~~ 让我们继续讨论 twitter 或 meta。和平。 (2认同)

Wil*_*ilt 53

将文件作为二进制内容发送(无表格或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);
Run Code Online (Sandbox Code Playgroud)

如果您不(想)使用表单而您只想上传一个文件,这是将您的文件包含在请求中的最简单方法.


Kor*_*gay 8

我有这个示例Java代码:

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
public class TestClass {
    public static void main(String[] args) throws IOException {
        final ServerSocket socket = new ServerSocket(8081);
        final Socket accept = socket.accept();
        final InputStream inputStream = accept.getInputStream();
        final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
        char readChar;
        while ((readChar = (char) inputStreamReader.read()) != -1) {
            System.out.print(readChar);
        }
        inputStream.close();
        accept.close();
        System.exit(1);
    }
}
Run Code Online (Sandbox Code Playgroud)

我有这个test.html文件:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>File Upload!</title>
</head>
<body>
<form method="post" action="http://localhost:8081" enctype="multipart/form-data">
    <input type="file" name="file" id="file">
    <input type="submit">
</form>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

最后我将用于测试目的的文件名为a.dat,其内容如下:

0x39 0x69 0x65
Run Code Online (Sandbox Code Playgroud)

如果您将上面的字节解释为ASCII或UTF-8字符,它们实际上将代表:

9ie
Run Code Online (Sandbox Code Playgroud)

让我们运行我们的Java代码,在我们最喜欢的浏览器中打开test.html,上传a.dat并提交表单,看看我们的服务器收到了什么:

POST / HTTP/1.1
Host: localhost:8081
Connection: keep-alive
Content-Length: 196
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary06f6g54NVbSieT6y
DNT: 1
Accept-Encoding: gzip, deflate
Accept-Language: en,en-US;q=0.8,tr;q=0.6
Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF

------WebKitFormBoundary06f6g54NVbSieT6y
Content-Disposition: form-data; name="file"; filename="a.dat"
Content-Type: application/octet-stream

9ie
------WebKitFormBoundary06f6g54NVbSieT6y--
Run Code Online (Sandbox Code Playgroud)

好吧,我看到字符9ie并不奇怪,因为我们告诉Java打印它们将它们视为UTF-8字符.您也可以选择将它们作为原始字节读取..

Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF 
Run Code Online (Sandbox Code Playgroud)

实际上是这里的最后一个HTTP标头.之后是HTTP Body,其中可以看到我们上传的文件的元和内容.


fla*_*g19 6

HTTP消息可能在标题行之后发送了一组数据.在响应中,这是将请求的资源返回给客户端的地方(消息体的最常见用法),或者如果出现错误则可能是解释性文本.在请求中,这是用户输入的数据或上载的文件发送到服务器的位置.

http://www.tutorialspoint.com/http/http_messages.htm