Progettazione URL RESTful per la ricerca


427

Sto cercando un modo ragionevole per rappresentare le ricerche come URL RESTful.

L'installazione: ho due modelli, Cars e Garages, dove Cars può essere in Garage. Quindi i miei URL sembrano:

/car/xxxx
  xxx == car id
  returns car with given id

/garage/yyy
  yyy = garage id
  returns garage with given id

Una macchina può esistere da sola (da qui la / macchina), oppure può esistere in un garage. Qual è il modo giusto di rappresentare, per esempio, tutte le auto in un determinato garage? Qualcosa di simile a:

/garage/yyy/cars     ?

Che ne dici dell'unione di auto in garage yyy e zzz?

Qual è il modo giusto di rappresentare una ricerca di auto con determinati attributi? Dì: mostrami tutte le berline blu a 4 porte:

/car/search?color=blue&type=sedan&doors=4

o dovrebbe essere / auto invece?

L'uso della "ricerca" sembra inappropriato lì - qual è un modo / termine migliore? Dovrebbe essere solo:

/cars/?color=blue&type=sedan&doors=4

I parametri di ricerca dovrebbero far parte di PATHINFO o QUERYSTRING?

In breve, sto cercando una guida per la progettazione di URL REST tra modelli e per la ricerca.

[Aggiornamento] Mi piace la risposta di Justin, ma non copre il caso di ricerca multi-campo:

/cars/color:blue/type:sedan/doors:4

o qualcosa di simile. Come andiamo

/cars/color/blue

al caso di più campi?


16
Anche se sembra migliore in inglese, mescolare /carse /carnon è semantico e quindi una cattiva idea. Usa sempre il plurale quando c'è più di un oggetto in quella categoria.
Zaz,

4
Queste sono risposte sbagliate. La ricerca dovrebbe usare stringhe di query. Le stringhe di query sono RESTful al 100% se utilizzate correttamente (ad es. Per la ricerca).
pbreitenbach,

Risposte:


435

Per la ricerca, utilizzare stringhe di query. Questo è perfettamente RESTful:

/cars?color=blue&type=sedan&doors=4

Un vantaggio delle stringhe di querys regolari è che sono standard e ampiamente comprese e che possono essere generate da form-get.


42
Questo è corretto. L'intero punto delle stringhe di query è fare cose come la ricerca.
aehlke,

22
In effetti questo è corretto poiché, secondo RFC3986 , il percorso e la stringa di query identificano la risorsa. Inoltre, la denominazione corretta sarebbe semplicemente /cars?color=whatever.
Lloeki,

35
Che dire dei casi in cui desideri comparatori (>, <, <=,> =)? / auto? punteggio <= 3?
Jesse,

3
Cosa succede se si desidera accedere alle risorse nidificate sotto la stringa di query? Ad esempio /cars?color=blue&type=sedan&doors=4/engines, non funzionerà
Abe Voelker,

9
@mjs /cars?param=valueè per un semplice filtro sulla lista delle auto ed /cars/search?param=valueè per creare una ricerca (con ou senza persistenza) in cui il risultato può contenere punteggi di ricerca, categorizzazione, ecc. Puoi anche creare / eliminare una ricerca con nome come /cars/search/mysearch. Guarda che: stackoverflow.com/a/18933902/1480391
Yves M.

121

Il bel design RESTful dell'URL riguarda la visualizzazione di una risorsa basata su una struttura (struttura simile a una directory, data: articoli / 2005/5/13, oggetto e relativi attributi, ..), la barra /indica la struttura gerarchica, utilizzare -idinvece.

Struttura gerarchica

Personalmente preferirei:

/garage-id/cars/car-id
/cars/car-id   #for cars not in garages

Se un utente rimuove la /car-idparte, porta l' carsanteprima - intuitiva. L'utente sa esattamente dove si trova nell'albero, cosa sta guardando. Sa dal primo sguardo che garage e macchine sono in relazione. /car-iddenota anche che appartiene insieme a differenza /car/id.

ricerca

La query di ricerca è OK così com'è , c'è solo la tua preferenza, cosa dovrebbe essere preso in considerazione. La parte divertente arriva quando si uniscono le ricerche (vedi sotto).

