【Rails】GETとPOSTの違いを検証する

#はじめに
Railsの勉強中、<b>GETは「情報を受け取る」</b>、<b>POSTは「情報を送る」</b>という認識でコードを書いていました。
ですが実際は、<b>GETを指定しても入力値を送信してDBを更新</b>できますし、
<b>POSTを指定してもredirectやrenderをせずとも対応したページの情報を受け取って表示</b>しています。
同じくRailsを勉強している仲間も疑問に思っていたとのことで、今回GETとPOSTの違いについて検証しました。
同じテーマの記事をいくつか拝見しましたが、トークンについて触れている記事は少ないように見受けられましたので、ぜひ最後までご覧ください。

#HTTPリクエストとHTTPレスポンスとは
前提として、HTTPリクエストについて再確認します。
HTTPリクエストとはWebブラウザ（PC上で見ている画面）が、Webサーバー（サイトページの情報が存在するコンピュータ）に対して送る要求（リクエスト）です。
この要求に対してWebサーバーが応答（レスポンス）することでPC上で見ている画面に望んでいたサイトページが表示されます。
![タイトルなし.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/9e875b1c-3ad3-2912-f1ce-1ccf2127ce8e.png)

#HTTPメソッドとは
HTTPリクエストの際にサーバーに何をしてほしいか指示するものです。
HTTPリクエストではWebサーバーに様々な情報を渡しており、そのなかにリクエストしたいメソッドやURLが含まれます。
これはブラウザの検証ツール（F12で開けます）の「Network」タブを見ることで確認することができます。
![リクエストの確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/f5e9a376-a9bf-534d-1331-10a6aa75778c.png)

#検証してみる
まずは簡単に新規投稿画面を作成します。
現時点で使用するテーブルは空の状態としてあります。

```:Console
pry(main)> Test.all.count
   (0.2ms)  SELECT COUNT(*) FROM "tests"
=> 0
```

##POSTメソッドの場合
まずは通常通りPOSTメソッドで実行します。  
![new.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/ec9a57c9-71b6-7012-4924-4f4e8af57610.png)

⇒redirectやrenderは指定していませんが、問題なくcreateページが表示されています
![create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/f317fc33-6e12-79db-05ab-9c8f91dc8870.png)

検証ツールは下図のようになりました。
入力した内容は「FormData」に保存されて送信されているようです。
![create12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/431d51a8-0ccc-9447-7434-4bd21b102d61.png)

念のためデータ件数も確認します

```:Console
pry(main)> Test.all.count
   (0.6ms)  SELECT COUNT(*) FROM "tests"
=> 1
```

##GETメソッドの場合
次にform_withでメソッドをGETに指定して実行してみます。
![newget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/f26e6ead-e925-f8b7-135e-c87506aceb7d.png)

⇒POST同様、問題なくcreateページが表示されています。
　しかしGETではURLにパラメーターがついています。
![createget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/ad503909-bc7b-beb1-4ce0-0c82d6ec6fe6.png)

検証ツールを確認します。
RequestURLにパラメーターのようなものが含まれます。
また、先ほどは「FormData」であったところが「QuerySettingParameters」となっており、トークンも発行されていません。
![createget12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/21cf4960-7404-480c-d8d1-efeae3339442.png)


念のためこちらもデータ件数を確認します。

```:Console
pry(main)> Test.all.count
   (0.6ms)  SELECT COUNT(*) FROM "tests"
=> 2
```

##発行されたURLを再度送信してみる
先ほどのテストで発行されたURLをそれぞれ別のタブにコピー＆ペーストします。

- まずはPOSTメソッドで発行されたURL  
![refresh.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/aa215d00-554d-07af-5b74-0868673cd060.png)

パラメーターが空じゃないかと怒られてしまいました。
本来はHTTPリクエストのFormDataにパラメータが入っているはずですが、ここではURLをコピー＆ペーストしただけなので、createアクションのDB更新処理時に値がないよといわれたのです。
エラーが出たため、当然データ件数も変更はありませんでした。

```:Console
pry(main)> Test.all.count
   (0.6ms)  SELECT COUNT(*) FROM "tests"
=> 2
```
※こちらのテストには補足があります。語弊が生まれないようご確認いただくことをお勧めします。[^1]


- 次にGETメソッドで発行されたURL
![refreshget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/f513efb7-b8bc-49e0-5f0a-f603a4245656.png)
createのページが表示されました。  
データ件数を確認すると、1件増えています。  
createアクションがパラメーター値を受け取ってDB更新を行うことを考えれば納得の結果といえます。

