Querystring nell’URL della risorsa REST

Oggi ho avuto una discussione con un collega sull’utilizzo di stringhe di query negli URL REST. Prendi questi 2 esempi:

1. http://localhost/findbyproductcode/4xxheua 2. http://localhost/findbyproductcode?productcode=4xxheua 

La mia posizione era che gli URL dovrebbero essere progettati come nell’esempio 1. Questo è più pulito e ciò che ritengo sia corretto all’interno di REST. Ai miei occhi saresti completamente corretto a restituire un errore 404 dell’esempio 1 se il codice prodotto non esistesse mentre con l’esempio 2 restituire un 404 sarebbe errato in quanto la pagina dovrebbe esistere. La sua posizione era che non importava davvero e che entrambi facevano la stessa cosa.

Poiché nessuno di noi è stato in grado di trovare prove concrete (certamente la mia ricerca non era ampia) mi piacerebbe conoscere le opinioni di altre persone su questo.

Nelle tipiche API REST, l’esempio 1 è più corretto. Le risorse sono rappresentate come URI e # 1 lo fa di più. Restituire un 404 quando il codice prodotto non viene trovato è assolutamente il comportamento corretto. Detto questo, modifico leggermente # 1 per essere un po ‘più espressivo come questo:

 http://localhost/products/code/4xheaua 

Guarda altre API REST ben progettate: ad esempio, guarda StackOverflow. Hai:

 stackoverflow.com/questions stackoverflow.com/questions/tagged/rest stackoverflow.com/questions/3821663 

Questi sono tutti modi diversi per ottenere “domande”.

Non c’è differenza tra i due URI dal punto di vista del cliente. Gli URI sono opachi al cliente. Utilizza le mappe in modo più pulito nell’infrastruttura lato server.

Per quanto riguarda REST non c’è assolutamente alcuna differenza. Credo che il motivo per cui così tante persone credano che sia solo il componente del percorso che identifica la risorsa è dovuto alla seguente riga in RFC 2396

Il componente di query è una stringa di informazioni che deve essere interpretata dalla risorsa.

Questa riga è stata successivamente modificata in RFC 3986 per essere:

Il componente di query contiene dati non gerarchici che, insieme ai dati nel componente del percorso (Sezione 3.3), servono per identificare una risorsa

IMHO significa che sia la stringa di query sia il segmento di percorso sono funzionalmente equivalenti quando si tratta di identificare una risorsa.


Aggiornamento per rispondere al commento di Steve.

Perdonami se obietto all’aggettivo “pulitore”. È semplicemente troppo soggettivo. Hai ragione, però, di aver perso una parte significativa della domanda.

Penso che la risposta a se restituire 404 dipenda da quale risorsa viene recuperata. È una rappresentazione di un risultato di ricerca o è una rappresentazione di un prodotto? Per saperlo devi davvero guardare alla relazione di collegamento che ci ha portato all’URL.

Se l’URL deve restituire una rappresentazione del prodotto, è necessario restituire un 404 se il codice non esiste. Se l’URL restituisce un risultato di ricerca, non dovrebbe restituire un 404.

Il risultato finale è che l’aspetto dell’URL non è il fattore determinante. Detto questo, è convenzione che le stringhe di query vengano utilizzate per restituire i risultati di ricerca in modo che sia più intuitivo utilizzare tale stile di URL quando non si desidera restituire 404.

Esistono due casi d’uso per GET

  1. Ottieni una risorsa identificata in modo univoco
  2. Cerca una o più risorse in base a determinati criteri

Usa caso 1 Esempio:

/ Prodotti / 4xxheua
Ottieni un prodotto identificato in modo univoco, restituisce 404 se non trovato.

Usa caso 2 Esempio:

/ Prodotti? Size = grande & color = red
Cerca un prodotto, restituisce un elenco di prodotti corrispondenti (da 0 a molti).

Se consideriamo l’API di Google Maps, possiamo vedere che utilizzano una stringa di query per la ricerca.

ad es. http://maps.googleapis.com/maps/api/geocode/json?address=los+angeles,+ca&sensor=false

Quindi entrambi gli stili sono validi per i propri casi d’uso.

IMO il componente percorso dovrebbe sempre indicare ciò che si desidera recuperare. Un URL come http: // localhost / findbyproductcode dice solo che voglio recuperare qualcosa per codice prodotto, ma cosa esattamente?

Quindi recuperi i contatti con http: // localhost / contatti e utenti con http: // localhost / users . La stringa di query viene utilizzata solo per recuperare un sottoinsieme di tale elenco in base agli attributi di risorsa. L’unica eccezione è quando questo sottoinsieme viene ridotto a un record basato sulla chiave primaria, quindi viene utilizzato qualcosa come http: // localhost / contact / [primary_key].

Questo è il mio approccio, il tuo chilometraggio può variare 🙂

La fine di questi due URI non è molto significativa per REST.

Tuttavia, la parte “findbyproductcode” potrebbe sicuramente essere più riposante. Perché non solo http: // localhost / product / 4xxheau ?

Nella mia esperienza limitata, se si dispone di un identificatore univoco, sarebbe semplice per build l’URI come … / product / {id} Tuttavia, se il codice prodotto non è univoco, potrei progettarlo più come # 2.

Tuttavia, come ha osservato Darrel, il cliente non dovrebbe preoccuparsi di come sia l’URI.

A mio avviso, il percorso URI definisce la risorsa, mentre le querystring facoltative forniscono informazioni definite dall’utente. Così

 https://domain.com/products/42 

identifica un particolare prodotto mentre

 https://domain.com/products?price=under+5 

potrebbe cercare prodotti sotto $ 5.

