Wie entwerfe ich RESTful Search / Filtering? [geschlossen]

457

Ich entwerfe und implementiere derzeit eine RESTful-API in PHP. Es ist mir jedoch nicht gelungen, mein ursprüngliches Design zu implementieren.

GET /users # list of users
GET /user/1 # get user with id 1
POST /user # create new user
PUT /user/1 # modify user with id 1
DELETE /user/1 # delete user with id 1

Soweit ziemlich normal, oder?

Mein Problem ist mit dem ersten GET /users. Ich habe überlegt, Parameter im Anforderungshauptteil zu senden, um die Liste zu filtern. Dies liegt daran, dass ich in der Lage sein möchte, komplexe Filter anzugeben, ohne eine super lange URL zu erhalten, wie zum Beispiel:

GET /users?parameter1=value1&parameter2=value2&parameter3=value3&parameter4=value4

Stattdessen wollte ich so etwas haben wie:

GET /users
# Request body:
{
    "parameter1": "value1",
    "parameter2": "value2",
    "parameter3": "value3",
    "parameter4": "value4"
}

Das ist viel besser lesbar und bietet Ihnen großartige Möglichkeiten, komplexe Filter einzustellen.

Der Anfragetext file_get_contents('php://input')für GETAnfragen wurde sowieso nicht zurückgegeben . Ich habe es auch versucht http_get_request_body(), aber das von mir verwendete Shared Hosting hat es nichtpecl_http . Ich bin mir nicht sicher, ob es trotzdem geholfen hätte.

Ich fand diese Frage und stellte fest, dass GET wahrscheinlich keinen Anfragetext haben soll. Es war ein bisschen nicht schlüssig, aber sie rieten davon ab.

Jetzt bin ich mir nicht sicher, was ich tun soll. Wie entwirft man eine RESTful-Such- / Filterfunktion?

Ich nehme an, ich könnte es gebrauchen POST, aber das scheint nicht sehr ruhig zu sein.

Erik B.
quelle
7
Mögliches Duplikat des RESTful-URL-Designs für die Suche
outis
60
Achtung!!! Die GET-Methode muss IDEMPOTENT sein und "zwischenspeicherbar" sein. Wenn Sie Informationen im Textkörper senden Wie kann das System Ihre Anfrage zwischenspeichern? HTTP ermöglicht das Zwischenspeichern von GET-Anforderungen nur über die URL, nicht über den Anforderungshauptteil. Zum Beispiel werden diese beiden Anforderungen: example.com {test: "some"} example.com {anotherTest: "some2"} vom Cache-System als gleich angesehen: Beide haben genau die gleiche URL
jfcorugedo
15
Zum Hinzufügen sollten Sie POST an / users (Sammlung) und nicht an / user (Einzelbenutzer) senden.
Mladen B.
1
Ein weiterer zu berücksichtigender Punkt ist, dass die meisten App-Server über Zugriffsprotokolle verfügen, die die URL protokollieren und daher möglicherweise dazwischen liegen. Es kann also zu einem nicht beabsichtigten Informationsleck bei GET kommen.
user3206144
2
Mögliches Duplikat des RESTful URL-Designs für die Suche
ivan_pozdeev

Antworten:

397

Der beste Weg, eine RESTful-Suche zu implementieren, besteht darin, die Suche selbst als Ressource zu betrachten. Dann können Sie das POST-Verb verwenden, weil Sie eine Suche erstellen. Sie müssen nicht buchstäblich etwas in einer Datenbank erstellen, um einen POST zu verwenden.

Zum Beispiel:

Accept: application/json
Content-Type: application/json
POST http://example.com/people/searches
{
  "terms": {
    "ssn": "123456789"
  },
  "order": { ... },
  ...
}

Sie erstellen eine Suche vom Standpunkt des Benutzers aus. Die Implementierungsdetails hierfür sind irrelevant. Einige RESTful-APIs benötigen möglicherweise nicht einmal Persistenz. Das ist ein Implementierungsdetail.

