web-dev-qa-db-fra.com

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

N'oubliez pas 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 sur un travail CRON qui fait aboyer le chien toutes les 10 minutes pour le reste de l'éternité. A quoi ressemble cet appel? J'ai un peu envie de faire ceci:

Demande d'URL:

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

Dans le corps de la demande:

{"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 de la façon dont je devrais appeler une méthode côté serveur de manière RESTful. :)

MODIFIER POUR CLARIFICATION

Quelques précisions supplémentaires sur le fonctionnement de la méthode "écorce". Voici quelques options pouvant entraîner des appels d'API structurés différemment:

  1. aboie envoie juste un email à dog.email et n'enregistre rien.
  2. bark envoie un courrier électronique à dog.email et incrémente dog.barkCount de 1.
  3. bark crée un nouvel enregistrement "bark" 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 au propriétaire du chien lui indiquant que le nouveau code du chien est en cours de production.
132
Kirk Ouimet

Pourquoi viser un design RESTful?

Les principes RESTful apportent les fonctionnalités qui rendent les sites Web faciles (pour un tilisateur humain aléatoire pour les "surfer") à la conception de l'API de services Web , de sorte qu'elles 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 bon surtout parce que c'est simple .

La simplicité de HTTP simple (sans SOAP enveloppes et URL unique surchargée POST services)), quels certains peuvent appeler " manque de fonctionnalités ", est en réalité sa plus grande force . Dès le départ, HTTP vous demande de have adressability et statelessness: les deux décisions de conception fondamentales qui permettent à HTTP de rester évolutif jusqu'aux méga-sites (et méga-services) actuels.

Mais REST n'est pas l'argent): Parfois, un style RPC ("Appel de procédure distante" - tel que SOAP ) peut être approprié , et parfois d'autres besoins priment sur les vertus du Web. C'est bon. Ce que nous n'aimons pas vraiment est une complexité inutile . Trop souvent, un programmeur ou une entreprise fait appel à des services de type RPC pour un travail que HTTP ancien peut gérer correctement. réduit à un protocole de transport pour une énorme charge utile XML qui explique ce qui se passe réellement (ce ne sont pas l'URI ou la méthode HTTP qui le donne). Le service résultant est beaucoup trop complexe, impossible à déboguer et ne fonctionnera pas. sauf si vos clients ont la configuration exacte prévue par le développeur.

De la même manière, un code Java/C # peut être not ​​orienté objet, le simple fait d'utiliser HTTP ne rend pas une conception RESTful. On peut être pris dans la précipitation de penser à propos de ses services en termes d’actions et de méthodes distantes qui devrait être appelé. Rien d'étonnant à ce que cela aboutisse principalement à 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 des façons (la plus simple, pourrait-on dire) est de penser à votre application en termes de ressources, pas d'actions:

  • Au lieu de penser en termes d’actions ("faire une recherche de lieux sur la carte"),
  • Pensez en termes de résultats de cette action ("la liste des lieux sur la carte correspondant à un critère de recherche").

Je vais aller pour 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 article .)

A propos du premier design

Jetons un coup d'oeil à la conception proposée:

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 est indésirable pour plusieurs raisons:

  • (1) Etant donné uniquement l'URI du service, comment un programmeur "aléatoire" saura-t-il que le verbe ACTION existe?
  • (2) Si le programmeur sait qu'il existe, comment va-t-il connaître sa sémantique? Que veut dire ce verbe?
  • (3) Quelles propriétés (sécurité, idempotence) faut-il attendre de ce verbe?
  • (4) Que se passe-t-il si le programmeur a un client très simple ne gérant que les verbes HTTP standard?
  • (5) ...

Maintenant, considérons l’utilisation de POST (nous expliquerons pourquoi ci-dessous, prenons juste ma Parole pour le moment):

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

{"action":"bark"}

Ceci pourrait ​​soit OK ... mais seulement si :

  • {"action":"bark"} était un document; et
  • /v1/dogs/1/ était un URI de "processeur de document" (de type usine). Un "processeur de document" est un URI que vous souhaitez "jeter" et "oublier" à leur sujet - le processeur peut vous rediriger vers une ressource nouvellement créée après le "lancement". Par exemple. l'URI de publication de messages sur un service de courtier de messages, qui, après la publication, vous redirige vers un URI indiquant le statut du traitement du message.

