複数のアクションが異なるステータスで終了した場合に返すHTTPステータスコードは何ですか?


72

ユーザーがサーバーに1つのHTTPリクエストで複数のアクションを実行するように要求できるAPIを構築しています。結果は、アクションごとに1つのエントリを持つJSON配列として返されます。

これらの各アクションは、互いに独立して失敗または成功する場合があります。たとえば、最初のアクションが成功し、2番目のアクションへの入力のフォーマットが不適切で検証に失敗し、3番目のアクションが予期しないエラーを引き起こす場合があります。

アクションごとに1つのリクエストがある場合、ステータスコード200、422、および500をそれぞれ返します。しかし、リクエストが1つしかない場合、どのステータスコードを返す必要がありますか?

いくつかのオプション:

  • 常に200を返し、より詳細な情報を本文に記載します。
  • リクエストに複数のアクションがある場合にのみ上記のルールに従うのでしょうか?
  • すべてのリクエストが成功した場合は200を返し、そうでない場合は500(または他のコード)を返しますか?
  • アクションごとに1つの要求を使用し、余分なオーバーヘッドを受け入れます。
  • まったく違うものですか?

3
あなたの質問は私に他のことについて考えさせました:Programmers.stackexchange.com/questions/309147/…–
AilurusFulgens

7
少し関連性がある:Programmers.stackexchange.com/questions/305250/…(HTTPステータスコードとアプリケーションコードの分離に関する受け入れられた回答を参照)

4
これらのリクエストをグループ化することで得られる利点は何ですか?それは、複数のリソースにわたるトランザクションのようなビジネスロジックに関するものですか、それともパフォーマンスに関するものですか?または、他の何か?
リュックフランケン

5
わかりました。その場合、他の領域でパフォーマンスを改善することを強くお勧めします。この複雑さをビジネスレイヤーに実装する前に、楽観的なUI、リクエストのバッチ処理、キャッシュなどを試してください。ほとんどの時間を失う場所について明確な洞察を持っていますか?
リュックフランケン

4
...人々がそれらのステータスを正しく見ることをあまり期待しないでください。ほとんどのプログラムは、最も一般的なもののみをチェックし、予期しないステータスコードを受け取った場合、失敗または誤動作します。(ブラウザーが無視し、クローラーがエラーになるとウェブサイトのその部分のクロールを停止する理由を単に表示するランダムな終了ステータスを送信することにより、サイトをクローラーから保護することに関するDefConでのプレゼンテーションもあったことを覚えています)
バクリウ

回答:


21

短く、直接的な答え

リクエストはタスクのリストの実行について話すので(タスクはここで言うリソースです)、タスクグループが実行に移された場合(つまり、実行結果に関係なく)、それは賢明です応答ステータスがであること200 OK。それ以外の場合、タスクオブジェクトの検証失敗など、タスクグループの実行を妨げる問題が発生した場合、または必要なサービスが利用できない場合、応答ステータスはそのエラーを示す必要があります。それを過ぎて、タスクの実行が開始されると、実行するタスクが要求本文にリストされているのを見て、実行結果が応答本文にリストされることを期待します。


長い哲学的答え

HTTPの設計目的から逸脱しているため、このジレンマを経験しています。リソースを管理するために対話するのではなく、リモートメソッド呼び出しの手段として使用しています(これはあまり奇妙ではありませんが、事前に考えられたスキームがないとうまく機能しません)。

上記が述べられており、この答えを長々と説得力のあるガイドに変える勇気がなければ、以下はリソース管理アプローチに適合するURIスキームです:

  • /tasks
    • GET ページ分割されたすべてのタスクをリストします
    • POST 単一のタスクを追加します
  • /tasks/task/[id]
    • GET 単一のタスクの状態オブジェクトで応答します
    • DELETE タスクをキャンセル/削除します
  • /tasks/groups
    • GET ページネーションされたすべてのタスクグループをリストします
    • POST タスクのグループを追加します
  • /tasks/groups/group/[id]
    • GET タスクグループの状態で応答します
    • DELETE タスクグループをキャンセル/削除します

