Best Practices für die API-Paginierung


288

Ich würde gerne Hilfe bei der Bearbeitung eines seltsamen Randfalls mit einer paginierten API erhalten, die ich erstelle.

Wie viele APIs paginiert diese große Ergebnisse. Wenn Sie / foos abfragen, erhalten Sie 100 Ergebnisse (z. B. foo # 1-100) und einen Link zu / foos? Page = 2, der foo # 101-200 zurückgeben sollte.

Wenn foo # 10 aus dem Datensatz gelöscht wird, bevor der API-Konsument die nächste Abfrage durchführt, wird / foos? Page = 2 leider um 100 versetzt und foos # 102-201 zurückgegeben.

Dies ist ein Problem für API-Konsumenten, die versuchen, alle Foos zu ziehen - sie erhalten kein foo # 101.

Was ist die beste Vorgehensweise, um damit umzugehen? Wir möchten es so leicht wie möglich machen (dh vermeiden, Sitzungen für API-Anfragen zu bearbeiten). Beispiele von anderen APIs wären sehr dankbar!


1
Was ist das Problem hier? scheint mir in Ordnung zu sein, so oder so bekommt der Benutzer 100 Artikel.
NARKOZ

2
Ich habe mich dem gleichen Problem gestellt und nach einer Lösung gesucht. AFAIK, es gibt wirklich keinen soliden garantierten Mechanismus, um dies zu erreichen, wenn jede Seite eine neue Abfrage ausführt. Die einzige Lösung, die mir in den Sinn kommt, besteht darin, eine aktive Sitzung beizubehalten und die Ergebnismenge auf der Serverseite beizubehalten. Anstatt für jede Seite neue Abfragen auszuführen, greifen Sie einfach zum nächsten zwischengespeicherten Datensatz.
Jerry Dodge


1
@java_geek Wie wird der Parameter Since_ID aktualisiert? Auf der Twitter-Webseite scheinen beide Anfragen mit dem gleichen Wert für Since_ID zu stellen. Ich frage mich, wann es aktualisiert wird, damit neuere Tweets berücksichtigt werden können, wenn sie hinzugefügt werden.
Petar

1
@Petar Der Parameter Since_id muss vom Konsumenten der API aktualisiert werden. Wenn Sie sehen, bezieht sich das Beispiel dort auf Clients, die Tweets verarbeiten
java_geek

Antworten:


175

Ich bin mir nicht ganz sicher, wie Ihre Daten behandelt werden. Dies kann also funktionieren oder auch nicht. Haben Sie darüber nachgedacht, mit einem Zeitstempelfeld zu paginieren?

Wenn Sie / foos abfragen, erhalten Sie 100 Ergebnisse. Ihre API sollte dann ungefähr so ​​zurückgeben (unter der Annahme von JSON, aber wenn XML benötigt wird, können dieselben Prinzipien befolgt werden):

{
    "data" : [
        {  data item 1 with all relevant fields    },
        {  data item 2   },
        ...
        {  data item 100 }
    ],
    "paging":  {
        "previous":  "http://api.example.com/foo?since=TIMESTAMP1" 
        "next":  "http://api.example.com/foo?since=TIMESTAMP2"
    }

}

Nur eine Anmerkung, nur die Verwendung eines Zeitstempels hängt von einem impliziten "Limit" in Ihren Ergebnissen ab. Möglicherweise möchten Sie ein explizites Limit hinzufügen oder auch eine untilEigenschaft verwenden.

Der Zeitstempel kann dynamisch anhand des letzten Datenelements in der Liste ermittelt werden. Dies scheint mehr oder weniger die Art und Weise zu sein, wie Facebook in seiner Graph-API paginiert (scrollen Sie nach unten, um die Paginierungslinks in dem oben angegebenen Format anzuzeigen).

Ein Problem kann sein, wenn Sie ein Datenelement hinzufügen, aber basierend auf Ihrer Beschreibung klingt es so, als würden sie am Ende hinzugefügt (wenn nicht, lassen Sie es mich wissen und ich werde sehen, ob ich dies verbessern kann).


29
Zeitstempel sind garantiert nicht eindeutig. Das heißt, mehrere Ressourcen können mit demselben Zeitstempel erstellt werden. Dieser Ansatz hat also den Nachteil, dass auf der nächsten Seite möglicherweise die letzten (wenigen?) Einträge der aktuellen Seite wiederholt werden.
Rubel

4
@prmatta Abhängig von der Datenbankimplementierung ist ein Zeitstempel garantiert eindeutig .
Ramblinjan

2
@jandjorgensen Von Ihrem Link: "Der Zeitstempeldatentyp ist nur eine inkrementelle Zahl und behält weder ein Datum noch eine Uhrzeit bei. ... In SQL Server 2008 und höher wurde der Zeitstempeltyp in Zeilenversion umbenannt , vermutlich um ihn besser widerzuspiegeln Zweck und Wert. " Es gibt hier also keine Beweise dafür, dass Zeitstempel (diejenigen, die tatsächlich einen Zeitwert enthalten) eindeutig sind.
Nolan Amy

3
@jandjorgensen Ich mag Ihren Vorschlag, aber würden Sie keine Informationen in den Ressourcenlinks benötigen, damit wir wissen, ob wir vorher oder als nächstes gehen? Etwas wie: "previous": " api.example.com/foo?before=TIMESTAMP " "next": " api.example.com/foo?since=TIMESTAMP2 " Wir würden auch unsere Sequenz-IDs anstelle eines Zeitstempels verwenden. Sehen Sie irgendwelche Probleme damit?
Longliveenduro

5
Eine andere ähnliche Option ist die Verwendung des in RFC 5988 (Abschnitt 5) angegebenen Link-Header-Felds: tools.ietf.org/html/rfc5988#page-6
Anthony F

28

Sie haben mehrere Probleme.

Zunächst haben Sie das von Ihnen zitierte Beispiel.

Sie haben auch ein ähnliches Problem, wenn Zeilen eingefügt werden. In diesem Fall erhält der Benutzer jedoch doppelte Daten (möglicherweise einfacher zu verwalten als fehlende Daten, aber immer noch ein Problem).

Wenn Sie keinen Schnappschuss des Originaldatensatzes machen, ist dies nur eine Tatsache des Lebens.

Sie können den Benutzer einen expliziten Schnappschuss erstellen lassen:

POST /createquery
filter.firstName=Bob&filter.lastName=Eubanks

Welche Ergebnisse:

HTTP/1.1 301 Here's your query
Location: http://www.example.org/query/12345

Dann können Sie das den ganzen Tag lang durchblättern, da es jetzt statisch ist. Dies kann relativ leicht sein, da Sie nur die tatsächlichen Dokumentschlüssel und nicht die gesamten Zeilen erfassen können.

Wenn der Anwendungsfall einfach darin besteht, dass Ihre Benutzer alle Daten wollen (und brauchen), können Sie sie ihnen einfach geben:

GET /query/12345?all=true

und senden Sie einfach das gesamte Kit.


1
(Die Standardart der Foos ist das Erstellungsdatum, daher ist das Einfügen von Zeilen kein Problem.)
2arrs2ells

Tatsächlich reicht es nicht aus, nur Dokumentschlüssel zu erfassen. Auf diese Weise müssen Sie die vollständigen Objekte nach ID abfragen, wenn der Benutzer sie anfordert. Möglicherweise sind sie jedoch nicht mehr vorhanden.
Scadge

27

Wenn Sie eine Paginierung haben, sortieren Sie die Daten auch nach einem Schlüssel. Lassen Sie API-Clients den Schlüssel des letzten Elements der zuvor zurückgegebenen Auflistung in die URL aufnehmen und fügen Sie WHEREIhrer SQL-Abfrage eine Klausel hinzu (oder eine entsprechende Klausel, wenn Sie kein SQL verwenden), sodass nur die Elemente zurückgegeben werden, für die ist der Schlüssel größer als dieser Wert?


4
Dies ist kein schlechter Vorschlag, aber nur weil Sie nach einem Wert sortieren, bedeutet dies nicht, dass es sich um einen "Schlüssel" handelt, dh um einen eindeutigen.
Chris Peacock

Genau. In meinem Fall ist das Sortierfeld beispielsweise ein Datum, und es ist alles andere als eindeutig.
Sa Thiru

19

Abhängig von Ihrer serverseitigen Logik kann es zwei Ansätze geben.

Ansatz 1: Wenn der Server nicht intelligent genug ist, um Objektzustände zu verarbeiten.

Sie können alle zwischengespeicherten eindeutigen IDs des Datensatzes an den Server senden, z. B. ["id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8", "id9", "id10"] und ein boolescher Parameter, um festzustellen, ob Sie neue Datensätze anfordern (zum Aktualisieren ziehen) oder alte Datensätze (mehr laden).

Ihr Server sollte dafür verantwortlich sein, neue Datensätze (mehr Datensätze oder neue Datensätze per Pull zum Aktualisieren laden) sowie IDs gelöschter Datensätze aus ["id1", "id2", "id3", "id4", "id5", "zurückzugeben. id6 "," id7 "," id8 "," id9 "," id10 "].

Beispiel: - Wenn Sie mehr laden möchten, sollte Ihre Anfrage ungefähr so ​​aussehen: -

{
        "isRefresh" : false,
        "cached" : ["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10"]
}

Angenommen, Sie fordern alte Datensätze an (mehr laden) und nehmen an, dass der Datensatz "id2" von jemandem aktualisiert wird und die Datensätze "id5" und "id8" vom Server gelöscht werden. Dann sollte Ihre Serverantwort ungefähr so ​​aussehen: -

{
        "records" : [
{"id" :"id2","more_key":"updated_value"},
{"id" :"id11","more_key":"more_value"},
{"id" :"id12","more_key":"more_value"},
{"id" :"id13","more_key":"more_value"},
{"id" :"id14","more_key":"more_value"},
{"id" :"id15","more_key":"more_value"},
{"id" :"id16","more_key":"more_value"},
{"id" :"id17","more_key":"more_value"},
{"id" :"id18","more_key":"more_value"},
{"id" :"id19","more_key":"more_value"},
{"id" :"id20","more_key":"more_value"}],
        "deleted" : ["id5","id8"]
}

In diesem Fall ist Ihre Anforderungszeichenfolge jedoch zu lang, wenn Sie viele lokal zwischengespeicherte Datensätze mit 500 angenommen haben: -

{
        "isRefresh" : false,
        "cached" : ["id1","id2","id3","id4","id5","id6","id7","id8","id9","id10",………,"id500"]//Too long request
}

Ansatz 2: Wenn der Server intelligent genug ist, um Objektzustände nach Datum zu verarbeiten.

Sie können die ID des ersten Datensatzes und des letzten Datensatzes sowie die Epoche der vorherigen Anforderung senden. Auf diese Weise ist Ihre Anfrage immer klein, selbst wenn Sie eine große Anzahl zwischengespeicherter Datensätze haben

Beispiel: - Wenn Sie mehr laden möchten, sollte Ihre Anfrage ungefähr so ​​aussehen: -

{
        "isRefresh" : false,
        "firstId" : "id1",
        "lastId" : "id10",
        "last_request_time" : 1421748005
}

Ihr Server ist dafür verantwortlich, die IDs der gelöschten Datensätze zurückzugeben, die nach der letzten_Anforderungszeit gelöscht werden, sowie den aktualisierten Datensatz nach der letzten_Anforderungszeit zwischen "ID1" und "ID10" zurückzugeben.

{
        "records" : [
{"id" :"id2","more_key":"updated_value"},
{"id" :"id11","more_key":"more_value"},
{"id" :"id12","more_key":"more_value"},
{"id" :"id13","more_key":"more_value"},
{"id" :"id14","more_key":"more_value"},
{"id" :"id15","more_key":"more_value"},
{"id" :"id16","more_key":"more_value"},
{"id" :"id17","more_key":"more_value"},
{"id" :"id18","more_key":"more_value"},
{"id" :"id19","more_key":"more_value"},
{"id" :"id20","more_key":"more_value"}],
        "deleted" : ["id5","id8"]
}

Zum Aktualisieren ziehen: -

Geben Sie hier die Bildbeschreibung ein

Mehr laden

Geben Sie hier die Bildbeschreibung ein


14

Es kann schwierig sein, Best Practices zu finden, da die meisten Systeme mit APIs dieses Szenario nicht berücksichtigen, da dies ein extremer Vorteil ist oder sie normalerweise keine Datensätze löschen (Facebook, Twitter). Facebook sagt tatsächlich, dass auf jeder "Seite" möglicherweise nicht die Anzahl der angeforderten Ergebnisse angezeigt wird, da nach der Paginierung gefiltert wurde. https://developers.facebook.com/blog/post/478/

Wenn Sie diesen Randfall wirklich aufnehmen müssen, müssen Sie sich "merken", wo Sie aufgehört haben. Der Vorschlag von jandjorgensen ist genau richtig, aber ich würde ein Feld verwenden, das garantiert eindeutig ist, wie der Primärschlüssel. Möglicherweise müssen Sie mehr als ein Feld verwenden.

Nach dem Ablauf von Facebook können (und sollten) Sie die bereits angeforderten Seiten zwischenspeichern und nur diejenigen mit gelöschten Zeilen zurückgeben, die gefiltert wurden, wenn sie eine bereits angeforderte Seite anfordern.


2
Dies ist keine akzeptable Lösung. Es ist sehr zeit- und speicherintensiv. Alle gelöschten Daten zusammen mit den angeforderten Daten müssen im Speicher aufbewahrt werden, der möglicherweise überhaupt nicht verwendet wird, wenn derselbe Benutzer keine weiteren Einträge anfordert.
Deepak Garg

3
Ich bin nicht einverstanden. Nur die eindeutigen IDs beizubehalten, verbraucht überhaupt nicht viel Speicher. Sie dürfen die Daten nicht unbegrenzt aufbewahren, sondern nur für die "Sitzung". Mit memcache ist dies einfach. Stellen Sie einfach die Ablaufdauer ein (dh 10 Minuten).
Brent Baisley

Speicher ist billiger als Netzwerk- / CPU-Geschwindigkeit. Wenn das Erstellen einer Seite sehr teuer ist (in Bezug auf das Netzwerk oder CPU-intensiv), ist das Zwischenspeichern von Ergebnissen ein gültiger Ansatz @DeepakGarg
U Avalos

9

Paginierung ist im Allgemeinen eine "Benutzer" -Operation. Um eine Überlastung sowohl des Computers als auch des menschlichen Gehirns zu verhindern, geben Sie im Allgemeinen eine Teilmenge an. Anstatt jedoch zu denken, dass wir nicht die gesamte Liste erhalten, ist es vielleicht besser zu fragen, ob es wichtig ist.

Wenn eine genaue Live-Bildlaufansicht erforderlich ist, sind REST-APIs, die Anforderungs- / Antwortansichten sind, für diesen Zweck nicht gut geeignet. Zu diesem Zweck sollten Sie WebSockets oder vom HTML5-Server gesendete Ereignisse berücksichtigen, um Ihr Front-End über Änderungen zu informieren.

Nun, wenn es nötig ist eine Momentaufnahme der Daten , würde ich nur einen API-Aufruf bereitstellen, der alle Daten in einer Anforderung ohne Paginierung bereitstellt. Wohlgemerkt, Sie benötigen etwas, das das Streaming der Ausgabe ermöglicht, ohne sie vorübergehend in den Speicher zu laden, wenn Sie über einen großen Datensatz verfügen.

In meinem Fall bezeichne ich implizit einige API-Aufrufe, um das Abrufen der gesamten Informationen (hauptsächlich Referenztabellendaten) zu ermöglichen. Sie können diese APIs auch sichern, damit Ihr System nicht beschädigt wird.


8

Option A: Keyset-Paginierung mit einem Zeitstempel

Um die von Ihnen erwähnten Nachteile der Offset-Paginierung zu vermeiden, können Sie eine Keyset-basierte Paginierung verwenden. Normalerweise haben die Entitäten einen Zeitstempel, der ihre Erstellungs- oder Änderungszeit angibt. Dieser Zeitstempel kann für die Paginierung verwendet werden: Übergeben Sie einfach den Zeitstempel des letzten Elements als Abfrageparameter für die nächste Anforderung. Der Server, der wiederum verwendet den Zeitstempel als Filterkriterium (z WHERE modificationDate >= receivedTimestampParameter)

{
    "elements": [
        {"data": "data", "modificationDate": 1512757070}
        {"data": "data", "modificationDate": 1512757071}
        {"data": "data", "modificationDate": 1512757072}
    ],
    "pagination": {
        "lastModificationDate": 1512757072,
        "nextPage": "https://domain.de/api/elements?modifiedSince=1512757072"
    }
}

Auf diese Weise verpassen Sie kein Element. Dieser Ansatz sollte für viele Anwendungsfälle gut genug sein. Beachten Sie jedoch Folgendes:

  • Es kann zu Endlosschleifen kommen, wenn alle Elemente einer einzelnen Seite denselben Zeitstempel haben.
  • Sie können viele Elemente mehrmals an den Client senden, wenn Elemente mit demselben Zeitstempel zwei Seiten überlappen.

Sie können diese Nachteile weniger wahrscheinlich machen, indem Sie die Seitengröße erhöhen und Zeitstempel mit Millisekundengenauigkeit verwenden.

Option B: Erweiterte Keyset-Paginierung mit einem Fortsetzungstoken

Um die genannten Nachteile der normalen Keyset-Paginierung zu beheben, können Sie dem Zeitstempel einen Versatz hinzufügen und ein sogenanntes "Continuation Token" oder "Cursor" verwenden. Der Versatz ist die Position des Elements relativ zum ersten Element mit demselben Zeitstempel. Normalerweise hat das Token ein Format wie Timestamp_Offset. Es wird in der Antwort an den Client übergeben und kann an den Server zurückgesendet werden, um die nächste Seite abzurufen.

{
    "elements": [
        {"data": "data", "modificationDate": 1512757070}
        {"data": "data", "modificationDate": 1512757072}
        {"data": "data", "modificationDate": 1512757072}
    ],
    "pagination": {
        "continuationToken": "1512757072_2",
        "nextPage": "https://domain.de/api/elements?continuationToken=1512757072_2"
    }
}

Das Token "1512757072_2" zeigt auf das letzte Element der Seite und gibt an, dass der Client bereits das zweite Element mit dem Zeitstempel 1512757072 erhalten hat. Auf diese Weise weiß der Server, wo er fortfahren soll.

Bitte beachten Sie, dass Sie Fälle behandeln müssen, in denen die Elemente zwischen zwei Anforderungen geändert wurden. Dies erfolgt normalerweise durch Hinzufügen einer Prüfsumme zum Token. Diese Prüfsumme wird über die IDs aller Elemente mit diesem Zeitstempel berechnet. Wir haben also ein Token-Format wie das folgende:Timestamp_Offset_Checksum .

Weitere Informationen zu diesem Ansatz finden Sie im Blog-Beitrag " Web-API-Paginierung mit Fortsetzungstoken ". Ein Nachteil dieses Ansatzes ist die schwierige Implementierung, da viele Eckfälle berücksichtigt werden müssen. Aus diesem Grund können Bibliotheken wie Continuation-Token nützlich sein (wenn Sie Java / eine JVM-Sprache verwenden). Haftungsausschluss: Ich bin der Autor des Beitrags und Mitautor der Bibliothek.


4

Ich denke, derzeit reagiert Ihre API tatsächlich so, wie es sollte. Die ersten 100 Datensätze auf der Seite in der Gesamtreihenfolge der Objekte, die Sie verwalten. Ihre Erklärung besagt, dass Sie eine Art von Bestell-IDs verwenden, um die Reihenfolge Ihrer Objekte für die Paginierung zu definieren.

Wenn Sie möchten, dass Seite 2 immer bei 101 beginnt und bei 200 endet, müssen Sie die Anzahl der Einträge auf der Seite als variabel festlegen, da sie gelöscht werden müssen.

Sie sollten so etwas wie den folgenden Pseudocode tun:

page_max = 100
def get_page_results(page_no) :

    start = (page_no - 1) * page_max + 1
    end = page_no * page_max

    return fetch_results_by_id_between(start, end)

1
Genau. Anstatt nach Datensatznummer abzufragen (was nicht zuverlässig ist), sollten Sie nach ID abfragen. Ändern Sie Ihre Abfrage (x, m) so, dass sie "nach ID sortierte Datensätze mit ID> x bis zu m Datensätzen zurückgibt". Anschließend können Sie x einfach auf die maximale ID aus dem vorherigen Abfrageergebnis setzen.
John Henckel

Richtig, entweder nach IDs sortieren oder wenn Sie ein konkretes Geschäftsfeld haben, nach dem Sie sortieren können, wie z. B. Creation_Date usw.
Mickeymoon

4

Nur um diese Antwort von Kamilk zu ergänzen: https://www.stackoverflow.com/a/13905589

Hängt stark davon ab, wie groß der Datensatz ist, an dem Sie arbeiten. Kleine Datensätze arbeiten effektiv mit der Offset-Paginierung , große Echtzeit-Datasets erfordern jedoch eine Cursor-Paginierung.

Es wurde ein wunderbarer Artikel darüber gefunden, wie Slack die Paginierung seiner API weiterentwickelt hat, da die Datensätze in jeder Phase die positiven und negativen Aspekte erklärten: https://slack.engineering/evolving-api-pagination-at-slack-1c1f644f8e12


3

Ich habe lange und gründlich darüber nachgedacht und schließlich die Lösung gefunden, die ich unten beschreiben werde. Es ist ein ziemlich großer Schritt in der Komplexität, aber wenn Sie diesen Schritt machen, erhalten Sie das, wonach Sie wirklich suchen, was deterministische Ergebnisse für zukünftige Anforderungen sind.

Ihr Beispiel für das Löschen eines Elements ist nur die Spitze des Eisbergs. Was ist, wenn Sie filtern, color=blueaber jemand die Artikelfarben zwischen den Anforderungen ändert? Es ist unmöglich, alle Elemente auf ausgelagerte Weise zuverlässig abzurufen ... es sei denn ... wir implementieren den Revisionsverlauf .

Ich habe es implementiert und es ist weniger schwierig als ich erwartet hatte. Folgendes habe ich getan:

  • Ich habe eine einzelne Tabelle erstellt changelogs mit einer ID-Spalte mit automatischer Inkrementierung erstellt
  • Meine Entitäten haben eine id Feld, aber dies ist nicht der Primärschlüssel
  • Die Entitäten haben ein changeIdFeld, das sowohl der Primärschlüssel als auch ein Fremdschlüssel für Änderungsprotokolle ist.
  • Wenn ein Benutzer einen Datensatz erstellt, aktualisiert oder löscht, fügt das System einen neuen Datensatz ein changelogs, greift auf die ID zu und weist sie einer neuen Version der Entität zu, die es dann in die Datenbank einfügt
  • Meine Abfragen wählen die maximale Änderungs-ID (gruppiert nach ID) aus und verbinden sich selbst, um die neuesten Versionen aller Datensätze zu erhalten.
  • Filter werden auf die neuesten Datensätze angewendet
  • Ein Statusfeld verfolgt, ob ein Element gelöscht wird
  • Die maximale Änderungs-ID wird an den Client zurückgegeben und in nachfolgenden Anforderungen als Abfrageparameter hinzugefügt
  • Weil nur neue Änderungen erstellt werden, jede einzelne changeId eine eindeutige Momentaufnahme der zugrunde liegenden Daten zum Zeitpunkt der Erstellung der Änderung dar.
  • Dies bedeutet, dass Sie die Ergebnisse von Anforderungen, in denen der Parameter enthalten ist changeId, für immer zwischenspeichern können. Die Ergebnisse werden niemals verfallen, weil sie sich niemals ändern werden.
  • Dies eröffnet auch aufregende Funktionen wie Rollback / Revert, Synchronisieren des Client-Cache usw. Alle Funktionen, die vom Änderungsverlauf profitieren.

Ich bin verwirrt. Wie löst dies den von Ihnen erwähnten Anwendungsfall? (Ein zufälliges Feld ändert sich im Cache und Sie möchten den Cache ungültig machen)
U Avalos

Für alle Änderungen, die Sie selbst vornehmen, sehen Sie sich nur die Antwort an. Der Server stellt eine neue Änderungs-ID bereit, die Sie bei Ihrer nächsten Anforderung verwenden. Bei anderen Änderungen (die von anderen Personen vorgenommen wurden) rufen Sie entweder ab und zu die neueste Änderungs-ID ab, und wenn sie höher als Ihre eigene ist, wissen Sie, dass noch Änderungen ausstehen. Oder Sie richten ein Benachrichtigungssystem ein (lange Abfrage. Server-Push, Websockets), das den Client benachrichtigt, wenn noch Änderungen ausstehen.
Stijn de Witt

0

Eine weitere Option für die Paginierung in RESTFul-APIs ist die Verwendung des hier eingeführten Link-Headers . Zum Beispiel verwendet Github es wie folgt:

Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next",
  <https://api.github.com/user/repos?page=50&per_page=100>; rel="last"

Die möglichen Werte für relsind: first, last, next, previous . Bei Verwendung des LinkHeaders ist es jedoch möglicherweise nicht möglich, total_count (Gesamtzahl der Elemente) anzugeben .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.