web-dev-qa-db-ja.com

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

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

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

解決策は、もちろん、ページングです。 Dojoは、カスタム範囲単位Rangeitemsヘッダーを使用するRFC2616準拠の巧妙な拡張機能により、この問題を JsonRestStore で解決しました。結果は、要求された範囲のみを返す206 Partial Contentです。クエリパラメータに対するこのアプローチの利点は、クエリ文字列を...クエリに残すことです(例:GET /db/questions/?score>200またはその他、そうです、エンコードされます%3E)。

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

requestは、必要な範囲を示すRangeヘッダーフィールド( section 14.35 )を含まなければならず、If-要求を条件付きにするための範囲ヘッダーフィールド( セクション14.27 )。

これは、ヘッダーの標準的な使用状況では意味がありますが、素朴なクライアントやランダムな人が探検するために、206応答をデフォルトにしたいので問題です。

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

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

  • 200ヘッダーでContent-Rangeを返します!-これは間違っているとは思いませんが、応答は部分的なコンテンツのみです。
  • Return 400 Range Required-必要なヘッダーに特別な400応答コードがないため、デフォルトのエラーを使用して手動で読み取る必要があります。また、これにより、Webブラウザー(またはRestyのような他のクライアント)を介した探索がより困難になります。
  • クエリパラメータを使用する-標準的なアプローチですが、永続的なクエリを許可したいと考えており、これによりクエリのネームスペースが削減されます。
  • _206!を返すだけです!-ほとんどのクライアントは驚かないと思いますが、RFCのMUSTに反対したくない
  • 仕様を拡張してください! Return 266 Partial Content-206とまったく同じように動作しますが、Rangeヘッダーを含んではならないリクエストに応答します。私は266が衝突の問題にぶつからないように十分に高いと考えており、それは私には理にかなっていますが、これがタブーと見なされるかどうかはわかりません。

私はこれはかなり一般的な問題だと思うので、これを一種の事実上のやり方で見たいので、私や他の誰かが車輪を再発明することはありません。

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

127
Karl Guertin

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

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

22
Julian Reschke

私はあなた方の何人かに本当に同意しません。私はRESTサービスのこの機能に何週間も取り組んできました。私がやったことは本当に簡単です。私のソリューションはREST =人々はコレクションを呼び出します。

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

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

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

ETag/If-MatchとLast-Modified/If-Unmodified-Sinceを併用してキャッシュを最適化します。ブラウザとプロキシは、キャッシングアルゴリズムをそれらの一方または両方に依存する場合があります。

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

34
Mohamed

Accept-Ranges応答コードでContent-Rangesおよび200を引き続き返すことができます。これらの2つの応答ヘッダーは、206応答コードが明示的に提供するのと同じ情報をinferするのに十分な情報を提供します。

ページネーションにRangeを使用し、単純なGETに対して200を返すようにします。

これは、100%RESTfulであり、はブラウジングをこれ以上難しくしないと感じています。

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

6
John Gietzen

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

/db/questions、リターン300 Multiple Choices各ページへのアクセス方法を指定するLinkヘッダーと、URLのリストを含むJSONオブジェクトまたはHTMLページ。

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

結果の各ページに1つのLinkヘッダーがあり(空の文字列は現在のURLを意味し、URLは各ページで同じで、異なる範囲でアクセスされるだけです)、関係は-として定義されます 今後のLink仕様ごとにカスタム1 。この関係により、カスタム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 GETのHTMLページですが、範囲を使用するのではなく、新しいページング関係でAcceptヘッダーの使用を定義します。後続のHTTPリクエストは次のようになります。

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

Acceptヘッダーを使用すると、受け入れ可能なコンテンツタイプ(JSONリターン)に加えて、そのタイプ(ページ番号)の拡張可能なパラメーターを定義できます。私のoEmbedの記事からメモをリフします(ここにリンクできません。プロファイルにリストします)。pageパラメータは将来を意味します。

5
Vitorio

編集:

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

また、リソースのサブセットは独自のリソースである(リレーショナル代数と同様)ので、URLでの表現に値するという意見もあります。

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


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

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

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

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

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

4

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

私は最近、この同じ問題に苦しんでいて、RESTful Web Services本でインスピレーションを探しました。個人的には、ヘッダーの要件のために206は適切ではないと思います。私の考えも私を300に導きましたが、それはさまざまなmimeタイプに向いていると思ったので、リチャードソンとRubyが377ページの付録Bの主題について何を言っていたのかを調べました。彼らは、サーバーが優先表現を選択し、200でそれを送り返すことを提案します。基本的には300であるという概念を無視します。

それはまた、私たちがアトムから持っている次のリソースへのリンクの概念と一致します。私が実装した解決策は、「次の」キーと「前の」キーを、私が送り返していたjsonマップに追加し、それを完了することでした。

後で、多分すべきことは、307-/ db/questions/1,25のようなリンクへの一時的なリダイレクトを送信することであると考え始めました。これは、元のURIを正規のリソース名として残しますが、適切な名前の従属リソース。これは413から見たい動作ですが、307は良い妥協点のようです。ただし、実際にはまだコードでこれを試していません。さらに良いのは、最近の質問の実際のIDを含むURLにリダイレクトするリダイレクトです。たとえば、各質問に整数IDがあり、システムに100個の質問があり、最新の10個を表示する場合、/ db/questionsへのリクエストは/ db/questions/100,91に307'dする必要があります

これは非常に良い質問です。質問してくれてありがとう。あなたは私が何日もそれについて考えて過ごしたことに夢中になっていないことを私に確認しました。

3
stinkymatt

Atom Feed Protocolのようなモデルを使用することを検討してください。コレクションの健全なHTTPモデルとそれらの操作方法(非常識はWebDAVを意味します)があるためです。

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

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

3
dajobe

rfc723x の公開により、未登録の範囲単位は、specの明示的な推奨に反します。 rfc72 (非推奨のrfc2616)を検討してください:

" 新しい範囲単位をIANAに登録する必要があります "( HTTP範囲単位レジストリ への参照とともに)。

1
Sam

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

1
Greg

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

1
user64141

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

0
Dathan

この目的のために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>

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

0
jeremyh