Jason Harrelson
quelle
209
Eine wesentliche Einschränkung bei der Verwendung einer POST-Anforderung für einen Suchendpunkt besteht darin, dass dieser nicht mit einem Lesezeichen versehen werden kann. Das Lesezeichen von Suchergebnissen (insbesondere komplexe Abfragen) kann sehr nützlich sein.
Couch und
73
Die Verwendung von POST für Suchvorgänge kann die REST-Cache-Einschränkung aufheben. whatisrest.com/rest_constraints/cache_excerps
Filipe
56
Suchvorgänge sind von Natur aus vorübergehend: Daten entwickeln sich zwischen zwei Suchvorgängen mit denselben Parametern. Daher denke ich, dass eine GET-Anforderung dem Suchmuster nicht sauber zugeordnet werden kann. Stattdessen sollte die Suchanforderung POST (/ Resource / search) sein. Anschließend können Sie diese Suche speichern und zu einem Suchergebnis umleiten, z. B. / Resource / search / iyn3zrt. Auf diese Weise sind GET-Anfragen erfolgreich und sinnvoll.
Sleblanc
32
Ich denke nicht, dass Post eine geeignete Methode für die Suche ist, Daten für normale GET-Anfragen können auch im Laufe der Zeit variieren.
Wunder
82
Dies ist absolut die schlechteste Antwort. Ich kann nicht glauben, dass es so viele positive Stimmen gibt. Diese Antwort erklärt warum: programmers.stackexchange.com/questions/233164/…
richard
141

Wenn Sie den Anforderungshauptteil in einer GET-Anforderung verwenden, verstoßen Sie gegen das REST-Prinzip, da Ihre GET-Anforderung nicht zwischengespeichert werden kann, da das Cache-System nur die URL verwendet.

Und was noch schlimmer ist, Ihre URL kann nicht mit einem Lesezeichen versehen werden, da die URL nicht alle Informationen enthält, die erforderlich sind, um den Benutzer auf diese Seite umzuleiten

Verwenden Sie URL- oder Abfrageparameter anstelle von Anforderungshauptteilparametern.

z.B:

/myapp?var1=xxxx&var2=xxxx
/myapp;var1=xxxx/resource;var2=xxxx 

In der Tat sagt der HTTP RFC 7231, dass:

Eine Nutzlast innerhalb einer GET-Anforderungsnachricht hat keine definierte Semantik. Das Senden eines Nutzdatenkörpers für eine GET-Anforderung kann dazu führen, dass einige vorhandene Implementierungen die Anforderung ablehnen.

Weitere Informationen finden Sie hier

jfcorugedo
quelle
29
Lernen Sie aus meinem Fehler - Ich habe eine API unter Verwendung des Vorschlags der akzeptierten Antwort (POSTing json) entworfen, gehe aber zu den URL-Parametern über. Lesezeichenfähigkeit kann wichtiger sein als Sie denken. In meinem Fall musste der Datenverkehr auf bestimmte Suchanfragen (Werbekampagne) geleitet werden. Außerdem ist die Verwendung der Verlaufs-API bei URL-Parametern sinnvoller.
Jake
2
Es hängt davon ab, wie es verwendet wird. Wenn Sie eine Verknüpfung zu einer URL herstellen, die die Seite basierend auf diesen Parametern lädt, ist dies sinnvoll. Wenn die Hauptseite jedoch nur einen AJAX-Aufruf ausführt, um die Daten basierend auf Filterparametern abzurufen, können Sie dies ohnehin nicht als Lesezeichen speichern, da dies eine ist Ajax Call und hat keine Peilung. Natürlich können Sie auch eine URL mit einem Lesezeichen versehen, die einen Filter und POSTs für den Ajax-Aufruf erstellt, wenn Sie dorthin gehen. Dies funktioniert einwandfrei.
Daniel Lorenz
@DanielLorenz Für eine optimale Benutzererfahrung sollte die URL in diesem Fall weiterhin über die Verlaufs-API geändert werden. Ich kann es nicht ertragen, wenn eine Website die Verwendung der Browser-Zurück-Funktion zum Navigieren zu vorherigen Seiten nicht zulässt. Wenn es sich um eine standardmäßige serverseitig generierte Seite handelt, besteht die einzige Möglichkeit, sie als Lesezeichen zu speichern, darin, eine GET-Anforderung zu verwenden. Es scheint, dass gute alte Abfrageparameter die beste Lösung sind.
Nathan
@ Nathan Ich glaube, ich habe diese Antwort falsch verstanden. Ich habe über die Verwendung von Abfragezeichenfolgenparametern in einem get gesprochen. Sie sollten niemals Körperparameter in einem GET-Aufruf verwenden, da dies völlig nutzlos wäre. Ich habe mehr darüber gesprochen, dass ein GET mit Abfragezeichenfolge verwendet / mit einem Lesezeichen versehen werden kann. Beim Start der Seite können Sie diese Parameter verwenden, um einen Filter für POST aufzubauen, und diese Parameter verwenden, um die Daten abzurufen. Die Geschichte würde in diesem Szenario immer noch gut funktionieren.
Daniel Lorenz
@ DanielLorenz Ah okay, das macht Sinn. Ich glaube, ich habe falsch verstanden, was Sie gesagt haben.
Nathan
70

