Come progettare la ricerca / filtro RESTful? [chiuso]


457

Attualmente sto progettando e implementando un'API RESTful in PHP. Tuttavia, non sono riuscito a implementare il mio progetto iniziale.

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

Finora piuttosto standard, giusto?

Il mio problema è con il primo GET /users. Stavo valutando l'invio di parametri nel corpo della richiesta per filtrare l'elenco. Questo perché voglio essere in grado di specificare filtri complessi senza ottenere un URL super lungo, come:

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

Invece volevo avere qualcosa di simile:

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

che è molto più leggibile e ti dà grandi possibilità di impostare filtri complessi.

Comunque, file_get_contents('php://input')non ha restituito il corpo della GETrichiesta per le richieste. Ho anche provato http_get_request_body(), ma l'hosting condiviso che sto usando non ha pecl_http. Non sono sicuro che avrebbe aiutato comunque.

Ho trovato questa domanda e mi sono reso conto che GET probabilmente non dovrebbe avere un corpo di richiesta. Era un po 'inconcludente, ma sconsigliarono.

Quindi ora non sono sicuro di cosa fare. Come si progetta una funzione di ricerca / filtro RESTful?

Suppongo che potrei usare POST, ma questo non sembra molto RESTful.



60
Stai attento!!! Il metodo GET deve essere IDEMPOTENT e deve essere "memorizzabile nella cache". Se invii informazioni nel corpo Come può il sistema memorizzare nella cache la tua richiesta? HTTP consente di memorizzare nella cache le richieste GET utilizzando solo l'URL, non il corpo della richiesta. Ad esempio, queste due richieste: example.com {test: "some"} example.com {anotherTest: "some2"} sono considerati uguali dal sistema cache: entrambi hanno esattamente lo stesso URL
jfcorugedo

15
Solo per aggiungere, dovresti POST in / utenti (raccolta) e non / utente (singolo utente).
Mladen B.

1
Un altro punto da considerare è che la maggior parte dei server delle app ha registri di accesso che registrano l'URL e quindi potrebbero essere qualcosa in mezzo. Quindi potrebbe esserci una perdita di informazioni non intenzionale su GET.
user3206144

Risposte:


396

Il modo migliore per implementare una ricerca RESTful è considerare la ricerca stessa come una risorsa. Quindi puoi utilizzare il verbo POST perché stai creando una ricerca. Non è necessario creare letteralmente qualcosa in un database per utilizzare un POST.

Per esempio:

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

Stai creando una ricerca dal punto di vista dell'utente. I dettagli di implementazione di questo sono irrilevanti. Alcune API RESTful potrebbero non aver nemmeno bisogno di persistenza. Questo è un dettaglio di implementazione.


209
Una limitazione significativa all'utilizzo di una richiesta POST per un endpoint di ricerca è che non può essere aggiunto ai segnalibri. Il bookmarking dei risultati della ricerca (query particolarmente complesse) può essere molto utile.
sabato

73
L'uso di POST per effettuare ricerche potrebbe interrompere il vincolo della cache REST. whatisrest.com/rest_constraints/cache_excerps
Filipe,

56
Le ricerche, per loro natura, sono transitorie: i dati si evolvono tra due ricerche con gli stessi parametri, quindi penso che una richiesta GET non si associ in modo pulito al modello di ricerca. Invece, la richiesta di ricerca dovrebbe essere POST (/ Risorsa / ricerca), quindi è possibile salvare quella ricerca e reindirizzare a un risultato di ricerca, ad esempio / Risorsa / ricerca / iyn3zrt. In questo modo, le richieste GET hanno esito positivo e senso.
slitta,

32
Non credo che la posta sia un metodo adatto per la ricerca, i dati per le normali richieste GET potrebbero anche variare nel tempo.
meraviglia

82
Questa è assolutamente la risposta peggiore possibile. Non posso credere che abbia così tanti voti positivi. Questa risposta spiega perché: programmers.stackexchange.com/questions/233164/…
richard

141

Se usi il corpo della richiesta in una richiesta GET, stai infrangendo il principio REST, perché la tua richiesta GET non sarà in grado di essere memorizzata nella cache, perché il sistema cache utilizza solo l'URL.