この構造は、リソースを扱うものではなく、リソースについて述べています。リソースで行われているのは、別のサービスの懸念です。

もう1つ重要な点は、HTTPリクエストハンドラーで長時間ブロックしないことをお勧めします。UIと同様に、HTTPインターフェースは応答性が高くなければなりません-タイムスケールは数桁遅いです(このレイヤーはIOを処理するため)。

リソースが厳密に管理されるHTTPインターフェイスの設計に移行することは、ボタンがクリックされたときにUIスレッドから作業を移動するのと同じくらい難しいでしょう。HTTPサーバーは、要求ハンドラーでタスクを実行するのではなく、他のサービスと通信してタスクを実行する必要があります。これは浅い実装ではなく、方向の変更です。


そのようなURIスキームがどのように使用されるかのいくつかの例

単一のタスクを実行して進行状況を追跡する:

  • POST /tasks 実行するタスクで
    • GET /tasks/task/[id]completed現在のステータス/進行状況を表示しながら、応答オブジェクトが正の値を持つまで

単一のタスクを実行し、その完了を待っています:

  • POST /tasks 実行するタスクで
    • GET /tasks/task/[id]?awaitCompletion=trueuntil completedが正の値を持つ(タイムアウトが発生する可能性が高いため、これをループする必要がある)

タスクグループの実行と進捗の追跡:

  • POST /tasks/groups 実行するタスクのグループ
    • GET /tasks/groups/group/[groupId]応答オブジェクトのcompletedプロパティに値があり、個々のタスクステータス(5つのうち3つのタスクが完了したなど)を示すまで

タスクグループの実行をリクエストし、その完了を待っています:

  • POST /tasks/groups 実行するタスクのグループ
    • GET /tasks/groups/group/[groupId]?awaitCompletion=true 完了を示す結果で応答するまで(タイムアウトがある可能性が高いため、ループする必要があります)

意味的に意味をなすものについて話すことは、これにアプローチする正しい方法だと思います。ありがとう!
アンデルス

2
この回答がまだなかった場合は、提案します。1つのHTTP要求で複数の要求を行うことはできません。一方、「次のアクションを実行し、結果を教えてください」という単一の HTTPリクエストを作成することは完全に可能です。そして、これがここで起こっていることです。
マーティンコチャンスキー

私はこの答えを、最も多くの票からはほど遠いものでさえ受け入れます。他の答えも良いですが、これがHTTPのセマンティクスについての唯一の理由だと思います。
アンデルス

87

私の投票は、これらのタスクを個別のリクエストに分割することです。しかし、あまりにも多くの往復が懸念される場合、HTTP応答コード207-マルチステータスに遭遇しました

このリンクからコピー/貼り付け:

マルチステータスレスポンスは、複数のステータスコードが適切な場合に、複数のリソースに関する情報を伝えます。デフォルトのマルチステータスレスポンスボディは、「multistatus」ルート要素を持つtext / xmlまたはapplication / xml HTTPエンティティです。その他の要素には、メソッド呼び出し中に生成された200、300、400、および500シリーズのステータスコードが含まれます。100シリーズのステータスコードは、「応答」XML要素に記録されるべきではありません。

全体の応答ステータスコードとして「207」が使用されますが、受信者は、メソッド実行の成功または失敗に関する詳細情報について、マルチステータス応答本文の内容を参照する必要があります。応答は、成功、部分的な成功、および失敗の状況でも使用できます。


22
207OPは望んでいるように見えますが、このマルチリクエストインワンアプローチを採用することはおそらく悪い考えであることを強調したいと思います。懸念がパフォーマンスであるなら、あなたは(HTTPベースのシステムはで素晴らしいですものです)クラウド・スタイル水平にスケーラブルなシステムのために設計するべきである
モニカ元に戻し