Es scheint, dass das Filtern / Suchen von Ressourcen auf REST-fähige Weise implementiert werden kann. Die Idee ist, einen neuen Endpunkt namens /filters/oder einzuführen /api/filters/.

Mit diesem Endpunkt Filter kann als Ressource betrachtet werden und somit über erstellt POSTMethode. Auf diese Weise kann - body - natürlich verwendet werden, um alle Parameter zu übertragen, und es können komplexe Such- / Filterstrukturen erstellt werden.

Nach dem Erstellen eines solchen Filters gibt es zwei Möglichkeiten, um das Such- / Filterergebnis zu erhalten.

  1. Eine neue Ressource mit eindeutiger ID wird zusammen mit zurückgegeben 201 Created Statuscode zurückgegeben. Unter Verwendung dieser ID GETkann dann eine Anfrage gestellt werden, um Folgendes zu /api/users/mögen:

    GET /api/users/?filterId=1234-abcd
    
  2. Nachdem ein neuer Filter über erstellt wurde POST antwortet er nicht mit, 201 Createdsondern sofort mit 303 SeeOtherzusammen mit dem LocationHeader, der auf zeigt /api/users/?filterId=1234-abcd. Diese Umleitung wird automatisch über die zugrunde liegende Bibliothek abgewickelt.

In beiden Szenarien müssen zwei Anforderungen gestellt werden, um die gefilterten Ergebnisse zu erhalten. Dies kann insbesondere für mobile Anwendungen als Nachteil angesehen werden. Für mobile Anwendungen würde ich Single POSTCall to verwenden /api/users/filter/.

Wie werden erstellte Filter beibehalten?

Sie können in der Datenbank gespeichert und später verwendet werden. Sie können auch in einem temporären Speicher gespeichert werden, z. B. redis, und haben eine TTL, nach der sie ablaufen und entfernt werden.

Was sind die Vorteile dieser Idee?

Filter und gefilterte Ergebnisse können zwischengespeichert werden und können sogar mit Lesezeichen versehen werden.

Opal
quelle
2
Nun, dies sollte die akzeptierte Antwort sein. Sie verstoßen nicht gegen REST-Prinzipien und können lange komplexe Abfragen an Ressourcen durchführen. Es ist schön, sauber und Lesezeichen kompatibel. Der einzige zusätzliche Nachteil ist die Notwendigkeit, Schlüssel / Wert-Paare für erstellte Filter zu speichern, und die bereits erwähnten zwei Anforderungsschritte.
Dantebarba
2
Das einzige Problem bei diesem Ansatz ist, wenn Sie Datums- / Zeitfilter in der Abfrage haben (oder einen sich ständig ändernden Wert). Dann ist die Anzahl der Filter, die in der Datenbank (oder im Cache) gespeichert werden sollen, unzählig.
Rvy Pandey
17

Ich denke, Sie sollten mit Anforderungsparametern arbeiten, aber nur solange es keinen geeigneten HTTP-Header gibt, um das zu erreichen, was Sie tun möchten. Die HTTP-Spezifikation besagt nicht explizit, dass GET keinen Body haben kann. In diesem Papier heißt es jedoch:

Gemäß der Konvention werden bei Verwendung der GET-Methode alle zur Identifizierung der Ressource erforderlichen Informationen in der URI codiert. In HTTP / 1.1 gibt es keine Konvention für eine sichere Interaktion (z. B. Abrufen), bei der der Client dem Server Daten in einem HTTP-Entitätstext und nicht im Abfrageteil eines URI bereitstellt. Dies bedeutet, dass URIs für einen sicheren Betrieb lang sein können.

