「伸び悩んでいる3年目Webエンジニアのための、Python Webアプリケーション自作入門」を更新しました

# オンラインブックを更新しました

[チャプター「POSTパラメータを扱えるようにする」](https://zenn.dev/bigen1925/books/introduction-to-web-application-with-python/viewer/post-parameters) を更新しました。

続きを読みたい方は、ぜひBookの「いいね」か「筆者フォロー」をお願いします ;-)

----

以下、書籍の内容の抜粋です。

------

# リクエストボディを扱う

前章の最後に、Chromeで`/show_requests`へアクセスした結果を見てみるとリクエストボディが空になっていたことが分かりました。

しかし、仮にボディが空でなかったとして、私達のWebアプリケーションはリクエストボディを変換したり解釈したりする処理はまだないのでした。
せっかくなので、ここいらでリクエストボディを扱えるようにしておきましょう。

リクエストボディはクライアントからサーバーへ付加的な情報（パラメータとも言う）を送るのに用いられ、一例として`POST`メソッドのリクエスト（以下、POSTリクエスト）などで使われます。

本章では、`POST`メソッドのパラメータに関する処理を実装することで、リクエストボディの取り扱いについて学びましょう。

# POSTリクエストを送信し、ボディを観察してみる。
アレコレと説明する前に、まずはリクエストボディが実際にどのように使われているのか観察するところから始めましょう。

## POSTリクエストを送信する
POSTリクエストをブラウザがどのようなときに送るかというと、代表的なのは`<form>`タグを用いて作られたフォームの`submit`ボタンが押された時です。

実際にフォームを含むHTMLを作成し、実験してみましょう。

下記のHTMLを`study/static`内に作成してください。

内容は初歩的なHTMLで、詳しく説明する必要はないでしょう。
1つの`<form>`タグの中に、テキストボックスやプルダウン、セレクトボックスなど、色々な種類の入力フォームが入っているだけです。

**`study/static/form.html`**
https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter15/static/form.html

また、今回からレスポンスヘッダーを少し変更します。
`Content-Type`ヘッダーには文字列のエンコーディングを指定することができ、ブラウザで日本語を表示させるためには日本語に対応したエンコーディングの指定が必要になります。

エンコーディングについては話が込み入ってしまいますので、ピンと来ない方はおなじないだと思って追記しておいてください。

**`study/workerthread.py`**
https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter15/workerthread.py#L169

このファイルは **`static`ディレクトリの中に入っており静的ファイル配信の対象となります** ので、サーバーを起動した状態でChromeから `http://localhsot:8080/form.html` へアクセスすると表示することができます。

