添付ファイル付きでこのような単純なフォームを送信すると、
<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
、およびファイルデータを含むセクションを見ることができます。ファイル名はContent-Disposition
ヘッダーの一部です。
完全な詳細は ここ です。
どのようにしてファイルを内部的に送信しますか?
フォーマットは multipart/form-data
と呼ばれます。 enctype = 'multipart/form-data'とはどういう意味ですか?
私はするつもりだ:
enctype
には 3つの可能性 があります:
x-www-urlencoded
multipart/form-data
(specは RFC2388 )を指していますtext-plain
。これは「コンピュータでは確実に解釈できない」ので、本番環境では決して使用しないでください。これ以上は検討しません。それぞれの方法の例を見れば、それらがどのように機能するか、そしていつそれぞれを使用すべきかが明らかになります。
あなたは例を使って例を作り出すことができます:
nc -l
またはECHOサーバー: GET/POSTリクエストを受け付けるHTTPテストサーバーフォームを最小限の.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
はω
であり、これは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、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
を使ってそれを検証することができます。
61 CF 89 62
送信されました(61
== 'a'および62
== 'b')。
したがって、次のことが明らかです。
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
はブラウザによって自動的に決定されます。
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
のような通常の印刷可能文字が1バイトで送信され、0xCF
や0x89
のような印刷不可能文字は 3バイト each:%CF%89
!
ファイルのアップロードには印刷できない文字(画像など)が多く含まれていますが、テキスト形式ではほとんど含まれていません。
例から私達はそれを見た:
multipart/form-data
:メッセージに数バイトの境界オーバーヘッドを追加し、それを計算するのにしばらく時間を費やす必要がありますが、各バイトを1バイトで送信します。
application/x-www-form-urlencoded
:フィールドごとに1バイト境界(&
)がありますが、すべての非印刷可能文字について、 3x のlinearオーバーヘッド係数が追加されます。
したがって、たとえapplication/x-www-form-urlencoded
を付けてファイルを送信できたとしても、やりたくないでしょう。それはとても非効率的だからです。
しかし、テキストフィールドにある印刷可能な文字については、それは問題ではなく、オーバーヘッドも少なくなるので、ここで使用しています。
与えられた回答/例では、ファイルは(おそらく)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つのファイルのみをアップロードすることに興味があるのであれば、これが要求にファイルを含める最も簡単な方法です。
このサンプル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 の文字を見ても驚くには当たりません。これらの文字をUTF-8文字として扱って印刷するようにJavaに指示したからです。あなたは生のバイトとしてそれらを読むことを選ぶかもしれません..
Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF
ここで実際に最後のHTTPヘッダーです。その後、HTTP Bodyが来ます。ここで、実際にアップロードしたファイルのメタとコンテンツを見ることができます。
HTTPメッセージには、ヘッダー行の後に送信されるデータ本体があります。応答では、要求されたリソースがクライアントに返される場所(メッセージ本文の最も一般的な使い方)、またはエラーがある場合はおそらく説明文です。要求では、これはユーザー入力データまたはアップロードされたファイルがサーバーに送信される場所です。