Conception d'URL RESTful pour la recherche

427

Je recherche un moyen raisonnable de représenter les recherches en tant qu'URL RESTful.

La configuration: j'ai deux modèles, Cars et Garages, où les voitures peuvent être dans les garages. Donc mes URL ressemblent à:

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

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

Une voiture peut exister par elle-même (d'où la / voiture), ou elle peut exister dans un garage. Quelle est la bonne façon de représenter, disons, toutes les voitures d'un garage donné? Quelque chose comme:

/garage/yyy/cars     ?

Que diriez-vous de l'union des voitures dans le garage yyy et zzz?

Quelle est la bonne façon de représenter une recherche de voitures avec certains attributs? Dites: montrez-moi toutes les berlines bleues à 4 portes:

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

ou devrait-il plutôt être / cars?

L'utilisation de la «recherche» semble inappropriée là-bas - quel est le meilleur moyen / terme? Faut-il que ce soit:

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

Les paramètres de recherche doivent-ils faire partie de PATHINFO ou QUERYSTRING?

En bref, je recherche des conseils pour la conception d'URL REST entre modèles et pour la recherche.

[Mise à jour] J'aime la réponse de Justin, mais il ne couvre pas le cas de recherche multi-champs:

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

ou quelque chose comme ça. Comment allons-nous

/cars/color/blue

au cas à champs multiples?

Parand
la source
16
Bien qu'il ressemble mieux en anglais, le mélange /carset /carn'est pas sémantique et donc une mauvaise idée. Utilisez toujours le pluriel lorsqu'il y a plus d'un élément dans cette catégorie.
Zaz
4
Ce sont de mauvaises réponses. La recherche doit utiliser des chaînes de requête. Les chaînes de requête sont 100% RESTful lorsqu'elles sont utilisées correctement (c'est-à-dire pour la recherche).
pbreitenbach

Réponses:

435

Pour la recherche, utilisez des chaînes de requête. C'est parfaitement RESTful:

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

Un avantage des chaînes de requête régulières est qu'elles sont standard et largement comprises et qu'elles peuvent être générées à partir de form-get.

pbreitenbach
la source
42
C'est correct. L'intérêt des chaînes de requête est de faire des choses comme la recherche.
aehlke
22
En effet, cela est correct car, selon la RFC3986 , le chemin et la chaîne de requête identifient la ressource. De plus, un nom correct serait tout simplement /cars?color=whatever.
Lloeki
35
Qu'en est-il des cas où vous voulez des comparateurs (>, <, <=,> =)? / voitures? note <= 3?
Jesse
3
Que faire si vous souhaitez accéder aux ressources imbriquées sous la chaîne de requête? Par exemple /cars?color=blue&type=sedan&doors=4/engines, ne fonctionnera pas
Abe Voelker
9
@mjs /cars?param=valueest pour un filtrage simple sur la liste des voitures et /cars/search?param=valuepour créer une recherche (avec ou sans persistance) où le résultat peut contenir un score de recherche, une catégorisation, etc. Vous pouvez également créer / supprimer une recherche nommée comme /cars/search/mysearch. Regardez ça: stackoverflow.com/a/18933902/1480391
Yves M.
121

La jolie conception d'URL RESTful consiste à afficher une ressource basée sur une structure (structure de type répertoire, date: articles / 2005/5/13, objet et ses attributs, ..), la barre oblique /indique une structure hiérarchique, utilisez -idplutôt la.

Structure hiérarchique

Je préférerais personnellement:

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

Si un utilisateur supprime la /car-idpartie, il apporte l' carsaperçu - intuitif. L'utilisateur sait exactement où il se trouve dans l'arbre, qu'est-ce qu'il regarde. Il sait dès le premier regard que les garages et les voitures sont en relation. /car-idindique également qu'il appartient ensemble contrairement à /car/id.

Recherche

La requête de recherche est OK telle quelle , il n'y a que votre préférence, ce qui doit être pris en compte. La partie amusante survient lors de la jointure de recherches (voir ci-dessous).