![](https://storage.googleapis.com/zenn-user-upload/10tw1udmnxex3mayh325zn0az6cj)

------

ブラウザは、`type="submit"`の要素（以下、`submit`ボタンと呼びます）がクリックされると、`<form>`タグの`action`属性で指定されたURLへPOSTリクエストを送信します。

`action`属性で指定するURLについて、ホストやポートを省略すると、現在開いているページと同じホスト/ポートへ送信されます。
つまり、今回のように

- 現在開いているページが `http://localhost:8080/form.html` である
- `<form action="/show_requests">`である

という場合では、POSTリクエストは`http://localhost:8080/show_requests` へ送信されます。

-----

では、下図のようにフォームに値を入力して、送信ボタンを押してみましょう。

![](https://storage.googleapis.com/zenn-user-upload/pnd9bojbf6owzp3n0oug8jvl6l5p)

先程説明した通り、このフォームの入力内容は、POSTリクエスト`/show_requests`へ送られます。
`/show_requests`は前章でHTTPリクエストの内容が表示されるようにしておいたはずですので、これでPOSTリクエストの内容が見れるだろう、という算段です。

実際、送信ボタンを押すと、次の画面で下記のように表示されるはずです。

![](https://storage.googleapis.com/zenn-user-upload/z75ja4m8kbe0hnvn3m11mcdq55kn)

単にURLバーに`/show_requests`と入力してページ遷移した場合と違って、リクエストボディに値が含まれていることが分かります。

また、

```http
Content-Type: application/x-www-form-urlencoded
```

というヘッダーも新たに追加されていることにも注目しておいてください。
後ほど、このヘッダーが大きな意味を持つことを説明します。

## POSTリクエストのボディを観察する

さて、POSTリクエストのボディの具体的な中身が見れたことですので、観察してお勉強していきましょう。
リクエストボディを見てみると、`テキストボックス`や`パスワード`などの個々の入力フォームの値が決まったフォーマットで連結されて渡されてきているのが分かります。

そのフォーマットとは、1つの入力フォームに対して
`[HTML要素のname属性の値]=[フォームに入力された値]`
というペアがあり、別々のフォームの値同士は`&`で連結されているようなフォーマットです。

また、`半角スペース`は`+`という記号に置き換えられ、`改行コード`や`日本語`は**`%`で始まる謎の文字列**に変換されていることが分かります。
（日本語の入力値は、hidden_valueの値を見てください）


また、`<select>`要素のように複数選択を許可する入力フォームでは、同じnameで複数の値が送られてきているようです。
例）`check_name=check2&check_name=check3`

また、アップロードしたファイルを見てみると*ファイル名だけ*しか送られておらず、ファイルの内容は送信されていません。

## POSTパラメータのフォーマットについて

POSTリクエストで送りたいデータ（以下、POSTパラメータ）をリクエストボディを使ってサーバーへ送る際、どのようなフォーマットで送るかは重要です。
ここでフォーマットと言っているのは、リクエストボディの中でパラメータの`name`と`value`を表すのになんの記号を使うのか、複数のデータを分けるのになんの記号を使うのか、マルチバイト文字をどのように表現するのか、画像ファイルのようなバイナリデータをどのように表現するのか、などです。

フォーマットは様々な種類が考えられますが、このフォーマット方式の認識がクライアント側とサーバー側で違うと、送ったパラメータをサーバ側で正しく認識できません。
（クライアント側は`=`という記号は「`name`と`value`を分ける記号」だと思って使っているのに、サーバー側ではこれを「改行コード」だと思って解釈してしまうと、訳の分からない事になってしまうわけです。）

そこで、POSTリクエストを送る時は、必ずリクエストボディのフォーマットを示す`Content-Type`というヘッダーをつけてフォーマットを明示してあげる必要があります。

今回でいうと、
```http
Content-Type: application/x-www-form-urlencoded
```
がそれに当たるというわけです。

以下では、よく使われるフォーマット(`Content-Type`)について3つ紹介しておきます。

### `application/x-www-form-urlencoded`
こちらは、ブラウザが`<form>`タグで`enctype`属性を指定しなかった場合に使われるデフォルトのフォーマットです。
別名「URLエンコーディング」や「パーセントエンコーディング」とも呼ばれ、URLとして利用可能な文字のみを使って様々なデータを表せるようにフォーマットが決められています。

既にさきほど見た通り、

1. 項目の`name`と`value`は`=`で連結する
1. 複数の項目を送る際は`&`で連結する
1. 半角スペースは`+`を使う
1. その他のURLに使えない文字は、`UTF-8`で符号化した上で、そのバイト列を`%XX`で表す
1. UTF-8で符号化できないバイナリデータは扱えない（ファイルアップロージ時、ファイルの中身は送信しない）

などが特徴です。

### `multipart/form-data`
こちらは、`<form enctype="multipart/form-data">`のように、`enctype`属性で明示的に指定することで利用できます。
説明する前に実際に中身を見てみましょう。

さきほど作成した`form.html`の`<form>`要素に、enctypeを指定して、フォームを送信してみてください。

#### 注意
**ファイルアップロードした時の表示を確認したい場合は、`Chrome`ではなく`Firefox`というブラウザを使い、小さいデータ（数KBの画像など）を送るようにしてください。**

ChromeやSafariは、ファイルデータを送る際にkeep-aliveを使って複数回に分けてリクエストを送信する挙動となっていますが、私たちのWebサーバーはkeep-aliveに対応しておらずデータを正常に受け取れないためです。

Firefoxは小さいデータであれば1リクエストで送ってきますので、正常にデータを受け取ることができます。

---
下記ではFirefoxで挙動確認した画面を掲載しますが、Firefoxをインストールするのが面倒な方は、Chromeでファイルは**アップロードせずに**動作確認してみてください。
テキストフォームなど、ファイル以外のフォームであればデータが受け取れるはずです。

![](https://storage.googleapis.com/zenn-user-upload/jnbaf6b4w9y1qfs4obhgky9zyc2x)

![](https://storage.googleapis.com/zenn-user-upload/gs3wa6fl7hilf9fn65b55iuig83r)

まず最初に注目するのは、フォームデータの各項目が特殊なセパレータによって分割されている点です。
(今回でいうとセパレータは`---------------------------10847194838586372301567045317`)

--------

# 続きはBookで！

[チャプター「POSTパラメータを扱えるようにする」](https://zenn.dev/bigen1925/books/introduction-to-web-application-with-python/viewer/post-parameters)