HTTP文件上傳如何工做?

當我提交帶有附件的簡單表格時: html

<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正文發送? 在此請求的標題中,沒有看到與文件名相關的任何內容。 html5

我只是想知道發送文件時HTTP的內部工做原理。 java


#1樓

它如何在內部發送文件? web

該格式稱爲multipart/form-data ,請參見enctype ='multipart / form-data'是什麼意思? 算法

我要去: 瀏覽器

  • 添加更多HTML5參考
  • 用表單提交示例解釋爲何他是對的

HTML5參考

enctype三種可能性服務器

如何生成示例

一旦您看到每種方法的示例,就會很清楚它們的工做方式以及什麼時候使用每種方法。 app

您可使用如下示例生成示例: socket

將表單保存到最小的.html文件中: ide

<!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>

咱們將默認文本值設置爲a&#x03C9;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 8000 localhost; done

在瀏覽器上打開HTML,選擇文件,而後單擊Submit並檢查終端。

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; ,字段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-form-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

顯然,沒有發送文件數據,僅發送了基名。 所以,這不能用於文件。

對於文本字段,咱們看到一般可打印的字符(例如ab )以一個字節發送,而不可打印的0xCF (例如0xCF0x89 )每一個佔用3個字節%CF%89

比較方式

文件上載一般包含許多不可打印的字符(例如圖像),而文本形式幾乎歷來沒有。

從示例中咱們能夠看到:

  • multipart/form-data :給消息增長了一些邊界開銷字節,而且必須花費一些時間來計算它,可是每一個字節以一個字節發送。

  • application/x-www-form-urlencoded :每一個字段( & )具備單個字節邊界,但爲每一個不可打印字符增長了3倍線性開銷因子。

所以,即便咱們能夠發送帶有application/x-www-form-urlencoded ,咱們也不application/x-www-form-urlencoded ,由於它效率很低。

可是對於在文本字段中找到的可打印字符來講,這可有可無,而且產生的開銷更少,所以咱們只使用它。


#2樓

將文件做爲二進制內容發送(不帶表單或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);

若是您不(不想)使用表單,而只對上傳單個文件感興趣,這是在請求中包括文件的最簡單方法。


#3樓

我有如下示例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);
    }
}

我有這個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>

最後,我將用於測試目的的文件a.dat具備如下內容:

0x39 0x69 0x65

若是您將上述字節解釋爲ASCII或UTF-8字符,則它們實際上將表示:

9ie

所以,讓咱們運行咱們的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--

好吧,我看到字符9ie並不感到驚訝,由於咱們告訴Java將它們打印爲UTF-8字符。 您也能夠選擇將它們讀取爲原始字節。

Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF

其實是這裏的最後一個HTTP標頭。 以後是HTTP正文,能夠在其中實際看到咱們上傳的文件的元數據和內容。


#4樓

HTTP消息可能具備在標頭行以後發送的數據主體。 在響應中,這是將請求的資源返回給客戶端的地方(消息正文的最經常使用用法),若是有錯誤,也多是解釋性文本。 在請求中,將用戶輸入的數據或上載的文件發送到服務器。

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


#5樓

讓咱們看一下選擇文件並提交表單時發生的狀況(爲簡潔起見,我已將標題刪節了):

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標頭的一部分。

詳細信息在這裏

相關文章
相關標籤/搜索