/cars?color=blue;type=sedan   #most prefered by me
/cars;color-blue+doors-4+type-sedan   #looks good when using car-id
/cars?color=blue&doors=4&type=sedan   #I don't recommend using &*

O praticamente tutto ciò che non è una barra come spiegato sopra.
La formula:, /cars[?;]color[=-:]blue[,;+&]* anche se non userei il &segno in quanto non è riconoscibile dal testo a prima vista.

** Sapevi che passare l'oggetto JSON in URI è RESTful? **

Elenco di opzioni

/cars?color=black,blue,red;doors=3,5;type=sedan   #most prefered by me
/cars?color:black:blue:red;doors:3:5;type:sedan
/cars?color(black,blue,red);doors(3,5);type(sedan)   #does not look bad at all
/cars?color:(black,blue,red);doors:(3,5);type:sedan   #little difference

possibili funzionalità?

Annulla le stringhe di ricerca (!)
Per cercare qualsiasi auto, ma non nero e rosso :
?color=!black,!red
color:(!black,!red)

Ricerche unite
Cerca auto rosse o blu o nere con 3 porte in garage id 1..20 o 101..103 o 999 ma non 5 /garage[id=1-20,101-103,999,!5]/cars[color=red,blue,black;doors=3]
Puoi quindi costruire query di ricerca più complesse. (Osserva la corrispondenza degli attributi CSS3 per l'idea della corrispondenza delle sottostringhe. Ad esempio, cerca gli utenti che contengono "barra" user*=bar.)

Conclusione

Ad ogni modo, questa potrebbe essere la parte più importante per te, perché puoi farlo come preferisci, tieni presente che RESTful URI rappresenta una struttura che è facilmente comprensibile, ad esempio /directory/file, simile a una directory /collection/node/item, date /articles/{year}/{month}/{day}.. E quando ometti uno degli ultimi segmenti, sai immediatamente cosa ottieni.

Quindi .., tutti questi personaggi sono ammessi senza codifica :

  • senza prenotazione: in a-zA-Z0-9_.-~
    genere consentito sia codificato che non, entrambi gli usi sono quindi equivalenti.
  • personaggi speciali: $-_.+!*'(),
  • riservato: ;/?:@=&
    può essere utilizzato non codificato per lo scopo che rappresenta, altrimenti deve essere codificato.
  • non sicuro: <>"#%{}|\^~[]`
    perché non sicuro e perché dovrebbe piuttosto essere codificato: RFC 1738 vedi 2.2

    Vedi anche RFC 1738 # pagina-20 per ulteriori classi di caratteri.

RFC 3986 vedi 2.2
Nonostante ciò che ho detto in precedenza, ecco una distinzione comune di delimitatori, nel senso che alcuni "sono" più importanti di altri.

  • delimitatori generici: :/?#[]@
  • sub-delimitatori: !$&'()*+,;=

Altre letture:
Gerarchia: vedi 2.3 , vedi 1.2.3
sintassi del parametro url path
Attributo CSS3 corrispondente
IBM: Servizi Web RESTful - Nozioni di base
Nota: RFC 1738 è stato aggiornato da RFC 3986


3
Non credo di non aver pensato di usare JSON nella stringa di query. È la risposta a un problema che stavo affrontando: struttura di ricerca complessa senza utilizzo POST. Inoltre, anche altre idee che hai dato nella tua risposta sono molto apprezzabili. Grazie mille!
gustavohenke,

4
@Qwerty: ottimo post! Mi chiedevo: l'unica ragione per usare ;al contrario di &leggibilità? Perché se è così, penso che preferirei il fatto &che è il delimitatore più comune ... giusto? :) Grazie!
Flo

3
@Flo Sì esattamente :), ma tieni presente che &come delimitatore è noto solo agli sviluppatori. Genitori, nonni e la popolazione non istruita accetta i delimitatori utilizzati nel testo scritto comune.
Qwerty,

17
Perché creare uno schema non standard quando le stringhe di query sono ben comprese e standard?
pbreitenbach,

1
@Qwerty niente ti impedisce di / ricerca? Auto = rosso, blu, verde e garage = 1,2,3 O se usi un modulo <multiselect>: / ricerca? Auto = rosso & auto = blu e garage = 1 & garage = 2
pbreitenbach

36

