Appeler une méthode côté serveur sur une ressource de manière REST

142

Gardez à l'esprit que j'ai une compréhension rudimentaire de REST. Disons que j'ai cette URL:

http://api.animals.com/v1/dogs/1/

Et maintenant, je veux que le serveur fasse aboyer le chien. Seul le serveur sait comment faire cela. Disons que je veux le faire fonctionner sur un travail CRON qui fait aboyer le chien toutes les 10 minutes pour le reste de l'éternité. À quoi ressemble cet appel? Je veux faire ça:

Demande d'URL:

ACTION http://api.animals.com/v1/dogs/1/

Dans le corps de la requête:

{"action":"bark"}

Avant de vous fâcher contre moi pour avoir créé ma propre méthode HTTP, aidez-moi et donnez-moi une meilleure idée sur la façon dont je devrais invoquer une méthode côté serveur de manière REST. :)

MODIFIER POUR CLARIFICATION

Quelques précisions supplémentaires sur ce que fait la méthode "aboyer". Voici quelques options qui peuvent entraîner des appels d'API structurés différemment:

  1. aboyer envoie simplement un e-mail à dog.email et n'enregistre rien.
  2. bark envoie un e-mail à dog.email et incrémente dog.barkCount de 1.
  3. bark crée un nouvel enregistrement "aboiement" avec enregistrement bark.timestamp lorsque l'écorce s'est produite. Il incrémente également dog.barkCount de 1.
  4. bark exécute une commande système pour extraire la dernière version du code chien de Github. Il envoie ensuite un message texte à dog.owner leur indiquant que le nouveau code de chien est en cours de production.
Kirk Ouimet
la source
14
Fait intéressant, l'ajout d'une prime semble avoir attiré des réponses pires que celles que vous aviez initialement ;-) Lors de l'évaluation des réponses, rappelez-vous que: 1) Les spécifications des verbes HTTP excluent tout choix autre que POST. 2) REST n'a rien à voir avec la structure des URL - il s'agit d'une liste générique de contraintes (sans état, pouvant être mis en cache, en couches, interface uniforme, etc.) qui confère des avantages (évolutivité, fiabilité, visibilité, etc.). 3) La pratique actuelle (comme l'utilisation de POST dans les spécifications RPC) l'emporte sur les définitionnalistes qui créent leurs propres règles d'API. 4) REST nécessite une interface uniforme (suivant la spécification HTTP).
Raymond Hettinger
@Kirk que pensez-vous des nouvelles réponses? Y a-t-il quelque chose que vous voulez toujours savoir mais qui n'a été abordé dans aucun d'entre eux? Je serais plus qu'heureux de modifier à nouveau ma réponse si cela peut être plus utile.
Jordan
@RaymondHettinger PATCHpeut être approprié. J'explique pourquoi vers la fin de ma réponse .
Jordan
PATCH ne serait approprié que pour incrémenter dog.barkCount de un. POST est la méthode pour envoyer un e-mail, créer un nouvel enregistrement d'aboiement, exécuter des commandes à télécharger depuis Github ou déclencher un message texte. @Jordan, votre lecture de la RFC PATCH est imaginative mais quelque peu en contradiction avec son intention en tant que variante de PUT pour la modification partielle des ressources. Je ne pense pas que vous aidiez l'OP en proposant des lectures non conventionnelles des spécifications HTTP plutôt que de reconnaître la pratique standard consistant à utiliser POST pour les appels de procédure à distance.
Raymond Hettinger
@RaymondHettinger dont la pratique standardise de facto POST? Toutes les interfaces RPC standard que j'ai vues identifient une ressource par entité (pas RESTful), vs URI, donc une réponse valide donnant la priorité à la convention RPC devrait de toute façon être non conventionnelle, ce qui, je pense, réfute la valeur du RPC conventionnel: on est imaginatif ou incohérent . Vous ne pouvez jamais vous tromper avec POST car c'est le fourre-tout pour le traitement des données, mais il existe des méthodes plus spécifiques. REST signifie nommer les ressources et décrire les changements dans leur état, et non nommer les procédures de changement d'état. PATCH et POST décrivent tous deux les changements d'état.
Jordan

Réponses:

280

Pourquoi viser un design RESTful?

Les principes RESTful apportent les fonctionnalités qui rendent les sites Web faciles (pour un utilisateur humain aléatoire de les «surfer») à la conception de l'API des services Web , de sorte qu'ils sont faciles à utiliser pour un programmeur. REST n'est pas bon parce que c'est REST, c'est bon parce que c'est bon. Et c'est bien surtout parce que c'est simple .

La simplicité de HTTP simple (sans enveloppes SOAP et POSTservices surchargés à URI unique ), ce que certains peuvent appeler «manque de fonctionnalités» , est en fait sa plus grande force . Dès le départ, HTTP vous demande d' adressabilité et d' apatridie : les deux décisions de conception de base qui permettent à HTTP de rester évolutif jusqu'aux méga-sites (et méga-services) d'aujourd'hui.

Mais REST n'est pas la solution miracle: parfois un style RPC ("Remote Procedure Call" - comme SOAP) peut être approprié , et parfois d'autres besoins ont priorité sur les vertus du Web. C'est bon. Ce que nous n'aimons pas vraiment, c'est la complexité inutile . Trop souvent, un programmeur ou une entreprise fait appel à des services de style RPC pour un travail que l'ancien HTTP pourrait gérer très bien. L'effet est que HTTP est réduit à un protocole de transport pour une énorme charge XML qui explique ce qui se passe "vraiment" (pas l'URI ou la méthode HTTP en donnent un indice). Le service qui en résulte est beaucoup trop complexe, impossible à déboguer et ne fonctionnera pas à moins que vos clients n'aient la configuration exacte prévue par le développeur.

