[PR] 本記事にはプロモーションが含まれています。

【Web開発】なぜGETとPOSTばかりで、PUT・PATCH・DELETEは使われにくいのか|HTMLのフォームの制約とLaravelでの対応

HTML

HTTPには、取得の GET、作成の POST、更新の PUT・PATCH、削除の DELETE など、操作ごとの「メソッド」があります。APIの設計の本では、これらを使い分ける「REST」という設計がよく紹介されます。ところが実際のWebアプリでは、更新も削除も POST で済ませているものが少なくありません。この記事では、その理由をまとめます。

理由1:HTMLのフォームが GET と POST しか送れない

いちばん大きな理由は、HTMLの決まりです。form タグの method に書けるのは、get と post(と、ダイアログを閉じるための dialog)だけです。

HTML
<!-- POST で送られる -->
<form method="POST" action="/user/1">

<!-- DELETE は書けない。書いても GET として送られてしまう -->
<form method="DELETE" action="/user/1">

method に知らない値を書くと、ブラウザは GET として扱います。削除のつもりが、「表示してほしい」というリクエストになってしまうので、エラーにもならず気づきにくいところです。

JavaScriptを使わないふつうのページでは、削除ボタンも POST で送るしかありません。そのため「Webアプリは GET と POST で作る」というやり方が長く続いてきました。JavaScriptの fetch を使えば、PUT や DELETE も送れます。

Laravelでは @method で DELETE として扱う

Laravel や Ruby on Rails などのフレームワークは、「POST で送り、隠し項目で本当のメソッドを伝える」という方法でこの制約を乗り越えています。LaravelのBladeでは、次のように書きます。

Blade
<form action="{{ route('posts.destroy', $post) }}" method="POST">
    @csrf
    @method('DELETE')
    <button type="submit">削除</button>
</form>

@method('DELETE') は <input type="hidden" name="_method" value="DELETE"> という隠し項目になり、Laravelはこれを見て DELETE のリクエストとして扱います。ルートは Route::delete() で定義できます。

GETでデータを変えてはいけない

POST の代わりに、削除を <a href="/posts/1/delete"> のようなリンク(GET)で作るのは危険です。GET は「表示するだけ」のメソッドなので、ブラウザの先読みや検索エンジンのクローラーが、人が押していなくてもそのURLを開くことがあります。データを変える操作は、必ず POST(または PUT・PATCH・DELETE)にします。ログアウトを POST にするのも、同じ理由です。

理由2:古いプロキシや厳しい設定で止められることがある

会社のネットワークのプロキシや、アプリの前に置かれたファイアウォールの中には、GET と POST 以外のメソッドを止める設定になっているものがあります。昔のWebサーバーでは、PUT や DELETE を許可するとサーバー上のファイルを書き換えられたり消されたりする危険があったためです。

その場合、開発中のパソコンでは動いていたのに、特定の利用者の環境だけ 403(Forbidden)や 405(Method Not Allowed)のエラーになる、ということが起きます。今は少なくなっていますが、いろいろな会社の環境から使われるシステムでは、「確実に通る POST にしておく」という判断をすることがあります。上の Laravel の方法なら、通信としては POST なので、この問題も起きません。

理由3:PUT と PATCH の使い分けが分かりにくい

更新に使う PUT と PATCH には、次の違いがあります。

メソッド意味
PUT送った内容で、そのデータを丸ごと置き換える
PATCH送った項目だけを書き換える(2010年に RFC 5789 で後から加わった)

本来の意味では、PUT で一部の項目だけを送ると、送らなかった項目は消えてもおかしくありません。実際には PUT で一部だけ更新する作りのAPIも多く、チームの中で意味がそろっていないと、「PUT だと思って送ったら他の項目が消えた」という事故になります。そうした迷いを避けるために、「更新は POST にして、何を変えるかは送る内容で表す」と決めるやり方もあります。

理由4:操作をURLで表す設計もある

APIの設計には、大きく2つの考え方があります。

考え方URLとメソッド例
RESTURLは「モノ」、操作はメソッドで表すDELETE /users/123
RPCURLに「操作」を入れ、メソッドは POST にそろえるPOST /users/123/delete

RPC(Remote Procedure Call)的な設計は、URLを見れば何をするリクエストかが分かり、ログも読みやすくなります。Slack のAPI(POST /api/chat.postMessage など)や、GraphQL、gRPC も、通信は主に POST で行います。一方で、GitHub や Discord のAPIのように、PATCH や DELETE を使う REST 的な設計のAPIもたくさんあります。

POSTにそろえるときに気をつけたい「冪等性」

メソッドを使い分けると、「冪等性(べきとうせい)」という性質を生かせます。冪等性は、「同じリクエストを何度送っても、結果が1回送ったときと同じ」という性質です。

メソッド冪等か例
GET冪等何度読んでもデータは変わらない
PUT冪等同じ内容で何度置き換えても同じ
DELETE冪等2回目はすでにないだけで、結果は同じ
POST冪等ではない2回送ると投稿が2つできることがある

通信が途切れて送り直したとき、PUT や DELETE なら何度送っても安全ですが、POST だと「注文が2回入った」のようなことが起こります。POST にそろえる場合は、二重送信を防ぐ仕組み(送信ボタンを押したら無効にする、同じリクエストに同じIDを付けて重なりを弾くなど)を自分で用意します。

まとめ

やり方よいところ気をつけるところ
メソッドを使い分ける(REST)操作の意味がメソッドで分かる。冪等性を生かせるHTMLのフォームだけでは送れない。古い環境で止められることがある
POSTにそろえるどの環境でも通りやすいURLに操作が入る。二重送信の対策を自分で考える
  • HTMLのフォームが GET と POST しか送れないことが、いちばん大きな理由。method="DELETE" は GET になってしまう
  • Laravelでは @method('DELETE') で、POST で送りながら DELETE として扱える
  • データを変える操作は GET にしない
  • POST にそろえるのは手抜きではなく、環境や運用に合わせた選び方。その代わり二重送信に気をつける

form の action と method については「formのaction属性とは」で紹介しています。

ほかの開発・プログラミングの記事は、開発・プログラミングメモの目次から、テーマ別に探せます。

このブログは、にほんブログ村と人気ブログランキングに参加しています。記事が役に立ったら、下のランキングのバナーのどれかを押して応援してもらえると励みになります。

ブログランキング・にほんブログ村へにほんブログ村

サーバー構築・運用ランキング プログラムランキング

HTML
watasoftdevをフォローする

コメント