この記事では、フォーム送信時に実際にどのようなデータが送信されるのか、ファイルを送信するためにはどうすればよいのか、などを Deno や curl で確認しながら見ていく。
動作確認に使った実行環境やツールのバージョンは以下の通り。
- Google Chrome 85.0.4183.121
- Deno 1.4.4
- curl 7.54.0
サーバを立てる
まず、リクエストを受けるためのサーバを Deno で立てる。
// server.ts import { listenAndServe, ServerRequest, } from "https://deno.land/std@0.74.0/http/mod.ts"; import { bold } from "https://deno.land/std@0.74.0/fmt/colors.ts"; const getRouting = async (req: ServerRequest) => { switch (req.url) { case "/": { const html = await Deno.readTextFile("./form.html"); req.respond({ status: 200, headers: new Headers({ "content-type": "text/html", }), body: html, }); break; } default: req.respond({ status: 404, headers: new Headers({ "content-type": "text/plain", }), body: "Not found\n", }); break; } }; const postRouting = (req: ServerRequest) => { switch (req.url) { case "/": req.respond({ status: 200, headers: new Headers({ "content-type": "text/plain", }), body: "Your post was successful\n", }); break; default: req.respond({ status: 404, headers: new Headers({ "content-type": "text/plain", }), body: "Not found\n", }); break; } }; listenAndServe({ port: 8080 }, async (req: ServerRequest) => { console.log(`Request Method -> ${req.method}`); req.headers.forEach((value, key) => { console.log(`${bold(key)}: ${value}`); }); const decoder = new TextDecoder("utf-8"); const body = await Deno.readAll(req.body); console.log("Request Body"); console.log(decoder.decode(body)); switch (req.method) { case "GET": getRouting(req); break; case "POST": postRouting(req); break; default: req.respond({ status: 405, }); } });
form.html
の内容は以下の通り。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Form</title> </head> <body> <form method="POST" action="http://localhost:8080/"> <label for="task">task: </label><input type="text" name="task" id="task"><br> <label for="priority">priority: </label><input type="text" name="priority" id="priority"><br> <p><input type="submit" value="submit"></p> </form> </body> </html>
deno run --allow-net --allow-read server.ts
を実行すると、サーバが起動する。
URL エンコード
http://localhost:8080/
にアクセスするとフォームが表示されるので、task
にlearn JS & TS
、priority
にhigh
を入力して、送信してみる。
すると、以下の内容がログに表示される。
Request Method -> POST host: localhost:8080 connection: keep-alive content-length: 34 cache-control: max-age=0 upgrade-insecure-requests: 1 origin: http://localhost:8080 content-type: application/x-www-form-urlencoded user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36 accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 sec-fetch-site: same-origin sec-fetch-mode: navigate sec-fetch-user: ?1 sec-fetch-dest: document referer: http://localhost:8080/ accept-encoding: gzip, deflate, br accept-language: ja,en-US;q=0.9,en;q=0.8 Request Body task=learn+JS+%26+TS&priority=high
最後のtask=learn+JS+%26+TS&priority=high
がリクエストボディ。
各項目はキーと値が=
で結合され、複数の項目がある場合は&
で結合されて、一つの文字列になっている。
キーと値に含まれる&
は%26
に変換されるため、区切り文字の&
と混同されることはない。
今回の例だとスペースも+
に変換されている。
learn JS & TS ↓ エンコード learn+JS+%26+TS
フォーム送信と同様のリクエストを curl で発行するには、-d
もしくは--data-urlencode
を使う。
それぞれのフラグの後ろにキー="値"
という形で、送信したいデータを指定する。
まずは-d
で送信してみる。
$ curl -d task="learn JS & TS" -d priority="high" http://localhost:8080
Request Method -> POST host: localhost:8080 user-agent: curl/7.54.0 accept: */* content-length: 32 content-type: application/x-www-form-urlencoded Request Body task=learn JS & TS&priority=high
無事に POST メソッドのリクエストを発行できた。
だがよく見てみると、スペースや&
がエンコードされず、そのまま送信されてしまっている。
フォームでの送信と同様に自動的にエンコードしてもらいたい場合は、-d
の代わりに--data-urlencode
を使う。
$ curl --data-urlencode task="learn JS & TS" --data-urlencode priority="high" http://localhost:8080
Request Method -> POST host: localhost:8080 user-agent: curl/7.54.0 accept: */* content-length: 40 content-type: application/x-www-form-urlencoded Request Body task=learn%20JS%20%26%20TS&priority=high
&
が%26
にエンコードされているのはフォームの例と同じだが、スペースは+
ではなく%20
になっている。
これは、encodeURIComponent
と同じ挙動。
console.log(encodeURIComponent("learn JS & TS")) // "learn%20JS%20%26%20TS"
このように、クライアントによってエンコードのロジックが異なる。
multipart/form-data
ここまでの例では、リクエストヘッダのcontent-type
は全てapplication/x-www-form-urlencoded
だった。
この他に、multipart/form-data
もある。
form
要素のenctype
属性にmultipart/form-data
を指定すると、content-type
がmultipart/form-data
になる。
@@ -5,7 +5,7 @@ <title>Form</title> </head> <body> - <form method="POST" action="http://localhost:8080/"> + <form method="POST" action="http://localhost:8080/" enctype="multipart/form-data"> <label for="task">task: </label><input type="text" name="task" id="task"><br> <label for="priority">priority: </label><input type="text" name="priority" id="priority"><br> <p><input type="submit" value="submit"></p>
先程と同じ値をフォームに入力して、送信してみる。
すると、リクエストは以下のような内容になった。
(中略) content-type: multipart/form-data; boundary=----WebKitFormBoundaryKqKCxRXPgWo8hEBw (中略) Request Body ------WebKitFormBoundaryKqKCxRXPgWo8hEBw Content-Disposition: form-data; name="task" learn JS & TS ------WebKitFormBoundaryKqKCxRXPgWo8hEBw Content-Disposition: form-data; name="priority" high ------WebKitFormBoundaryKqKCxRXPgWo8hEBw--
content-type
にboundary
属性が設定されており、その値は----WebKitFormBoundaryKqKCxRXPgWo8hEBw
になっている。
そしてボディでは、この文字列で、各項目を区切っている。
curl で同様のことを行うには、-d
の代わりに-F
を使えばよい。
$ curl -F task="learn JS & TS" -F priority="high" http://localhost:8080
ファイルを送信したい場合は、multipart/form-data
を使う必要がある。
application/x-www-form-urlencoded
だと、ファイルの情報を送信することができない。
例えば、フォームの内容を以下のように変える。
<form method="POST" action="http://localhost:8080/"> <input type="file" name="item" id="item"><br> <p><input type="submit" value="submit"></p> </form>
そして、以下の内容のtest.txt
を送信する。
a b
そうすると、リクエストボディは以下のようになる。
item=test.txt
name属性の値=ファイル名
という情報しか存在せず、ファイルの内容を取得できない。
multipart/form-data
を使えば、ファイルの内容を取得できるようになる。
- <form method="POST" action="http://localhost:8080/"> + <form method="POST" action="http://localhost:8080/" enctype="multipart/form-data"> <input type="file" name="item" id="item"><br> <p><input type="submit" value="submit"></p> </form>
------WebKitFormBoundaryIFek0BpUCfkWvtrd Content-Disposition: form-data; name="item"; filename="test.txt" Content-Type: text/plain a b ------WebKitFormBoundaryIFek0BpUCfkWvtrd--
ファイルの内容の他、そのファイル自身のContent-Type
なども取得できている。
curl で-F
オプションを使いながらファイル送信を行うには、attachment-file=@ファイルパス
とすればよい。
curl -F attachment-file=@test.txt http://localhost:8080/