/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 &*

Ou fondamentalement tout ce qui n'est pas une barre oblique comme expliqué ci-dessus.
La formule /cars[?;]color[=-:]blue[,;+&]:, * bien que je n'utiliserais pas le &signe car il n'est pas reconnaissable dans le texte à première vue.

** Saviez-vous que le passage d'un objet JSON dans l'URI est RESTful? **

Listes d'options

/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

fonctionnalités possibles?

Annulez les chaînes de recherche (!)
Pour rechercher des voitures, mais pas en noir et rouge :
?color=!black,!red
color:(!black,!red)

Recherches jointes
Rechercher des voitures rouges ou bleues ou noires avec 3 portes dans les garages id 1..20 ou 101..103 ou 999 mais pas 5 /garage[id=1-20,101-103,999,!5]/cars[color=red,blue,black;doors=3]
Vous pouvez ensuite construire des requêtes de recherche plus complexes. (Regardez la correspondance d'attributs CSS3 pour l'idée de faire correspondre les sous-chaînes. Par exemple, recherche d'utilisateurs contenant "bar" user*=bar.)

Conclusion

Quoi qu'il en soit, cela pourrait être la partie la plus importante pour vous, car vous pouvez le faire comme vous le souhaitez, gardez à l'esprit que l' URI RESTful représente une structure qui est facilement compréhensible, par exemple /directory/file, comme un répertoire,, /collection/node/itemdates /articles/{year}/{month}/{day}.. Et lorsque vous omettez l'un des derniers segments, vous savez immédiatement ce que vous obtenez.

Alors .., tous ces caractères sont autorisés non encodés :

  • non réservé: a-zA-Z0-9_.-~
    généralement autorisé à la fois codé et non, les deux utilisations sont alors équivalentes.
  • caractères spéciaux: $-_.+!*'(),
  • réservé: ;/?:@=&
    peut être utilisé non codé aux fins qu'il représente, sinon il doit être codé.
  • dangereux: <>"#%{}|\^~[]`
    pourquoi dangereux et pourquoi devrait être plutôt encodé: RFC 1738 voir 2.2

    Voir également RFC 1738 # page-20 pour plus de classes de caractères.

RFC 3986 voir 2.2
Malgré ce que j'ai dit précédemment, voici une distinction courante des délimètres, ce qui signifie que certains "sont" plus importants que d'autres.

  • délimètres génériques: :/?#[]@
  • sous-délimètres: !$&'()*+,;=

Pour en savoir plus:
Hiérarchie: voir 2.3 , voir 1.2.3
syntaxe du paramètre de chemin url
Attribut CSS3 correspondant à
IBM: services Web RESTful - Notions de base
Remarque: RFC 1738 a été mis à jour par RFC 3986

Qwerty
la source
3
Je ne pense pas que je n'ai pas pensé à utiliser JSON dans la chaîne de requête. C'est la réponse à un problème auquel j'étais confronté - une structure de recherche complexe sans utiliser POST. De plus, d'autres idées que vous avez données dans votre réponse sont également très appréciables. Merci beaucoup!
gustavohenke
4
@Qwerty: super article! Je me demandais: la seule raison d'utiliser ;par opposition à la &lisibilité? Parce que si c'est le cas, je pense que je préférerais en fait le &car c'est le délimiteur le plus courant ... non? :) Merci!
Flo
3
@Flo Oui exactement :), mais gardez à l'esprit que &le délimiteur n'est connu que des développeurs. Les parents, les grands-parents et la population non éduquée acceptent les délimiteurs tels qu'ils sont utilisés dans les textes écrits courants.
Qwerty
17
Pourquoi créer un schéma non standard lorsque les chaînes de requête sont bien comprises et standard?
pbreitenbach
1
@Qwerty rien ne vous empêche de / search? Cars = rouge, bleu, vert & garages = 1,2,3 Ou si vous utilisez un formulaire <multiselect>: / search? Cars = red & cars = blue & garages = 1 & garages = 2
pbreitenbach
36

