Ich entwickle einen REST API-Service für eine große Social-Networking-Website, an der ich beteiligt bin. Bisher funktioniert er hervorragend. Ich kann GET
, POST
, PUT
und DELETE
fordern Objekt-URLs an und wirken sich auf meine Daten aus. Diese Daten werden jedoch paginiert (jeweils auf 30 Ergebnisse begrenzt).
Was wäre jedoch der beste REST-Weg, um die Gesamtzahl der Mitwirkenden über meine API zu ermitteln?
Derzeit stelle ich Anfragen an eine URL-Struktur wie die folgende:
Meine Frage ist: Wie würde ich dann eine ähnliche URL-Struktur verwenden, um die Gesamtzahl der Mitglieder in meiner Bewerbung zu ermitteln? Offensichtlich wäre es ineffektiv, nur das id
-Feld anzufordern (ähnlich wie bei der Grafik-API von Facebook) und die Ergebnisse zu zählen, da nur ein Teil von 30 Ergebnissen zurückgegeben würde.
Während die Antwort an/API/users ausgelagert wird und nur 30 Datensätze zurückgibt, hindert nichts Sie daran, die Gesamtzahl der Datensätze und andere relevante Informationen wie die Seitengröße, die Seitenzahl/den Versatz usw. In die Antwort einzubeziehen .
Die StackOverflow-API ist ein gutes Beispiel für dasselbe Design. Hier ist die Dokumentation für die Users-Methode - https://api.stackexchange.com/docs/users
Ich bevorzuge die Verwendung von HTTP-Headern für diese Art von Kontextinformationen.
Für die Gesamtzahl der Elemente verwende ich X-total-count
Header.
Für Links zur nächsten, vorherigen Seite usw. verwende ich den http Link
-Header:
http://www.w3.org/wiki/LinkHeader
Github macht es genauso: https://developer.github.com/v3/#pagination
Meiner Meinung nach ist es sauberer, da es auch verwendet werden kann, wenn Sie Inhalte zurückgeben, die keine Hyperlinks unterstützen (z. B. Binärdateien, Bilder).
Ich habe mich in letzter Zeit eingehend mit diesen und anderen Fragen im Zusammenhang mit REST Paging befasst und es für konstruktiv befunden, hier einige meiner Ergebnisse einzufügen. Ich erweitere die Frage ein wenig, um sowohl die Paging-Gedanken als auch die Anzahl zu berücksichtigen, da sie eng miteinander verbunden sind.
Die Paging-Metadaten sind in der Antwort in Form von Antwortköpfen enthalten. Der große Vorteil dieses Ansatzes besteht darin, dass die Antwortnutzlast selbst nur die tatsächliche Datenanforderung ist, nach der der Benutzer gefragt hat. Erleichterung der Verarbeitung der Antwort für Clients, die nicht an den Paging-Informationen interessiert sind.
Es gibt eine Reihe von (Standard- und benutzerdefinierten) Headern, die in freier Wildbahn verwendet werden, um pagingbezogene Informationen zurückzugeben, einschließlich der Gesamtanzahl.
X-Total-Count: 234
Dies wird in einigenAPIs verwendet, die ich in freier Wildbahn gefunden habe. Es gibt auch NPM-Pakete , um Unterstützung für diesen Header z.B. Loopback. Einige Artikel empfehlen, auch diesen Header zu setzen.
Es wird häufig in Kombination mit dem Header Link
verwendet. Dies ist eine gute Lösung für das Blättern, es fehlen jedoch die Informationen zur Gesamtanzahl.
Link: </TheBook/chapter2>;
rel="previous"; title*=UTF-8'de'letztes%20Kapitel,
</TheBook/chapter4>;
rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel
Ich bin der Meinung, dass es nach vielem Lesen zu diesem Thema allgemein üblich ist, den Link
-Header zu verwenden, um Paging-Links für Clients mit rel=next
, rel=previous
Usw. Das Problem dabei ist, dass es keine Informationen darüber gibt, wie viele Datensätze insgesamt vorhanden sind, weshalb viele APIs dies mit dem Header X-Total-Count
Kombinieren.
Alternativ können einige APIs und z.B. Verwenden Sie im JsonApi -Standard das Link
-Format, fügen Sie jedoch die Informationen in einem Antwortumschlag anstelle eines Headers hinzu. Dies vereinfacht den Zugriff auf die Metadaten (und schafft einen Platz zum Hinzufügen der Gesamtzahlinformationen) auf Kosten der zunehmenden Komplexität des Zugriffs auf die eigentlichen Daten (durch Hinzufügen eines Umschlags).
Content-Range: items 0-49/234
Gefördert durch einen Blog-Artikel namens Range-Header, ich wähle Sie (für die Paginierung)! . Der Autor spricht sich stark dafür aus, die Überschriften Range
und Content-Range
Für die Paginierung zu verwenden. Wenn wir the RFC auf diesen Headern sorgfältig lesen, stellen wir fest, dass das Erweitern ihrer Bedeutung über Bytebereiche hinaus tatsächlich vom RFC erwartet wurde und ist ausdrücklich erlaubt. Wenn der Range-Header im Kontext von items
anstelle von bytes
verwendet wird, haben wir die Möglichkeit, einen bestimmten Bereich von Elementen anzufordern und anzugeben, auf welchen Bereich des Gesamtergebnisses sich die Antwortelemente beziehen . Diese Kopfzeile bietet auch eine gute Möglichkeit, die Gesamtzahl anzuzeigen. Und es ist ein wahrer Standard, der Paging meistens eins zu eins zuordnet. Es ist auch in freier Wildbahn verwendet .
Viele APIs, einschließlich die von unserer bevorzugten Q & A-Website verwenden einen Umschlag , einen Wrapper um die Daten, die zum Hinzufügen verwendet werden Meta-Informationen zu den Daten. Außerdem verwenden die Standards OData und JsonApi beide einen Antwortumschlag.
Der große Nachteil dabei (imho) ist, dass die Verarbeitung der Antwortdaten komplexer wird, da die tatsächlichen Daten irgendwo im Umschlag gefunden werden müssen. Es gibt auch viele verschiedene Formate für diesen Umschlag und Sie müssen das richtige verwenden. Es ist aufschlussreich, dass die Antwortumschläge von OData und JsonApi sehr unterschiedlich sind, da OData Metadaten an mehreren Stellen in der Antwort mischen.
Ich denke, das wurde in den anderen Antworten ausreichend behandelt. Ich habe nicht so viel untersucht, weil ich den Kommentaren zustimme, dass dies verwirrend ist, da Sie jetzt mehrere Arten von Endpunkten haben. Ich finde es am schönsten, wenn jeder Endpunkt eine (Sammlung von) Ressource (n) darstellt.
Wir müssen nicht nur die Paging-Metainformationen in Bezug auf die Antwort kommunizieren, sondern dem Client auch ermöglichen, bestimmte Seiten/Bereiche anzufordern. Es ist interessant, auch diesen Aspekt zu betrachten, um eine kohärente Lösung zu erhalten. Auch hier können wir Header (der Header Range
scheint sehr gut geeignet zu sein) oder andere Mechanismen wie Abfrageparameter verwenden. Einige befürworten, Ergebnisseiten als separate Ressourcen zu behandeln, was in einigen Anwendungsfällen sinnvoll sein kann (z. B. /books/231/pages/52
). Am Ende habe ich eine Reihe häufig verwendeter Anforderungsparameter ausgewählt, z. B. pagesize
, page[size]
Und limit
etc zusätzlich zur Unterstützung des Headers Range
(und auch als Anforderungsparameter).
Sie können die Anzahl als benutzerdefinierten HTTP-Header als Antwort auf eine HEAD) - Anforderung zurückgeben. Auf diese Weise müssen Sie die aktuelle Liste nicht zurückgeben, wenn ein Client nur die Anzahl möchte Keine zusätzliche URL erforderlich.
(Wenn Sie sich in einer kontrollierten Umgebung von Endpunkt zu Endpunkt befinden, können Sie ein benutzerdefiniertes HTTP-Verb wie COUNT verwenden.)
Franci Penovs Antwort ist sicherlich der beste Weg, damit Sie immer Artikel mit allen zusätzlichen Metadaten zu Ihren angeforderten Entitäten zurückgeben. So sollte es gemacht werden.
manchmal ist es jedoch nicht sinnvoll, alle Daten zurückzugeben, da Sie sie möglicherweise gar nicht benötigen. Möglicherweise benötigen Sie nur diese Metadaten zu Ihrer angeforderten Ressource. Wie Gesamtzahl oder Anzahl der Seiten oder etwas anderes. In diesem Fall können Sie jederzeit eine URL-Abfrage veranlassen, Ihrem Service mitzuteilen, dass keine Artikel zurückgegeben werden sollen, sondern nur Metadaten wie:
/api/members?metaonly=true
/api/members?includeitems=0
oder etwas ähnliches...
Ich würde empfehlen, Header für das gleiche hinzuzufügen, wie:
HTTP/1.1 200
Pagination-Count: 100
Pagination-Page: 5
Pagination-Limit: 20
Content-Type: application/json
[
{
"id": 10,
"name": "shirt",
"color": "red",
"price": "$23"
},
{
"id": 11,
"name": "shirt",
"color": "blue",
"price": "$25"
}
]
Einzelheiten finden Sie unter:
https://github.com/adnan-kamili/rest-api-response-format
Für Swagger-Datei:
Ab "X -" - Präfix veraltet. (siehe: https://tools.ietf.org/html/rfc6648 )
Wir haben festgestellt, dass die "Accept-Ranges" die beste Wahl sind, um den Paginierungsbereich abzubilden: https://tools.ietf.org/html/rfc7233#section-2. Wie die "Range Units" entweder "bytes" oder "token". Beide repräsentieren keinen benutzerdefinierten Datentyp. (siehe: https://tools.ietf.org/html/rfc7233#section-4.2 ) Trotzdem heißt es, dass
HTTP/1.1-Implementierungen KÖNNEN Bereiche ignorieren, die mit anderen Einheiten angegeben wurden.
Was bedeutet: Die Verwendung von benutzerdefinierten Bereichseinheiten ist nicht gegen das Protokoll, kann jedoch ignoriert werden.
Auf diese Weise müssten wir die Akzeptanzbereiche auf "Mitglieder" oder den erwarteten Einheitentyp festlegen. Stellen Sie außerdem den Inhaltsbereich auf den aktuellen Bereich ein. (siehe: https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.12 )
In jedem Fall würde ich mich an die Empfehlung von RFC7233 ( https://tools.ietf.org/html/rfc7233#page-8 ) halten, eine 206 anstelle von 200 zu senden:
Wenn alle Voraussetzungen erfüllt sind, unterstützt der Server den Bereich
Header-Feld für die Zielressource und die angegebenen Bereiche sind
gültig und zufriedenstellend (wie in Abschnitt 2.1 definiert), SOLLTE der Server
sendet eine 206-Antwort (Teilinhalt) mit einer Nutzlast, die eine enthält
oder mehr Teildarstellungen, die dem Erfüllbaren entsprechen
angeforderte Bereiche gemäß Definition in Abschnitt 4.
Als Ergebnis hätten wir also die folgenden HTTP-Header-Felder:
Für Teilinhalte:
206 Partial Content
Accept-Ranges: members
Content-Range: members 0-20/100
Für den vollständigen Inhalt:
200 OK
Accept-Ranges: members
Content-Range: members 0-20/20
Was ist mit einem neuen Endpunkt>/api/members/count, der nur Members.Count () aufruft und das Ergebnis zurückgibt?
Scheint am einfachsten, einfach eine hinzuzufügen
GET
/api/members/count
und geben die Gesamtzahl der Mitglieder zurück
Manchmal erfordern Frameworks (wie $ resource/AngularJS) ein Array als Abfrageergebnis, und Sie können nicht wirklich eine Antwort wie {count:10,items:[...]}
In diesem Fall speichere ich "count" in responseHeaders.
P. S. Eigentlich kannst du das mit $ resource/AngularJS machen, aber es braucht ein paar Verbesserungen.