Sebbene avere i parametri nel percorso presenti alcuni vantaggi, ci sono, IMO, alcuni fattori di peso.

  • Non tutti i caratteri necessari per una query di ricerca sono consentiti in un URL. La maggior parte dei caratteri di punteggiatura e Unicode dovrebbe essere codificata come URL come parametro della stringa di query. Sto lottando con lo stesso problema. Vorrei usare XPath nell'URL, ma non tutta la sintassi XPath è compatibile con un percorso URI. Quindi, per percorsi semplici, /cars/doors/driver/lock/combinationsarebbe opportuno individuare l' combinationelemento " " nel documento XML della porta del conducente. Ma /car/doors[id='driver' and lock/combination='1234']non è così amichevole.

  • C'è una differenza tra filtrare una risorsa in base a uno dei suoi attributi e specificare una risorsa.

    Ad esempio, da allora

    /cars/colors restituisce un elenco di tutti i colori per tutte le auto (la risorsa restituita è una raccolta di oggetti colore)

    /cars/colors/red,blue,green restituirebbe un elenco di oggetti colorati che sono rossi, blu o verdi, non una raccolta di automobili.

    Per restituire le auto, il percorso sarebbe

    /cars?color=red,blue,green o /cars/search?color=red,blue,green

  • I parametri nel percorso sono più difficili da leggere perché le coppie nome / valore non sono isolate dal resto del percorso, che non è coppie nome / valore.

Un ultimo commento Preferisco /garages/yyy/cars(sempre plurale) a /garage/yyy/cars(forse era un refuso nella risposta originale) perché evita di cambiare il percorso tra singolare e plurale. Per le parole con una "s" aggiunta, il cambiamento non è poi così grave, ma cambiare /person/yyy/friendsin /people/yyysembra ingombrante.


2
sì, sono d'accordo ... oltre a ciò la cosa che urla la struttura del percorso dovrebbe riflettere le relazioni naturali tra le entità, una sorta di mappa delle mie risorse, come un garage ha molte macchine, un'auto appartiene a un garage e così ... e lascia i parametri del filtro, perché è di questo che stiamo parlando, a que querystring ... che ne pensi?
apertura

31

Per espandere la risposta di Peter, potresti fare della Ricerca una risorsa di prima classe:

POST    /searches          # create a new search
GET     /searches          # list all searches (admin)
GET     /searches/{id}     # show the results of a previously-run search
DELETE  /searches/{id}     # delete a search (admin)

La risorsa di ricerca avrebbe campi per il colore, il modello, lo stato in garage, ecc. E potrebbe essere specificata in XML, JSON o qualsiasi altro formato. Come la risorsa Auto e Garage, è possibile limitare l'accesso alle ricerche in base all'autenticazione. Gli utenti che eseguono frequentemente le stesse ricerche possono memorizzarli nei loro profili in modo che non debbano essere ricreati. Gli URL saranno abbastanza corti che in molti casi possono essere facilmente scambiati via e-mail. Queste ricerche memorizzate possono essere la base di feed RSS personalizzati e così via.

Ci sono molte possibilità di usare le Ricerche quando le consideri come risorse.

L'idea è spiegata in modo più dettagliato in questo Railscast .


6
questo approccio non va contro l'idea di lavorare con un protocollo irrequieto? Voglio dire, persistere in una ricerca su un db è come avere una connessione stateful ... non è vero?
apertura

5
È più come avere un servizio con stato. Stiamo anche cambiando lo stato del servizio ogni volta che aggiungiamo una nuova auto o garage. Una ricerca è solo un'altra risorsa che può essere utilizzata con l'intera gamma di verbi HTTP.
Rich Apodaca,

2
In che modo quanto sopra definisce una convenzione URI?
Rich Apodaca,

3
REST non ha nulla a che fare con graziosi URI o URI nidificati ecc. Se si definiscono URI come parte dell'API, non è REST.
aehlke,

2
Ho discusso questo prima. Questo non è affatto stato, ma è una cosa terribile. L '"eliminazione" della ricerca non è perfettamente chiara, qui stai dicendo che elimina questa entità di ricerca, ma vorrei usarla per eliminare i risultati che ho trovato attraverso quella ricerca. Non aggiungere "ricerche" come risorsa.
thecoshman,

12