Daff
quelle
5
ElasticSearch macht auch GET mit Körper und funktioniert gut!
Tarun Sapra
Ja, aber sie steuern die Serverimplementierung möglicherweise nicht in der Xase in den Interwebs.
user432024
7

Da ich ein Laravel / PHP- Backend verwende, tendiere ich dazu, Folgendes zu wählen :

/resource?filters[status_id]=1&filters[city]=Sydney&page=2&include=relatedResource

PHP []wandelt Parameter automatisch in ein Array um. In diesem Beispiel erhalte ich eine $filterVariable, die ein Array / Objekt von Filtern sowie eine Seite und alle zugehörigen Ressourcen enthält, die ich unbedingt laden möchte.

Wenn Sie eine andere Sprache verwenden, ist dies möglicherweise immer noch eine gute Konvention, und Sie können einen Parser erstellen, der []in ein Array konvertiert wird .

der Zug
quelle
Dieser Ansatz sieht gut aus, aber es könnte Probleme mit der Verwendung von eckigen Klammern in URLs geben, siehe, welche Zeichen man in einer URL verwenden kann
Sky
2
@Sky Dies könnte durch URI-Codierung des [und vermieden werden ]. Die Verwendung codierter Darstellungen dieser Zeichen zum Gruppieren von Abfrageparametern ist eine bekannte Praxis. Es wird sogar in der JSON: API-Spezifikation verwendet .
Jelhan
6

Ärgern Sie sich nicht zu sehr, wenn Ihre anfängliche API vollständig REST-fähig ist oder nicht (insbesondere, wenn Sie sich gerade in der Alpha-Phase befinden). Lassen Sie die Back-End-Installation zuerst funktionieren. Sie können jederzeit eine Art URL-Transformation / Umschreiben durchführen, um die Dinge abzubilden, und iterativ verfeinern, bis Sie etwas erhalten, das für umfassende Tests stabil genug ist ("Beta").

Sie können URIs definieren, deren Parameter durch Position und Konvention auf den URIs selbst codiert werden, wobei ein Pfad vorangestellt wird, von dem Sie wissen, dass Sie ihn immer etwas zuordnen werden. Ich kenne PHP nicht, aber ich würde annehmen, dass eine solche Funktion existiert (wie sie in anderen Sprachen mit Web-Frameworks existiert):

.ie. Führen Sie eine "Benutzer" -Suche mit param [i] = value [i] für i = 1..4 in Geschäft Nr. 1 durch (mit value1, value2, value3, ... als Abkürzung für URI-Abfrageparameter):

1) GET /store1/search/user/value1,value2,value3,value4

oder

2) GET /store1/search/user,value1,value2,value3,value4

oder wie folgt (obwohl ich es nicht empfehlen würde, dazu später mehr)

3) GET /search/store1,user,value1,value2,value3,value4

Mit Option 1 /store1/search/userordnen Sie alle URIs dem Suchhandler (oder der PHP-Bezeichnung) zu, die standardmäßig nach Ressourcen unter store1 suchen (entspricht)/search?location=store1&type=user .

Gemäß der von der API dokumentierten und erzwungenen Konvention werden die Parameterwerte 1 bis 4 durch Kommas getrennt und in dieser Reihenfolge dargestellt.

Option 2 fügt den Suchtyp (in diesem Fall user) als Positionsparameter 1 hinzu. Jede Option ist nur eine kosmetische Wahl.

Option 3 ist ebenfalls möglich, aber ich glaube nicht, dass es mir gefallen würde. Ich denke, die Fähigkeit der Suche innerhalb bestimmter Ressourcen sollte in der URI selbst vor der Suche selbst dargestellt werden (als ob in der URI klar angegeben würde, dass die Suche innerhalb der Ressource spezifisch ist.)

Dies hat gegenüber der Übergabe von Parametern an den URI den Vorteil, dass die Suche Teil des URI ist (wodurch eine Suche als Ressource behandelt wird, eine Ressource, deren Inhalt sich im Laufe der Zeit ändern kann und wird). Der Nachteil besteht darin, dass die Parameterreihenfolge obligatorisch ist .

