Ich bin daran interessiert, eine direkte REST-Schnittstelle für Sammlungen von JSON-Dokumenten bereitzustellen (denken Sie an CouchDB oder Persevere ). Das Problem, auf das ich stoße, ist, wie der GET
Vorgang im Sammlungsstamm behandelt wird, wenn die Sammlung groß ist.
Stellen Sie sich als Beispiel vor, ich mache die Questions
Tabelle von StackOverflow verfügbar, in der jede Zeile als Dokument verfügbar gemacht wird (nicht, dass es unbedingt eine solche Tabelle gibt, sondern nur ein konkretes Beispiel für eine umfangreiche Sammlung von 'Dokumenten'). Die Sammlung würde zur Verfügung gestellt wird /db/questions
mit dem üblichen CRUD api GET /db/questions/XXX
, PUT /db/questions/XXX
, POST /db/questions
ist im Spiel. Der Standardweg, um die gesamte Sammlung GET /db/questions
abzurufen, besteht darin, aber wenn dadurch jede Zeile naiv als JSON-Objekt ausgegeben wird, erhalten Sie einen ziemlich umfangreichen Download und viel Arbeit seitens des Servers.
Die Lösung ist natürlich Paging. Dojo hat dieses Problem in seinem JsonRestStore über eine clevere RFC2616-kompatible Erweiterung der Verwendung des Range
Headers mit einer benutzerdefinierten Bereichseinheit gelöst items
. Das Ergebnis ist ein 206 Partial Content
, das nur den angeforderten Bereich zurückgibt. Der Vorteil dieses Ansatzes gegenüber einem Abfrageparameter besteht darin, dass die GET /db/questions/?score>200
Abfragezeichenfolge für ... Abfragen verbleibt (z. B. oder so, und ja, das würde codiert %3E
).
Dieser Ansatz deckt das gewünschte Verhalten vollständig ab. Das Problem ist, dass RFC 2616 dies bei einer 206-Antwort angibt (Hervorhebung von mir):
Die Anforderung MUSS ein Bereichs-Header-Feld ( Abschnitt 14.35 ) enthalten, das den gewünschten Bereich angibt, und KANN ein If-Range-Header-Feld ( Abschnitt 14.27 ) enthalten, um die Anforderung bedingt zu machen.
Dies ist im Zusammenhang mit der Standardverwendung des Headers sinnvoll, stellt jedoch ein Problem dar, da ich möchte, dass die Antwort 206 die Standardeinstellung für naive Clients / zufällige Personen ist.
Ich habe den RFC im Detail durchgesehen und nach einer Lösung gesucht, war aber mit meinen Lösungen unzufrieden und bin daran interessiert, dass SO das Problem aufgreift.
Ideen, die ich hatte:
- Kehre
200
mit einemContent-Range
Header zurück! - Ich denke nicht, dass dies falsch ist, aber ich würde es vorziehen, wenn ein offensichtlicherer Indikator dafür ist, dass die Antwort nur Teilinhalt ist. - Rückgabe
400 Range Required
- Es gibt keinen speziellen 400-Antwortcode für die erforderlichen Header, daher muss der Standardfehler von Hand verwendet und gelesen werden. Dies erschwert auch die Erkundung über einen Webbrowser (oder einen anderen Client wie Resty). - Verwenden Sie einen Abfrageparameter - Der Standardansatz, aber ich hoffe, Abfragen a la Persevere zuzulassen, und dies schneidet in den Abfrage-Namespace.
- Komm einfach zurück
206
! - Ich denke, die meisten Kunden würden nicht ausflippen, aber ich würde lieber nicht gegen ein MUSS im RFC vorgehen - Erweitern Sie die Spezifikation! Return
266 Partial Content
- Verhält sich genau wie 206, ist jedoch eine Antwort auf eine Anfrage, die denRange
Header NICHT enthalten darf. Ich denke, dass 266 hoch genug ist, um nicht auf Kollisionsprobleme zu stoßen, und es macht für mich Sinn, aber ich bin mir nicht sicher, ob dies als tabu angesehen wird oder nicht.
Ich würde denken, dass dies ein ziemlich häufiges Problem ist und ich würde es gerne de facto sehen, damit ich oder jemand anderes das Rad nicht neu erfinden.
Was ist der beste Weg, um eine vollständige Sammlung über HTTP verfügbar zu machen, wenn die Sammlung groß ist?
quelle
Range = "Range" ":" ranges-specifier
wo letztere in tools.ietf.org/html/rfc2616#section-14.35.1 lediglich als " Bytebereichs -Spezifizierer" beschrieben wird, der mit "Byte-Einheit" beginnen muss, die als Zeichenfolge "Bytes" definiert ist ".Content-Range
Header gilt für den Body (kann mit Anfrage beim Hochladen großer Dateien usw. oder als Antwort beim Herunterladen verwendet werden). DerRange
Header wird verwendet, um einen bestimmten Bereich anzufordern. Man sollte antworten,206
wann derRange
Header in der Anfrage enthalten war. Wenn dies nicht derContent-Range
Fall ist , enthält die Antwort möglicherweise noch einen Header, der Antwortcode sollte jedoch lauten200
. Dieser Header scheint eigentlich ideal zum Blättern zu sein.Antworten:
Mein Bauchgefühl ist, dass die HTTP-Bereichserweiterungen nicht für Ihren Anwendungsfall entwickelt wurden und Sie es daher nicht versuchen sollten. Eine teilweise Antwort impliziert
206
und206
muss nur gesendet werden, wenn der Client danach gefragt hat.Möglicherweise möchten Sie einen anderen Ansatz in Betracht ziehen, z. B. den in Atom verwendeten (bei dem die Darstellung aufgrund des Entwurfs teilweise sein kann und mit einem Status
200
und möglicherweise Paging-Links zurückgegeben wird). Siehe RFC 4287 und RFC 5005 .quelle
items
Bereichseinheit nicht versteht , gibt er eine vollständige Antwort zurück. Ich bin mit Atom vertraut, aber das ist nicht die allgemeine Lösung für Rest Paging. Dies ist keine Lösung für einen Einzelfall, sondern eher die allgemeine Lösung. Nicht alle Dokumente / Sammlungen passen zum Atom-Modell, und es gibt keinen Grund, es zu erzwingen, es sei denn, dies ist erforderlich.Range
undContent-Range
zu Paging-Zwecken.Ich stimme einigen von euch nicht wirklich zu. Ich arbeite seit Wochen an diesen Funktionen für meinen REST-Service. Was ich letztendlich gemacht habe, ist wirklich einfach. Meine Lösung macht nur Sinn für das, was REST-Leute eine Sammlung nennen.
Der Client MUSS einen "Range" -Header einfügen, um anzugeben, welchen Teil der Sammlung er benötigt, oder auf andere Weise bereit sein, einen 413 REQUESTED ENTITY TOO LARGE-Fehler zu behandeln, wenn die angeforderte Sammlung zu groß ist, um in einem einzigen Roundtrip abgerufen zu werden.
Der Server sendet eine 206 PARTIAL CONTENT-Antwort, wobei der Content-Range-Header angibt, welcher Teil der Ressource gesendet wurde, und ein ETag-Header, um die aktuelle Version der Sammlung zu identifizieren. Normalerweise verwende ich ein Facebook-ähnliches ETag {last_modification_timestamp} - {resource_id} und bin der Meinung, dass das ETag einer Sammlung das der zuletzt geänderten Ressource ist, die es enthält.
Um einen bestimmten Teil einer Sammlung anzufordern, MUSS der Client den Header "Range" verwenden und den Header "If-Match" mit dem ETag der Sammlung füllen, das aus zuvor durchgeführten Anforderungen zum Erwerb anderer Teile derselben Sammlung erhalten wurde. Der Server kann daher überprüfen, ob sich die Sammlung nicht geändert hat, bevor der angeforderte Teil gesendet wird. Wenn eine neuere Version vorhanden ist, wird eine Antwort 412 PRECONDITION FAILED zurückgegeben, um den Client aufzufordern, die Sammlung von Grund auf neu abzurufen. Dies ist erforderlich, da dies bedeuten kann, dass einige Ressourcen vor oder nach dem aktuell angeforderten Teil hinzugefügt oder entfernt wurden.
Ich verwende ETag / If-Match zusammen mit Last-Modified / If-Unmodified-Since, um den Cache zu optimieren. Browser und Proxys verlassen sich möglicherweise auf einen oder beide von ihnen für ihre Caching-Algorithmen.
Ich denke, dass eine URL sauber sein sollte, es sei denn, sie soll eine Such- / Filterabfrage enthalten. Wenn Sie darüber nachdenken, ist eine Suche nichts anderes als eine Teilansicht einer Sammlung. Anstelle der Autos / Suche? Q = BMW Typ von URLs sollten wir mehr Autos sehen? Hersteller = BMW.
quelle
If-Unmodified-Since
, wasIf-Match
eher der E-Tag Variante entspricht alsIf-Modified-Since
. Abhängig von Ihrem Anwendungsfall können Sie diese Einschränkung jedoch auch entfernen. Angenommen, Sie haben eine Sammlung, die nur von oben wächst (wie eine Sammlung im "neuesten ersten" Stil). Das Schlimmste, was passieren kann, wenn sich diese Sammlung zwischen Anforderungen ändert, ist, dass ein Benutzer, der eine Sammlung durchblättert, Einträge zweimal sieht. (Was an sich auch eine nützliche Information ist: Es teilt dem Benutzer mit, dass sich die Sammlung geändert hat)413
. Dies ist ein Fehlercode, der bedeutet, dass der Client etwas sendet , das der Server aufgrund seiner Größe nicht akzeptiert. Nicht umgekehrt! Siehe tools.ietf.org/html/rfc7231#section-6.5.11 (beachten Sie, dass dort Anforderungsnutzlast steht . Nicht Antwortnutzlast )!Sie können immer noch zurückkehren
Accept-Ranges
undContent-Ranges
mit einem200
Antwortcode. Diese beiden Antwortheader geben Ihnen genügend Informationen, um auf dieselben Informationen zu schließen, die ein206
Antwortcode explizit bereitstellt.Ich würde es
Range
für die Paginierung verwenden und es einfach200
für eine Ebene zurückgeben lassenGET
.Dies fühlt sich zu 100% RESTful an und erschwert das Surfen nicht.
Bearbeiten: Ich habe einen Blog-Beitrag dazu geschrieben: http://otac0n.com/blog/2012/11/21/range-header-i-choose-you.html
quelle
Wenn es mehr als eine Seite mit Antworten gibt und Sie nicht die gesamte Sammlung auf einmal anbieten möchten, bedeutet dies, dass es mehrere Möglichkeiten gibt?
Bei einer Anfrage an
/db/questions
kehren Sie300 Multiple Choices
mitLink
Überschriften zurück, die angeben, wie zu jeder Seite gelangt werden soll, sowie mit einem JSON-Objekt oder einer HTML-Seite mit einer Liste von URLs.Sie hätten einen
Link
Header für jede Ergebnisseite (eine leere Zeichenfolge bedeutet die aktuelle URL, und die URL ist für jede Seite gleich, auf die nur mit unterschiedlichen Bereichen zugegriffen wird), und die Beziehung wird gemäß der kommendenLink
Spezifikation als benutzerdefinierte definiert . Diese Beziehung würde Ihren Brauch266
oder Ihre Verletzung von erklären206
. Diese Header sind Ihre maschinenlesbare Version, da alle Ihre Beispiele ohnehin einen verständnisvollen Client erfordern.(Wenn Sie sich an die "Range" -Route halten, ist Ihr eigener
2xx
Rückkehrcode, wie Sie ihn beschrieben haben , meiner Meinung nach das beste Verhalten. Von Ihnen wird erwartet, dass Sie dies für Ihre Anwendungen tun, und solche ["HTTP-Statuscodes sind erweiterbar. "] und du hast gute Gründe.)300 Multiple Choices
sagt, Sie sollten einem Körper auch eine Möglichkeit bieten, die der Benutzeragent auswählen kann. Wenn Ihr Client versteht, sollte er dieLink
Header verwenden. Wenn ein Benutzer manuell surft, möglicherweise eine HTML-Seite mit Links zu einer speziellen "ausgelagerten" Stammressource, die das Rendern dieser bestimmten Seite basierend auf der URL handhaben kann?/humanpage/1/db/questions
oder so etwas abscheuliches?Die Kommentare zu Richard Levasseurs Beitrag erinnern mich an eine zusätzliche Option: den
Accept
Header (Abschnitt 14.1). Als die oEmbed-Spezifikation herauskam, fragte ich mich, warum sie nicht vollständig über HTTP erstellt worden war, und schrieb eine Alternative mit ihnen.Behalten Sie die
300 Multiple Choices
, dieLink
Header und die HTML-Seite für ein anfängliches naives HTTP beiGET
, aber anstatt Bereiche zu verwenden, lassen Sie Ihre neue Paging-Beziehung die Verwendung desAccept
Headers definieren. Ihre nachfolgende HTTP-Anfrage könnte folgendermaßen aussehen:In der
Accept
Kopfzeile können Sie einen akzeptablen Inhaltstyp (Ihre JSON-Rückgabe) sowie erweiterbare Parameter für diesen Typ (Ihre Seitenzahl) definieren. Wenn Sie meine Notizen aus meinem oEmbed-Artikel lesen (kann hier nicht verlinkt werden, ich werde sie in meinem Profil auflisten), können Sie sehr explizit sein und hier eine Spezifikations- / Beziehungsversion angeben, falls Sie neu definieren müssen, was derpage
Parameter bedeutet in der Zukunft.quelle
Bearbeiten:
Nachdem ich ein bisschen mehr darüber nachgedacht habe, bin ich geneigt zuzustimmen, dass Range-Header nicht für die Paginierung geeignet sind. Die Logik ist, dass der Range-Header für die Antwort des Servers vorgesehen ist, nicht für die Anwendungen. Wenn Sie 100 Megabyte an Ergebnissen bereitgestellt haben, der Server (oder Client) jedoch jeweils nur 1 Megabyte verarbeiten konnte, ist dies der Zweck des Range-Headers.
Ich bin auch der Meinung, dass eine Teilmenge von Ressourcen eine eigene Ressource ist (ähnlich der relationalen Algebra), daher verdient sie die Darstellung in der URL.
Im Grunde genommen widerrufe ich meine ursprüngliche Antwort (unten) über die Verwendung eines Headers.
Ich denke, Sie haben Ihre eigene Frage mehr oder weniger beantwortet - geben Sie 200 oder 206 mit Inhaltsbereich zurück und verwenden Sie optional einen Abfrageparameter. Ich würde den Benutzeragenten und den Inhaltstyp beschnüffeln und, abhängig von diesen, nach einem Abfrageparameter suchen. Andernfalls benötigen Sie die Bereichskopfzeilen.
Sie haben im Wesentlichen widersprüchliche Ziele: Lassen Sie die Benutzer ihren Browser zum Erkunden verwenden (was benutzerdefinierte Header nicht einfach zulässt), oder zwingen Sie die Benutzer, einen speziellen Client zu verwenden, der Header festlegen kann (der sie nicht untersuchen lässt).
Sie können ihnen je nach Anforderung einfach den speziellen Client zur Verfügung stellen. Wenn es wie ein einfacher Browser aussieht, senden Sie eine kleine Ajax-App, die die Seite rendert und die erforderlichen Überschriften festlegt.
Natürlich gibt es auch die Debatte darüber, ob die URL den gesamten erforderlichen Status für solche Dinge enthalten soll. Das Festlegen des Bereichs mithilfe von Headern kann von einigen als "nicht erholsam" angesehen werden.
Abgesehen davon wäre es schön, wenn Server mit einem "Can-Specify: Header1, Header2" -Header antworten könnten und Webbrowser eine Benutzeroberfläche präsentieren würden, damit Benutzer auf Wunsch Werte eingeben könnten.
quelle
Sie könnten in Betracht ziehen, ein Modell wie das Atom Feed Protocol zu verwenden, da es ein vernünftiges HTTP-Modell für Sammlungen enthält und wie man sie manipuliert (wobei verrückt WebDAV bedeutet).
Es gibt das Atom Publishing-Protokoll , das das Sammlungsmodell und die REST-Vorgänge definiert. Außerdem können Sie mit RFC 5005 - Feed Paging and Archiving große Sammlungen durchsuchen .
Der Wechsel von Atom XML zu JSON-Inhalten sollte die Idee nicht beeinflussen.
quelle
Ich denke, das eigentliche Problem hier ist, dass die Spezifikation nichts enthält, was uns sagt, wie automatische Weiterleitungen durchgeführt werden sollen, wenn 413 - Angeforderte Entität zu groß ist.
Ich hatte kürzlich mit demselben Problem zu kämpfen und suchte nach Inspiration im RESTful Web Services- Buch. Persönlich halte ich 206 aufgrund der Header-Anforderung nicht für angemessen. Meine Gedanken führten mich auch zu 300, aber ich dachte, das wäre mehr für verschiedene MIME-Typen, also habe ich nachgeschlagen, was Richardson und Ruby zu diesem Thema in Anhang B, Seite 377 zu sagen hatten. Sie schlagen vor, dass der Server nur den bevorzugten auswählt Darstellung und senden Sie es mit einer 200 zurück, wobei Sie im Grunde die Vorstellung ignorieren, dass es eine 300 sein sollte.
Das stimmt auch mit der Vorstellung von Links zu den nächsten Ressourcen überein, die wir von Atom haben. Die Lösung, die ich implementiert habe, bestand darin, der json-Karte, die ich zurückgesendet habe, "nächste" und "vorherige" Schlüssel hinzuzufügen und damit fertig zu sein.
Später begann ich zu überlegen, ob ich vielleicht eine 307 - Temporäre Weiterleitung an einen Link senden sollte, der so etwas wie / db / question / 1,25 wäre -, der die ursprüngliche URI als kanonischen Ressourcennamen belässt, aber Sie dazu bringt eine entsprechend benannte untergeordnete Ressource. Dies ist ein Verhalten, das ich gerne von einem 413 sehen würde, aber 307 scheint ein guter Kompromiss zu sein. Ich habe dies jedoch noch nicht im Code versucht. Noch besser wäre es, wenn die Umleitung zu einer URL umleitet, die die tatsächlichen IDs der zuletzt gestellten Fragen enthält. Wenn beispielsweise jede Frage eine Ganzzahl-ID hat und 100 Fragen im System vorhanden sind und Sie die zehn neuesten anzeigen möchten, sollten die Anforderungen an / db / question 307 an / db / question / 100,91 gesendet werden
Dies ist eine sehr gute Frage, danke, dass Sie sie gestellt haben. Sie haben mir bestätigt, dass ich nicht verrückt bin, weil ich tagelang darüber nachgedacht habe.
quelle
Sie können den
Range
Header erkennen und Dojo imitieren, wenn es vorhanden ist, und Atom imitieren, wenn es nicht vorhanden ist. Es scheint mir, dass dies die Anwendungsfälle ordentlich unterteilt. Wenn Sie auf eine REST-Abfrage aus Ihrer Anwendung antworten, erwarten Sie, dass diese mit einemRange
Header formatiert wird . Wenn Sie auf einen gelegentlichen Browser antworten und Paging-Links zurückgeben, bietet das Tool eine einfache Möglichkeit, die Sammlung zu erkunden.quelle
Eines der großen Probleme bei Range-Headern besteht darin, dass viele Unternehmens-Proxys sie herausfiltern. Ich würde empfehlen, stattdessen einen Abfrageparameter zu verwenden.
quelle
Mit der Veröffentlichung von rfc723x , gehen nicht registrierten Bereich Einheiten gegen eine explizite Empfehlung in der Spezifikation . Betrachten Sie rfc7233 (veraltet rfc2616):
" Neue Bereichseinheiten sollten bei IANA registriert werden " (zusammen mit einem Verweis auf eine HTTP- Bereichseinheitenregistrierung ).
quelle
Mir scheint, dass der beste Weg, dies zu tun, darin besteht, den Bereich als Abfrageparameter einzuschließen. zB GET / db / question /? date> mindate & date <maxdate . Bei einem GET zu / db / question / ohne Abfrageparameter geben Sie 303 mit dem Speicherort: / db / question /? Query-parameters-to-Retrieve-the-default-page zurück . Geben Sie dann eine andere URL an, unter der derjenige, der Ihre API verwendet, um Statistiken über die Sammlung abzurufen (z. B. welche Abfrageparameter verwendet werden sollen, wenn er die gesamte Sammlung haben möchte).
quelle
Obwohl es möglich ist, den Range-Header für diesen Zweck zu verwenden, glaube ich nicht, dass dies die Absicht war. Es scheint für den Umgang mit flockigen Verbindungen sowie für die Begrenzung der Daten konzipiert worden zu sein (sodass der Client einen Teil der Anfrage anfordern kann, wenn etwas fehlte oder die Größe zu groß für die Verarbeitung war). Sie hacken die Paginierung in etwas, das wahrscheinlich für andere Zwecke auf der Kommunikationsebene verwendet wird. Die "richtige" Art, mit Paginierung umzugehen, sind die Typen, die Sie zurückgeben. Anstatt ein Fragenobjekt zurückzugeben, sollten Sie stattdessen einen neuen Typ zurückgeben.
Also, wenn Fragen so sind:
<questions> <question index=1></question> <question index=2></question> ... </questions>
Der neue Typ könnte ungefähr so aussehen:
<questionPage> <startIndex>50</startIndex> <returnedCount>10</returnedCount> <totalCount>1203</totalCount> <questions> <question index=50></question> <question index=51></question> .. </questions> <questionPage>
Natürlich steuern Sie Ihre Medientypen, damit Sie Ihre "Seiten" zu einem Format machen können, das Ihren Anforderungen entspricht. Wenn Sie etwas generisches machen, können Sie einen einzelnen Parser auf dem Client haben, um das Paging für alle Typen gleich zu behandeln. Ich denke, das ist eher im Sinne der HTTP-Spezifikation, als den Range-Parameter für etwas anderes zu verfälschen.
quelle