Bien que le fait d'avoir les paramètres sur le chemin présente certains avantages, il y a, à l'OMI, certains facteurs supérieurs

  • Tous les caractères nécessaires à une requête de recherche ne sont pas autorisés dans une URL. La plupart des signes de ponctuation et Unicode devraient être encodés en URL en tant que paramètre de chaîne de requête. Je lutte avec le même problème. Je voudrais utiliser XPath dans l'URL, mais toutes les syntaxes XPath ne sont pas compatibles avec un chemin URI. Ainsi, pour les chemins simples, il /cars/doors/driver/lock/combinationserait approprié de localiser l' combinationélément ' ' dans le document XML de la porte du conducteur. Mais ce /car/doors[id='driver' and lock/combination='1234']n'est pas si sympa.

  • Il existe une différence entre le filtrage d'une ressource en fonction de l'un de ses attributs et la spécification d'une ressource.

    Par exemple, depuis

    /cars/colors renvoie une liste de toutes les couleurs pour toutes les voitures (la ressource retournée est une collection d'objets de couleur)

    /cars/colors/red,blue,green renvoie une liste d'objets de couleur rouge, bleu ou vert, et non une collection de voitures.

    Pour rendre les voitures, le chemin serait

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

  • Les paramètres du chemin sont plus difficiles à lire car les paires nom / valeur ne sont pas isolées du reste du chemin, qui n'est pas une paire nom / valeur.

Un dernier commentaire. Je préfère /garages/yyy/cars(toujours au pluriel) à /garage/yyy/cars(c'était peut-être une faute de frappe dans la réponse originale) car cela évite de changer le chemin entre le singulier et le pluriel. Pour les mots avec un «s» ajouté, le changement n'est pas si mauvais, mais le passage /person/yyy/friendsà /people/yyysemble lourd.

Doug Domeny
la source
2
oui, je suis d'accord ... en plus je pense que la structure du chemin urls doit refléter les relations naturelles entre les entités, une sorte de carte de mes ressources, comme un garage a beaucoup de voitures, une voiture appartient à un garage et ainsi ... et laissez les paramètres du filtre, parce que c'est ce dont nous parlons, que querystring ... qu'en pensez-vous?
ouvre
31

Pour développer la réponse de Peter - vous pouvez faire de la recherche une ressource de première 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 ressource de recherche aurait des champs pour la couleur, le modèle de fabrication, l'état garaged, etc. et pourrait être spécifiée en XML, JSON ou tout autre format. Comme la ressource Car and Garage, vous pouvez restreindre l'accès aux recherches en fonction de l'authentification. Les utilisateurs qui exécutent fréquemment les mêmes recherches peuvent les stocker dans leurs profils afin qu'ils n'aient pas besoin d'être recréés. Les URL seront suffisamment courtes pour que dans de nombreux cas, elles puissent être facilement échangées par e-mail. Ces recherches stockées peuvent être à la base de flux RSS personnalisés, etc.

Il existe de nombreuses possibilités d'utilisation des recherches lorsque vous les considérez comme des ressources.

L'idée est expliquée plus en détail dans ce Railscast .

Rich Apodaca
la source
6
cette approche ne va-t-elle pas à l'encontre de l'idée de travailler avec un protocole agité? Je veux dire, persister dans une recherche sur un db, c'est en quelque sorte avoir une connexion avec état ... n'est-ce pas?
ouvre
5
C'est plus comme avoir un service avec état. Nous changeons également l'état du service chaque fois que nous ajoutons une nouvelle voiture ou un nouveau garage. Une recherche est juste une autre ressource qui peut être utilisée avec la gamme complète des verbes HTTP.
Rich Apodaca
2
Comment ce qui précède définit-il une convention URI?
Rich Apodaca
3
REST n'a rien à voir avec de jolis URI ou imbrication d'URI, etc. Si vous définissez des URI dans le cadre de votre API, ce n'est pas REST.
aehlke
2
J'ai déjà discuté de celui-ci. Ce n'est pas du tout un état, mais c'est une chose terrible. La «suppression» de la recherche n'est pas parfaitement claire, ici vous dites qu'elle supprime cette entité de recherche, mais je voudrais l'utiliser pour supprimer les résultats que j'ai trouvés grâce à cette recherche. Veuillez ne pas ajouter de «recherches» comme ressource.
thecoshman
12

La réponse de Justin est probablement la voie à suivre, bien que dans certaines applications, il puisse être judicieux de considérer une recherche particulière comme une ressource à part entière, comme si vous souhaitez prendre en charge les recherches enregistrées nommées:

/search/{searchQuery}

ou

/search/{savedSearchName}
Peter Hilton
la source
11
non. il n'a jamais de sens qu'une action soit une ressource.
thecoshman
3
@thecoshman comme mentionné dans un commentaire ci-dessus, la recherche est également un substantif.
andho
6

J'utilise deux approches pour implémenter les recherches.

1) Cas le plus simple, pour interroger les éléments associés et pour la navigation.

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

Cela signifie, interrogez les voitures dont l'ID de garage est égal à 1.

Il est également possible de créer des recherches plus complexes:

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

Voitures dans tous les garages de FirstStreet qui ne sont pas rouges (3e page, 100 éléments par page).

2) Les requêtes complexes sont considérées comme des ressources régulières créées et récupérables.

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

Le corps POST pour la création de recherche est le suivant:

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

Il est basé sur Grails (critères DSL): http://grails.org/doc/2.4.3/ref/Domain%20Classes/createCriteria.html

user2108278
la source
5

Ce n'est pas REST. Vous ne pouvez pas définir d'URI pour les ressources à l'intérieur de votre API. La navigation dans les ressources doit être basée sur l'hypertexte. C'est bien si vous voulez de jolis URI et de grandes quantités de couplage, mais ne l'appelez pas simplement REST, car il viole directement les contraintes de l'architecture RESTful.

Voir cet article de l'inventeur de REST.

aehlke
la source
28
Vous avez raison, ce n'est pas REST, c'est la conception d'URL pour un système RESTful. Cependant, vous avez également tort de dire qu'il viole l'architecture RESTful. La contrainte hypertexte de REST est orthogonale à une bonne conception d'URL pour un système RESTful; Je me souviens qu'il y a plusieurs années, avec Roy T. Fielding, une discussion sur la liste REST à laquelle j'ai participé, comme il l'a dit explicitement. Autrement dit, il est possible d'avoir une conception hypertexte et URL. La conception d'URL pour les systèmes RESTful est comme l'indentation dans la programmation; non requis mais une très bonne idée (en ignorant Python, etc.)
MikeSchinkel
2
Je suis désolé, vous avez raison. Le PO m'a donné l'impression qu'il allait sensibiliser les clients à la façon de construire des URL - il ferait des "dispositions" d'URL une partie de son API. Ce serait une violation de REST.
aehlke
@aehlke, vous devez mettre à jour votre réponse pour qu'elle corresponde à votre commentaire.
James McMahon
1
Il est conforme au modèle de maturité Richardson niveau 2 . Vous faites référence au niveau 3. Acceptez simplement REST comme quelque chose qui peut être progressivement adopté.
Jules Randolph
1
@Jules Randolph - excuses, ma réponse a été écrite quelques mois seulement après la création du modèle de maturité Richardson et avant que Martin Fowler et d'autres auteurs ne le popularisent :) En effet, c'est un modèle instructif à suivre. N'hésitez pas à modifier la réponse.
aehlke
1

Bien que j'aime la réponse de Justin, je pense qu'elle représente plus précisément un filtre plutôt qu'une recherche. Et si je veux en savoir plus sur les voitures dont le nom commence par cam?

De la façon dont je le vois, vous pouvez l'intégrer dans la façon dont vous gérez des ressources spécifiques:
/ cars / cam *

Ou, vous pouvez simplement l'ajouter dans le filtre:
/ cars / doors / 4 / name / cam * / colors / red, bleu, vert