E, peggio ancora, l'URL non può essere aggiunto ai segnalibri, poiché l'URL non contiene tutte le informazioni necessarie per reindirizzare l'utente a questa pagina

Utilizzare i parametri URL o Query anziché i parametri del corpo della richiesta.

per esempio:

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

In effetti, HTTP RFC 7231 afferma che:

Un payload all'interno di un messaggio di richiesta GET non ha una semantica definita; l'invio di un corpo di payload su una richiesta GET potrebbe causare il rifiuto di alcune implementazioni esistenti.

Per maggiori informazioni dai un'occhiata qui


29
Impara dal mio errore: ho progettato un API usando il suggerimento della risposta accettata (POSTing json), ma sto passando ai parametri url. L'abilità dei segnalibri può essere più importante di quanto si pensi. Nel mio caso, c'era la necessità di indirizzare il traffico verso determinate query di ricerca (campagna pubblicitaria). Inoltre, l'utilizzo dell'API della cronologia ha più senso con i parametri URL.
Jake,

2
Dipende da come è usato. Se stai collegando a un URL che carica la pagina in base a tali parametri, ha senso, ma se la pagina principale sta eseguendo una chiamata AJAX solo per ottenere i dati in base ai parametri del filtro, non puoi comunque aggiungerlo ai segnalibri perché è un ajax call e non ha alcuna rilevanza. Naturalmente, potresti anche aggiungere un URL ai segnalibri che quando vai lì crea un filtro e POST che alla chiamata ajax e funzionerebbe bene.
Daniel Lorenz,

@DanielLorenz Per la migliore esperienza utente, l'URL deve comunque essere modificato tramite l'API Cronologia in quel caso. Non sopporto quando un sito Web non consente di utilizzare la funzionalità Indietro del browser per navigare alle pagine precedenti. E se si tratta di una pagina standard generata dal lato server, l'unico modo per renderlo segnalibro sarebbe utilizzare una richiesta GET. Sembra che i vecchi parametri di query siano la soluzione migliore.
Nathan,

@Nathan Penso di aver letto male questa risposta. Stavo parlando dell'utilizzo dei parametri della stringa di query in un get. Non si dovrebbero mai usare i parametri del corpo in una chiamata GET perché sarebbe completamente inutile. Stavo parlando di più di un GET con stringa di query che poteva essere utilizzato / aggiunto ai segnalibri e quindi all'avvio della pagina, è possibile utilizzare quei parametri per creare un filtro per POST, usando quei parametri per ottenere i dati. La storia funzionerebbe ancora bene in quello scenario.
Daniel Lorenz,

@DanielLorenz Ah, va bene, ha senso. Penso di aver frainteso quello che stavi dicendo.
Nathan,

70

Sembra che il filtro / ricerca delle risorse possa essere implementato in modo RESTful. L'idea è di introdurre un nuovo endpoint chiamato /filters/o /api/filters/.

L'uso di questo filtro endpoint può essere considerato come una risorsa e quindi creato tramite POSTmetodo. In questo modo - ovviamente - body può essere utilizzato per trasportare tutti i parametri e creare strutture di ricerca / filtro complesse.

Dopo aver creato tale filtro ci sono due possibilità per ottenere il risultato di ricerca / filtro.

  1. Verrà restituita una nuova risorsa con ID univoco insieme al 201 Createdcodice di stato. Quindi, utilizzando questo ID, è GETpossibile effettuare una richiesta /api/users/come:

    GET /api/users/?filterId=1234-abcd
    
  2. Dopo che il nuovo filtro viene creato tramite POSTesso non risponderà con 201 Createdma contemporaneamente 303 SeeOthercon l' Locationintestazione che punta a /api/users/?filterId=1234-abcd. Questo reindirizzamento verrà gestito automaticamente tramite la libreria sottostante.

In entrambi gli scenari è necessario formulare due richieste per ottenere i risultati filtrati: ciò può essere considerato un inconveniente, soprattutto per le applicazioni mobili. Per le applicazioni mobili utilizzerei la POSTchiamata singola a /api/users/filter/.

Come mantenere i filtri creati?