De la même manière qu'un code Java / C # ne peut pas être orienté objet, le simple fait d'utiliser HTTP ne rend pas une conception RESTful. On peut être pris dans la précipitation de penser à leurs services en termes d'actions et de méthodes à distance qu'il faudrait appeler. Pas étonnant que cela finisse principalement dans un service de style RPC (ou un hybride REST-RPC). La première étape consiste à penser différemment. Une conception RESTful peut être réalisée de plusieurs manières, l'une d'entre elles consiste à penser votre application en termes de ressources et non d'actions:

💡 Au lieu de penser en termes d'actions qu'il peut effectuer ("faire une recherche de lieux sur la carte") ...

... essayez de penser en termes de résultats de ces actions ("la liste des lieux sur la carte correspondant à un critère de recherche").

Je vais chercher des exemples ci-dessous. (Un autre aspect clé de REST est l'utilisation de HATEOAS - je ne le brosse pas ici, mais j'en parle rapidement dans un autre post .)


Problèmes de la première conception

Jetons un coup d'œil au design proposé:

ACTION http://api.animals.com/v1/dogs/1/

Tout d'abord, nous ne devrions pas envisager de créer un nouveau verbe HTTP ( ACTION). De manière générale, cela n'est pas souhaitable pour plusieurs raisons:

  • (1) Étant donné uniquement l'URI du service, comment un programmeur "aléatoire" saura-t-il que le ACTIONverbe existe?
  • (2) si le programmeur sait qu'il existe, comment connaîtra-t-il sa sémantique? Que veut dire ce verbe?
  • (3) quelles propriétés (sécurité, idempotence) doit-on s'attendre à ce que ce verbe ait?
  • (4) Et si le programmeur a un client très simple qui ne gère que les verbes HTTP standard?
  • (5) ...

Considérons maintenant l' utilisationPOST (je vais expliquer pourquoi ci-dessous, croyez-moi sur parole maintenant):

POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com

{"action":"bark"}

Cela pourrait être OK ... mais seulement si :

  • {"action":"bark"}était un document; et
  • /v1/dogs/1/était un URI de "processeur de document" (semblable à une usine). Un «processeur de document» est un URI sur lequel vous «jetez des choses» et «oubliez» - le processeur peut vous rediriger vers une ressource nouvellement créée après le «lancement». Par exemple, l'URI pour publier des messages dans un service de courtier de messages, qui, après la publication, vous redirigera vers un URI qui montre l'état du traitement du message.

Je ne sais pas grand-chose de votre système, mais je parie déjà que les deux ne sont pas vrais:

  • {"action":"bark"} n'est pas un document , c'est en fait la méthode que vous essayez de vous faufiler ninja dans le service; et
  • l' /v1/dogs/1/URI représente une ressource "chien" (probablement le chien avec id==1) et non un processeur de document.

Donc, tout ce que nous savons maintenant, c'est que la conception ci-dessus n'est pas si RESTful, mais qu'est-ce que c'est exactement? Qu'y a-t-il de si mauvais? Fondamentalement, c'est mauvais parce que c'est un URI complexe avec des significations complexes. Vous ne pouvez rien en déduire. Comment un programmeur pourrait-il savoir qu'un chien a une barkaction qui peut être secrètement infusée avec un POST?


Concevoir les appels API de votre question

Alors allons droit au but et essayons de concevoir ces aboiements de manière REST en pensant en termes de ressources . Permettez-moi de citer le livre Restful Web Services :

Une POSTdemande est une tentative de création d'une nouvelle ressource à partir d'une ressource existante. La ressource existante peut être le parent de la nouvelle au sens de la structure de données, de la même manière que la racine d'un arbre est le parent de tous ses nœuds feuilles. Ou la ressource existante peut être une ressource "usine" spéciale dont le seul but est de générer d'autres ressources. La représentation envoyée avec une POSTrequête décrit l'état initial de la nouvelle ressource. Comme avec PUT, une POSTdemande n'a pas du tout besoin d'inclure une représentation.

Suite à la description ci-dessus, nous pouvons voir que cela barkpeut être modélisé comme unedog sous -ressource de a (puisque a barkest contenu dans un chien, c'est-à-dire qu'un aboiement est "aboyé" par un chien).

De ce raisonnement, nous avons déjà obtenu:

  • La méthode est POST
  • La ressource est /barks, sous-ressource de chien:, /v1/dogs/1/barksreprésentant une bark"usine". Cet URI est unique pour chaque chien (car il est sous /v1/dogs/{id}).

Désormais, chaque cas de votre liste a un comportement spécifique.

1. Bark envoie juste un e-mail à dog.emailet n'enregistre rien.

Premièrement, aboyer (envoyer un e-mail) est-il une tâche synchrone ou asynchrone? Deuxièmement, la barkdemande nécessite-t-elle un document (l'e-mail, peut-être) ou est-il vide?


1.1 bark envoie un e-mail à dog.emailet n'enregistre rien (en tant que tâche synchrone)

Ce cas est simple. Un appel à la barksressource d'usine donne immédiatement un aboiement (un e-mail envoyé) et la réponse (si OK ou non) est donnée immédiatement:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(entity-body is empty - or, if you require a **document**, place it here)

200 OK

Comme il n'enregistre (ne change) rien, 200 OKc'est assez. Cela montre que tout s'est déroulé comme prévu.


1.2 bark envoie un e-mail à dog.emailet n'enregistre rien (en tant que tâche asynchrone)

Dans ce cas, le client doit avoir un moyen de suivre la barktâche. La barktâche doit alors être une ressource avec son propre URI:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

De cette façon, chacun barkest traçable. Le client peut alors émettre un GETà l' barkURI pour connaître son état actuel. Peut-être même utiliser un DELETEpour l'annuler.


2. bark envoie un e-mail à dog.email, puis incrémente dog.barkCountde 1

Celui-ci peut être plus délicat, si vous voulez informer le client que la dogressource est modifiée:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}

