最近はハンドリングしくてもいいや的な、入力改ざんで発生するバリデーションエラーをそのまま500のHTTPステータスで返すと、攻撃者が「なんか攻撃成功しちゃいそう」って思っちゃうとかなんとかで、監査的なところから「500はやめろ」って言われることがあります。
一理ある
ということで、安易に500を返さない方法を考えてみます。
HTTPステータスコードにあまり馴染みのない方は、こちら…ではなく、こちらをまず読んでください。
HTTPステータスコードの使い分け基礎
400
まず、ユーザの入力値、データの状態によってエラーになるケースは 400 Bad Request とします。エラーメッセージを表示して再入力を促すHTMLページを返す、一般的な入力エラー系の遷移は200 OKを返しても、実用上問題はないかと思います。
ユーザの操作が原因で、サーバ処理がエラーになった場合も400で扱うのはおかしいことではないので、
- トークンチェックエラー
- 楽観ロックエラー
あたりも、400で返します。もう少しこだわる場合は、適当な4xxのステータスコードにしてもよいかと思います。
HTMLページを返す場合は、512バイト以上にしないと、IEの場合ステキな画面が表示されるので注意して下さい。
また、バリデーションで下手なロジック組むと、型変換でExceptionがあがることがありますが、これら全部catchして500にならないように注意して下さい。
503
それから環境や負荷状況によって発生するエラーは「503 Service Temporarily Unavailable」にふります。クローラがサイトの負荷を高めることもあるので、同じところから頻繁にアクセスされる場合は、503を返してクローラに時間をおいてアクセスするように促します(行儀の良いクローラにしか効果はありませんが…)。
401 or 403
さらに、認証/認可に関するエラーはそれぞれ、401と403で返します。認証と認可はハッキリと区別し、認証が通ったリクエストのみ、認可チェックを行い、NGの場合403を返すようにして下さい。さもなくば、秘密のURLが存在していることを攻撃者に知らせることになってるぞっ、ということで恰好の指摘を受けるネタになります。
404
もう少し細かくするなら、SQLなどであるはず(あったはず)なのにデータが見つからなかったとき、404にします。
「あるはずなのに」が重要です。たとえば、検索導線のドリルダウン中に該当データがない場合…
これをの0件のリンク先に(ちなみに上記はSUUMOの例ですが、0件の場合はこのようにカテゴリの横に(0)と明記して、リンクを落とすのが定石です)、アクセスしたときに404を返すのは、今たまたまデータがないだけなので、好ましくありません。
一方で、詳細ページで物件が削除された場合などは、404を返すようにします。
3xx系にもこだわる
以上のことをやっておけば、指摘されることは無くなると思いますが、3xx台も使い分けておくとよいです。
URLを変更した場合に、検索エンジンなど外部サイトからの流入がデッドリンクになっては困るので、Webサーバのリライト機能などを、使って旧URLから新URLにリダイレクトさせることがあります。この用途では「301 Permanent redirect」を使います。
この実装は、WebサーバのURLリライト機能を使うケースが多く、リライトでリダイレクトする設定例に、よく「R=301」が出てくるのはそのためです。
RewriteRule ^/2015/(.*)$ /2016/$1 [R=301,L]
認証のかかっているURLへのリクエストは、未認証の場合ログインページに飛ばし、ログイン成功したら戻ってくるのが、よくあるUIですが、このように前提条件を満たしてないときに、一時的に別のURLで処理をさせるようなケースで「302 Found」を使います。
また、ログイン成功して戻る場合や一連の更新画面遷移の最後で、リロード対策としてPRGパターンを実装する場合には、「303 See other」を使います。
302,303の使い分け、どっちでもいい気がしますが、IE9はPOSTのリクエストに対して302を返すと、リダイレクト先のURLにもPOSTで遷移するRFC strictな実装がされているので、PRGパターンでの期待と違う動きになります。
302/303の使い分けに迷ったら現段階ではざっくり、GETリクエストからのリダイレクトは302、POST/PUT/DELETEのリクエストからのリダイレクトは303としておけばよいと思います。
HTTPステータスコードへの飽くなきこだわり
REST APIを設計・実装しようとなると、もっとHTTPステータスコードにこだわりたくなります。そんなとき私は、このLiberatorのデシジョングラフを参考にしています。
これを辿って行くと、例えばどういうときに409 Conflictで、どういうときに410 Goneを使う感じか指針めいたものが分かってきます。
ちなみに、Liberatorはこのデシジョングラフの全てのノードで、任意の処理をフックでき、それを宣言的に書くことができるライブラリです。開発時には、すべてのリクエストについて、どのデシジョンノードを通ってHTTPステータスコードにたどり着いたかを、↑のデシジョングラフに色付けして表示してくれます。
HTTPステータスコードマニアは、このためだけにでもClojureを使う価値があるのではないでしょうか。
まとめ
- Internal Server Errorは、本当のトラブルのときだけ出るようにしよう。
- HTTPステータスコードマニアはLiberator(Clojure)を使おう