Possono essere memorizzati nel DB e utilizzati in seguito. Possono anche essere archiviati in un deposito temporaneo, ad esempio redis e hanno un po 'di TTL dopo il quale scadranno e saranno rimossi.

Quali sono i vantaggi di questa idea?

Filtri, risultati filtrati sono memorizzabili nella cache e possono anche essere aggiunti ai segnalibri.


2
bene, questa dovrebbe essere la risposta accettata. Non violi i principi REST e puoi fare query complesse e lunghe alle risorse. È bello, pulito e compatibile con i segnalibri. L'unico svantaggio aggiuntivo è la necessità di memorizzare coppie chiave / valore per i filtri creati e i già citati due passaggi di richiesta.
dantebarba,

2
L'unica preoccupazione con questo approccio è, se hai filtri data-ora nella query (o un valore in costante cambiamento). Quindi il numero di filtri da archiviare in db (o cache) sono innumerevoli.
Rvy Pandey,

17

Penso che dovresti andare con i parametri di richiesta, ma solo finché non c'è un'intestazione HTTP appropriata per realizzare ciò che vuoi fare. La specifica HTTP non dice esplicitamente che GET non può avere un corpo. Tuttavia, questo documento afferma:

Per convenzione, quando viene utilizzato il metodo GET, tutte le informazioni necessarie per identificare la risorsa vengono codificate nell'URI. Non esiste una convenzione in HTTP / 1.1 per un'interazione sicura (ad esempio, il recupero) in cui il client fornisce i dati al server in un corpo di entità HTTP anziché nella parte della query di un URI. Ciò significa che per operazioni sicure, gli URI potrebbero essere lunghi.


6
ElasticSearch fa anche OTTENERE con il corpo e funziona bene!
Tarun Sapra,

Sì, ma controllano l'implementazione del server potrebbe non essere presente negli interwebs.
user432024

7

Dato che sto usando un backend laravel / php, tendo ad andare con qualcosa del genere:

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

PHP trasforma automaticamente i []parametri in un array, quindi in questo esempio finirò con una $filtervariabile che contiene un array / oggetto di filtri, insieme a una pagina e tutte le risorse correlate che voglio siano caricate.

Se usi un'altra lingua, questa potrebbe essere comunque una buona convenzione e puoi creare un parser da convertire []in un array.


Questo approccio sembra carino, ma potrebbero esserci problemi con l'uso di parentesi quadre negli URL, vedi what-characters-can-one-use-in-a-url
Sky

2
@Sky Questo potrebbe essere evitato codificando l'URI [e ]. L'uso di rappresentazioni codificate di questi caratteri per raggruppare i parametri di query è una pratica ben nota. È persino usato in JSON: specifica API .
jelhan,

6

Non preoccuparti troppo se l'API iniziale è completamente RESTful o meno (specialmente quando sei solo nelle fasi alfa). Fai in modo che l'impianto idraulico back-end funzioni prima. Puoi sempre fare una sorta di trasformazione / riscrittura degli URL per mappare le cose, perfezionando iterativamente fino a ottenere qualcosa di abbastanza stabile per test diffusi ("beta").

Puoi definire gli URI i cui parametri sono codificati per posizione e convenzione sugli stessi URI, preceduti da un percorso che sai che mapperai sempre a qualcosa. Non conosco PHP, ma suppongo che esista una tale funzione (come esiste in altre lingue con framework web):

.ie. Esegui un tipo di ricerca "utente" con param [i] = valore [i] per i = 1..4 sullo store n. 1 (con value1, value2, value3, ... come scorciatoia per i parametri della query URI):

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

o

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

o come segue (anche se non lo consiglierei, ne parlerò più avanti)

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

Con l'opzione 1, si mappano tutti gli URI con prefisso /store1/search/useral gestore di ricerca (o qualunque sia la designazione PHP) per impostazione predefinita per effettuare ricerche in risorse in store1 (equivalente a/search?location=store1&type=user .

Per convenzione, documentato e applicato dall'API, i valori dei parametri da 1 a 4 sono separati da virgole e presentati in quell'ordine.

L'opzione 2 aggiunge il tipo di ricerca (in questo caso user ) come parametro posizionale n. 1. Entrambe le opzioni sono solo una scelta cosmetica.