303 See Other
Location: http://api.animals.com/v1/dogs/1

Dans ce cas, l' locationintention de l'en- tête est de faire savoir au client qu'il doit jeter un coup d'œil dog. Depuis le RFC HTTP sur303 :

Cette méthode existe principalement pour permettre la sortie d'un POSTscript activé pour rediriger l'agent utilisateur vers une ressource sélectionnée.

Si la tâche est asynchrone, une sous- barkressource est nécessaire tout comme la 1.2situation et 303doit être renvoyée en a GET .../barks/Ylorsque la tâche est terminée.


3. aboiement crée un nouveau " bark" enregistrement avec bark.timestampenregistrement lorsque l'écorce s'est produite. Il incrémente également dog.barkCountde 1.

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

Ici, le barkest créé en raison de la demande, le statut 201 Createdest donc appliqué.

Si la création est asynchrone, un 202 Acceptedest requis ( comme le dit la RFC HTTP ) à la place.

L'horodatage enregistré fait partie de la barkressource et peut être récupéré avec un GETto. Le chien mis à jour peut également être "documenté" GET dogs/X/barks/Y.


4. bark exécute une commande système pour extraire la dernière version du code chien de Github. Il envoie ensuite un message texte pour dog.ownerleur dire que le nouveau code chien est en cours de production.

Le libellé de celui-ci est compliqué, mais c'est à peu près une tâche asynchrone simple:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

Le client émettrait alors GETs pour /v1/dogs/1/barks/a65h44connaître l'état actuel (si le code était extrait, l'e-mail était envoyé au propriétaire et autre). Chaque fois que le chien change, un 303est applicable.


Emballer

Citant Roy Fielding :

La seule chose que REST exige des méthodes est qu'elles soient uniformément définies pour toutes les ressources (c'est-à-dire, afin que les intermédiaires n'aient pas à connaître le type de ressource pour comprendre la signification de la requête).

Dans les exemples ci-dessus, POSTest conçu de manière uniforme. Cela fera le chien " bark". Ce n'est ni sûr (ce qui veut dire que bark a des effets sur les ressources), ni idempotent (chaque requête en produit un nouveau bark), ce qui correspond bien au POSTverbe.

Un programmeur saurait: un POSTà barksdonne un bark. Les codes d'état de la réponse (également avec le corps de l'entité et les en-têtes si nécessaire) expliquent ce qui a changé et comment le client peut et doit procéder.

Remarque: Les principales sources utilisées étaient: le livre " Restful Web Services ", le HTTP RFC et le blog de Roy Fielding .




Éditer:

La question et donc la réponse ont beaucoup changé depuis leur création. La question initiale posée sur la conception d'un URI comme:

ACTION http://api.animals.com/v1/dogs/1/?action=bark

Vous trouverez ci-dessous l'explication des raisons pour lesquelles ce n'est pas un bon choix:

La manière dont les clients disent au serveur QUE FAIRE avec les données est l' information sur la méthode .

  • Les services Web RESTful transmettent les informations de méthode dans la méthode HTTP.
  • Les services de style RPC et SOAP typiques conservent les leurs dans le corps d'entité et l'en-tête HTTP.

SUR QUELLE PARTIE des données [le client veut que le serveur] opère est l' information de portée .

  • Les services RESTful utilisent l'URI. Les services de style SOAP / RPC utilisent à nouveau le corps d'entité et les en-têtes HTTP.

À titre d'exemple, prenez l'URI de Google http://www.google.com/search?q=DOG. Là, les informations de méthode sont GETet les informations de portée sont /search?q=DOG.

Longue histoire courte:

  • Dans les architectures RESTful , les informations de méthode vont dans la méthode HTTP.
  • Dans les architectures orientées ressources , les informations de portée vont dans l'URI.

Et la règle d'or:

Si la méthode HTTP ne correspond pas aux informations de la méthode, le service n'est pas RESTful. Si les informations de portée ne sont pas dans l'URI, le service n'est pas orienté ressources.

Vous pouvez mettre l ' "action" "aboyer" dans l'URL (ou dans le corps de l'entité) et l'utiliser POST. Pas de problème là-bas, cela fonctionne et peut être le moyen le plus simple de le faire, mais ce n'est pas RESTful .

Pour garder votre service vraiment RESTful, vous devrez peut-être prendre du recul et réfléchir à ce que vous voulez vraiment faire ici (quels effets cela aura-t-il sur les ressources).

Je ne peux pas parler de vos besoins commerciaux spécifiques, mais laissez-moi vous donner un exemple: considérez un service de commande RESTful où les commandes sont à des URI comme example.com/order/123.

Maintenant, disons que nous voulons annuler une commande, comment allons-nous le faire? On peut être tenté de penser qu'il s'agit d'une «action» d' annulation et de la concevoir comme .POST example.com/order/123?do=cancel

Ce n'est pas RESTful, comme nous l'avons mentionné ci-dessus. Au lieu de cela, nous pourrions PUTune nouvelle représentation du orderavec un canceledélément envoyé à true:

PUT /order/123 HTTP/1.1
Content-Type: application/xml

<order id="123">
    <customer id="89987">...</customer>
    <canceled>true</canceled>
    ...
</order>

Et c'est tout. Si la commande ne peut pas être annulée, un code de statut spécifique peut être retourné. (Une conception de sous-ressource, comme POST /order/123/canceledavec le corps de l'entité truepeut, pour plus de simplicité, être également disponible.)

