Ingénieur IT, photographe amateur, musicien du dimanche
Les bases de REST
Les bases de REST

Les bases de REST

Les web-services sont montés en popularité ces dernières années grâce à l’attrait des API consommables par différents supports comme les web-applications, les app mobiles ou autres développements connectés.
C’est donc tout naturellement que les services REST ont évolués afin de s’adapter aux demandes des applications actuelles.
Mais cette popularité ainsi que sa facilité d’utilisation ont quelque peu vulgarisé le terme REST au point qu’il est parfois utilisé à tord . Voici donc un rapide rappel de REST et de ses différentes facettes.

Les niveaux de REST

L’évolution de REST a ajouté des améliorations au fil du temps et celles-ci sont présentées sous formes de niveaux.

Level 0

Le level zéro définit la technologie HTTP 1.1 en tant que système de transport de données sans utiliser les mécanismes propres au Web. On accède simplement aux ressources par POST ou GET, via une url unique et récupère les données selon les paramètres.
Ex :
http://mydomain.com/webservice?client=23
http://mydomain.com/webservice?contract=76
Il est intéressant de noter que la technologie Soap est de niveau 0, ce qui fait de Soap une technologie REST.

Level 1 – Resources

Le niveau 1 définit 1 url propre à chaque ressource. Ainsi, le seul fait d’analyser l’url nous permet de savoir quel type de réponse nous aurons.
La base du chemin définit le type de ressources, et les paramètres suivants, facultatifs, permettent de cibler une ressource spécifique à traiter.
Ex:
http://mydomain.com/client/
http://mydomain.com/client/23
http://mydomain.com/contract/76

Level 2 – HTTP verbs

Le niveau 2 différencie les méthodes HTTP à utiliser selon le traitement, comme par exemple GET, POST, PUT, DELETE,…
Ainsi, nous pouvons maintenant définir ce que nous faisons avec la ressource, uniquement en regardant le verbe de la méthode.
Ces différents verbes se catégorisent par deux états : Nullipotent ou Idempotent.
Nullipotent
Un traitement nullipotent n’a pas d’effet sur les données. Il les retourne sans faire de modification sur la structure existante.
Idempotent
Un traitement idempotent peut être exécuté à plusieurs reprises sur le même objet. Dans ce cas, seul la première exécution aura un effet sur les données. Les autres seront inutiles car elles mettront l’objet dans l’état où il est déjà, donc sans effet (ex: PUT et DELETE).
C’est notions sont importante car les comportements seront différents. Par exemple un matériel réseau sait faire la différence entre idempotent et nullipotent, du coup si une requête idempotente échoue, il pourra faire plusieurs essais sans risquer de corrompre les données. Par contre pour un nullipotent, il essaiera une fois et si celui-ci plante, la tentative sera stoppée et retournée sous forme d’erreur.

GET

Est l’action Read du CRUD.
Récupère simplement les informations d’une ressource sans faire d’autres traitements.
Nullipotent  => Ne fait aucune transformation de données

PUT

Est le Create et le Update du CRUD
Écrit des ressources

Idempotent car soit on créé une ressource, soit on écrase une existante. Pas de mise à jour partielle, donc toujours le même comportement.

A noter que dans le cas d’un create, pour respecter l’état Idempotent, le put devra être fait avec l’identifiant de la ressource. (voir tableau plus bas).

POST

Est le Create (voir le Update) du CRUD
Il modifie la ressource et chaque appel apportera un état différent.
Différence PUT VS POST

Les deux verbes pouvant être utilisé lors du CREATE et du UPDATE, on pourrait se poser la question de son utilité. La différence est dans le fait que PUT est considéré Idempotent. Selon la norme, il faudrait donc les utiliser différemment :

Action
Verbe
Route
Remarques
Création POST /users On créé un nouvel objet en passant ses valeurs via les paramètres POST
Création PUT /users/{id} On créé un objet en connaissant l’identifiant (ex: si l’identifiant n’est pas généré automatiquement mais utilise un username).

Idempotent fait qu’on écrase l’enregistrement existant s’il y’en a un. Ce qui reviendrai à le modifier…

Modification POST /users/{id} On modifie l’enregistrement existant.
Modification PUT /users/{id} On modifie l’enregistrement existant.

 DELETE

Est le Delete du CRUD
Idempotent car il supprime la première fois l’objet et s’il est exécuté à nouveau, aucune modification ne sera faite car la ressource n’existe plus, mais le retour sera le même.
A noter que l’exécution d’un DELETE sur l’url de base (ex: /person ), est considéré comme une demande de suppression de la collection entière.

PATCH

Mettre partiellement à jour des données

OPTIONS

(Nullipotent)
Retourne les methods supportées pour une URI.
Ex: OPTIONS : /client => return GET, POST, PUT
Use case :
  • Découvrir les méthodes possibles
  • Tester la disponibilité

HEAD

(Nullipotent)
Retourne l’entête d’un message d’une requête GET sans le corps de requête. (code 206)
Use case :
  • Vérifier qu’un objet existe. Ex: lorsque l’on veut uniquement savoir si un objet existe, sans en avoir le détail qui pourrait être très gros et ainsi altérer les performances inutilement. Dans ce cas la requête HEAD répondra simplement par un code 200 OK ou 404 not found.
  • Vérifier si une ressource a une nouvelle version ou si le contenu en cache est toujours valide. Dans ce cas, les réponses seront avec le code 200 OK si le contenu a changé, sinon un code 304 Not Modified signifiera que le cache peut être utilisé.
 Cas particulier
Un paramètre GET method permet de spécifier la méthode utilisée pour les anciens navigateurs qui ne supporte pas la totalité des verbes HTTP.
Ex :
/client?method=put