Non sono d’accordo con chi ha detto che l’uso di querystrings per identificare una risorsa è coerente con REST. Gran parte di REST è la creazione di un’API che imita un file system gerarchico statico (senza bisogno letteralmente di un sistema di questo tipo nel back-end), questo fa sì che gli identificatori di risorse semantici siano intuitivi. Querystrings rompono questa gerarchia. Ad esempio gli orologi sono accessori che hanno accessori. Nello stile REST è abbastanza chiaro cosa

  https://domain.com/accessories/watches 

e

 https://domain.com/watches/accessories 

ciascuno si riferisce a. Con querystrings,

  https://domain.com?product=watches&category=accessories 

non è molto chiaro

Per lo meno, lo stile REST è migliore di querystring perché richiede circa la metà delle informazioni in quanto un forte ordinamento dei parametri ci consente di abbandonare i nomi dei parametri.

La stringa di query è inevitabile in molti modi pratici … Considerare cosa accadrebbe se la ricerca consentisse più campi (facoltativi) a tutti i ve specificati. Nella prima forma, le loro posizioni nella gerarchia dovrebbero essere fissate e riempite …

Immagina di codificare un SQL generale “where clause” in quel formato …. Tuttavia, come stringa di query, è piuttosto semplice.

Questa domanda è detcruita, qual è l’approccio più pulito. Ma voglio concentrarmi su un aspetto diverso, chiamato sicurezza. Quando ho iniziato a lavorare intensamente sulla sicurezza delle applicazioni, ho scoperto che un attacco XSS riflesso può essere prevenuto con successo utilizzando PathParams (appraoch 1) invece di QueryParams (approccio 2).

(Naturalmente, il prerequisito di un attacco XSS riflesso è che l’input dell’utente malintenzionato viene riflesso all’interno del sorgente html al client. Sfortunatamente alcune applicazioni lo faranno, ed è per questo che PathParams potrebbe prevenire gli attacchi XSS)

Il motivo per cui questo funziona è che il payload XSS in combinazione con PathParams genererà un percorso URL sconosciuto e non definito a causa delle barre all’interno del payload stesso.

http://victim.com/findbyproductcode/**

Considerando che questo attacco avrà successo usando un QueryParam !

 http://localhost/findbyproductcode?productcode= 

Filosoficamente parlando, le pagine non “esistono”. Quando metti libri o documenti nella tua libreria, loro restano lì. Hanno un’esistenza separata su quello scaffale. Tuttavia, una pagina esiste solo finché è ospitata su un computer che è acceso e in grado di fornirlo su richiesta. La pagina può, naturalmente, essere sempre generata al volo, quindi non è necessario avere un’esistenza speciale prima della tua richiesta.

Ora pensaci dal punto di vista del server. Supponiamo che sia, per esempio, configurato correttamente Apache — non un server python a una sola linea che sta mappando tutte le richieste al file system. Quindi il particolare percorso specificato nell’URL potrebbe non avere nulla a che fare con la posizione di un particolare file nel filesystem. Quindi, ancora una volta, una pagina non “esiste” in alcun senso chiaro. Forse richiedi http://some.url/products/intel.html e ottieni una pagina; quindi si richiede http://some.url/products/bigmac.html e non si vede nulla. Ciò non significa che ci sia un file ma non l’altro. Potresti non avere le autorizzazioni per accedere all’altro file, quindi il server restituisce 404, o forse bigmac.html doveva essere servito da un server Mc’Donalds remoto, che è temporaneamente inattivo.

Quello che sto cercando di spiegare è che 404 è solo un numero. Non c’è niente di speciale: potrebbe essere stato 40404 o -2349.23847 , abbiamo appena accettato di usare 404 . Significa che il server è lì, comunica con te, probabilmente ha capito cosa volevi e non ha nulla da restituirti. Se ritieni che sia opportuno restituire 404 per http://some.url/products/bigmac.html quando il server decide di non pubblicare il file per qualsiasi motivo, puoi anche accettare di restituire 404 per http://some.url/products?id=bigmac .

Ora, se vuoi essere utile per gli utenti con un browser che stanno cercando di modificare manualmente l’URL, potresti reindirli a una pagina con l’elenco di tutti i prodotti e alcune funzionalità di ricerca invece di dare loro un 404 — o puoi dare un 404 come codice e un link a tutti i prodotti. Ma poi, puoi fare la stessa cosa con http://some.url/products/bigmac.html : reindirizza automaticamente a una pagina con tutti i prodotti.

Dal client REST la struttura URI non ha importanza, poiché segue i collegamenti annotati con la semantica e non analizza mai l’URI.

Dallo sviluppatore che scrive la logica di routing e la logica di generazione del collegamento, e probabilmente vuole capire il registro controllando gli URL che la struttura URI ha importanza. Con REST mappiamo gli URI alle risorse e non alle operazioni: dissertazione sul campo / interfaccia uniforms / identificazione delle risorse .

Quindi entrambe le strutture URI sono probabilmente difettose, perché contengono verbi nel loro formato attuale.

1. /findbyproductcode/4xxheua
2. /findbyproductcode?productcode=4xxheua

Puoi rimuovere find dagli URI in questo modo:

1. /products/code:4xxheua
2. /products?code="4xxheua"

Da una prospettiva REST, non importa quale scegli.

È ansible definire la propria convenzione di denominazione, ad esempio: “riducendo la raccolta a una singola risorsa utilizzando un identificativo univoco, l’identificativo univoco deve essere sempre parte del percorso e non la query”. Questo è esattamente lo stesso dello standard URI: il percorso è gerarchico, la query non è gerarchica. Quindi vorrei usare /products/code:4xxheua .