Sobald Sie so etwas tun, können Sie GET verwenden, und es wäre eine schreibgeschützte Ressource (da Sie nicht POSTEN oder PUTEN können - es wird aktualisiert, wenn es GET'ed ist). Es wäre auch eine Ressource, die nur dann existiert, wenn sie aufgerufen wird.

Sie können auch mehr Semantik hinzufügen, indem Sie die Ergebnisse für einen bestimmten Zeitraum zwischenspeichern oder mit einem LÖSCHEN den Cache löschen. Dies kann jedoch dem widersprechen, wofür Benutzer normalerweise DELETE verwenden (und weil Benutzer das Caching normalerweise mit Caching-Headern steuern).

Wie Sie vorgehen, wäre eine Designentscheidung, aber so würde ich vorgehen. Es ist nicht perfekt, und ich bin sicher, dass es Fälle geben wird, in denen dies nicht das Beste ist (insbesondere für sehr komplexe Suchkriterien).

luis.espinal
quelle
7
Yo, wenn Sie (jemand, wer / was auch immer) Dinge angemessen sind, um meine Antwort abzulehnen, würde es Ihrem Ego schaden, zumindest einen Kommentar abzugeben, der genau angibt, womit Sie nicht einverstanden sind? Ich weiß, es ist das Interweebz, aber ...;)
luis.espinal
107
Ich habe nicht abgelehnt, aber die Tatsache, dass die Frage mit "Ich entwerfe und implementiere derzeit eine RESTful-API" beginnt und Ihre Antwort mit "Ärgern Sie sich nicht zu sehr, wenn Ihre ursprüngliche API vollständig RESTful ist oder nicht" beginnt, fühlt sich an falsch zu mir. Wenn Sie eine API entwerfen, entwerfen Sie eine API. Die Frage ist, wie die API am besten entworfen werden kann und nicht, ob die API entworfen werden soll.
Gardarh
14
Die API ist das System. Arbeiten Sie zuerst an der API, nicht an der Backend-Installation. Die erste Implementierung könnte / sollte nur ein Mock sein. HTTP hat einen Mechanismus zum Übergeben von Parametern. Sie schlagen vor, ihn neu zu erfinden, aber schlimmer (geordnete Parameter anstelle von Name-Wert-Paaren). Daher die Abwahl.
Steven Herod
14
@ Gardarh - ja, es fühlt sich falsch an, aber manchmal ist es pragmatisch. Das Hauptziel besteht darin, eine API zu entwerfen, die für den jeweiligen Geschäftskontext geeignet ist. Wenn ein vollständig RESTFULL-Ansatz für das jeweilige Unternehmen geeignet ist, entscheiden Sie sich dafür. Wenn dies nicht der Fall ist, versuchen Sie es nicht. Entwerfen Sie also eine API, die Ihren spezifischen Geschäftsanforderungen entspricht. Der Versuch, es als Hauptanforderung RESTfull zu machen, unterscheidet sich nicht wesentlich von der Frage "Wie verwende ich das Adaptermuster bei X / Y-Problemen?". Beschlagen Sie keine Hornparadigmen, es sei denn, sie lösen tatsächliche, wertvolle Probleme.
Luis.espinal
1
Ich betrachte eine Ressource als eine Sammlung von Zuständen und Parameter als ein Mittel, um die Darstellung dieses Zustands parametrisch zu manipulieren. Stellen Sie sich das so vor: Wenn Sie Knöpfe und Schalter verwenden könnten, um die Anzeige der Ressource anzupassen (bestimmte Teile davon ein- / ausblenden, anders anordnen usw.), sind diese Steuerelemente Parameter. Wenn es sich tatsächlich um eine andere Ressource handelt (z. B. '/ Alben' vs '/ Künstler'), sollte sie im Pfad dargestellt werden. Das ist für mich sowieso intuitiv.
Eric Elliott
2

Zu Ihrer Information: Ich weiß, dass dies etwas spät ist, aber für alle, die interessiert sind. Abhängig davon, wie RESTful Sie sein möchten, müssen Sie Ihre eigenen Filterstrategien implementieren, da die HTTP-Spezifikation diesbezüglich nicht sehr klar ist. Ich möchte vorschlagen, alle Filterparameter mit einer URL zu codieren, z

GET api/users?filter=param1%3Dvalue1%26param2%3Dvalue2

Ich weiß, dass es hässlich ist, aber ich denke, es ist der RESTVOLLSTE Weg, es zu tun und sollte auf der Serverseite leicht zu analysieren sein :)

Schenkel
quelle