web-dev-qa-db-fra.com

Comment concevoir une recherche / filtrage RESTful?

Je conçois et implémente actuellement une API RESTful en PHP. Cependant, j'ai échoué dans la mise en œuvre de ma conception initiale.

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

Jusqu'ici jolie norme, non?

Mon problème est avec le premier GET /users. J'envisageais d'envoyer des paramètres dans le corps de la demande pour filtrer la liste. En effet, je veux pouvoir spécifier des filtres complexes sans obtenir une URL très longue, telle que:

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

Au lieu de cela, je voulais avoir quelque chose comme:

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

ce qui est beaucoup plus lisible et vous donne de grandes possibilités pour définir des filtres complexes.

Quoi qu'il en soit, file_get_contents('php://input') n'a pas renvoyé le corps de la requête pour les requêtes GET. J'ai aussi essayé http_get_request_body(), mais l'hébergement partagé que j'utilise n'a pas pecl_http. Pas sûr que ça aurait aidé de toute façon.

J'ai trouvé cette question et réalisé que GET n'est probablement pas supposé avoir un corps de requête. C'était un peu peu concluant, mais ils l'ont déconseillé.

Alors maintenant, je ne sais pas quoi faire. Comment concevez-vous une fonction de recherche/filtrage RESTful?

Je suppose que je pourrais utiliser POST, mais cela ne semble pas très reposant.

406
Erik B

Le meilleur moyen d'implémenter une recherche RESTful est de considérer la recherche elle-même comme une ressource. Ensuite, vous pouvez utiliser le verbe POST parce que vous créez une recherche. Il n'est pas nécessaire de créer littéralement quelque chose dans une base de données pour utiliser un POST.

Par exemple:

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

Vous créez une recherche du point de vue de l'utilisateur. Les détails de la mise en œuvre ne sont pas pertinents. Certaines API RESTful peuvent même ne pas nécessiter de persistance. C'est un détail de mise en œuvre.

379
Jason Harrelson

Si vous utilisez le corps de la requête dans une requête GET, vous cassez le principe REST, car votre requête GET ne pourra pas être mise en cache, car le système de cache utilise uniquement l'URL.

Et pire encore, votre URL ne peut pas être mise en signet, car elle ne contient pas toutes les informations nécessaires pour rediriger l'utilisateur vers cette page.

Utilisez des paramètres d'URL ou de requête au lieu des paramètres de corps de requête.

par exemple.:

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

En fait, le HTTP RFC 7231 dit que:

Une charge utile dans un message de demande GET n'a pas de sémantique définie; l'envoi d'un corps de données utiles sur une requête GET peut entraîner le rejet de la requête par certaines implémentations existantes.

Pour plus d'informations, jetez un oeil à ici

130
jfcorugedo

Il semble que le filtrage/la recherche de ressources puisse être implémenté de manière RESTful. L'idée est d'introduire un nouveau point de terminaison appelé /filters/ ou /api/filters/.

L'utilisation de ce noeud final filtre peut être considérée comme une ressource et donc créée via la méthode POST. Bien entendu, body peut être utilisé pour transporter tous les paramètres, ainsi que pour créer des structures de recherche/filtrage complexes.

Après avoir créé ce filtre, il existe deux possibilités pour obtenir le résultat de la recherche/filtrage.

  1. Une nouvelle ressource avec un identifiant unique sera renvoyée avec le code de statut 201 Created. Ensuite, en utilisant cet ID, une requête GET peut être adressée à /api/users/ comme:

    GET /api/users/?filterId=1234-abcd
    
  2. Une fois le nouveau filtre créé via POST, il ne répond pas avec 201 Created, mais en même temps avec 303 SeeOther avec l'en-tête Location pointant vers /api/users/?filterId=1234-abcd. Cette redirection sera automatiquement gérée via la bibliothèque sous-jacente.

Dans les deux scénarios, deux demandes doivent être effectuées pour obtenir les résultats filtrés - ceci peut être considéré comme un inconvénient, en particulier pour les applications mobiles. Pour les applications mobiles, j'utiliserais un seul appel POST vers /api/users/filter/.

Comment conserver les filtres créés?

Ils peuvent être stockés dans la base de données et utilisés ultérieurement. Ils peuvent également être stockés dans un stockage temporaire, par ex. redis et avoir quelques TTL après quoi ils expireront et seront supprimés.

Quels sont les avantages de cette idée?

Les filtres et les résultats filtrés peuvent être mis en mémoire cache et peuvent même être marqués d'un signet.

62
Opal

Je pense que vous devriez utiliser des paramètres de requête, mais seulement tant qu'il n'y a pas d'en-tête HTTP approprié pour accomplir ce que vous voulez faire. Le spécification HTTP ne dit pas explicitement que GET ne peut pas avoir de corps. Cependant ce document déclare:

Par convention, lorsque la méthode GET est utilisée, toutes les informations requises pour identifier la ressource sont codées dans l'URI. Il n'y a pas de convention dans HTTP/1.1 pour une interaction sécurisée (récupération, par exemple) dans laquelle le client fournit des données au serveur dans un corps d'entité HTTP plutôt que dans la partie requête d'un URI. Cela signifie que pour des opérations sécurisées, les URI peuvent être longs.

15
Daff

Ne vous inquiétez pas trop si votre API initiale est totalement RESTful ou non (spécialement si vous êtes juste dans les étapes alpha). Commencez par faire fonctionner la plomberie principale. Vous pouvez toujours faire une sorte de transformation/réécriture d'URL pour cartographier les choses, en peaufinant de manière itérative jusqu'à ce que vous obteniez quelque chose d'assez stable pour des tests généralisés ("bêta").

Vous pouvez définir des URI dont les paramètres sont codés par position et convention sur les URI eux-mêmes, préfixés par un chemin, vous savez que vous allez toujours mapper sur quelque chose. Je ne connais pas PHP, mais je supposerais qu'une telle fonctionnalité existe (comme dans d'autres langages avec des frameworks web):

.c'est à dire. Effectuez une recherche de type "utilisateur" avec param [i] = valeur [i] pour i = 1..4 sur le magasin n ° 1 (avec valeur1, valeur2, valeur3, ... en abrégé pour les paramètres de requête URI):

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

ou

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

ou comme suit (bien que je ne le recommande pas, plus à ce sujet plus tard)

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

Avec l'option 1, vous mappez tous les URI avec le préfixe /store1/search/user au gestionnaire de recherche (ou à la désignation PHP par défaut) recherchant des ressources sous store1 (équivalent à /search?location=store1&type=user.

Par convention documentée et appliquée par l'API, les valeurs des paramètres 1 à 4 sont séparées par des virgules et présentées dans cet ordre.

L'option 2 ajoute le type de recherche (dans ce cas user) en tant que paramètre de position n ° 1. L'une ou l'autre option n'est qu'un choix cosmétique.

L'option 3 est également possible, mais je ne pense pas que j'aimerais le faire. Je pense que la capacité de recherche dans certaines ressources devrait être présentée dans l'URI lui-même précédant la recherche elle-même (comme si elle indiquait clairement dans l'URI que la recherche est spécifique dans la ressource.)

L'avantage de ce dépassement des paramètres de passage sur l'URI est que la recherche fait partie de l'URI (il s'agit donc d'une recherche, d'une ressource dont le contenu peut - et va - changer - dans le temps.) L'inconvénient est que l'ordre des paramètres est obligatoire. .

Une fois que vous faites quelque chose comme ceci, vous pouvez utiliser GET, et ce serait une ressource en lecture seule (étant donné que vous ne pouvez pas POST ni PUT - elle est mise à jour lorsqu'elle est GET'ed). Ce serait également une ressource qui n'existe que lorsqu'elle est invoquée.

On pourrait également lui ajouter davantage de sémantique en mettant en cache les résultats pendant un certain temps ou avec un DELETE entraînant la suppression du cache. Toutefois, cela pourrait aller à l'encontre de l'usage habituel de DELETE par les utilisateurs (et parce qu'ils contrôlent généralement la mise en cache avec des en-têtes de mise en cache)

Ce serait une décision de conception, mais ce serait la façon dont je procéderais. Ce n'est pas parfait, et je suis sûr qu'il y aura des cas où ce n'est pas la meilleure chose à faire (spécialement pour des critères de recherche très complexes).

8
luis.espinal

Comme j'utilise un laravel/php backend, j'ai tendance à aller avec quelque chose comme ceci:

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

PHP transforme automatiquement [] params en tableau, de sorte que dans cet exemple, je vais me retrouver avec une variable $filter contenant un tableau/objet de filtres, ainsi qu'une page et toutes les ressources connexes que je souhaite ardemment chargé.

Si vous utilisez une autre langue, cela pourrait être une bonne convention et vous pouvez créer un analyseur syntaxique pour convertir [] en un tableau.

8
the-a-train

FYI: Je sais que c'est un peu tard mais pour tous ceux qui sont intéressés. En fonction du niveau de REST souhaité, vous devrez implémenter vos propres stratégies de filtrage car la spécification HTTP n’est pas très claire à ce sujet. J'aimerais suggérer le codage d'URL de tous les paramètres de filtre, par exemple.

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

Je sais que c'est moche, mais je pense que c'est le moyen le plus reposant de le faire et qu'il devrait être facile à analyser côté serveur :)

2
shanks