Anche l'opzione 3 è possibile, ma non credo che mi piacerebbe. Penso che la capacità di ricerca all'interno di determinate risorse debba essere presentata nell'URI stesso prima della ricerca stessa (come se indicasse chiaramente nell'URI che la ricerca è specifica all'interno della risorsa).

Il vantaggio di questo oltre il passaggio di parametri sull'URI è che la ricerca fa parte dell'URI (trattando così una ricerca come una risorsa, una risorsa il cui contenuto può - e cambierà - nel tempo). Lo svantaggio è che l'ordine dei parametri è obbligatorio .

Una volta che fai qualcosa del genere, puoi usare GET e sarebbe una risorsa di sola lettura (dal momento che non puoi POST o PUT ad esso - viene aggiornato quando viene OTTENUTO). Sarebbe anche una risorsa che esiste solo quando viene invocata.

Si potrebbe anche aggiungere più semantica ad esso memorizzando nella cache i risultati per un periodo di tempo o con un DELETE che causa l'eliminazione della cache. Ciò, tuttavia, potrebbe essere in contrasto con ciò che le persone usano in genere DELETE (e perché le persone in genere controllano la memorizzazione nella cache con le intestazioni della cache.)

Il modo in cui lo faresti sarebbe una decisione di progettazione, ma questo sarebbe il modo in cui procederei. Non è perfetto, e sono sicuro che ci saranno casi in cui non è la cosa migliore da fare (specialmente per criteri di ricerca molto complessi).


7
Yo, se tu (qualcuno, chiunque / qualunque cosa) le cose appropriate per sottovalutare la mia risposta, ti farebbe male al tuo ego inserire almeno un commento che indichi con cosa esattamente non sei d'accordo? So che è l'interweebz, ma ...;)
luis.espinal

107
Non ho espresso il mio voto negativo, ma il fatto che la domanda inizi con: "Attualmente sto progettando e implementando un'API RESTful" e la tua risposta inizia con "Non preoccuparti troppo se l'API iniziale è completamente RESTful o no" sbagliato per me. Se stai progettando un'API, stai progettando un'API. La domanda è: come progettare meglio l'API, non se l'API debba essere progettata.
Gardarh,

14
L'API è il sistema, lavora prima sull'API, non sull'impianto idraulico di back-end, la prima implementazione potrebbe / dovrebbe essere solo una simulazione. HTTP ha un meccanismo per passare parametri, stai suggerendo di reinventarlo, ma peggio (parametri ordinati invece di coppie nome-valore). Da qui il voto negativo.
Steven Herod,

14
@gardarh - sì, sembra sbagliato, ma a volte è pragmatico. L'obiettivo principale è progettare un'API che funzioni per il contesto aziendale a portata di mano. Se un approccio completamente RESTFULL è appropriato per l'azienda a portata di mano, allora provaci. In caso contrario, non provarci. Ossia, progetta un'API che soddisfi i tuoi requisiti aziendali specifici. Andare in giro cercando di renderlo RESTfull come requisito principale non è molto diverso dal chiedere "come posso usare il modello di adattatore nel problema X / Y". Non calpestare i paradigmi del corno a meno che non risolvano problemi reali e preziosi.
luis.espinal,

1
Vedo una risorsa come una raccolta di stati e parametri come mezzo per manipolare parametricamente la rappresentazione di quello stato. Pensala in questo modo, se potessi usare manopole e interruttori per regolare la modalità di visualizzazione della risorsa (mostra / nascondi alcuni bit di essa, ordinala in modo diverso, ecc ...) quei controlli sono parametri. Se in realtà è una risorsa diversa ('/ album' vs '/ artisti', per esempio), è allora che dovrebbe essere rappresentata nel percorso. Questo è ciò che è intuitivo per me, comunque.
Eric Elliott,

2

Cordiali saluti: So che è un po 'tardi, ma per chiunque sia interessato. Dipende da quanto RESTful vuoi essere, dovrai implementare le tue strategie di filtro poiché le specifiche HTTP non sono molto chiare su questo. Vorrei suggerire la codifica URL di tutti i parametri del filtro, ad es

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

So che è brutto ma penso che sia il modo più RESTful per farlo e dovrebbe essere facile da analizzare sul lato server :)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.