La risposta di Justin è probabilmente la strada da percorrere, anche se in alcune applicazioni potrebbe avere senso considerare una ricerca particolare come una risorsa a sé stante, ad esempio se si desidera supportare ricerche salvate con nome:

/search/{searchQuery}

o

/search/{savedSearchName}

11
no. non ha mai senso che un'azione sia una risorsa.
thecoshman,

3
@thecoshman come menzionato in un commento sopra, la ricerca è anche un sostantivo.
andho,

6

Uso due approcci per implementare le ricerche.

1) Caso più semplice, per interrogare gli elementi associati e per la navigazione.

    /cars?q.garage.id.eq=1

Ciò significa che interroga le auto con ID garage uguale a 1.

È anche possibile creare ricerche più complesse:

    /cars?q.garage.street.eq=FirstStreet&q.color.ne=red&offset=300&max=100

Auto in tutti i garage di FirstStreet che non sono rosse (3a pagina, 100 elementi per pagina).

2) Le query complesse sono considerate come risorse normali create e che possono essere recuperate.

    POST /searches  => Create
    GET  /searches/1  => Recover search
    GET  /searches/1?offset=300&max=100  => pagination in search

Il corpo POST per la creazione della ricerca è il seguente:

    {  
       "$class":"test.Car",
       "$q":{
          "$eq" : { "color" : "red" },
          "garage" : {
             "$ne" : { "street" : "FirstStreet" }
          }
       }
    }

Ha sede in Grails (criteri DSL): http://grails.org/doc/2.4.3/ref/Domain%20Classes/createCriteria.html


5

Questo non è RIPOSO. Non puoi definire gli URI per le risorse all'interno della tua API. La navigazione delle risorse deve essere guidata dall'ipertesto. Va bene se vuoi URI piuttosto e pesanti quantità di accoppiamento, ma semplicemente non chiamarlo REST, perché viola direttamente i vincoli dell'architettura RESTful.

Vedi questo articolo dall'inventore di REST.


28
Hai ragione che non è REST, è la progettazione di URL per un sistema RESTful. Inoltre, è errato nel dire che viola l'architettura RESTful. Il vincolo ipertestuale di REST è ortogonale alla buona progettazione di URL per un sistema RESTful; Ricordo che alcuni anni fa ho discusso con Roy T. Fielding sulla lista REST a cui ho partecipato in cui ha dichiarato in modo così esplicito. Detto in altro modo è possibile avere l'ipertesto e la progettazione di URL. La progettazione di URL per sistemi RESTful è come un rientro nella programmazione; non necessaria ma un'ottima idea (ignorando Python, ecc.)
MikeSchinkel

2
Mi dispiace, hai ragione. Ho avuto l'impressione dall'OP che avrebbe reso i clienti consapevoli di come costruire gli URL: avrebbe inserito i "layout" degli URL nella sua API. Che sarebbe una violazione di riposo.
aehlke,

@aehlke, dovresti aggiornare la tua risposta in modo che corrisponda al tuo commento.
James McMahon,

1
È conforme al modello di maturità Richardson di livello 2 . Ti riferisci al livello 3. Accetta semplicemente REST come qualcosa di progressivamente adottabile.
Jules Randolph,

1
@Jules Randolph - mi scuso, la mia risposta è stata scritta solo pochi mesi dopo che il modello di maturità di Richardson è stato coniato per la prima volta e prima che Martin Fowler e altri autori lo diffondessero :) In effetti, è un modello istruttivo da seguire. Sentiti libero di modificare la risposta.
aehlke,

1

Anche se mi piace la risposta di Justin, ritengo che rappresenti più accuratamente un filtro piuttosto che una ricerca. Cosa succede se desidero conoscere le auto con nomi che iniziano con cam?

Per come la vedo io, potresti incorporarlo nel modo in cui gestisci risorse specifiche:
/ cars / cam *

O potresti semplicemente aggiungerlo nel filtro:
/ cars / doors / 4 / name / cam * / colors / red, blu, verde

Personalmente, preferisco quest'ultimo, tuttavia non sono affatto un esperto di REST (dopo averne sentito parlare solo circa 2 settimane fa ...)


In questo modo:/cars?name=cam*
DanMan,

1

RESTful non consiglia di usare i verbi nell'URL / auto / ricerca non è riposante. Il modo giusto per filtrare / cercare / impaginare le tue API è attraverso i parametri di query. Tuttavia potrebbero esserci casi in cui devi infrangere la norma. Ad esempio, se stai cercando tra più risorse, devi usare qualcosa come / search? Q = query

Puoi consultare http://saipraveenblog.wordpress.com/2014/09/09/29/rest-api-best-practices/ per comprendere le migliori pratiche per la progettazione di API RESTful


1
Anche la ricerca è un sostantivo 😀
jith912

1

Inoltre suggerirei anche:

/cars/search/all{?color,model,year}
/cars/search/by-parameters{?color,model,year}
/cars/search/by-vendor{?vendor}

Qui, Searchè considerata una risorsa figlio di Carsrisorse.


1

Ci sono molte buone opzioni per il tuo caso qui. Tuttavia dovresti considerare l'utilizzo del corpo POST.

La stringa di query è perfetta per il tuo esempio, ma se hai qualcosa di più complicato, ad esempio un lungo elenco arbitrario di elementi o condizionali booleani, potresti voler definire il post come documento che il client invia tramite POST.

Ciò consente una descrizione più flessibile della ricerca, oltre a evitare il limite di lunghezza dell'URL del server.


-4

Il mio consiglio sarebbe questo:

/garages
  Returns list of garages (think JSON array here)
/garages/yyy
  Returns specific garage
/garage/yyy/cars
  Returns list of cars in garage
/garages/cars
  Returns list of all cars in all garages (may not be practical of course)
/cars
  Returns list of all cars
/cars/xxx
  Returns specific car
/cars/colors
  Returns lists of all posible colors for cars
/cars/colors/red,blue,green
  Returns list of cars of the specific colors (yes commas are allowed :) )

Modificare:

/cars/colors/red,blue,green/doors/2
  Returns list of all red,blue, and green cars with 2 doors.
/cars/type/hatchback,coupe/colors/red,blue,green/
  Same idea as the above but a lil more intuitive.
/cars/colors/red,blue,green/doors/two-door,four-door
  All cars that are red, blue, green and have either two or four doors.

Spero che questo ti dia l'idea. In sostanza, l'API Rest dovrebbe essere facilmente individuabile e dovrebbe consentirti di navigare tra i tuoi dati. Un altro vantaggio dell'utilizzo di URL e non delle stringhe di query è la possibilità di sfruttare i meccanismi di memorizzazione nella cache nativi presenti sul server Web per il traffico HTTP.

Ecco un link a una pagina che descrive i mali delle stringhe di query in REST: http://web.archive.org/web/20070815111413/http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

Ho usato la cache di Google perché la pagina normale non funzionava per me, ecco anche quel link: http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful


1
Grazie per la risposta dettagliata Nell'ultima cosa, se volessi cercare sia per colore che per numero di porte? / automobili / colori / rosso, blu, verde / porte / 4 Non sembra giusto.
Parand,

2
Le virgole nell'URL non mi sembrano giuste, ma sono comunque valide. Penso che sia solo un cambio di paradigma.
Justin Bozonier,

21
Non mi piace questo suggerimento. Come conosceresti la differenza tra /cars/colors/red,blue,greene /cars/colors/green,blue,red? L'elemento path dell'URI dovrebbe essere gerarchico e non vedo davvero che sia il caso qui. Penso che questa sia una situazione in cui la stringa di query è la scelta più appropriata.
troelskn,

62
Questa è una risposta scadente. In effetti, il modo corretto di implementare la ricerca è con le stringhe di query. Le stringhe di query non sono affatto malvagie se utilizzate correttamente. L'articolo citato non si riferisce alla ricerca. Gli esempi forniti sono chiaramente torturati e non reggono bene con più parametri.
pbreitenbach,

4
le stringhe di query sono state fatte principalmente per risolvere il problema di interrogare una risorsa, anche con più parametri. La perversione dell'URI per abilitare un'API "RESTful" sembra pericolosa e poco lungimirante, specialmente dal momento che dovresti scrivere le tue mappature complesse solo per gestire le varie permutazioni dei parametri sull'URI. Meglio ancora, usa la nozione già esistente di usare i punti e virgola nei tuoi URI: doriantaylor.com/policy/http-url-path-parameter-syntax
Anatoly G
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.