Dans votre scénario spécifique, vous pouvez essayer quelque chose de similaire. De cette façon, pendant qu'un chien aboie, par exemple, un GETat /v1/dogs/1/pourrait inclure cette information (par exemple <barking>true</barking>) . Ou ... si c'est trop compliqué, assouplissez votre exigence RESTful et tenez-vous-en POST.

Mettre à jour:

Je ne veux pas que la réponse soit trop grande, mais cela prend un certain temps pour maîtriser l'exposition d'un algorithme (une action ) en tant qu'ensemble de ressources. Au lieu de penser en termes d'actions ( "faire une recherche de lieux sur la carte" ), il faut penser en termes de résultats de cette action ( "la liste des lieux sur la carte correspondant à un critère de recherche" ).

Vous pouvez vous retrouver à revenir à cette étape si vous constatez que votre conception ne correspond pas à l'interface uniforme de HTTP.

Les variables de requête sont des informations de portée , mais ne dénotent pas de nouvelles ressources (il /post?lang=ens'agit clairement de la même ressource que /post?lang=jp, juste une représentation différente). Au contraire, ils sont utilisés pour transmettre l'état du client (comme ?page=10, pour que l'état ne soit pas conservé dans le serveur; ?lang=enest également un exemple ici) ou des paramètres d'entrée aux ressources algorithmiques ( /search?q=dogs, /dogs?code=1). Encore une fois, pas de ressources distinctes.

Propriétés des verbes HTTP (méthodes):

Un autre point clair qui apparaît ?action=somethingdans l'URI n'est pas RESTful, ce sont les propriétés des verbes HTTP:

  • GETet HEADsont sûrs (et idempotents);
  • PUTet DELETEsont idempotents seulement;
  • POST est ni.

Sécurité : Une demande GETou HEADest une demande de lecture de certaines données, pas une demande de modification d'un état de serveur. Le client peut faire une demande GETou HEAD10 fois et c'est la même chose qu'une seule fois, ou ne jamais le faire du tout .

Idempotence : Une opération idempotente en un qui a le même effet que vous l'appliquiez une ou plusieurs fois (en maths, multiplier par zéro est idempotent). Si vous avez DELETEune ressource une fois, la suppression à nouveau aura le même effet (la ressource est GONEdéjà).

POSTn'est ni sûr ni idempotent. Faire deux POSTdemandes identiques à une ressource «usine» entraînera probablement deux ressources subordonnées contenant les mêmes informations. Avec surchargé (méthode en URI ou entité-corps) POST, tous les paris sont ouverts.

Ces deux propriétés étaient importantes pour le succès du protocole HTTP (sur des réseaux peu fiables!): Combien de fois avez-vous mis à jour ( GET) la page sans attendre qu'elle soit complètement chargée?

Créer une action et la placer dans l'URL rompt clairement le contrat des méthodes HTTP. Encore une fois, la technologie vous permet, vous pouvez le faire, mais ce n'est pas une conception RESTful.

acdcjunior
la source
Je suis en désaccord avec l'idée que l'appel d'une action sur un serveur, désigné comme une action dans l'URL, n'est pas RESTful. POSTa été conçu pour "fournir un bloc de données ... à un processus de traitement de données" . Il semble que beaucoup de gens distinguent les ressources des actions, mais en réalité les actions ne sont qu'un type de ressource.
Jacob Stevens le
1
@JacobStevens L'OP a un peu changé la question, donc je dois mettre à jour ma réponse pour la rendre plus directe (vérifiez la question d'origine , vous verrez peut-être ce que je veux dire). Je suis d'accord avec POST"fournir un bloc de données ... à un processus de traitement des données", mais la différence est vraiment que, un bloc de données , pas un bloc de données et la procédure (action, méthode, commande) à être exécuté le alors. C'est une POSTsurcharge, et la POSTsurcharge est une conception de style RPC, pas RESTful.
acdcjunior
Je présume que la logique action / méthode serait hébergée sur le serveur, sinon quel serait le but de l'appel? Dans le cas que vous décrivez, je suis d'accord, ce ne serait pas une bonne conception. Mais la méthode ou le sous-programme qui effectue l'action serait spécifié par l'URI (ce qui est une autre raison pour laquelle une ressource d'action désignée comme un verbe à la fin d'une URL est utile et RESTful, bien que beaucoup le déconseillent).
Jacob Stevens
6
La réponse nous a mis à jour. C'est un peu long car une explication approfondie semblait nécessaire ("Gardez à l'esprit que j'ai une compréhension rudimentaire de REST."). C'était une sorte de lutte pour le rendre aussi clair que possible. J'espère que c'est utile d'une certaine manière.
acdcjunior
2
Excellente explication, j'ai voté mais l'en-tête Location ne doit pas être utilisé dans la réponse 202 Accepted. Cela semble être une mauvaise interprétation que beaucoup de gens font de RFC. Vérifiez ce stackoverflow.com/questions/26199228/…
Delmo
6

J'ai répondu plus tôt , mais cette réponse contredit ma vieille réponse et suit une stratégie très différente pour arriver à une solution. Il montre comment la requête HTTP est construite à partir des concepts qui définissent REST et HTTP. Il utilise également à la PATCHplace de POSTou PUT.

Il passe par les contraintes REST, puis les composants de HTTP, puis une solution possible.

DU REPOS

REST est un ensemble de contraintes destiné à être appliqué à un système hypermédia distribué afin de le rendre évolutif. Même pour en comprendre le sens dans le contexte du contrôle à distance d'une action, vous devez penser au contrôle à distance d'une action comme faisant partie d'un système hypermédia distribué - une partie d'un système de découverte, de visualisation et de modification d'informations interconnectées. Si cela pose plus de problèmes que cela ne vaut la peine, alors il n'est probablement pas bon d'essayer de le rendre RESTful. Si vous voulez juste une interface graphique de type "panneau de contrôle" sur le client qui peut déclencher des actions sur le serveur via le port 80, alors vous voulez probablement une interface RPC simple comme JSON-RPC via des requêtes / réponses HTTP ou un WebSocket.

Mais REST est une façon de penser fascinante et l'exemple de la question se trouve être facile à modéliser avec une interface RESTful, alors relevons le défi pour le plaisir et pour l'éducation.

REST est défini par quatre contraintes d'interface:

identification des ressources; manipulation des ressources par des représentations; messages auto-descriptifs; et, hypermédia comme moteur de l'état de l'application.

Vous vous demandez comment vous pouvez définir une interface, répondant à ces contraintes, via laquelle un ordinateur demande à un autre ordinateur de faire aboyer un chien. Plus précisément, vous voulez que votre interface soit HTTP et vous ne voulez pas ignorer les fonctionnalités qui rendent HTTP RESTful lorsqu'il est utilisé comme prévu.

Commençons par la première contrainte: l'identification des ressources .

Toute information qui peut être nommée peut être une ressource: un document ou une image, un service temporel (par exemple "la météo d'aujourd'hui à Los Angeles"), une collection d'autres ressources, un objet non virtuel (par exemple une personne), etc. .

Un chien est donc une ressource. Il doit être identifié.

Plus précisément, une ressource R est une fonction d'appartenance M R ( t ) variant dans le temps, qui pour le temps t correspond à un ensemble d'entités, ou valeurs, qui sont équivalentes. Les valeurs de l'ensemble peuvent être des représentations de ressources et / ou des identificateurs de ressources .

Vous modélisez un chien en prenant un ensemble d'identifiants et de représentations et en disant qu'ils sont tous associés les uns aux autres à un moment donné. Pour l'instant, utilisons l'identifiant "dog # 1". Cela nous amène aux deuxième et troisième contraintes: la représentation des ressources et l' auto-description .

Les composants REST exécutent des actions sur une ressource en utilisant une représentation pour capturer l'état actuel ou prévu de cette ressource et en transférant cette représentation entre les composants. Une représentation est une séquence d'octets, plus des métadonnées de représentation pour décrire ces octets.

Voici une séquence d'octets capturant l'état prévu du chien, c'est-à-dire la représentation que l'on souhaite associer à l'identifiant "dog # 1" (notez qu'il ne représente qu'une partie de l'état car il ne considère pas le nom du chien, sa santé , ou même des aboiements passés):