Personnellement, je préfère ce dernier, mais je ne suis en aucun cas un expert en REST (j'en ai entendu parler il y a seulement 2 semaines environ ...)


la source
Comme ceci:/cars?name=cam*
DanMan
1

RESTful ne recommande pas d'utiliser des verbes dans les URL / voitures / la recherche n'est pas reposante. La bonne façon de filtrer / rechercher / paginer vos API consiste à utiliser les paramètres de requête. Cependant, il peut arriver que vous deviez enfreindre la norme. Par exemple, si vous effectuez une recherche sur plusieurs ressources, vous devez utiliser quelque chose comme / search? Q = query

Vous pouvez consulter http://saipraveenblog.wordpress.com/2014/09/29/rest-api-best-practices/ pour comprendre les meilleures pratiques de conception des API RESTful.

java_geek
la source
1
La recherche est aussi un nom 😀
jith912
1

De plus, je suggérerais également:

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

Ici, Searchest considéré comme une ressource enfant de Carsressource.

aux
la source
1

Il y a beaucoup de bonnes options pour votre cas ici. Vous devriez toujours envisager d'utiliser le corps POST.

La chaîne de requête est parfaite pour votre exemple, mais si vous avez quelque chose de plus compliqué, par exemple une longue liste arbitraire d'éléments ou des conditions booléennes, vous voudrez peut-être définir la publication comme un document, que le client envoie via POST.

Cela permet une description plus flexible de la recherche et évite la limite de longueur d'URL du serveur.

estani
la source
-4

Mon conseil serait le suivant:

/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 :) )

Éditer:

/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.

J'espère que cela vous donne l'idée. Essentiellement, votre API Rest devrait être facilement détectable et devrait vous permettre de parcourir vos données. Un autre avantage de l'utilisation des URL et non des chaînes de requête est que vous pouvez tirer parti des mécanismes de mise en cache natifs qui existent sur le serveur Web pour le trafic HTTP.

Voici un lien vers une page décrivant les maux des chaînes de requête dans REST: http://web.archive.org/web/20070815111413/http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

J'ai utilisé le cache de Google parce que la page normale ne fonctionnait pas pour moi, voici également ce lien: http://rest.blueoxen.net/cgi-bin/wiki.pl?QueryStringsConsideredHarmful

Justin Bozonier
la source
1
Merci pour la réponse détaillée. Sur le dernier, que se passe-t-il si je souhaite effectuer une recherche par couleur et par nombre de portes? / voitures / couleurs / rouge, bleu, vert / portes / 4 Cela ne semble pas correct.
Parand
2
Les virgules dans l'URL ne me semblent pas correctes, mais elles restent valides. Je pense que c'est juste un changement de paradigme.
Justin Bozonier
21
Je n'aime pas cette suggestion. Comment sauriez-vous la différence entre /cars/colors/red,blue,greenet /cars/colors/green,blue,red? L'élément path de l'URI devrait être hiérarchique, et je ne vois pas vraiment que ce soit le cas ici. Je pense que c'est une situation où la chaîne de requête est le choix le plus approprié.
troelskn
62
C'est une mauvaise réponse. En fait, la bonne façon d'implémenter la recherche consiste à utiliser des chaînes de requête. Les chaînes de requête ne sont pas mauvaises du tout lorsqu'elles sont utilisées correctement. L'article cité ne fait pas référence à la recherche. Les exemples fournis sont clairement torturés et ne tiendraient pas bien avec plus de paramètres.
pbreitenbach
4
les chaînes de requête ont été faites principalement pour résoudre le problème d'interrogation d'une ressource, même avec plusieurs paramètres. Pervertir l'URI pour activer une API "RESTful" semble dangereux et à courte vue - d'autant plus que vous devez écrire vos propres mappages complexes juste pour gérer les différentes permutations de paramètres sur l'URI. Mieux encore, utilisez la notion déjà existante d'utiliser des points-virgules dans vos URI: doriantaylor.com/policy/http-url-path-parameter-syntax
Anatoly G