```:Console
pry(main)> Test.all.count
   (0.6ms)  SELECT COUNT(*) FROM "tests"
=> 3
```

#GETとPOSTの違いを考える
この検証でわかったことは以下のようになりました。

- GETメソッド
URLにパラメーター値が含まれるため、再度そのURLにアクセスすると、実行時とまったく同じアクションを行うことができる

- POSTメソッド
URLにパラメーター値は含まれないため、再度そのURLにアクセスしても同じアクションは行われない[^2]

今回はcreateアクションだったためDB更新時にエラーが出ましたが、例えば検索機能で条件をパラメーターで渡す場合、GETであればURLをブックマークをしておけば次に開いたときにも指定した条件で閲覧することができるのに対して、POSTではパラメーターが存在しないため条件の指定がされていない状態になります。

また、最も重要な違いとして検証ツールの中身です。
POSTではauthenticity_tokenが発行されているのに対し、GETでは発行されていません。  
**------authenticity_tokenとは-------------------------------------------------------------------------------**
RailsのCSRF対策の機能です。
CSRF対策とは、外部から悪意を持ったリクエストを送信できないようにするためのものです。
GET以外のメソッドはPOSTやPATCHなど更新系のメソッドであるため、authenticity_tokenを発行し、本人確認を行っています。
GETは本来データを受け取るだけのメソッドなので、必要ないということになります。
**---------------------------------------------------------------------------------------------------------------**

加えてPOSTでは「FormData」であったところがGETでは「QuerySettingParameters」となっていることから、POSTは入力情報を１つの集まりとして送っているのに対し、GETではあくまでもクエリを実行するために必要な個々のパラメーターとして渡していると考えることができます。
createアクションでは両方ともパラメーターとして受け取っているためややこしいですが、全くの別物ということですね。

#まとめ
上記のことから、GETとPOSTで行われている処理は全くの別物であることがわかりました。
「はじめに」でお話した疑問点については、

- GETを指定しても入力値を送信してDBを更新できる  
    ⇒クエリ用に渡したパラメータを使ってコントローラーに記述した更新処理をおこなっているだけ
- POSTを指定してもredirectやrenderをせずとも対応したページの情報を受け取って表示している  
  ⇒POSTとGETの違いは入力値の受け渡し方法やトークンを発行するかどうかという点になるため、POSTの中でredirectやrenderを指定しなくてもURLに紐付いた画面は表示される

GETでパラメーターを送信すれば、コントローラーで受け取ってデータの更新も可能ですが、CSRF対策の観点や、ブックマークをすると何度も同じ更新がされてしまうことから、望ましくないです。
また、場合によっては個人情報がパラメーターとして残ってしまったり、悪意を持ってパラメーターに渡す値を変更されてしまうなどの問題もありそうです。

POSTで表示をするのも条件検索を行うような場合では不便で、通常のページであってもHTTPリクエストがGETとPOSTを意味を持って区別していることを考えると好ましくないことがわかりました。

#おわりに
なんとなくリクエストの種類としてGETは受取りでPOSTは送信なんだなと使っていましたが、
内部を詳しく調べると使い分けるのにはきちんとした理由がありました。
検証は時間がかかりましたが、様々な発見があってとても為になる検証でした。
深く考えずにいましたが、理解できるとやっぱり面白いですね！
ネット記事を調べるよりも実際の挙動からわかることをまとめた記事になりますので、至らない点がございましたらコメント欄にてご指摘いただけますと幸いです。

#補足
[^1]: このテストではあくまでも「POSTメソッドで発行されたURLをペーストしている」だけなのでリクエスト自体はGETメソッドがリクエストされています。今回はテストの都合上GETとPOSTの両方のルーティングを設定しているためこのような挙動になりますが、本来のサイトのようにPOSTメソッド用のルーティングしか設定していない場合にはルーティングエラーが表示されます![routeerr.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1813704/741af2e8-d972-d65c-7513-3616e5af1100.png)


[^2]: link_toで直接渡す場合はPOSTでもパラメーターが含まれたURLが表示されます。（```例えば、「<%= link_to "post",tests_path(pram1: "post"), method: :post %>」ならURLは「...cloud9.us-east-1.amazonaws.com/tests?pram1=post」となる```）POSTはフォームの入力値をFormDataという箱に入れて送信しているため、フォーム入力値はURLに含まれませんが、フォーム入力値でないパラメーターについては箱に入れることができないので、GETと同じようにURLに表示されます。