Restコレクションでのページング
JSONドキュメントのコレクションへの直接のRESTインターフェイスを公開することに興味があります( CouchDB または Persevere と考えてください)。コレクションのサイズが大きい場合、コレクションルートでGET
操作を処理する方法を実行します。
例として、各行がドキュメントとして公開されているStackOverflowのQuestions
テーブルを公開しています(必ずしもそのようなテーブルがあるわけではなく、「ドキュメント」のかなりのコレクションの具体例です)。コレクションは、通常のCRUD API /db/questions
、GET /db/questions/XXX
、PUT /db/questions/XXX
が使用されているPOST /db/questions
で利用可能になります。コレクション全体を取得する標準的な方法はGET /db/questions
ですが、それが単純に各行をJSONオブジェクトとしてダンプする場合、かなり大きなダウンロードとサーバー側の多くの作業が行われます。
解決策は、もちろん、ページングです。 Dojoは、カスタム範囲単位Range
でitems
ヘッダーを使用する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経由で完全なコレクションを公開する最良の方法は何ですか?
私はあなた方の何人かに本当に同意しません。私は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がさらに表示されます。
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
応答のページが複数あり、コレクション全体を一度に提供したくない場合、それは複数の選択肢があることを意味しますか?
/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 Choices
、Link
ヘッダー、および初期の単純な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
パラメータは将来を意味します。
編集:
それについてもう少し考えた後、Rangeヘッダーがページネーションに適していないことに同意する傾向があります。ロジックであるRangeヘッダーは、アプリケーションではなくサーバーの応答を対象としています。 100メガバイトの結果を提供したが、サーバー(またはクライアント)は一度に1メガバイトしか処理できなかった場合、それがRangeヘッダーの目的です。
また、リソースのサブセットは独自のリソースである(リレーショナル代数と同様)ので、URLでの表現に値するという意見もあります。
したがって、基本的には、ヘッダーの使用に関する元の回答(下記)を取り消します。
あなたは多かれ少なかれあなた自身の質問に答えたと思います-content-rangeで200または206を返し、オプションでクエリパラメータを使用します。ユーザーエージェントとコンテンツタイプを嗅ぎ、それらに応じて、クエリパラメーターを確認します。それ以外の場合は、範囲ヘッダーが必要です。
あなたは本質的に矛盾する目標を持っています-人々がブラウザを使って探索することを許可します(簡単にカスタムヘッダーを許可しません)、または人々にヘッダーを設定できる特別なクライアントを使用させます(探索させません)。
リクエストに応じて特別なクライアントを提供するだけです。プレーンなブラウザのように見える場合は、ページをレンダリングして必要なヘッダーを設定する小さなajaxアプリを送信します。
もちろん、URLにこの種のものに必要なすべての状態を含めるべきかどうかについての議論もあります。一部の人は、ヘッダーを使用して範囲を指定することを「不安定」と見なすことができます。
余談ですが、サーバーが "Can-Specify:Header1、header2"ヘッダーで応答し、WebブラウザーがUIを表示して、ユーザーが必要に応じて値を入力できるようにすると良いでしょう。
ここでの本当の問題は、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する必要があります
これは非常に良い質問です。質問してくれてありがとう。あなたは私が何日もそれについて考えて過ごしたことに夢中になっていないことを私に確認しました。
Atom Feed Protocolのようなモデルを使用することを検討してください。コレクションの健全なHTTPモデルとそれらの操作方法(非常識はWebDAVを意味します)があるためです。
Atom Publishing Protocol はコレクションモデルを定義し、REST操作に加えて RFC 5005-Feed Paging and Archiving を使用してページングすることができます大きなコレクション。
Atom XMLからJSONコンテンツに切り替えても、アイデアに影響はありません。
rfc723x の公開により、未登録の範囲単位は、specの明示的な推奨に反します。 rfc72 (非推奨のrfc2616)を検討してください:
" 新しい範囲単位をIANAに登録する必要があります "( HTTP範囲単位レジストリ への参照とともに)。
Range
ヘッダーを検出し、Dojoが存在する場合はそれを模倣し、Atomが存在しない場合はそれを模倣します。アプリケーションからのRESTクエリに応答している場合、Range
ヘッダーでフォーマットされているはずです。カジュアルなブラウザに応答している場合は、ページングを返しますリンクを使用すると、ツールでコレクションを簡単に探索できます。
範囲ヘッダーの大きな問題の1つは、多くの企業プロキシがそれらを除外することです。代わりにクエリパラメータを使用することをお勧めします。
これを行う最善の方法は、クエリパラメータとして範囲を含めることであるように思えます。例:GET/db/questions /?date> mindate&date <maxdateクエリパラメータなしで/ db/questions /にGETすると、Location:/ db/questions /?query-parameters-to-retrieve-the-default-pageで303を返します。次に、コレクションに関する統計情報を取得するためにAPIを使用している人(たとえば、コレクション全体が必要な場合に使用するクエリパラメータ)を別のURLに指定します。
この目的のために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仕様の精神にあると思います。