Il aboie toutes les 10 minutes depuis le moment où ce changement d'état a été effectué et se poursuivra indéfiniment.

Il est censé être attaché aux métadonnées qui le décrivent. Ces métadonnées peuvent être utiles:

C'est une déclaration en anglais. Il décrit une partie de l'état prévu. S'il est reçu plusieurs fois, n'autorisez que le premier à avoir un effet.

Enfin, regardons la quatrième contrainte: HATEOAS .

REST ... voit une application comme une structure cohérente d'informations et de contrôle des alternatives grâce auxquelles un utilisateur peut effectuer une tâche souhaitée. Par exemple, la recherche d'un mot dans un dictionnaire en ligne est une application, tout comme la visite d'un musée virtuel ou l'examen d'un ensemble de notes de cours pour étudier en vue d'un examen. ... Le prochain état de contrôle d'une application réside dans la représentation de la première ressource demandée, obtenir cette première représentation est donc une priorité. ... L'application de modèle est donc un moteur qui passe d'un état à l'autre en examinant et en choisissant parmi les transitions d'état alternatives dans l'ensemble actuel de représentations.

Dans une interface RESTful, le client reçoit une représentation de ressource afin de déterminer comment il doit recevoir ou envoyer une représentation. Il doit y avoir une représentation quelque part dans l'application à partir de laquelle le client peut comprendre comment recevoir ou envoyer toutes les représentations qu'il devrait pouvoir recevoir ou envoyer, même s'il suit une chaîne de représentations pour arriver à cette information. Cela semble assez simple:

Le client demande une représentation d'une ressource identifiée comme la page d'accueil; en réponse, il obtient une représentation contenant un identifiant de chaque chien souhaité par le client. Le client en extrait un identifiant et demande au service comment il peut interagir avec le chien identifié, et le service dit que le client peut envoyer une déclaration en anglais décrivant une partie de l'état prévu du chien. Ensuite, le client envoie une telle déclaration et reçoit un message de réussite ou un message d'erreur.

HTTP

HTTP implémente les contraintes REST comme suit:

l' identification des ressources : URI

représentation de ressource : corps d'entité

auto-description : méthode ou code d'état, en-têtes et éventuellement des parties du corps de l'entité (par exemple l'URI d'un schéma XML)

HATEOAS : hyperliens

Vous avez choisi http://api.animals.com/v1/dogs/1comme URI. Supposons que le client ait obtenu cela sur une page du site.

Utilisons ce corps d'entité (la valeur de nextest un horodatage; une valeur de 0signifie `` lorsque cette demande est reçue ''):

{"barks": {"next": 0, "frequency": 10}}

Maintenant, nous avons besoin d'une méthode. PATCH correspond à la description "partie de l'état prévu" que nous avons choisi:

La méthode PATCH demande qu'un ensemble de changements décrits dans l'entité de demande soit appliqué à la ressource identifiée par Request-URI.

Et quelques en-têtes:

Pour indiquer la langue du corps de l'entité: Content-Type: application/json

Pour vous assurer que cela n'arrive qu'une seule fois: If-Unmodified-Since: <date/time this was first sent>

Et nous avons une demande:

PATCH /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
Content-Type: application/json
If-Unmodified-Since: <date/time this was first sent>
[other headers]

{"barks": {"next": 0, "frequency": 10}}

En cas de succès, le client doit recevoir un 204code d'état en réponse, ou un 205si la représentation de /v1/dogs/1/a changé pour refléter le nouveau calendrier d'aboiements.

En cas d'échec, il devrait recevoir un 403message expliquant pourquoi.

Il n'est pas essentiel de REST pour que le service reflète la planification des aboiements dans une représentation en réponse à GET /v1/dogs/1/, mais cela aurait plus de sens si une représentation JSON incluait ceci:

"barks": {
    "previous": [x_1, x_2, ..., x_n],
    "next": x_n,
    "frequency": 10
}

Traitez le travail cron comme un détail d'implémentation que le serveur cache à l'interface. C'est la beauté d'une interface générique. Le client n'a pas besoin de savoir ce que fait le serveur dans les coulisses; tout ce qui compte, c'est que le service comprenne et réponde aux changements d'état demandés.

Jordan
la source
3

La plupart des gens utilisent POST à cette fin. Il est approprié pour effectuer "toute opération non sécurisée ou non idempotente lorsqu'aucune autre méthode HTTP ne semble appropriée".

Les API telles que XMLRPC utilisent POST pour déclencher des actions qui peuvent exécuter du code arbitraire. L '"action" est incluse dans les données POST:

POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181

<?xml version="1.0"?>
<methodCall>
   <methodName>examples.getStateName</methodName>
   <params>
      <param>
         <value><i4>41</i4></value>
         </param>
      </params>
   </methodCall>

Le RPC est un exemple pour montrer que POST est le choix conventionnel des verbes HTTP pour les méthodes côté serveur. Voici les réflexions de Roy Fielding sur POST - il dit à peu près qu'il est RESTful d'utiliser les méthodes HTTP comme spécifié.

Notez que RPC lui-même n'est pas très REST car il n'est pas orienté ressources. Mais si vous avez besoin d'apatridie, de mise en cache ou de couches, il n'est pas difficile d'effectuer les transformations appropriées. Voir http://blog.perfectapi.com/2012/opinionated-rpc-apis-vs-restful-apis/ pour un exemple.

Raymond Hettinger
la source
Je pense que vous devriez encoder les paramètres par URL et ne pas les mettre dans la chaîne de requête
tacos_tacos_tacos
@Kirk Oui, mais avec une modification mineure, laissez tomber la barre oblique finale: POST api.animals.com/v1/dogs1?action=bark
Raymond Hettinger
si vous suivez les conseils de cette réponse, gardez à l'esprit que l'API résultante ne sera pas RESTful.
Nicholas Shanks
2
Ce n'est pas RESTful car HTTP établit l'URL comme identifiant d'une ressource et une URL de /RPC2ne fait rien pour identifier une ressource - il identifie une technologie de serveur. Au lieu de cela, cela methodNameessaie d '«identifier» la «ressource» - mais même dans ce cas, il ne bénéficie pas de la distinction nom / verbe; la seule chose semblable à un «verbe» ici est methodCall. C'est comme «faire la récupération du nom de l'état» au lieu de «récupérer le nom de l'état» - ce dernier a tellement plus de sens.
Jordan
+1 pour les liens; très instructif et l'expérience «RPC opiniâtre» est inventive.
Jordan
2

POSTest la méthode HTTP conçue pour

Fournir un bloc de données ... à un processus de traitement de données

Les méthodes côté serveur gérant les actions non mappées CRUD sont ce que Roy Fielding a prévu avec REST, donc vous êtes bon là-bas, et c'est pourquoi il a POSTété conçu pour être non idempotent. POSTtraitera la plupart des publications de données vers des méthodes côté serveur pour traiter les informations.

Cela dit, dans votre scénario d'aboiement de chien, si vous voulez qu'un aboiement côté serveur soit effectué toutes les 10 minutes, mais que pour une raison quelconque, vous avez besoin que le déclencheur provienne d'un client, PUTcela servirait mieux le but, en raison de son idempotence. Eh bien, strictement selon ce scénario, il n'y a pas de risque apparent que plusieurs requêtes POST provoquent un miaulement de votre chien, mais c'est de toute façon le but des deux méthodes similaires. Ma réponse à une question SO similaire peut vous être utile.

Jacob Stevens
la source
1
PUT vs POST est une question d'URL. Le troisième paragraphe après 9.6 PUT indique que le but des deux méthodes est que l' PUTURL se réfère à ce qui doit être remplacé par le contenu du client et l' POSTURL se réfère à ce qui devrait traiter le contenu du client comme il le souhaite.
Jordan
1

Si nous supposons que Barking est une ressource interne / dépendante / secondaire sur laquelle le consommateur peut agir, alors nous pourrions dire:

POST http://api.animals.com/v1/dogs/1/bark

le chien numéro 1 aboie

GET http://api.animals.com/v1/dogs/1/bark

renvoie le dernier horodatage de l'écorce

DELETE http://api.animals.com/v1/dogs/1/bark

ne s'applique pas! alors ignorez-le.