Level 3 – (nommé RESTful ou Hateoas)

La réponse contient la ressource demandée, ainsi que les urls vers les ressources liées.
Ainsi, pas besoin de documentation de l’API, ni de deviner des routes, il suffit de suivre les liens transmis.
A partir de la ressource d’entrée, possibilité de récupérer l’API complète en suivant les liens.

 But

  • ne pas deviner comment accéder à une ressource donnée => doc inutile : auto-discoverable
  • quelles sont les relations entre ressources et collections
Ex: pour l’url
http://mydomain.com/client/13
la réponse ressemblera à:
<client>
  <id>13</id>
  <nom>Dushmol</nom>
  <prenom>Jean</prenom>
  <link rel="self" href="/client/13" />
  <link rel="address" href="/address/22" />
  <link rel="events" href="/events/subscriptions/12" />
</client>

 

Pour une réponse au format json, la structure sera différente car ce format n’accepte pas de multiples balises de même nom. Il faudra donc utiliser un format personnalisé ou le format hal+json.

HTTP-Headers

Pour transmettre des informations complémentaires, l’utilisation des http-headers peut se révéler très utile.
On peut noter par exemple la pagination de collection, des liens vers des ressources complémentaires, …
Exemple de headers pour la pagination de collection :
// Utiliser la notion de range sur l'objet data:
Accept-Ranges : data
// Définir le range désiré ainsi que le nb total (ici: range = 5 à 8 sur un total de 50)
Content-Range : data 5-8/50
// L'url de la ressource
Content-Location : http://mydomain.com/my/data
// Liste de lien vers les prochaines pagination (next,previous, last, first,...) : web linking
Link : <http://mydomain.com/my/relatedData>;rel="relatedDatat",
      <http://mydomain.com/my/relatedData>;rel="next"

A noter que pour une pagination, il peut être intéressant de retourner un code 206 (partial content) pour spécifier que la liste n’est pas complète mais paginée.

Service de recherche

Concernant les recherches, plusieurs politiques d’implémentation existent.
On peut soit passer par une requête GET en passant le filtre de recherche en tant que paramètre d’url :
GET :: http://api.mydomain.ch/person/search?fname=john&lastname=doh

Celle-ci permet de respecter le côté « GET » en tant que verbe de récupération de données », mais elle a le défaut de devoir échapper les caractères (encodeURIComponent ) et de risquer d’être plus rapidement limité dans le nombre de caractères de la requête.

L’autre solution est de passer par la méthode POST en mettant les paramètres dans le payload. Cette fois-ci, pas de limite basse de caractères et l’échappement de caractères n’est pas nécessaires. Le côté « POST » pour « création de ressources » peut être satisfait en se disant que l’on créé une nouvelle recherche 🙂

POST :: http://api.mydomain.ch/person/search

Quelques conventions utiles

  • Utilisations du singulier même pour récupérer une collection. Évite les erreurs ou les problèmes de pluriels irréguliers :
    /person -> toutes les personnes
    /person/{id} -> une personne spécifique identifiée son id
  • Utiliser un tiret pour les mots-composés : concurrent-contract
  • Préférer l’utilisation de l’anglais qui ne comporte pas d’accent
  • Utiliser uniquement des minuscules afin de ne pas créer des erreurs à cause de la casse.
  • Et surtout : être constant dans ses choix dans toute l’application !

Optimisation

  • Afin d’optimiser les performances des services, il peut être intéressant de mettre en cache des résultats afin que le service puisse être plus réactif.
  • On évitera également de retourner des « deep relations ». Une architecture orientée REST propose des données unitaires qui ne sont pas trop profondément liées. Les dépendances indirectes peuvent ensuite être récupérées à l’aide des url du format RESTful.

Versioning d’api

Passer à une nouvelle version d’api via « accept-type » permet de ne pas changer d’url, mais uniquement de header.
L’avantage est de lister toutes les possibilités via un HEAD, également éviter des préfix supplémentaires dans la route.
Dans les cas où le header n’est pas spécifié, c’est au service de définir son comportement. C’est à dire soit retourner une erreur 406 (Not Acceptable), soit diriger par défaut vers la version la plus récente.
Exemple :
Accept: application/org.example.data-v2+json

 

! Attention avec CORS, il faut explicitement autoriser ces headers custom pour les utiliser.

2 commentaires

  1. Madis

    Salut, la présentation par niveau est intéressante. Cependant, je ne suis pas d’accord avec toi sur certains points :
    Pour moi, les niveaux sont des critères. Un système est REST si il vérifie TOUS les niveaux cités (HTTP, orienté ressource, hypermedia). Sinon, c’est juste un service HTTP évolué.
    Il est également important de préciser qu’un système RESTful doit être stateless.
    Ensuite, REST est un modèle d’architecture alors que SOAP est un protocole. Je ne dirais pas que SOAP est une « techno REST ». Un système basé sur SOAP peut éventuellement être RESTful seulement si il remplit tous les critères, mais ce n’est pas le but de SOAP.
    Le versionning d’API via le header est une technique intéressante, je n’y avait jamais pensé. Merci du tuyau 😉

    1. Merci pour ton message Madis.
      Concernant les niveaux de REST, cet article se base sur le modèle de maturité de Richardson. Ce n’est probablement pas suffisamment relevé dans mon article, mais celui-ci ne définit pas des niveaux de REST en tant que niveau d’implémentation, mais plutôt en tant que pré-conditions.

      Sinon je suis d’accord avec toi qu’une notion de Stateless aurait pu être ajouté. A voir avec un prochain edit d’article 😉

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *