レストコレクションでのページング


134

JSONドキュメントのコレクションに直接RESTインターフェースを公開することに興味があります(CouchDBまたはPersevereを考えてください)。私が直面している問題はGET、コレクションが大きい場合にコレクションルートでの操作を処理する方法です。

例として、Questions各行がドキュメントとして公開されているStackOverflowのテーブルを公開しています(必ずしもそのようなテーブルがあるわけではなく、「ドキュメント」のかなり大きなコレクションの具体例にすぎません)。コレクションはで利用可能になるでしょう/db/questions通常のCRUDのAPIを使用してGET /db/questions/XXXPUT /db/questions/XXXPOST /db/questions遊びです。コレクション全体を取得する標準的な方法は次のGET /db/questionsとおりですが、それが各行をJSONオブジェクトとして単純にダンプする場合、かなり大きなダウンロードとサーバー側での多くの作業が発生します。

もちろん、解決策はページングです。道場は、この問題を解決したJsonRestStore使用の巧妙なRFC2616に準拠した拡張を介してRangeカスタム範囲部とヘッダitems。結果は206 Partial Content、要求された範囲のみを返すです。クエリパラメータに対するこのアプローチの利点は、クエリ文字列(クエリなど)を残すことです(たとえばGET /db/questions/?score>200、エンコードされます%3E)。

このアプローチは、私が望む行動を完全にカバーしています。問題は、RFC 2616が206の応答について次のように指定していることです(強調は私のものです)。

要求が Rangeヘッダフィールド(含んでいなければなりませんセクション14.35を所望の範囲を示す)、及び場合-Rangeヘッダフィールド(含まれている可能性があり部14.27を要求する条件を作るために)。

これは、ヘッダーの標準的な使用法のコンテキストでは理にかなっていますが、私が206応答を、単純なクライアント/ランダムなユーザーの探索を処理するデフォルトにしたいので問題です。

私は解決策を探すために詳細にRFCを調査しましたが、私の解決策に不満があり、SOの問題への取り組みに興味があります。

私が持っていたアイデア:

  • 戻り値200Content-Rangeヘッダ!-私はこれが間違っているとは思いませんが、応答が部分的なコンテンツのみであることを示すより明白な指標が望ましいです。
  • 戻り値400 Range Required -必須ヘッダーに特別な400応答コードはないため、デフォルトのエラーを使用して手動で読み取る必要があります。これにより、Webブラウザー(またはRestyのような他のクライアント)での探索もより困難になります。
  • クエリパラメータを使用する -標準的なアプローチですが、永続的なクエリを許可すると、クエリの名前空間に割り込むことができます。
  • 戻るだけ206-ほとんどのクライアントはおかしくならないだろうと思うが、RFCのMUSTには反対したくない
  • スペックを拡張!Return266 Partial Content -206とまったく同じように動作しますが、含まれてはならないリクエストに応答しますRangeヘッダーをます。私は266が衝突の問題に遭遇してはならないほど十分に高いことを理解しています。それは私には理にかなっていますが、これがタブーと見なされるかどうかははっきりしません。

これはかなり一般的な問題だと思います。私や他の誰かがホイールを再発明しないように、これをある種の事実上の方法で実行してもらいたいと思います。

コレクションが大きい場合にHTTP経由で完全なコレクションを公開する最良の方法は何ですか?


21
うわー、これは以前に真剣な考えがあった質問の良い例です。
Heiko Rupp 2013


1
Rangeヘッダーを使用するDojoのアプローチに関する限り、Accept-Rangesは拡張を可能にしますが、私が知る限り、RangeのEBNFは次のことを行いません:tools.ietf.org/html/rfc2616#section-14.35.2。仕様はRange = "Range" ":" ranges-specifiertools.ietf.org / html / rfc2616#section - 14.35.1の後者が単に「byte-ranges-specifier」として記述されている場所を示しています。これは、文字列「bytes」として定義されている「bytes-unit」で始まる必要があります。 」
Brett Zamir 2013年

2
Content-Rangeヘッダは、(ダウンロード時などの大きなファイルをアップロードする際、要求と一緒に使用、または応答のためにすることができる)身体に適用されます。Rangeヘッダは、特定の範囲を要求するために使用されます。ヘッダーがリクエストに含まれていた206ときに応答する必要Rangeがあります。含まれていない場合でも、応答にはContent-Rangeヘッダーが含まれる場合がありますが、応答コードはになります200。このヘッダーは実際にはページングに理想的です。
Stijn de Witt