bolbol
la source
Ceci n'est RESTful que si vous considérez /v1/dogs/1/barkêtre une ressource en soi et POSTune description de la manière dont l'état interne de cette ressource doit changer. Je trouve qu'il est plus logique de simplement considérer /v1/dogs/1/comme une ressource et d'indiquer dans l'entité-corps qu'il doit aboyer.
Jordan
mmm .. eh bien, c'est une ressource dont vous pouvez changer son état. Parce que le résultat du changement de son état fait du bruit, ne le rend pas moins ressource! Vous regardez Bark comme un verbe (ce qui est) c'est pourquoi vous ne pouvez pas le considérer comme une ressource. Je le regarde comme une ressource dépendante dont son état peut être changé et comme son état est booléen, je ne vois aucune raison de le mentionner dans le corps de l'entité. C'est juste mon avis.
bolbol
1

Les révisions précédentes de certaines réponses suggéraient d'utiliser RPC. Vous n'avez pas besoin de vous tourner vers RPC car il est parfaitement possible de faire ce que vous voulez tout en respectant les contraintes REST.

Tout d'abord, ne mettez pas de paramètres d'action dans l'URL. L'URL définit à quoi vous appliquez l'action et les paramètres de requête font partie de l'URL. Il devrait être pensé entièrement comme un nom. http://api.animals.com/v1/dogs/1/?action=barkest une ressource différente - un nom différent - à http://api.animals.com/v1/dogs/1/. [nb Asker a supprimé l' ?action=barkURI de la question.] Par exemple, comparez http://api.animals.com/v1/dogs/?id=1à http://api.animals.com/v1/dogs/?id=2. Différentes ressources, distinguées uniquement par la chaîne de requête. Ainsi l'action de votre requête, à moins qu'elle ne corresponde directement à un type de méthode existant sans corps (TRACE, OPTIONS, HEAD, GET, DELETE, etc.) doit être définie dans le corps de la requête.

