HTTPファイルのアップロードはどのように機能しますか?


528

ファイルが添付された次のような簡単なフォームを送信すると:

<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の内部動作を知りたいだけです。


私はしばらくの間スニファを使用していませんが、リクエストで何が送信されているかを確認したい場合は(サーバーに対するものであるため、リクエストであるため)スニッフィングします。この質問は広すぎます。SOは、特定のプログラミングの質問に適しています。
パラッツォ

...スニファーが行くように、フィドラーは私の選択の武器です。独自のテストリクエストを作成して、それらがどのように送信されるかを確認することもできます。
Phil Cooper

興味のある方は、stackoverflow.comMAX_FILE_SIZE
q / 1381364/632951の

MAX_FILE_SIZEが変です。私はそれを投稿する前にクロームで私のhtmlを100000000に変更できるので、より良い値を投稿します。1.ソルトを介した安全なハッシュを使用してCookie内にあるので、Cookieが変更された場合、サーバーは検証して例外(Webpiecesやplayframeworkの両方の場合と同様)または何らかの変更がないフォーム検証をスローできます。@ 0xSina
Dean Hiller

回答:


320

ファイルを選択してフォームを送信するとどうなるか見てみましょう(簡潔にするためにヘッダーは省略しています)。

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

注:--最後の境界文字列の最後と同じように、各境界文字列の前に追加のを付ける必要があります。上記の例にはすでにこれが含まれていますが、見落としがちです。以下の@Andreasによるコメントを参照してください。

フォームパラメータをURLエンコードする代わりに、フォームパラメータ(ファイルデータを含む)は、リクエストの本文のマルチパートドキュメントのセクションとして送信されます。

上記の例MAX_FILE_SIZEでは、フォームに値が設定された入力と、ファイルデータを含むセクションを確認できます。ファイル名はContent-Dispositionヘッダーの一部です。

詳細はこちら


7
@ source.rar:いいえ。Webサーバーは(ほぼ?)常にスレッド化されているため、同時接続を処理できます。基本的に、ポート80でリッスンしているデーモンプロセスは、別の接続のリッスンに戻ることができるように、別のスレッド/プロセスにサービスを提供するタスクをすぐに渡します。2つの着信接続がまったく同じ瞬間に到着した場合でも、デーモンがそれらを読み取る準備ができるまで、それらはネットワークバッファにとどまります。
eggyal 2014

10
シングルスレッドとして設計され、状態マシンを使用して接続からデータのパケットをすばやく順番にダウンロードする高性能サーバーがあるため、スレッドの説明は少し間違っています。むしろ、TCP / IPでは、ポート80はリスニングポートであり、データが転送されるポートではありません。
slebetman、2014年

9
IPリスニングソケット(ポート80)が接続を受信すると、別のソケットが別のポートに作成され、通常は1000を超える乱数が生成されます。このソケットはリモートソケットに接続され、ポート80を解放して新しい接続をリッスンします。
slebetman 2014年

11
@slebetmanまず、これはHTTPについてです。FTPアクティブモードはここでは適用されません。次に、リスニングソケットがすべての接続でブロックされるわけではありません。1つのポートに接続できるのは、反対側にポートをバインドするポートがあるためです。
Slotos 2014年

33
Content-Typeヘッダーフィールドの一部として渡される境界文字列は、以下の個々の部分の境界文字列よりも2文字短いことに注意してください。最初の境界文字列にダッシュが4つしかなく、他の境界文字列にダッシュが6つあることに気づくことが非常に難しいため、アップローダーが機能しない理由を理解するために1時間費やしました。言い換えると、個々のフォームデータを区切るために境界文字列を使用する場合、2つのダッシュを前に付ける必要があります。-もちろんRFC1867に記載されていますが、ここでも指摘する必要があります
Andreas

279

内部でどのようにファイルを送信しますか?

形式はと呼ばれmultipart/form-dataenctype = '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>

デフォルトのテキスト値をに設定します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

小さなエコーサーバーを実行します。

while true; do printf '' | nc -l 8000 localhost; done

ブラウザでHTMLを開き、ファイルを選択して、送信をクリックし、ターミナルを確認します。

nc 受け取ったリクエストを出力します。

テスト済み:Ubuntu 14.04.3、ncBSD 1.105 、Firefox 40。

multipart / form-data

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 62aωbUTF-8)は文字通り送信されます。あなたはそれをnc -l localhost 8000 | hdバイトで確認することができます:

61 CF 89 62

送信されました(61== 'a'および62== 'b')。

したがって、次のことが明らかです。

  • Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150コンテンツタイプをに設定multipart/form-dataし、フィールドが指定されたboundary文字列で区切られていることを伝えます。

    ただし、次のことに注意してください。

    boundary=---------------------------735323031399963166993862150
    

    --実際のバリアより2つ少ないダッシュがあります

    -----------------------------735323031399963166993862150
    

    これは、規格では境界が2つのダッシュで始まる必要があるため--です。他のダッシュは、Firefoxが任意の境界を実装するために選択した方法のようです。RFC 7578は、これらの2つの先行ダッシュ--が必要であることを明確に述べています。

    4.1。multipart / form-dataの「境界」パラメーター

    他のマルチパートタイプと同様に、パーツは、CRLF、「-」、および「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タイプは、ブラウザによってどのように決定されますか?

application / 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

明らかにファイルデータは送信されず、ベース名のみが送信されました。したがって、これはファイルには使用できません。

テキストフィールドのように、我々は次のようにその通常の印刷可能な文字を見るaと、b非印刷可能なもののような一方で、1バイトで送信された0xCF0x89取り上げた3バイトごと:%CF%89

比較

多くの場合、ファイルのアップロードには印刷できない文字(画像など)がたくさん含まれていますが、テキストフォームにはほとんど含まれていません。

例から、次のことがわかりました。

  • multipart/form-data:数バイトの境界オーバーヘッドをメッセージに追加し、メッセージの計算にある程度の時間を費やす必要がありますが、各バイトを1バイトで送信します。

  • application/x-www-form-urlencoded:フィールドごとに1バイト境界(&)がありますが、印刷できない文字ごとに3倍の線形オーバーヘッド係数が追加されます。

したがって、でファイルを送信できたとしても、application/x-www-form-urlencoded効率が悪いため、送信したくありません。

ただし、テキストフィールドにある印刷可能な文字の場合、問題はなく、オーバーヘッドも少ないので、そのまま使用します。


1
どのようにバイナリ添付ファイルを追加しますか?(つまり、小さな画像)- Content-DispositionおよびContent-Type属性の値の変更を確認できますが、「コンテンツ」の処理方法は?
blurfus、2015

3
@ianbeksブラウザはリクエストを送信する前にそれを自動的に行います。どのヒューリスティックが使用されているかはわかりませんが、おそらくファイル拡張子がその中に含まれています。これは、質問に答えることがあります。stackoverflow.com/questions/1201945/...
チロSantilli郝海东冠状病六四事件法轮功

3
@CiroSantilli六四事件法轮功纳米比亚威视この答えは選ばれたものよりもはるかに良いと思います。ただし、無関係なコンテンツをプロフィールから削除してください。それはSOの精神に反しています。
smwikipedia 2015

2
@smwikipedia rfcの引用とこの回答を気に入ってくれてありがとう!ユーザー名について:私にとって、SOの精神は、誰もが常に最良の情報を持っているべきだということです。~~この議論をツイッターやメタにしていきましょう。平和。
Ciro Santilli郝海东冠状病六四事件法轮功

1
@KumarHarshに答えるには十分な詳細がないと思います。新しい詳細な質問を開いてください。
Ciro Santilli郝海东冠状病六四事件法轮功

62

バイナリコンテンツとしてファイルを送信(フォームまたは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);

フォームを使用したくない(したい)場合、1つのファイルをアップロードするだけでよいのであれば、これがリクエストにファイルを含める最も簡単な方法です。


Asp.Net 4.0でこのためのサーバー側サービスをどのように構成しますか?userId、path、captionTextなどの複数の入力パラメーターも処理しますか?
Asle G 2015

1
@AsleGいいえ、リクエストのコンテンツとして単一のファイルを送信するためだけのものです。私はAsp.Netのエキスパートではありませんが、リクエストからコンテンツ(BLOB)をプルContent-Typeし、ヘッダーのを使用してファイルに保存するだけです。
Wilt

@AsleG このリンクが役立つかもしれません
Wilt

@wiltフォームを使用しないがformdata APIを使用したい場合、そのようにできますか?
怒っているキウイ

1
@AnkitKhettryフォームまたはフォームAPIを使用してアップロードされたように聞こえます。あなたが参照するこれらの「奇妙な文字列」は、フォームデータをサーバー上のパーツに分離するために通常使用されるフォーム境界です。
Wilt、

9

私はこのサンプルJavaコードを持っています:

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;

public class TestClass {
    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(8081);
        Socket accept = socket.accept();
        InputStream inputStream = accept.getInputStream();

        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文字として扱うように印刷するように指示したので、文字9ieを見て驚くことはありません。生のバイトとして読み取ることもできます。

Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF 

ここで実際には最後のHTTPヘッダーです。その後、HTTP Bodyが続きます。ここには、アップロードしたファイルのメタとコンテンツが実際に表示されます。


6

HTTPメッセージには、ヘッダー行の後に送信されるデータの本文がある場合があります。応答では、要求されたリソースがクライアントに返される場所(メッセージ本文の最も一般的な使用法)、またはおそらくエラーが発生した場合の説明テキストです。リクエストでは、ユーザーが入力したデータまたはアップロードされたファイルがサーバーに送信される場所です。

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.