44
@DavidGrinberg私はこれ以上異議を唱えることはできませんでした。個々のアクションが安価な場合、リクエストを処理するオーバーヘッドはアクション自体よりもはるかに高くなる可能性があります。各行は個別の要求として送信されるため、データベース内の複数の行の更新が行ごとに個別のトランザクションを使用して行われるシナリオにつながる可能性があります。これはひどく非効率的であるだけでなく、必要な場合に複数の行をアトミックに更新することができないことも意味します。水平スケーリングは重要ですが、効率的な設計の代替ではありません。
カスペルド

4
よく言って、パフォーマンスやアトミシティなどのビジネスニーズの現実に無知な人々が行うREST API実装の典型的な問題を指摘しました。そのため、たとえば、OData REST仕様には、1回の呼び出しで複数の操作を行うバッチ形式があります-本当に必要です。
お知らせメールTomTom

8
@ TomTom、OP 原子性を必要としません。アトミック操作のステータスは1つしかないため、設計ははるかに簡単です。また、HTTP仕様では、HTTP / 2多重化を介したパフォーマンスのためのバッチ操作が許可されています(当然、HTTP / 2サポートも別の問題ですが、仕様では許可されています)。
ポールドレーパー

2
@David過去にHPCのいくつかの問題に取り組んだことがありますが、私の経験では、1バイトを送信するコストは1000を送信するコストとほぼ同じです(異なる転送メディアは確かに異なるオーバーヘッドを持っていますが、これより良いことはめったにありません)。したがって、パフォーマンスが懸念される場合、複数のリクエストを送信しても大きなオーバーヘッドが発生しないことがわかりません。同じ接続で複数のリクエストを多重化できる場合、この問題は解消されますが、私が理解しているように、それはHTTP / 2のオプションであり、サポートはかなり制限されています。それとも何か不足していますか?
-Voo

24

マルチステータスはオプションですが、すべてのリクエストが成功した場合は200(All is well)を返し、それ以外の場合はエラー(500または207)を返します。

標準ケースは通常200である必要があります-すべてが機能します。そして、クライアントはそれをチェックするだけでよいはずです。エラーケースが発生した場合にのみ、500(または207)を返すことができます。少なくとも1つのエラーの場合、207が有効な選択肢であると思いますが、パッケージ全体を1つのトランザクションと見なす場合は、500も送信できます。

常に207を送信しないのはなぜですか? -標準ケースは簡単で標準的である必要があるため。例外的なケースは例外的です。クライアントは、例外的な状況で必要な場合にのみ、応答本文を読み、さらに複雑な決定を行う必要があります。


6
私はまったく同意しません。サブリクエスト1と3が成功した場合、結合リソースを取得し、結合レスポンスを確認する必要があります。考慮すべきもう1つのケースがあります。応答= 200またはサブ応答1 = 200の場合、要求1は成功しました。応答= 200またはサブ応答2 = 200の場合、サブ応答をテストするだけでなく、リクエスト2が成功します。
gnasher729

1
@ gnasher729それは本当にアプリケーションに依存します。すべてのリクエストが成功すると、次のステップに(すべてが正常に)単純に流れるユーザー主導のアクションを想像します。-何かがうまくいかなかった場合(グローバル状態<= 200)、詳細なエラーを表示してワークフローを変更する必要があり、「handleAllOk」関数ではなく「handleMixedState」関数にいるため、サブリクエストごとに1回のチェックが必要です。 。
ファルコ

本当に意味に依存します。たとえば、取引戦略を制御するエンドポイントがあります。1回の実行で識別子のリストを「開始」できます。200を返すと、操作(処理)が成功したことを意味しますが、すべてが正常に開始されるわけではありません。これは、起動には数秒かかるため、即時の結果(開始される予定)には表示されません。マルチオペレーションコールのセマンティクスは、シナリオによって異なります。
トムトム