Ensuite, décidez si l'action est " idempotente ", ce qui signifie qu'elle peut être répétée sans effet indésirable (voir le paragraphe suivant pour plus d'explications). Par exemple, la définition d'une valeur sur true peut être répétée si le client n'est pas sûr que l'effet souhaité s'est produit. Ils envoient à nouveau la demande et la valeur reste vraie. Ajouter 1 à un nombre n'est pas idempotent. Si le client envoie la commande Add1, n'est pas sûr que cela a fonctionné et la renvoie à nouveau, le serveur en a-t-il ajouté un ou deux? Une fois que vous avez déterminé cela, vous êtes mieux placé pour choisir entre PUTet POSTpour votre méthode.

Idempotent signifie qu'une demande peut être répétée sans changer le résultat. Ces effets n'incluent pas la journalisation et d'autres activités d'administration de serveur de ce type. En utilisant vos premier et deuxième exemples, envoyer deux e-mails à la même personne entraîne un état différent de l'envoi d'un e-mail (le destinataire en a deux dans sa boîte de réception, qu'il pourrait considérer comme du spam), donc j'utiliserais certainement POST pour cela. . Si le barkCount dans l'exemple 2 est destiné à être vu par un utilisateur de votre API ou affecte quelque chose qui est visible par le client, alors c'est aussi quelque chose qui rendrait la demande non idempotente. S'il ne doit être visualisé que par vous, il compte comme une journalisation du serveur et doit être ignoré lors de la détermination de l'idempotence.

Enfin, déterminez si l'action que vous souhaitez effectuer devrait réussir immédiatement ou non. BarkDog est une action qui se termine rapidement. RunMarathon ne l'est pas. Si votre action est lente, envisagez de renvoyer un 202 Accepted, avec une URL dans le corps de la réponse pour qu'un utilisateur interroge pour voir si l'action est terminée. Vous pouvez également demander aux utilisateurs de POSTER sur une URL de liste comme /marathons-in-progress/, puis lorsque l'action est terminée, redirigez-les de l'URL d'ID en cours vers l' /marathons-complete/URL.
Pour les cas spécifiques n ° 1 et n ° 2, je demanderais au serveur d'héberger une file d'attente et que le client y poste des lots d'adresses. L'action ne serait pas SendEmails, mais quelque chose comme AddToDispatchQueue. Le serveur peut ensuite interroger la file d'attente pour voir s'il y a des adresses e-mail en attente et envoyer des e-mails s'il en trouve. Il met ensuite à jour la file d'attente pour indiquer que l'action en attente a maintenant été effectuée. Vous auriez un autre URI indiquant au client l'état actuel de la file d'attente. Pour éviter le double envoi d'e-mails, le serveur peut également conserver un journal de la personne à qui il a envoyé cet e-mail et vérifier chaque adresse par rapport à celle-ci pour s'assurer qu'il n'en envoie jamais deux à la même adresse, même si vous POSTUREZ la même liste deux fois à La queue.

Lorsque vous choisissez un URI pour quoi que ce soit, essayez de penser à cela comme un résultat, pas comme une action. Par exemple, google.com/search?q=dogsaffiche les résultats d'une recherche sur le mot «chiens». Il n'effectue pas nécessairement la recherche.

Les cas n ° 3 et n ° 4 de votre liste ne sont pas non plus des actions idempotentes. Vous suggérez que les différents effets suggérés pourraient affecter la conception de l'API. Dans les quatre cas, j'utiliserais la même API, car les quatre modifient «l'état du monde».

Nicholas Shanks
la source
Supposons que l'action consiste à parcourir une file d'attente de courrier électronique géante et à envoyer un message à un groupe de personnes. Est-ce idempotent? Les actions idempotentes sont-elles pour PUT ou POST?
Kirk Ouimet
@kirk J'ai développé ma réponse.
Nicholas Shanks
0

Voir ma nouvelle réponse - elle contredit celle-ci et explique REST et HTTP plus clairement et plus précisément.

Voici une recommandation qui se trouve être RESTful mais qui n'est certainement pas la seule option. Pour commencer à aboyer lorsque le service reçoit la demande:

POST /v1/dogs/1/bark-schedule HTTP/1.1
...
{"token": 12345, "next": 0, "frequency": 10}

token est un nombre arbitraire qui empêche les aboiements redondants quel que soit le nombre de fois que cette demande est envoyée.

nextindique l'heure de l'écorce suivante; une valeur de 0signifie «ASAP».

Chaque fois que vous GET /v1/dogs/1/bark-schedule, vous devriez obtenir quelque chose comme ça, où t est l'heure du dernier aboiement et u est t + 10 minutes:

{"last": t, "next": u}

Je vous recommande vivement d'utiliser la même URL pour demander un aboiement que vous utilisez pour connaître l'état d'aboiement actuel du chien. Ce n'est pas essentiel pour REST, mais cela met l'accent sur l'acte de modifier le calendrier.

Le code d'état approprié est probablement 205 . J'imagine un client qui regarde le planning actuel, POSTs à la même URL pour le changer, et est chargé par le service de donner un second regard au planning pour prouver qu'il a été changé.

Explication

DU REPOS

Oubliez HTTP pendant un moment. Il est essentiel de comprendre qu'une ressource est une fonction qui prend du temps en entrée et renvoie un ensemble contenant des identificateurs et des représentations . Simplifions cela: une ressource est un ensemble R d'identificateurs et de représentations; R peut changer - les membres peuvent être ajoutés, supprimés ou modifiés. (Bien qu'il soit mauvais, la conception instable pour supprimer ou modifier des identifiants.) Nous disons un identifiant qui est un élément de R identifie R , et une représentation qui est un élément de R représente R .

Disons que R est un chien. Vous identifiez R comme /v1/dogs/1. (Signification /v1/dogs/1est membre de R .) C'est juste l' une des nombreuses façons dont vous pouvez identifier R . Vous pouvez également identifier R au fur /v1/dogs/1/x-rayset à mesure /v1/rufus.

Comment représentez-vous R ? Peut-être avec une photo. Peut-être avec une série de rayons X. Ou peut-être avec une indication de la date et de l'heure de la dernière aboiement de R. Mais rappelez-vous que ce sont toutes des représentations de la même ressource . /v1/dogs/1/x-raysest un identifiant de la même ressource qui est représenté par une réponse à la question "quand R a- t-il aboyé pour la dernière fois?"

HTTP

Les représentations multiples d'une ressource ne sont pas très utiles si vous ne pouvez pas faire référence à celle que vous souhaitez. C'est pourquoi HTTP est utile: il vous permet de connecter des identifiants aux représentations . Autrement dit, il s'agit d'un moyen pour le service de recevoir une URL et de décider quelle représentation servir au client.

Du moins, c'est ce que GETfait. PUTest fondamentalement l'inverse de GET: vous PUTune représentation r à l'URL si vous souhaitez que les futures GETrequêtes à cette URL retournent r , avec quelques traductions possibles comme JSON en HTML.

POSTest une manière plus souple de modifier une représentation. Pensez à la logique d'affichage et à la logique de modification qui sont équivalentes l'une à l'autre - toutes deux correspondant à la même URL. Une demande POST est une demande pour que la logique de modification traite les informations et modifie toutes les représentations (pas seulement la représentation localisée par la même URL) comme le service le juge opportun. Faites attention au troisième paragraphe après 9.6 PUT : vous ne remplacez pas la chose à l'URL par un nouveau contenu; vous demandez à la chose à l'URL de traiter certaines informations et de répondre intelligemment sous la forme de représentations informatives.

Dans notre cas, nous demandons à la logique de modification at /v1/dogs/1/bark-schedule(qui est la contrepartie de la logique d'affichage qui nous indique quand il a aboyé pour la dernière fois et quand il aboyera ensuite) de traiter nos informations et de modifier certaines représentations en conséquence. En réponse aux futurs GETs, la logique d'affichage correspondant à la même URL nous dira que le chien aboie maintenant comme nous le souhaitons.

Considérez le travail cron comme un détail d'implémentation. HTTP traite de l'affichage et de la modification des représentations. À partir de maintenant, le service indiquera au client quand le chien a aboyé pour la dernière fois et quand il aboyera ensuite. Du point de vue du service, c'est honnête car ces heures correspondent aux tâches cron passées et prévues.

Jordan
la source
-1

REST est un standard orienté ressources, il n'est pas piloté par l'action comme le serait un RPC.

Si vous voulez que votre serveur aboie , vous devriez vous pencher sur différentes idées telles que JSON-RPC ou dans la communication Websockets.

À mon avis, chaque tentative de le garder RESTful échouera: vous pouvez émettre un POSTavec le actionparamètre, vous ne créez pas de nouvelles ressources mais comme vous pouvez avoir des effets secondaires, vous êtes plus en sécurité.

moonwave99
la source
POSTa été conçu pour "fournir un bloc de données ... à un processus de traitement de données" . Il semble que beaucoup de gens distinguent les ressources des actions, mais en réalité les actions ne sont qu'un type de ressource. L'appel d'une ressource d'action sur un serveur reste une interface uniforme, pouvant être mise en cache, modulaire et évolutive. Il est également sans état, mais cela peut être violé si le client est conçu pour attendre une réponse. Mais appeler une "méthode void" sur le serveur est ce que Roy Fielding a prévu avec REST .
Jacob Stevens
Comme je le décris dans ma réponse , vous pouvez implicitement dans REST amener le serveur à effectuer une action en lui demandant de dire, à partir de maintenant, "votre action est terminée", alors que RPC est basé sur l'idée de simplement demander au serveur d'effectuer l'action. Les deux ont un sens parfait, tout comme la programmation impérative et déclarative a du sens.
Jordan