しかし、RFC 2616自体は、「HTTP / 1.1実装は、他の単位を使用して指定された範囲を無視する可能性がある」と述べています。それで、ページネーションにRangeヘッダーを使用するのは良い習慣ですか?cozそれは相互運用性を危うくするかもしれません。
chetan choulwar

回答:


23

私の直感は、HTTP範囲拡張はユースケース用に設計されていないため、試してはいけないということです。部分応答はを意味し206206クライアントが要求した場合にのみ送信する必要があります。

Atomでの使用など、別のアプローチを検討することをお勧めします(設計による表現が部分的であり、ステータスと共に返され、場合によっては200ページングリンクが返されます)。RFC 4287およびRFC 5005を参照してください。


14
Dojoの使用法は完全に仕様の範囲内です。サーバーがitems範囲の単位を理解できない場合は、完全な応答を返します。私はAtomに精通していますが、それはRestページングの一般的な解決策ではありません。これは単一のケースのソリューションではなく、一般的なソリューションの詳細です。すべてのドキュメント/コレクションがAtomモデルに適合するわけではなく、必要でない限り強制する理由はありません。
Karl Guertin、2009年

1
@KarlGuertin同意する。コミュニティの多くが実際に採用RangeContent-Range、ページングを目的としているため、これは受け入れられる回答です。
Stijn de Witt

34

私はあなたたちの一部に本当に同意しません。私は自分のRESTサービスのこの機能に何週間も取り組んできました。私がやったことは本当に簡単です。私のソリューションは、RESTの人々がコレクションと呼ぶものに対してのみ意味があります。

クライアントは、コレクションのどの部分が必要かを示す「範囲」ヘッダーを含める必要があります。そうでない場合、要求されたコレクションが大きすぎて1回の往復で取得できない場合は、413 REQUESTED ENTITY TOO LARGEエラーを処理する準備ができている必要があります。

サーバーは206 PARTIAL CONTENT応答を送信します。送信されたリソースの部分を指定するContent-Rangeヘッダーと、コレクションの現在のバージョンを識別するETagヘッダーが含まれます。私は通常FacebookのようなETag {last_modification_timestamp}-{resource_id}を使用しており、コレクションのETagは、コレクションに含まれる最後に変更されたリソースのETagであると考えています。

コレクションの特定の部分をリクエストするには、クライアントは「Range」ヘッダーを使用し、同じコレクションの他の部分を取得するために以前に実行されたリクエストから取得したコレクションのETagを「If-Match」ヘッダーに入力する必要があります。したがって、サーバーは、要求された部分を送信する前に、コレクションが変更されていないことを確認できます。より新しいバージョンが存在する場合、412 PRECONDITION FAILED応答が返され、クライアントにコレクションを最初から取得するように要求します。これは、現在要求されているパーツの前または後に一部のリソースが追加または削除された可能性があるために必要です。

ETag / If-MatchをLast-Modified / If-Unmodified-Sinceと組み合わせて使用​​して、キャッシュを最適化します。ブラウザとプロキシは、キャッシュアルゴリズムをこれらの1つまたは両方に依存する場合があります。

検索/フィルタークエリを含める場合を除いて、URLはクリーンである必要があると思います。考えてみれば、検索はコレクションの部分的なビューにすぎません。cars / search?q = BMWタイプのURLの代わりに、より多くのcars?manufacturer = BMWが表示されます。


「416リクエストされた範囲は満足できません」または「413」リクエストエンティティは大きすぎますか?

