REST API Common Spec としての HTTP Status Code と Error の提案

この記事は Timee Advent Calendar 2023 シリーズ 2 の14日目の記事です。
はじめに
CTO室に10月に入社した id:cos31 です。 色々とやり始めたばかりで、ネタに苦しいので仕事にもタイミーには全く関係のないネタになります!*1
TL;DR
- RFC 9457 - Problem Details for HTTP APIs と RFC 9205 - Building Protocols with HTTP をあわせて使うとすっきりする
- インフラレイヤーとアプリレイヤー
HTTP Status codeが棲み分けできるステキ感 - エラーメッセージの責務としても、Backend / Frontend で共通の定義があるって便利なのよ
API HTTP Response Code Definition
RFC 9205 - Building Protocols with HTTP (日本語訳) の方針を参考に HTTP Status Codeへのマッピングを無理に行いません。
※ 当該セクションの要約
HTTP Status Codeは、多くの場合、アプリケーション自体以外でも生成されます、例えばネットワークエラーや、Proxy、CDN、LB、サーバ自体が過負荷になっている場合などです。特定のエラーを受けた際に、クライアントは汎用的なセマンティクスである場合に誤解します。- 個々のエラーに
HTTP Status Codeをアプリケーションエラーを 1対1 マッピングすると既存のHTTP Status Codeを利用するなど、多くの悪いプラクティスに繋がります。- HTTPを使用するアプリケーションは、エラーを定義して最も適用可能な
HTTP Status Codeを使用し、疑わしい場合は一般的なHTTP Status Code(200、400、および500)を寛大に使用する必要があります- 同じ
HTTP Status Codeにマッピングされた複数のエラー条件を区別し、上記の誤った分布の問題を回避するために、HTTPを使用したアプリケーションは、応答のメッセージコンテンツおよび/またはヘッダーフィールドにより細かいエラー情報を伝える必要があります。
上記を踏まえて、 HTTP Status Code でAPIの成功または失敗を応答します。
2xx範囲のStatus CodeはRequestが正常に処理されたことを示します。4xx範囲のStatus CodeはAPIクライアントが指定したRequest要求が正しくないことを示します。5xx範囲のStatus CodeはAPIサーバーでのエラーを示します。3xx範囲のStatus Codeは 利用しません。
2xx
Request がサーバーで正常に処理されたことを示します。
長さがゼロの payload ボディや空の payload を生成してもかまいませんが、200レスポンスは常に payload を持ちます。サーバーが応答に payload を送信したくない場合は、代わりに HTTP ステータス 204(No Content)を利用します。
| ステータス | 説明 |
|---|---|
200 OK |
Request が受け入れられました。応答には結果が含まれています。 GET では、Request されたリソースまたはデータは応答本文内にあります。 PUT または DELETE では、Request は正常に行われ、 payload には結果に関する情報 (新規リソース ID やリソース情報、変更後の情報など) があります。 |
201 Created |
この応答コードは PUT または POST の場合に利用します、新規リソースが正常に作成されたことを示しています。 payload には、例えば新規リソースに関する情報や検証情報 (アセットが更新された場合など) が含まれていることがあります。 |
202 Accepted |
Request は受け入れられたが、処理が完了していないことを示します。この HTTP Status Code は、実際の操作が本質的に非同期である場合に役立ちます。 |
204 No Content |
Request は受け入れられたが、何も戻されなかったことを示します。 これは、Request が処理されても、結果に関する追加情報が戻されなかった場合に戻されます。 |
Example
HTTP/1.1 200 OK Content-Type: application/json { "id": 1234, "name": "hoge", "category": "fuga", }
4xx
クライアントの Request 要求での不正時に利用します。これは、パラメータの指定や、パラメータに指定されたリソースに対する状態の不備時(指定されたリソースが存在しない、指定されたステータスへの変更ができない場合など)が含まれます
| ステータス | 説明 |
|---|---|
400 Bad Request |
Request が無効な場合に利用します。 このコードは、サーバーが処理しようとしたが、要求の一部が無効であった (例えば、不正なRequest 構文、無効なRequest メッセージパラメータ、指定のリソース自体が存在しない) 場合に戻されます。 Request のエラーに関する情報は、応答本文に記載されており、type と title を含んでいます。 |
401 Unauthorized |
認証に失敗した場合に、サーバが応答します。 |
403 Forbidden |
アクセス権限のないリソースにアクセスしようとしたことを示します。具体的には、access token に対しての scope が不足している場合などに用いられます |
404 Not Found |
指定された path に対する処理が存在しないことを示します。指定されたリソースが存在しない場合には 400 を利用してください |
405 Method Not Allowed |
Request された HTTP メソッドをサポートしていない場合に応答します。 |
406 Not Acceptable |
Accept Request ヘッダ に application/json 以外が要求さた場合に応答します。 |
415 Unsupported Media Type |
Content-Type が Request ヘッダで示されるクライアントが提供したメディアタイプを、APIが処理できないことを示します。 |
5xx
サーバ側での問題を示します。
| ステータス | 説明 |
|---|---|
500 Internal Server Error |
サーバーで内部エラーが発生した場合に応答します。 エラー情報は応答本文にあります。 |
503 Service Unavailable |
サーバーがメンテナンス中のために停止していることや、過負荷状態になっている場合に応答します。依存する外部サービスや、インフラ基盤側での問題時に利用されます |
Errors
Problem Details
4xx 範囲のプログラムで処理可能なエラーについては、RFC 9457 Problem Details for HTTP APIs (日本語訳) に従って詳細なエラー情報を含みます。
エラー応答時には Content-Type: application/problem+json としてエラー詳細を payload に記載し返却します
Problem Detail Object (bia: RFC 9457 #Section 3.1 区分 より以下のMemberを採用して利用します。 *2
member |
type |
condition |
detail |
|---|---|---|---|
type |
string |
required |
問題のタイプを識別するURI参照。 |
title |
string |
required |
人間が読める形式の問題タイプの概要。ローカライゼーションの目的を除いて、問題の発生ごとに変更するべきではありません |
detail |
string |
この問題の発生に固有の、人間が読める説明 |
また、Memberを拡張として、追加する場合には type毎に変動がないようにAPI毎に定義します。
type Format
https://example.com{/path}
pathは 半角小文字英数字 + _ ( [0-9a-z\_]+ ) とします。
# Good https://example.com/validation_error # Bad https://example.com/validation-error
Common types
type path |
title |
usage |
additonal member |
|---|---|---|---|
/request_unauthorized |
API key authentication failed |
認証失敗した場合 | - |
/json_invalid |
Your request json invalid |
JSON形式が不正の場合 | - |
/validation_error |
Your request parameters didn't validate |
OpenAPIなどの data schema としての validation error の場合や、DB データとの整合性を検証した validation error の場合 |
invalid_params: [] ※失敗したパラメータ箇所を列挙 |
/resource_not_found |
Specified resource does not exist |
指定したResourceが存在しない場合(※ コンテキストに応じて論理削除である状態も含む) | detail: 存在しなかった対象を明示 |
/resource_already_exist |
Specified resource already exist |
重複して指定したResourceを作成しようとした場合 | detail: 失敗対象の明示 |
/mutation_error |
Mutation failed |
DBへの変更操作(e.g. create, update, delete)に失敗した場合 |
detail: 失敗対象の明示 |
/server_error |
Something wrong occurs |
サーバー側での不測エラーが発生した場合(※ 500 Internal Server Error への対応を想定) |
- |
/user_unavailable |
Specified user unavailable |
指定したユーザーが処理を継続できない状態(e.g. ステータスが無効な状態) の場合 | detail: 何故できないかの詳細を記載します |
Example
HTTP/1.1 401 Unauthorized Content-Type: application/problem+json { "type": "https://example.com/unauthorized", "title": "API Key Authenticate failed" }
• detail が入るパターン
HTTP/1.1 400 Bad Request Content-Type: application/problem+json { "type": "https://example.com/resource_not_found", "title": "Specified resource does not exist", "detail": "User ID: 1234 not exists" }
• Memberを拡張するパターンのSample
HTTP/1.1 400 Bad Request Content-Type: application/problem+json { "type": "https://example.net/validation_error", "title": "Your request parameters didn't validate.", "invalid_params": [ { "name": "age", "reason": "must be a positive integer" }, { "name": "color", "reason": "must be 'green', 'red' or 'blue'" } ] }
まとめ
これくらい迷わない定義があると共通理解として整理もできて、クライアントとしても監視観点としても整理が明確になると思っています
しかしながら、めんどくさい部分としては、Problem Detail に type に 応じた Collection の増減の型定義ですが、microservice などでドメインが揃うAPI郡などでまとまると見通しが良い範囲に収まると思います
実例としては、Stripe では、type 指定された URI に対してドキュメントが準備されており、大変クライアントフレンドリーとなっています
stripe.com
他、APIとしての共通的な振る舞いに悩んだ場合には、 https://google.aip.dev/ を見ましょう。とても良いです。
特に Pagination とか独自提示したくもないけど標準仕様もない悩ましい Collection Resource への定義は秀逸なので是非見てください
このように自身で考えるよりも、標準仕様や先人たちの考えて成果にうまく乗っかりながら、程よいアレンジをして共通仕様として育てると治安の良いAPIになると思います!
We’re Hiring!
タイミーでは、ともに働くメンバーを募集しています!!
現在募集中のポジションはこちらです! hrmos.co
「より深く話を聞きたい」とか「で、お前今何やってんの?」と思われた方は、是非一度カジュアル面談でお話ししましょう!