また、一般的な問題(データベースのダウンなど)が発生した場合は500を送信するため、サーバーは個々のリクエストも試行せず、一般的なエラーを返すだけです。-ユーザーには3つの異なる結果があるため、1。すべてOK、2。一般的な問題、何も機能しない、3。一部の要求が失敗しました。->これは通常、完全に異なるプログラムフローにつながります。
ファルコ

1
わかりました、それで1つのアプローチは次のとおりです:207 =各要求のための個々のステータス。その他:返されるステータスは各リクエストに適用されます。200、401、≥500のための理にかなって
gnasher729

13

1つのオプションは、常にステータスコード200を返し、JSONドキュメント本文で特定のエラーを返すことです。これは、一部のAPIの設計方法とまったく同じです(常にステータスコード200を返し、エラーを本体にディスパッチします)。さまざまなアプローチの詳細については、http://archive.oreilly.com/pub/post/restful_error_handling.htmlを参照してください


2
この場合、を使用し200すべてが正常であることを示し、リクエストが受信され有効であることを示し、JSONを使用してそのリクエストで発生したこと(トランザクションの結果)の詳細を提供するというアイデアが好きです
rickcnagy

4

neilsimp1は正しいと思いますが206 - Accepted、後でデータを送信して処理できるように、送信するデータを再設計することをお勧めします。おそらくコールバックを使用して。

単一のリクエストで複数のアクションを送信しようとすると、各アクションが独自の「ステータス」を持つ必要があるという事実が問題になります

CSVのインポートを確認します(OPの内容は実際にはわかりませんが、単純なバージョンです)。CSVをPOSTして206を取得します。その後、CSVをインポートし、行ごとのエラーを示すURLに対してGET(200)を使用してインポートのステータスを取得できます。

POST /imports/ -> 206
GET  /imports/1 -> 200
GET  /imports/1/errors -> 200 -> Has a list of errors

これと同じパターンを多くのバッチ最適化に適用できます

POST /operations/ -> 206
GET  /operations/1 -> 200
GET  /operations/1/errors -> 200 - > Has a list of errors.

POSTを処理するコードは、操作データの形式が有効であることを確認するだけで済みます。その後、しばらくしてから操作を実行できます。バックグラウンドワーカーでは、たとえば、より簡単にスケーリングできます。その後、いつでも操作のステータスを確認できます。ポーリングまたはコールバック、またはストリームなどを使用して、一連の操作がいつ完了するかを知る必要性に対処できます。


2

ここにはすでに多くの良い答えがありますが、一つの側面が欠けています:

クライアントが期待する契約は何ですか?

HTTPリターンコードは、少なくとも成功/失敗の区別を示す必要があるため、「貧しい人々の例外」の役割を果たします。200は「契約が完全に履行された」ことを意味し、4xxまたは5xxは履行に失敗したことを示します。

単純に、複数アクションリクエストのコントラクトは「すべてのタスクを実行する」ことを期待し、そのうちの1つが失敗した場合、リクエストは(完全に)成功しませんでした。通常、クライアントとしては200を「すべて大丈夫」と理解し、400および500ファミリーのコードは(部分的な)障害の結果について考えるように強制します。したがって、「すべてのタスクが完了した」場合は200を使用し、部分的な障害の場合は500に説明的な応答を加えます。

別の仮想コントラクトは、「すべてのアクションを実行する」ことです。その後、(一部の)アクションが失敗した場合、契約に完全に沿ったものになります。そのため、常に200に加えて、個々のタスクの成功/失敗情報を見つける結果ドキュメントを返すことになります。

それで、あなたが従いたい契約は何ですか?両方とも有効ですが、最初のもの(すべてが完了した場合にのみ200)は私にとってより直感的であり、典型的なソフトウェアパターンに沿っています。また、サービスがすべてのタスクを完了した(願わくば)大部分のケースでは、クライアントがそのケースを検出するのは簡単です。

最後の重要な側面:契約の決定をクライアントにどのように伝えますか?たとえば、Javaでは、「doAll()」や「tryToDoAll()」などのメソッド名を使用します。HTTPでは、それに応じてエンドポイントURLに名前を付けることができ、クライアント開発者が名前を見て、読んで、理解することを期待します(私はそれに賭けません)。驚きの少ない契約を選ぶもう1つの理由。


0

回答:

アクションごとに1つの要求を使用し、余分なオーバーヘッドを受け入れます。

ステータスコードは、1つの操作のステータスを示します。したがって、リクエストごとに1つの操作を行うことは理にかなっています。

複数の独立した操作により、要求/応答モデルとステータスコードの基になるプリンシパルが壊れます。あなたは自然と戦っています。

HTTP / 1.1およびHTTP / 2により、HTTP要求のオーバーヘッドがはるかに低くなりました。私は、独立したリクエストをバッチ処理することが望ましい状況はほとんどないと推測しています。


とはいえ、

(1)PATCH要求(RFC 5789)を使用して複数の変更を行うことができます。ただし、これには変更が独立していないことが必要です。それらはアトミックに適用されます(すべてまたは何も)。

(2)207 Multi-Statusコードを指摘している人もいます。ただし、これはHTTPの拡張であるWebDAV(RFC 4918)に対してのみ定義されています。

207(マルチステータス)ステータスコードは、複数の独立した操作のステータスを提供します(詳細については、セクション13を参照してください)。

...

マルチステータスレスポンスは、複数のステータスコードが適切な場合に、複数のリソースに関する情報を伝えます。「multistatus」ルート[XML]要素は、任意の順序で0個以上の「response」要素を保持し、それぞれに個別のリソースに関する情報が含まれます。

207 WebDAV XML応答は、非WebDAV APIのアヒルと同じくらい奇妙です。これをしないでください。


1
あなたは本質的に@AndersにXY問題があると断言しています。あなたは正しいかもしれませんが、残念ながら、彼が尋ねた質問(マルチアクションリクエストに使用するステータスコード)に実際に答えていないことを意味します。
アズアロン

2
@Azuaron、どの種類のベルトが子供をatingるのに最適ですか?「N / A」は許容できる答えだと思います。また、Andresはアイデアのリストに複数のリクエストを含めました。私はそのオプションを心からサポートしました。
ポールドレーパー

私は彼がそれをリストしたことをどういうわけか逃した。その場合、私はそれが馬鹿げた質問だと断言します、あなたの名誉!
アズアロン

1
@Azuaronこれは有効な答えだと絶対に思います。もし私がそれをすべて間違っているなら、誰かにそう言って欲しいです。そして、崖から最善の方法を教えてくれません。
アンデルス

1
Content-Typeヘッダーが適切に設定され、クライアントが要求したもの(Acceptヘッダー)と一致する限り、207応答でJSONを送信することを禁止しません。
ドルメン

0

1つのリクエストに複数のアクションが本当に必要な場合、バックエンドのトランザクションですべてのアクションをラップしてみませんか?そうすれば、それらはすべて成功するか、すべて失敗します。

APIを使用するクライアントとして、API呼び出しの完全な成功または失敗に対処できます。結果として生じる可能性のあるすべての状態を処理する必要があるため、部分的な成功を処理するのは困難です。


2
リクエストがアトミックである場合、彼はこの質問を投稿しなかったでしょう。
アンディ

@Andyたぶん、しかし、あなたは彼がそのようなデザインのすべての意味を考慮していると仮定することはできません。
ディーン

リクエストはアトミックであってはなりません。たとえば、#2が失敗した場合、#1によって行われた変更は保持されます。したがって、1つのトランザクションですべてをラップすることはオプションではありません。
アンデルス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.