1
@MohamedとはIf-Unmodified-Since、EタグのバリアントIf-Matchに対応しているのではなく、という意味だと思いますIf-Modified-Since。とはいえ、ユースケースによっては、この制約を削除することも検討してください。たとえば、「最初から」スタイルのコレクションのように上から大きくなるコレクションがあるとします。そのコレクションがリクエストの間に変更された場合に発生する可能性のある最悪の事態は、コレクションをページングするユーザーがエントリを2回表示することです。(それ自体も有用な情報です:コレクションが変更されたことをユーザーに
通知します

20
413は「Request Entity Too Large」ではなく、「Request Entity Too Large」ではありません。これは、たとえばファイルのアップロード時など、リクエストのサイズがサーバーが処理するよりも大きいことを意味します。そのため、これを使用することは完全に適切であるとは思われません。
user247702 2013年

@Mohamedこれは古い質問ですが、コレクションのETagがコレクションに含まれる最後に変更されたリソースのETagである場合、コレクション内の1つのリソースを変更するときに使用する必要があるIf-Matchヘッダーの値はどれですか。クライアントがリソースの最後の状態を確認しなくても、クライアントはリソースを変更できるため、コレクションで返されたETagの値を使用することは間違っています。
Mickael Marrache、2015

8
を使用することに強く反対し413ます。これは、サイズが原因でサーバーが受け入れることを拒否するものをクライアントが送信していることを意味するエラーコードです。その逆ではありません!tools.ietf.org/html/rfc7231#section-6.5.11を参照してください(リクエストペイロードとあり、レスポンスペイロードではないことに注意してください)。
exhuma

7

あなたはまだ返すことができますAccept-RangesContent-Rangesして200応答コード。これらの2つの応答ヘッダーは、同じ情報を推測するのに十分な情報を提供します206応答コードが明示的に提供提供します。

私はRangeページネーションに使用し、単純に200プレーンを返すようにしますGET

これは100%RESTful 感じ、ブラウジングを難しくしません。

編集:私はこれについてブログ投稿を書きました:http : //otac0n.com/blog/2012/11/21/range-header-i-choose-you.html


5

回答のページが複数あり、一度にコレクション全体を提供したくない場合、それは複数の選択肢があることを意味しますか?

要求に応じて /db/questions復帰、300 Multiple ChoicesLinkURLのリストとそれぞれのページだけでなく、JSONオブジェクトまたはHTMLページを取得する方法を指定のヘッダー。

Link: <>; rel="http://paged.collection.example/relation/paged"
Link: <>; rel="http://paged.collection.example/relation/paged"
...

Link結果のページごとに1つのヘッダーがあり(空の文字列は現在のURLを意味し、URLは各ページで同じであり、異なる範囲でアクセスしただけです)、関係は次のカスタムリンクとして定義されますLink仕様。この関係はあなたの習慣266やあなたの違反を説明するでしょう206。いずれにしても、すべての例でクライアントを理解する必要があるため、これらのヘッダーは機械で読み取り可能なバージョンです。

(「範囲」ルート2xxを使用する場合は、ここで説明したように、独自の戻りコードがここでの最良の動作になると思います。これは、アプリケーションに対して行う必要があります。[HTTPステータスコードは拡張可能です。 "]、そしてあなたには正当な理由があります。)

300 Multiple Choicesまた、ユーザーエージェントが選択する方法を本文に提供する必要があると述べています。クライアントが理解している場合は、Linkヘッダーを使用する必要があります。ユーザーが手動で閲覧している場合、URLに基​​づいて特定のページのレンダリングを処理できる特別な「ページングされた」ルートリソースへのリンクが含まれているHTMLページでしょうか。 /humanpage/1/db/questionsまたはそのような恐ろしい何か?


Richard Levasseurの投稿へのコメントは、追加のオプションを思い出させます: Acceptヘッダー(セクション14.1)の。oEmbed仕様が出たとき、なぜそれが完全にHTTPを使用して行われていないのか疑問に思い、それらを使用して代替案を作成しました。

キープ300 Multiple ChoicesLink初期の素朴なHTTPのためのヘッダとHTMLページをGET新しいページング関係はの使用を定義していますが、むしろ、使用範囲よりも、Acceptヘッダを。その後のHTTPリクエストは次のようになります。

GET /db/questions HTTP/1.1
Host: paged.collection.example
Accept: application/json;PagingSpec=1.0;page=1

Acceptヘッダは、あなたがそのタイプ(ページ番号)のための許容可能なコンテンツタイプ(あなたのJSONリターン)、プラス拡張可能なパラメータを定義することができます。oEmbedの書き込みからのメモをリッフィングします(ここにリンクすることはできません。プロファイルにリストします)。非常に明示的で、pageパラメーターの意味を再定義する必要がある場合に備えて、仕様/関係バージョンをここに指定します。将来は。


1
+1リンクヘッダーですが、共通のfirst、prev、next、last relsだけでなく、RFC5005のprev-archive、next-archive、currentもお勧めします。
ジョセフ・ホルステン

> / db / questionsへのリクエストで、各ページに到達する方法を指定するリンクヘッダー付きの300マルチプルチョイスを返します[..] これ(およびほとんどの純粋なRESTデザイン)の問題は、レイテンシのために強制終了することです。目標は、ネットワーク要求を最小限に抑えることです。その最初のリクエストは結果をもたらすはずであり、最終的に必要なデータを提供するより多くのリクエストへのリンクではありません。
Stijn de Witt

4

編集:

もう少し考えた後、私はRangeヘッダーがページネーションに適していないことに同意する傾向があります。つまり、Rangeヘッダーは、アプリケーションではなくサーバーの応答を対象としています。100メガバイトの結果を提供したが、サーバー(またはクライアント)が一度に1メガバイトしか処理できなかった場合、それがRangeヘッダーの目的です。

また、リソースのサブセットはそれ自身のリソース(リレーショナル代数と同様)であるため、URLでの表現に値すると私は考えています。

したがって、基本的には、ヘッダーの使用に関する元の回答(下記)を取り消します。


あなたは多かれ少なかれあなた自身の質問に答えたと思います-content-rangeで200または206を返し、オプションでクエリパラメーターを使用します。ユーザーエージェントとコンテンツタイプを探り、それらに応じて、クエリパラメーターを確認します。それ以外の場合は、範囲ヘッダーが必要です。

あなたは本質的に相反する目標を持っています-人々にブラウザを使って探検させ(これは簡単にカスタムヘッダーを許可しません)、あるいはヘッダを設定できる特別なクライアントを使わせます(探求させません)。

リクエストに応じて、特別なクライアントを提供することもできます。プレーンなブラウザのように見える場合は、ページをレンダリングして必要なヘッダーを設定する小さなajaxアプリを送信してください。

もちろん、URLがこの種のものに必要なすべての状態を含むべきかどうかについての議論もあります。ヘッダーを使用して範囲を指定することは、一部の人にとって「落ち着きがない」と考えることができます。

余談ですが、サーバーが "Can-Specify:Header1、header2"ヘッダーで応答でき、WebブラウザーがUIを表示して、必要に応じてユーザーが値を入力できると便利です。


ご返信ありがとうございます。私はそのトピックについて考えましたが、セカンドオピニオンを得ることを望んでいました。ヘッダー引数へのポインターが必要ですか?
Karl Guertin、2009年

これが私がブックマークした唯一のものです(コメントの説明を参照してください):barelyenough.org/blog/2008/05/versioning-rest-web-services 別のサイトは、Rubyの.json、.xml、.whateverの使用を中心に決定しましたリクエストのコンテンツタイプ。いくつかの例:*言語-URLに配置すると、リンクを別の国に送信すると、間違った言語で表示されます。*ページネーション-ヘッダーに入れると、見ているものに人をリンクできません
Richard Levasseur

*コンテンツタイプ:言語とページネーションの問題の組み合わせ-URLに問題がある場合、クライアントがそのコンテンツタイプをサポートしていない場合はどうなりますか(例:.ajaxおよび.html拡張子)?逆に、URLにそのコンテンツタイプがないと、同じ表現が提供されることを保証できません。「新しいajaxサイト!example.com/cool.ajax」と「ここのクールな記事:example.com/article.ajax#id=123」。
Richard Levasseur

2
IMO、それがURLに含まれるかどうかは、それが何であるかに依存します。私の一般的なルールは、具体的なリソース(特定の状態のリソース、リソースの選択、または個別の結果)を識別する場合、URLに入るということです。検索クエリ、ページ分割、および安らかなトランザクションは、この良い例です。抽象表現を具象表現に変換するために必要なものがあれば、それはヘッダーに入ります。auth infoとcontent-typeはこの良い例です。
Richard Levasseur

URLのクエリ文字列は、指定されているリソースをクエリするためのオプションと考えています。
wprl 2013年

3

コレクションのまともなHTTPモデルとそれらの操作方法(非常識はWebDAVを意味します)があるため、Atom Feed Protocolのようなモデルの使用を検討する場合があります。

コレクションモデルとREST操作を定義するAtom Publishing Protocolに加えて、RFC 5005-Feed Paging and Archivingを使用できますをて大きなコレクションを。

Atom XMLからJSONコンテンツに切り替えても、アイデアに影響はありません。


3

ここでの本当の問題は、413-Requested Entity Too Largeに直面したときに自動リダイレクトを実行する方法を指示する仕様が何もないということです。

私は最近、これと同じ問題に苦しんでいて、RESTful Webサービスでインスピレーションを探しました本。個人的には、ヘッダー要件のため、206は適切ではないと思います。私の考えも私を300に導いたが、それは別のMIMEタイプの方が多いと思ったので、RichardsonとRubyが377ページの付録Bでこの件について述べていることを調べた。表現し、200で送り返します。基本的には、300である必要があるという概念を無視します。

それはまた、atomから持っている次のリソースへのリンクの概念でジャイブします。私が実装したソリューションは、「次の」キーと「前の」キーを、返送しているjsonマップに追加して、それで処理することでした。

後で私が考え始めたのは、おそらく307-Temporary Redirectを/ db / questions / 1,25のようなリンクに送信することです。これにより、元のURIは正規のリソース名のままになりますが、適切に名前が付けられた従属リソース。これは413で見たい動作ですが、307は適切な妥協案です。実際にはまだコードでこれを試していません。さらに良いのは、リダイレクトが、最近質問された質問の実際のIDを含むURLにリダイレクトすることです。たとえば、各質問に整数IDがあり、システムに100の質問があり、最近の10件を表示したい場合、/ db / questionsへのリクエストは/ db / questions / 100,91に307されます。

これは非常に良い質問です。質問していただきありがとうございます。あなたは私がそれについて何日も費やしてきたので気が狂わないことを私に確認しました。


この点で、303は307よりも優れています。307は、元のURLがクライアントの期待どおりにすぐに応答し始めることを意味します。
Nicholas Shanks、2012年

RFC 7231は HTTPステータスコード413をPayload Too Largeとして参照し、このコードをリクエストサイズと関連付けます。潜在的なレスポンスサイズではありません。
ビーウルフ2018年

1

Rangeヘッダーを検出し、存在する場合はDojoを模倣し、存在しない場合はAtomを模倣できます。これはユースケースをきれいに分けているように私には思えます。アプリケーションからのRESTクエリに応答する場合は、Rangeヘッダーでフォーマットされていることを期待しています。カジュアルなブラウザーに応答している場合、ページングリンクを返すと、ツールでコレクションを簡単に探索できます。


1

範囲ヘッダーの大きな問題の1つは、多くの企業プロキシがそれらを除外することです。代わりにクエリパラメータを使用することをお勧めします。



0

これを行う最善の方法は、クエリパラメータとして範囲を含めることです。たとえば、GET / db / questions /?date> mindate&date <maxdateです。クエリパラメータなしで/ db / questions /にGETすると、場所を指定して303を返します :/ db / questions /?query-parameters-to-retrieve-the-default-page。次に、APIを使用してコレクションに関する統計情報を取得する別のURLを指定します(たとえば、コレクション全体が必要な場合に使用するクエリパラメーター)。


0

この目的でRangeヘッダーを使用することは可能ですが、それが意図したことではないと思います。不安定な接続を処理するとともにデータを制限するように設計されているようです(何かが欠落している場合やサイズが大きすぎて処理できない場合、クライアントは要求の一部を要求できます)。あなたは、コミュニケーション層で他の目的に使用される可能性のあるものにページネーションをハッキングしています。ページネーションを処理する「適切な」方法は、返す型を使用することです。質問オブジェクトを返すのではなく、代わりに新しいタイプを返す必要があります。

したがって、質問が次のような場合:

<questions> <question index=1></question> <question index=2></question> ... </questions>

新しいタイプは次のようになります。

<questionPage> <startIndex>50</startIndex> <returnedCount>10</returnedCount> <totalCount>1203</totalCount> <questions> <question index=50></question> <question index=51></question> .. </questions> <questionPage>

もちろん、メディアタイプを制御するので、「ページ」をニーズに合ったフォーマットにすることができます。汎用的なものを作成する場合は、クライアント上に単一のパーサーを使用して、すべてのタイプで同じページングを処理できます。それは、他の何かのためにRangeパラメーターを曖昧にするのではなく、HTTP仕様の精神にあると思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.