Je ne connais pas grand chose de votre système, mais je parierais 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 êtes essayer de ninja-sneak dans le service; et
  • le /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 reposante, mais qu'est-ce que c'est exactement? Qu'est-ce qui est si mauvais? En gros, c'est mauvais parce que c'est un URI complexe avec des significations complexes. Vous ne pouvez rien en déduire. Comment un programmeur saurait-il qu'un chien a une action bark qui peut être secrètement infusée avec un POST?

Conception des appels API de votre question

Alors allons droit au but et essayons de concevoir ces écorces de manière RESTANTE en pensant en termes de ressources . Permettez-moi de citer le Restful Web Services book:

Une demande POST est une tentative de créer une nouvelle ressource à partir d'une ressource existante. La ressource existante peut être le parent du nouveau en termes de 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 bien la ressource existante peut être une ressource spéciale "factory" dont le seul but est de générer d'autres ressources. La représentation envoyée avec une demande POST décrit l'état initial de la nouvelle ressource. Comme avec PUT, une requête POST n'a pas besoin d'inclure une représentation.

Après la description ci-dessus, nous pouvons voir que bark peut être modélisé comme une sous-ressource d'un dog (étant donné qu'un bark est 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 du chien: /v1/dogs/1/barks, représentant une bark "usine". Cet URI est unique pour chaque chien (puisqu'il est sous /v1/dogs/{id}).

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

1. bark envoie simplement un e-mail à dog.email et n'enregistre rien.

Premièrement, l'aboiement (envoi d'un courrier électronique) est-il une tâche synchrone ou asynchrone? Deuxièmement, la demande bark nécessite-t-elle un document (le courrier électronique, peut-être) ou est-il vide?


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

Cette affaire est simple. Un appel à la ressource usine barks génère immédiatement un aboiement (un courrier électronique envoyé) et la réponse (si OK ou non) est immédiatement donnée:

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 enregistre (change) rien, 200 OK est assez. Cela montre que tout s'est passé comme prévu.


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

Dans ce cas, le client doit avoir un moyen de suivre la tâche bark. La tâche bark devrait 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, chaque bark est traçable. Le client peut ensuite envoyer un GET à l'URI bark pour connaître son état actuel. Peut-être même utilisez-vous un DELETE pour l'annuler.


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

Celui-ci peut être plus compliqué si vous voulez que le client sache que la ressource dog 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'en-tête location a pour but d'informer le client qu'il doit jeter un coup d'œil à dog. De la HTTP RFC sur 303 :

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

Si la tâche est asynchrone, une sous-ressource bark est nécessaire, tout comme la tâche 1.2 _ la situation et le 303 devrait être retourné à un GET .../barks/Y lorsque la tâche est terminée.


3. bark crée un nouvel enregistrement "bark" avec bark.timestamp enregistrement lorsque l’aboiement est survenu. Il incrémente également dog.barkCount par 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 bark est créé en raison de la demande, donc le statut 201 Created est appliqué.

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

L'horodatage enregistré fait partie de la ressource bark et peut être récupéré avec un objet GET. Le chien mis à jour peut être "documenté" dans ce GET dogs/X/barks/Y ainsi que.


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 en leur disant que le nouveau code de chien est en cours de production.

Le libellé de celui-ci est compliqué, mais il s’agit plutôt d’une simple tâche asynchrone:

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 en /v1/dogs/1/barks/a65h44 pour connaître l'état actuel (si le code a été extrait, l'e-mail a été envoyé au propriétaire, etc.). Chaque fois que le chien change, un 303 est applicable.


Emballer

Citant Roy Fielding :

La seule chose REST requiert des méthodes, c’est-à-dire qu’elles doivent être définies de manière uniforme pour toutes les ressources (c.-à-d. Que les intermédiaires ne doivent pas connaître le type de ressource pour comprendre le sens de la requête) .

Dans les exemples ci-dessus, POST est conçu de manière uniforme. Cela fera du chien "bark". Ce n'est pas sûr (signifiant que l'écorce a des effets sur les ressources), ni idempotent (chaque demande donne un nouveau bark), qui convient bien au verbe POST.

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

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




Modifier:

La question et donc la réponse ont beaucoup changé depuis leur création. La question originale portait sur la conception d'un URI tel que:

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

Vous trouverez ci-dessous une explication de la raison pour laquelle ce n’est pas un bon choix:

Comment les clients disent au serveur QUE FAIRE avec les données est le informations de la méthode.

  • Les services Web RESTful transmettent des informations sur les méthodes dans la méthode HTTP.
  • Les services RPC-Style typiques et SOAP restent dans l'entité-corps et l'en-tête HTTP).

QUELLE PARTIE des données [le client veut que le serveur] fonctionne est la informations de cadrage.

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

Par exemple, prenons l'URI de Google http://www.google.com/search?q=DOG. Là, les informations de méthode sont GET et les informations de portée sont /search?q=DOG.

Longue histoire courte:

  • Dans architectures RESTful , les informations sur la méthode sont intégrées à la méthode HTTP.
  • Dans Architectures orientées ressources , les informations de cadrage sont insérées dans l'URI.

Et la règle de base:

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 figurent pas dans l'URI, le service n'est pas axé sur les ressources.

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

Pour que votre service reste 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érons un service de commande RESTful où les commandes sont à des adresses URI telles que example.com/order/123.

Maintenant, disons que nous voulons annuler une commande, comment allons-nous le faire? On peut être tenté de penser que c'est un "annulation""action" et le concevoir comme POST example.com/order/123?do=cancel.

Ce n'est pas RESTful, comme nous avons parlé ci-dessus. Au lieu de cela, nous pourrions PUT une nouvelle représentation du order avec un élément canceled 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 d'état spécifique peut être renvoyé. (Une conception de sous-ressource, comme POST /order/123/canceled avec le corps d'entité true peut également être disponible pour des raisons de simplicité.)

Dans votre scénario spécifique, vous pouvez essayer quelque chose de similaire. Ainsi, lorsqu'un chien aboie, par exemple, un GET à /v1/dogs/1/ pourrait inclure cette information (par exemple. <barking>true</barking>). Ou ... si c'est trop compliqué, relâchez votre exigence RESTful et restez fidèle à POST.

Mise à jour:

Je ne veux pas que la réponse soit trop grande, mais il faut un certain temps avant de pouvoir exposer 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 de lieux sur la carte correspondant à un critère de recherche ").

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

Les variables de requête sont informations de portée, mais ne le font pas indique de nouvelles ressources (/post?lang=en est clairement la même ressource que /post?lang=jp, juste une représentation différente). Ils sont plutôt utilisés pour transmettre l'état du client (comme ?page=10, afin que cet état ne soit pas conservé sur le serveur; ?lang=en est également un exemple ici) ou paramètres d’entrée à 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 montre ?action=something dans l'URI n'est pas RESTful, sont les propriétés des verbes HTTP:

  • GET et HEAD sont en sécurité (et idempotents);
  • PUT et DELETE sont idempotents seulement;
  • POST n'est ni l'un ni l'autre.

Sécurité : Une requête GET ou HEAD est une requête visant à lire certaines données, pas une demande de changement d'état du serveur. Le client peut faire une demande GET ou HEAD 10 fois, ce qui revient à le faire une fois, ou ne le faisant jamais du tout.

Idempotence : Opération idempotente dans une opération ayant le même effet, que vous l'appliquiez une fois ou plus d'une fois (en mathématique, la multiplication par zéro est idempotente). Si vous DELETE une ressource une fois, sa suppression aura le même effet (la ressource est déjà GONE.).

POST n'est ni sûr ni idempotent. Faire deux requêtes POST identiques à une ressource 'usine' aboutira probablement à deux ressources subordonnées contenant les mêmes informations. Avec surchargé (méthode dans URI ou entité-corps) POST, tous les paris sont désactivés.

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 son chargement complet?

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

265
acdcjunior

I répondu plus tôt , mais cette réponse contredit mon ancienne réponse et suit une stratégie bien différente pour parvenir à une solution. comment la demande HTTP est construite à partir des concepts qui définissent REST et HTTP. Il utilise également PATCH à la place de POST ou PUT.

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

REST

REST est un ensemble de contraintes destiné à être appliqué à un système hypermédia distribué afin de le rendre évolutif. Même pour le comprendre dans le contexte du contrôle à distance d'une action, vous devez penser à le contrôler à distance en tant que partie d'un système hypermédia distribué - une partie d'un système permettant de rechercher, d'afficher et de modifier des informations interconnectées. Si cela vous cause plus de problèmes que cela ne vaut la peine, il est probablement inutile d'essayer de le rendre reposant. Si vous souhaitez simplement une interface graphique de type "Panneau de configuration" sur le client pouvant déclencher des actions sur le serveur via le port 80, vous souhaiterez probablement une interface RPC simple, telle que JSON-RPC via des requêtes/réponses HTTP ou WebSocket.

Mais REST est une façon de penser fascinante et l'exemple de la question s'avère facile à modéliser avec une interface RESTful, relevons donc 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 à travers des représentations; messages auto-descriptifs; et hypermédia en tant que moteur de l'état de l'application.

Vous demandez comment vous pouvez définir une interface, répondant à ces contraintes, via laquelle un ordinateur ordonne à un autre ordinateur de faire aboyer un chien. Spécifiquement, 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: identification de la ressource .

Toute information pouvant être nommée peut être une ressource: un document ou une image, un service temporel (par exemple, "le temps qu'il fait à Los Angeles"), un ensemble d'autres ressources, un objet non virtuel (par exemple, une personne), etc. .

Donc, un chien est une ressource. Il doit être identifié.

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

Vous modelez 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 "chien n ° 1". Cela nous amène aux deuxième et troisième contraintes: représentation de la ressource et auto-description .

Les composants REST effectuent 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.

Vous trouverez ci-dessous une séquence d’octets indiquant l’état souhaité du chien, c’est-à-dire la représentation que nous souhaitons associer à l’identifiant "dog # 1" (notez qu’il ne représente qu’une partie de l’état car il ne tient pas compte du nom du chien, état de santé, etc.). , 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 continuera indéfiniment.

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

C'est une déclaration anglaise. 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 ... considère une application comme une structure cohérente d'informations et de solutions de contrôle permettant à l'utilisateur d'effectuer la tâche souhaitée. Par exemple, rechercher un mot dans un dictionnaire en ligne est une application, tout comme parcourir un musée virtuel ou réviser un ensemble de notes de cours à é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. L'obtention de cette première représentation est donc une priorité. ... L'application 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 de représentations actuel.

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 exister une représentation quelque part dans l'application à partir de laquelle le client peut déterminer 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 étant la page d'accueil. en réponse, il obtient une représentation contenant un identifiant de chaque chien que le client pourrait vouloir. Le client en extrait un identifiant et demande au service comment il peut interagir avec le chien identifié. Le service indique 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:

identification de la ressource : URI

représentation de la ressource : entité-corps

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

[~ # ~] hateoas [~ # ~] : hyperliens

Vous avez décidé de http://api.animals.com/v1/dogs/1 comme l'URI. Supposons que le client l'ait reçue d'une page du site.

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

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

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

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

Et quelques en-têtes:

Pour indiquer le langage du corps-entité: Content-Type: application/json

Pour vous assurer que cela n'arrive qu'une 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 devrait recevoir un 204 code d'état en réponse, ou un 205 si la représentation de /v1/dogs/1/ a été modifié pour refléter le nouvel horaire d'aboiement.

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

Il n'est pas essentiel de REST pour que le service reflète l'horaire d'écorce dans une représentation en réponse à GET /v1/dogs/1/, mais cela aurait du 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 de l'interface. C'est la beauté d'une interface générique. Le client n'a pas besoin de savoir ce que le serveur fait en coulisse; tout ce qui compte, c’est que le service comprenne et réponde aux changements d’état demandés.

6
Jordan

La plupart des gens utilisent [~ # ~] post [~ # ~] à cette fin. Il convient d'effectuer "toute opération non sécurisée ou non-impotente lorsqu'aucune autre méthode HTTP ne semble appropriée".

API telles que XMLRPC utiliser [~ # ~] post [~ # ~] pour déclencher des actions pouvant 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 exemple est donné pour montrer que POST est le choix conventionnel des verbes HTTP pour les méthodes côté serveur. Voici pensées de Roy Fielding sur POST - il indique qu'il est RESTful d'utiliser les méthodes HTTP telles que spécifiées.

Notez que RPC lui-même n'est pas très RESTful car il n'est pas orienté ressources. Mais si vous avez besoin d'apatridie, de mise en cache ou de superposition, 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.

3

POST est le 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 qui gèrent les actions non mappées par CRUD sont ce qui Roy Fielding était prév avec REST, donc tout va bien, et c’est pourquoi POST a été créé pour être non idempotent. POST gérera la plupart des publications de données sur des méthodes côté serveur pour traiter les informations.

Cela dit, dans votre scénario d'aboiements de chiens, si vous souhaitez exécuter une aboiement côté serveur toutes les 10 minutes, mais que, pour une raison quelconque, le déclencheur ait pour origine un client, PUT servirait mieux l'objectif, à cause de son idempotence. Eh bien, strictement selon ce scénario, il n'y a pas de risque apparent de plusieurs POST demandes provoquant votre chien à miauler à la place, mais de toute façon c'est le but des deux méthodes similaires. Ma réponse à une similaire SO question peut vous être utile.

2
Jacob Stevens

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

Premièrement, 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 considéré entièrement comme un nom. http://api.animals.com/v1/dogs/1/?action=bark est une ressource différente - un nom différent - en http://api.animals.com/v1/dogs/1/. [nb Asker a supprimé le ?action=bark URI de la question.] Par exemple, comparez http://api.animals.com/v1/dogs/?id=1 à http://api.animals.com/v1/dogs/?id=2. Ressources différentes, distinguées uniquement par la chaîne de requête. Ainsi, l'action de votre demande, sauf si elle correspond directement à un type de méthode existant sans corps (TRACE, OPTIONS, HEAD, GET, DELETE, etc.) doit être définie dans le corps de la demande.

Ensuite, décidez si l'action est " idempotent ", ce qui signifie qu'elle peut être répétée sans effet négatif (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 certain que l'effet souhaité s'est produit. Ils envoient à nouveau la demande et la valeur reste vraie. Ajouter 1 à un numéro n'est pas idempotent. Si le client envoie la commande Add1, cela ne fonctionne-t-il pas correctement et l'envoie-t-il à nouveau, le serveur a-t-il ajouté un ou deux? Une fois que vous avez déterminé cela, vous êtes mieux placé pour choisir entre PUT et POST pour votre méthode.

Idempotent signifie qu'une requête peut être répétée sans changer le résultat. Ces effets n'incluent pas la journalisation et toute autre activité d'administration du serveur. En utilisant vos premier et deuxième exemples, envoyer deux courriels à la même personne crée un état différent de celui qui consiste à envoyer un seul courriel (le destinataire en a deux dans sa boîte de réception, ce qu’il pourrait considérer comme du spam), donc j’utiliserais certainement POST pour cela. Si barkCount dans l'exemple 2 est destiné à être vu par un utilisateur de votre API ou affecte un élément visible par le client, il s'agit également d'un élément qui rendrait la demande non idempotente. S'il ne doit être visualisé que par vous, il est considéré comme une journalisation du serveur et doit être ignoré lors de la détermination de idempotentcy.

Enfin, déterminez si l’action que vous souhaitez effectuer peut réussir immédiatement ou non. BarkDog est une action qui se termine rapidement. RunMarathon n'est pas. Si votre action est lente, envisagez de renvoyer un 202 Accepted, avec une URL dans le corps de la réponse qu'un utilisateur peut interroger pour savoir si l'action est terminée. Vous pouvez également avoir les utilisateurs POST sur une liste telle que /marathons-in-progress/ puis lorsque l'action est terminée, redirigez-les de l'URL d'ID en cours vers le /marathons-complete/ URL.
Pour les cas spécifiques n os 1 et 2, je ferais en sorte que le serveur héberge une file d’attente et que le client lui poste des lots d’adresses. L'action ne serait pas SendEmails, mais quelque chose comme AddToDispatchQueue. Le serveur peut alors interroger la file d'attente pour voir s'il y a des adresses électroniques en attente et envoyer des courriels 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 les doubles envois d’e-mails, le serveur peut également tenir un journal des destinataires de cet e-mail et vérifier chaque adresse pour vérifier qu’il n'en envoie jamais deux à la même adresse, même si vous POST la même liste deux fois à la file d'attente.

Lorsque vous choisissez un URI pour quelque chose que ce soit, essayez d'y penser comme un résultat, pas comme une action. Par exemple google.com/search?q=dogs montre les résultats d'une recherche du mot "chiens". Il ne faut pas nécessairement effectuer la recherche.

Les cas n ° 3 et n ° 4 de votre liste ne sont également pas des actions idempotentes. Vous suggérez que les différents effets suggérés peuvent affecter la conception de l'API. Dans les quatre cas, j'utiliserais la même API, car ils modifiaient tous les "états du monde".

1
Nicholas Shanks

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

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.

1
bolbol

Voir mon nouvelle réponse - il contredit celui-ci et explique REST et HTTP plus clairement et avec précision.

Voici une recommandation qui est RESTful mais 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, peu importe le nombre de fois que cette demande est envoyée.

next indique l'heure de la prochaine écorce; une valeur de 0 signifie 'ASAP'.

Quand tu GET /v1/dogs/1/bark-schedule, vous devriez obtenir quelque chose comme ceci, où t est l'heure de la dernière écorce et u est t + 10 minutes:

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

Je vous recommande vivement d'utiliser la même adresse 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 insiste sur le fait de modifier l'horaire.

Le code d'état approprié est probablement 205 . J'imagine un client qui examine l'horaire actuel, POSTs avec la même URL pour le modifier, et qui est chargé par le service de le réexaminer pour prouver qu'il a été modifié.

Explication

REST

Oubliez HTTP pour un moment. Il est essentiel de comprendre qu'un ressource est une fonction qui prend du temps en entrée et retourne un ensemble contenant identificateurs et représentations. Simplifions cela pour: 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 que ce soit une mauvaise conception instable de supprimer ou de modifier des identifiants.) Nous disons un identifiant qui est un élément de [~ # ~] r [~ # ~] identifie [~ # ~] r [~ # ~], et qu'une représentation qui est un élément de [~ # ~] r [~ # ~] représente [ ~ # ~] r [~ # ~].

Disons que [~ # ~] r [~ # ~] est un chien. Il vous arrive d’identifier [~ # ~] r [~ # ~] comme /v1/dogs/1. (Sens /v1/dogs/1 est un membre de [~ # ~] r [~ # ~].) Ce n’est qu’un moyen parmi d’autres que vous pourriez identifier [~ # ~] r [~ # ~]. Vous pouvez également identifier [~ # ~] r [~ # ~] comme /v1/dogs/1/x-rays et comme /v1/rufus.

Comment représentez-vous [~ # ~] r [~ # ~]? Peut-être avec une photo. Peut-être avec un ensemble de rayons X. Ou peut-être avec une indication de la date et de l'heure lorsque [~ # ~] r [~ # ~] aboyé pour la dernière fois. Mais rappelez-vous que ce sont toutes des représentations de la même ressource. /v1/dogs/1/x-rays _ est un identifiant de la même ressource qui est représenté par une réponse à la question "à quel moment [~ # ~] r [~ # ~] dernier aboiement?"

HTTP

Plusieurs représentations d'une ressource ne sont pas très utiles si vous ne pouvez pas vous référer à celle que vous voulez. C'est pourquoi HTTP est utile: il vous permet connecter des identifiants à des représentations . C'est-à-dire que le service peut recevoir une URL et décider de la représentation à servir au client.

Du moins, c’est ce que GET fait. PUT est fondamentalement l'inverse de GET: vous PUT une représentation r à l'URL si vous souhaitez pour l'avenir GET demande à cette URL de retourner r, avec certaines traductions possibles, telles que JSON en HTML.

POST est une manière plus souple de modifier une représentation. Pensez à la logique d’affichage et à la logique de modification qui sont équivalentes, toutes deux correspondant à la même URL. Une demande POST est une demande de la logique de modification permettant de traiter les informations et de modifier les représentations (pas seulement la représentation localisée par la même URL) que le service juge utile. Faites attention au troisième paragraphe. after 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 à /v1/dogs/1/bark-schedule (qui est la contrepartie de la logique d’affichage qui nous dit quand il a aboyé pour la dernière fois et quand il aboyera prochainement) pour traiter nos informations et modifier certaines représentations en conséquence. En réponse à la future 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 informera le client du dernier aboiement du chien et du prochain aboiement. Du point de vue du service, c'est honnête, car ces temps correspondent aux tâches cron passées et prévues.

0
Jordan