Accueil Le blog ebiznext
Bonnes pratiques REST

Objet


REST (Representational State Transfer) est un style d’architecture pour les systèmes hypermédia indépendant du protocole (HTTP /…) et du format (JSON/XML/ …). REST est défini par six contraintes décrites à l’origine par Roy Fielding qui définissent les bases d’une API REST.

Ces six contraintes sont : - Interface uniforme - Sans état - Mise en cache - Client / serveur - Structuré en couches - Exécution de code à la demande

Ce document est un guide du développeur qui regroupe les bonnes pratiques à respecter dans la cadre de la réalisation d’une architecture de services à base de services REST. Il se limite exclusivement au protocole HTTP et aux formats JSON/XML.

Les six contraintes de REST


Une interface uniforme

Cette contrainte se caractérise par quatre règles essentielles : - Identifiation unique des ressources : Chaque ressource est identifiée de manière unique au travers d’URIs. Le serveur renvoie une représentation de la ressource au client dans le respect d’un schéma prédéfini et indépendant de la représentation physique de la ressource sur le serveur. Les formats les plus communs sont JSON et XML encodés en UTF-8.

Sans état

L’approche sans état est clef dans REST. Elle signifie que l’ensemble des éléments requis à la manipulation des données est contenue dans l’URI, les paramètres de l’URI (query-string), le corps de la requête ou les entêtes. Une fois la requête traitée, le serveur renvoie au travers des entêtes, du corps ou du statut de la réponse , l’ensemble des informations requises pour d’éventuels appels ultérieurs.

Cela tranche avec l’approche classique qui consiste à stocker en session des informations qui resteront présentes d’une requête HTTP à une autre. Dans l’approche REST, le client doit inclure toutes les informations requises par le serveur pour exécuter la requête. L’approche sans état offre une meilleure scalabilité des applications dans la mesure où le serveur n’a pas besoin de maintenir et/ou répliquer l’état de session. De plus, les composants de régulation de charge n’ont pas à se préoccuper de l’affinité de session qui complexifie les paramétrages d’infrastructure et le déploiement de nouvelles versions d’applications.

P.S. Il n’est pas toujours possible d’avoir une approche stateless. Des services tels que OAuth requiert le maintien d’informations de session. La compatibilité avec des versions antérieures de l’application sont également des raisons de maintien d’information de session.

Si le maintien d’une session est indispensable, il faut : - retenir que dans la mesure du possible, les informations de session ne doivent pas servir comme maintien d’un état transitoire d’une requête HTTP à une autre mais uniquement comme un mécanisme de cache d’informations valide pendant toute la durée de la session. - s’assurer que la session est disponible sur l’ensemble des serveurs de la ferme (absence d’affinité de session).

Mise en cache

Les clients Web (Desktop et mobile) sont en mesure de mettre en cache les réponses. Les réponses doivent donc, implicitement ou explicitement, se définir comme étant candidates ou pas à être mises en cache pour éviter que le client ne fasse des requêtes serveurs inutiles ou utilise des informations obsolètes. Le bénéfice d’une bonne gestion de la mise en cache des données permet d’améliorer la scalabilité et la performance.

Client / Serveur

Le client est à l’initiative de la requête. Le client et le serveur communiquent au travers d’une interface normalisée indépendante de la représentation des données sur le serveur et de l’interface utilisateur côté client. Cela permet aux deux parties (client et serveur) d’évoluer indépendamment l’une de l’autre tant que l’interface d’échange est respectée ou reste compatible.

Structuration en couches

Un client ne peut pas savoir s’il est directement connecté au serveur d’application cible ou s’il est servi par un serveur intermédiaire de la chaine de liaison. Ces serveurs jouent en général le rôle de régulateur de charge, de cache partagé ou de service d’authentification et d’habilitation.

Exécution de code à la demande (Optionnel)

Cette contrainte est la seule optionnelle des six contraintes REST imposées. Elle permet de renvoyer au client du code qui sera exécuté sur le poste client pour étendre les fonctionnalités du serveur. Il peut s’agir d’applets Java ou de scripts javascript. Cela peut concerner notamment l’accès à des périphériques locaux.

Concepts


  • En REST, l’abstraction clef désignant l’unité d’information est la ressource. Tout information qui peut être nommée est potentiellement est une ressource. Plus généralement, tout concept qui peut être désigné par une référence hypertexte doit être considéré comme une ressource.
  • Une ressource est accédée au travers d’une URI.
  • Une URI peut être invoquée via une ou plusieurs méthodes (GET / PUT / POST / DELETE) en fonction de l’opération que l’on souhaite exécuter (Lecture / Mise à jour / Création / Suppression).
  • Une invocation de service REST peut inclure dans le corps de la requête des paramètres en entrée représentant un objet métier et recupérer dans la réponse une représentation de l’objet métier dans le flux de retour.
  • Plusieurs variantes d’une représentation sont possibles. En général on retrouve les variantes JSON et XML.

Le diagramme ci-dessous illustre ces concepts.

Concepts REST

Figure : http://www.apigee.com

Les méthodes HTTP


Les méthodes ou verbes HTTP permettent de classer les opérations de service REST en trois catégories : - Les verbes idempotents - Les verbes neutres - Le verbe POST

Idempotence

Un verbe est dit idempotent lorsque l’application du verbe à une URI produit le même résultat qu’on l’applique une ou plusieurs fois.

TipLes verbes PUT et DELETE (sauf dans certains cas pour ce dernier) sont dits idempotents

Neutralité

Un verbe est dit neutre lorsqu’il ne conduit à aucun effet de bord sur le serveur. Un verbe neutre est donc également idempotent.

TipLes verbes GET, HEAD, OPTIONS et TRACE sont des verbes neutres.

Le verbe GET pour la lecture

La méthode HTTP GET est utilisée pour récupérer une ressource. En cas de succès, la méthode GET renvoie une représentation XML ou JSON de la ressource sollicitée et un code réponse HTTP 20X. En cas d’erreur, cette méthode renvoie dans la plupart des cas un code réponse 404 (NOT FOUND) ou 400 (BAD REQUEST).

Exemples:

GET http://api.europcar.com/api/cars/12345 : Récupère le véhicule 12345

GET http://api.europcar.com/api/cars/12345/drivers : Récupère tous les drivers du véhicule 12345

Le verbe HTTP GET doit être utilisé exclusivement dans le cadre d’une opération neutre.

Le verbe PUT pour la mise à jour

La méthode PUT est utilisée dans le cadre de la mise à jour d’une ressource sur le serveur. Le corps de la requête contient alors au format JSON ou XML la représentation mise à jour de la ressource référencée.

La méthode PUT peut être également utilisée dans le cadre de la création d’une ressource mais uniquement lorsque le choix de l’identifiant de la ressource est de la responsabilité du client.

En cas de succès, le code réponse à renvoyer est le suivant : - Code 200 s’il s’agit d’une mise à jour et que le corps de la réponse n’est pas vide - Code 204 s’il s’agit d’une mise à jour et que le corps de la réponse est vide - Code 201 s’il s’agit d’une création. Dans ce cas, le corps de la réponse peut être vide.

PUT n’est pas une opération neutre mais une opération idempotente, il faut donc veiller à ce que plusieurs appels à la méthode produisent le même effet.

Exemples :

PUT http://api.europcar.com/cars/12345 : Met à jour le véhicule 12345

PUT http://api.europcar.com/cars/12345/drivers/67890 : Met à jour le driver 67890 du véhicule 12345

Le verbe POST pour la création

Le verbe POST est souvent utilisé pour la création de nouvelles ressources. Le serveur est responsable de l’association d’un identifiant unique à la ressource.

Exemples:

POST http://api.europcar.com/cars : Crée un véhicule, les données associées au véhicule sont transmises dans le corps de la requête

POST http://api.europcar.com/cars/12345/drivers : Crée un driver pour le véhicule 12345.les données associées au driver sont transmises dans le corps de la requête.

En cas de succès, le code réponse HTTP 201 est renvoyé avec l’entête “Location” qui a pour valeur le lien qui référence directement la ressource ainsi créée.

Pourquoi et quand Utiliser POST au lieu de PUT

TipUtiliser PUT lorsque : - La ressource existe et doit être mise à jour - La ressource n’existe pas et le client est chargé de déterminer l’identifiant unique de la ressource

Utiliser POST lorsque : - La ressource n’existe pas et qu’elle doit être créée avec un identifiant déterminé par le serveur - Lorsque l’opération n’est ni idempotente, ni neutre.

Le verbe DELETE pour la suppression

Le verbe DELETE est utilisé pour supprimer la ressource identifiée par une URI.

Exemples: DELETE http://api.europcar.com/cars/12345 : Supprime le véhicule 12345 DELETE http://api.europcar.com/cars/12345/drivers : Supprime tous les drivers du véhicule 12345

Selon la spécification HTTP, la méthode DELETE est idempotente car la suppression d’une ressource plusieurs fois d’affilée produit le même résultat, la ressource n’étant plus là.

Codes réponses des verbes HTTP

Le tableau ci-dessous dresse la liste des codes HTTP qu’il est recommandé de renvoyer quand l’opération s’applique à une liste ou une seule ressource.

Verbe HTTP /cars /cars/{id}
GET 200 (OK), renvoi d’une liste de véhicules. 200 (OK), renvoi d’un seul véhicule. 404 (NOT FOUND) si l’identifiant n’est pas trouvé ou est invalide.
PUT 404 (NOT FOUND) 200 (OK) ou 204 (NO CONTNTENT). 404 (NOT FOUND) si l’identifiant n’est pas trouvé ou est invalide.
POST 201 (CREATED), avec l’entête “Location” valorisé avec le client /cars/{id} contenant le nouvel identifiant 404 (NOT FOUND)
DELETE 404 (NOT FOUND), sauf si l’on souhaite supprimer toute la collection. 200 (OK). 404 (NOT FOUND) si l’identifiant n’est pas trouvé ou est invalide.

Les verbes utilisés dans le contexte d’Europcar sont : GET, POST, PUT, DELETE

Nommage des ressources (URI)


En plus d’utiliser les verbes HTTP de manière appropriée, il est indispensable de créer des URI qui rendent l’usage de l’API simple et intuitive.

Une API REST est constituée : - d’une collection d’URIs - d’appels HTTP à ces URIs (les verbes) - de représentations XML/JSON des ressources

Chaque ressource du serveur est accessible au travers de l’URI. Par opposition à un verbe qui décrit une action, une ressource est un nom commun qui décrit une entité. Le nommage des URIs doit suivre une structure hiérarchique cohérente afin de garantir son utilisabilité.

Les bonnes pratiques

Pour illustrer les bonnes pratiques, nous nous appuyons sur les entités clients, commandes, lignes de commandes et produits.

Le nommage des ressources peut être le suivant :

Ressource Nommage de la ressource
Client customers
Commande orders
LigneDeCommande lineitems
Produit products

TipLes ressources sont nommées au pluriel et en minuscules.

Ceci est du au fait que le service renverra une collection d’objets lorsqu’aucun identifiant n’est présent dans l’URI. Ci-dessous quelques exemples d’utilisation:

Opération URI
Création d’un nouveau client POST http://api.europcar.com/customers
Récupération/Mise à jour/Suppression du client 12345 GET/PUT/DELETE http://api.europcar.com/customers/12345
Création d’une nouvelle commande pour le client 12345 POST http://api.europcar.com/customers/12345/orders
Récupération des commandes du client 12345 GET http://api.europcar.com/customers/12345/orders
Création d’une ligne de commande dans la commande 67890 pour le client 12345 POST http://api.europcar.com/customers/12345/orders/67890/lineitems
Récupération de la première ligne de commande dans la commande 67890 pour le client 12345 GET http://api.europcar.com/customers/12345/orders/67890/lineitems/1

Les exemples ci-dessous illustrent le concept de hiérarchie à appliquer et la traversabilité de la hiérarchie au travers d’un identifiant unique qui vient s’intercaler entre les noms communs représentants les entités traversées.

Pratiques à éviter absolument

Le premier anti-pattern consiste à regrouper les services REST au sein d’une même URI et d’utiliser les paramètres de la query-string pour distinguer les services les uns des autres. Par exemple, l’URI de mise à jour d’un client pourrait être : GET http://bad-api.europcar.com/services?op=update_customer&id=12345&format=json

Le problème de cette URI est qu’elle n’est pas auto-décrite. 1. le verbe neutre GET désigne une opération qui ne l’est pas. 2. L’URI possède une structure hiérarchique identique pour l’ensemble des ressources ne permettant pas de différencier les ressources accédées juste au travers de la lecture de l’URI. 3. Le format du message retourné doit être indiqué dans le Header (Header Accept: application/json). Il s’agit d’une information protocole indépendante du contexte fonctionnel de la requête, cette information doit donc être mise dans le Header.

Un autre contre-exemple est d’indiquer l’opération dans l’URI GET http://bad-api.europcar.com/customers/12345/create

L’URI doit désigner une resssource et non une opération sur une ressource. L’URI doit être identique quelque soit l’opération qui sera effectuée sur la ressource (POST/ GET / PUT / DELETE)

Pluriel versus singulier

TipLa règle à respecter est la suivante. Lorsque la ressource désigne une collection d’objets alors il faut utiliser le pluriel.

Mais que se passe-t-il lorsqu’aucune ressource n’existe réellement sur le serveur ? Considérons que nous souhaitions construire un service qui vérifie que l’email saisi respecte un pattern bien défini.

Il s’agit d’une opération neutre, le verbe HTTP approprié est le verbe GET.

Dans notre cas, il n’y a pas d’entité Pattern en base, nosu serions tenté d’utiliser le singulier avec une URI du type : GET http://bad-api.europcar.com/pattern-valid

Cela viole le principe hiérarchique. Une solution est de considérer qu’il existe une multitude de patterns, nous pouvons donc en déduire que le pattern est une entité virtuelle qui sera désignée par un nom au pluriel. Pour valider l’email, nous pourrons considérer que l’identifiant “email” désigne notre patterns dans cette collection virtuelle infinie de patterns.

L’URI de désignation du pattern pourra alors être : GET http://api.europcar.com/patterns/email

La validité pourra être considérée comme une propriété et donc sera plus bas au niveau hiérarchique. L’URI définitive devient alors : GET http://api.europcar.com/patterns/email/validity

Cette URI désignant de manière unique la ressource, la valeur saisie par le client sera transmise dans la query string. Cela nous donne l’appel suivant :

GET http://api.europcar.com/patterns/email/validity?value=name@domain.com

Exposition d’objets


Les objets peuvent être exposés au client comme une hiérarchie de composition ou d’association d’objets. C’est cette composition ou association qui doit permettre d’obtenir l’URI d’accès à la ressource.

Composition / aggregation : la hiérachie matérialise la relation entre une ressource est des sous-ressources qui la composent. Celles-ci ont le cycle de vie de la ressource principale et ne sont accessible que via cette dernière.

Association : cette relation matérialise la navigabilité entre des ressources autonomes (qui peuvent exister en dehors de cette relation).

Considérons le schéma relationnel suivant :

Rental Diagram

  • Une commande est le rapprochement d’une ou plusieurs lignes de commandes à un et un seul client.
  • Un client peut n’avoir jamais passé de commande.
  • Un produit peut n’avoir jamais été commandé.

Les structures d’association suivantes sont alors possibles :

Type URI Choix de conception
URI1
URI2

Le choix de l’une ou de l’autre des modélisations est entièrement à l’appréciation de l’analyste métier et de la vue hiérarchique qu’il souhaite donner au système d’informations. Les URIs suivantes sont équivalentes :

Verbe HTTP URI1 / URI2
Description l’analyste métier impose uniquement la connaissance de la commande pour accéder aux lignes de commande de la dite commande. Dans le second cas, l’accès aux lignes de commande impose de connaître également le client pour de la commande.
GET /orders/12345/lineitems /customers/6785/orders/12345/lineitems

Dans les deux cas, rien n’empêche l’analyste métier de permettre l’accès aux commandes, clients ou lignes de commande directement via une clef d’accès unique. Là encore il s’agit d’un choix métier quant à l’exposition des ressources via l’API REST.

TipLors de l’accès à un objet, on peut vouloir le discriminer par un attribut. Lorsque cet attribut identifie de manière unique dans la hiérarchie accédée la ressource, alors il fera partie de l’URI. L’accès à une sous-ressource sans identifiant peut être fait par une position / index Dans les autres cas, ce sera un critère de recherche.

Dans l’exemple ci-dessous, on souhaite les commandes passée le 10/09/2015.

.../orders/20150910 est à proscrire car plusieurs commandes peuvent avoir été passées le 10/09/2015. Une requête correcte est la suivante : .../orders?date=20150910 et le retour sera une collection.

TipComposition versus association : On modélisera une composition lorsque l’appel REST renverra dans le flux la sous-ressource, et une association lorsque l’appel REST renvoie uniquement un identifiant technique et/ou fonctionnel accompagné d’un libellé (Cf. fragments externes / XF)

Exécution d’opérations métier


Dans les cas métiers (autres que CRUD), l’invocation d’un service REST renvoie généralement le résultat de l’exécution d’un acte métier. On peut citer les actions métiers suivantes : - Signer le contrat - Valider la location - Notifier le client

Dans ces cas, on remplacera le verbe par le substantif adéquat comme le montre le tableau ci-dessous :

Action Exemple d’URI Description
Signer la commande POST /orders/12345/signature Créer une signature et l’associer à la commande
Valider la commande PUT /orders/12345/validation Mettre à valide du statut de la commande
Notifier le client POST /customers/789067/notification Création d’une notification et association au client

Dans le tableau ci-dessus, la description indique la perception qu’a le client du service REST. Dans la réalité, les opérations qui ont lieu sur le S.I. sont beaucoup plus riches et complexes.

Note : suivant les cas métier, l’invocation du service sera accompagnée ou non d’un payload

L’usage de ces substantifs permet de conserver l’intention métier et le bon niveau d’abstraction des opérations

Le choix du verbe (PUT, GET , POST, DELETE) dépendra de l’idempotence et de la neutralité de l’action que l’on souhaite exécuter.

Représentation des données en entrée / sortie

Note : dans le contexte d’Europcar, on ne traite que de la gestion d’échanges JSON Note : ajouter un paragraphe sur le nommage des attributs (notamment, usage du camelCase)

Objets

  • Lorsqu’un objet est renvoyé, il doit être auto-décrit. Cela signifie qu’on retrouvera tous les critères ayant permis de renvoyer l’objet dans l’objet renvoyé en retour s’ils font partie de l’objet renvoyé. Ainsi lors d’un accès par ID, l’ID devra être également inclus dans l’objet retourné. /orders/12345 renverra une commande comme suit :

{ "id":"12345", // id est repris dans la réponse bien qu'il soit dans l'URI de la requête ... } - L’objet renvoyé n’est jamais nommé. S’il s’agit d’un type structuré JSON, il se présentera avec les attributs encadrés par une accolade ouvrante et une accolade fermante comme cela est le cas dans l’exemple ci-dessus. Aucun attribut ne doit préfixer l’objet renvoyé.

  • L’exposition d’une ressource fait fi de sa représentation réelle dans le système d’information (cf. exemple pattern précédent). S’il s’agit d’une ressource résultant de l’agrégation de plusieurs autres ressources, alors il elle doit être perçue comme étant un objet composé.

TipDans les relations de composition, c’est l’objet tout entier (ou une vue sur celui-ci) qui doit être renvoyé. Dans les relations d’association, on renvoie le minimum à savoir l’id et le nom fonctionnel par exemple.

Vues d’un objet

TipLorsque l’on souhaite accéder à une vue particulière d’une ressource, par exemple pour le client récupérer uniquement son identité ou uniquement ses données de paiement. On préfixera la vue que l’on souhaite accéder par views:

/customers/12345/views/ident /customers/12345/views/payment

L’absence du préfixe viewssignifie que l’on souhaite la vue par défaut: /customers/12345.

Collections

Les objets sont renvoyées comme une liste homogène de données, encadrées par un crochet ouvran tet un crocher ouvrant. Tout comme précédemment, les listes sont des objets et ne doivent donc jamais être nommées. Ci-dessous un exemple de liste avec deux éléments : [ { "id":"12345", "firstName": "ABC", "lastName":"DEF" ... }, { "id":"57643", "firstName": "XYZ", "lastName":"UVW" ... } ]

Une URI qui renvoie une liste est de préférences terminée par un segment au pluriel comme dans l’exemple suivant : GET http://api.europcar.com/orders/1234/linetitems

Cet appel renvoie la vue par défaut. Pour envoyer une vue spécifique, on pourra suffixer par views/nomdelavue. Dans l’exemple ci-dessous on souhaite restituer uniquement les ids des lignes de commande

GET http://api.europcar.com/orders/1234/linetitems/views/names pourra produire la réponse suivante : [ { "id":"12345", "name": "ABCF DEF" }, {"id":"57643", "name": "XYZ UVW" } ]

Les dates

Les dates sont transmises de deux manières différentes selon qu’elles soient précisées dans un entête HTTP ou dans le corps de la requête / réponse. Dans l’entête HTTP, les dates doivent respecter la RFC 1123 qui consiste à transmettre la date au format suivant : Mon, 3 Aug 2015 09:26:12 GMT Cette date comme on peut le voir ne permet pas d’avoir accès aux millisecondes. La pattern Java à appliquer pour l’obtenir est EEE, dd MMM yyyy HH:mm:ss 'GMT' Dans le corps de la requête ou de la réponse, les dates sont envoyées au format ISO8601 : yyyy-MM-dd'T'HH:mm:ss.SSS'Z' Pour une date sans heur, il faut quand même transmettre l’information horaire qui pourra être ignorée par l’applicatif.

Pagination


Lorsque le volume de données à renvoyer est important, il peut être utile de découper le résultat en pages afin de permettre une meilleure lecture des données côté client et de limiter l’usage de la bande passante. La pagination consiste à renvoyer un certain nombre d’éléments à partir d’une position donnée. La position 0 décrivant le début de la collection. Ces deux informations, “position” et “nombre d’items” doivent être transmis par la requête pour limiter le nombre d’éléments à renvoyer par le serveur. La pagination peut être à l’initiative du client ou du serveur lorsque le volume de données retourné est trop important. Dans tous les cas, le serveur peut imposer ses règles de pagination (qui induirait un décalage entre la demande du client et le résultat effectif).

Pagination avec le header

Dans ce cas, on utilise l’entête HTTP “Range” qui respecte le format suivant : items={position}-{nombre}. Ainsi une requête avec l’entête HTTP suivant renverra 20 items à partir du troisième item : Range: items=2-22

Cet entête se lit comme suit : Renvoyer 20 items à partir de l’item à la position 2.

Dans la query string

Dans ce cas on n’indique pas par un intervalle mais par une position initiale et un nombre maximum d’éléments à renvoyer. Ainsi la requête suivante renverra les 20 premiers clients à partir de la position 2 incluse. GET http://api.europcar.com/customers?offset=2&limit=20

Tip Privilégier la pagination au travers de paramètres dans la query string.

Format de la réponse

Uen requête de pagination, que ce soit via l’entête ou la query string doit renvoyer le nombre d’items retournés et le nombre total d’items. Cet entête se présente comme suit et indique que les 50 premiers items sur un total de 97 ont été renvoyés dans la réponse : Content-Range: items 0-49/97

Dans l’exemple suivant, on indique que les 10 derniers items ont été renvoyés Content-Range: items 87-96/97

Lorsque le nombre total d’items n’est pas connu au moment de la requête, il est possible de remplacer le nombre total par l’astérisque comme suit : Content-Range: items 0-49/*

Filtrage


Le filtrage correspond aux opérations de recherches sur les ressources

Les options de filtrage sont placés dans la query-string uniquement sous la forme d’un paramètre supplémentaire : “filter”. Le nom du critère est séparé de sa valeur par le chaîne de caractères “::” et les critères de filtre sont séparés les uns des autres par le caractère ‘|’.

La spécification de l’API doit préciser les attributs sur lesquels peuvent être effectuées les recherches. Les critères de recherche d’appliquent à la ressource indiquée par l’URI. Le nommage des paramètres de la recherche est indépendant du nommage des attributs de la ressources considérée.

Ainsi, l’URI suivante pourrait permettre de récupérer les clients avec une commande d’un montant supérieur 1000€ et avec un seul article:

GET http://api.europcar.com/orders?filter=amount:gt:1000|count::1

On peut complémenter les critères de filtres par des intervalles temporels en ajoutant les paramètres “before” et “after”. Ainsi dans l’exemple suivant on obtient les commandes précédentes entre le 1er janvier 2015 et le 15 janvier 2015.

GET http://api.europcar.com/orders?filter=amount:gt:1000|count::1&after=timestamp&before=timestamp

Lorsque l’on souhaite des opérateurs plus riches, il faut alors définir une syntaxe dédiée. Pour l’inégalité on pourra alors utiliser la syntaxe suivante :

Opérateur Description
:: Egalité
:lt: Inférieur
:gt: Supérieur
:le Inférieur ou égal
:ge: Supérieur ou égal
:starts-with: Commence par
:contains: Contient
:end-with: Se termine par

Pour les opérations de filtre encore plus complexes, il convient alors d’utiliser la librairie Apache Olingo qui implémente le concept de query défini dans l’OpenData protocol (OData) Filter System Query Option

Filtres prédéfinis

Dans des cas complexes, certains filtres peuvent être prédéfinis sur le serveur et nommés. Dans ce cas, l’URI aura la forme suivante :

GET http://api.europcar.com/orders?filtername=name&param1=val1&...

Dans cet exemple, le filtre est nommé name et les paramètres attendus par le filtre sont passés dans l’URI.

Lorsque l’on souhaite restituer une vue particulière, on utilisera le mécanisme de vue décrit précédemment.

Dans l’exemple ci-dessous, la liste des commandes renvoyées va restreindre le résultat au attributs définis par la vue amounts. GET http://api.europcar.com/orders/views/amounts?filtername=name&param1=val1&...

Tri


Les options de tri sont placées dans la query-string.

TipLe paramètre de tri est nommé “sort” et les critères de tri sont placés dans l’ordre séparés par une barre verticale “ ”. Lorsque le tri su un critère est descendant, il doit être préfixé par le caractère “-“.

Dans l’exemple ci-dessous, on renvoie la liste des véhicules triés par marque ascendante et par date de mise en service descendante:

GET http://api.europcar.com/cars?sort=brand|-date_of_service Les attributs sur lesquels le tri peut être opéré doivent être indiqués dans la documentation de l’API

La réponse du service REST


La structure de réponse d’un appel de service REST doit être la suivante :

  • Un code réponse HTTP
  • Un statut
  • Un message
  • Les données.

Le code réponse HTTP

Il s’agit d’un entier indiquant le code réponse HTTP. Ce code pourra également être inclus dans le corps de la réponse sous la forme d’un attribut

L’attribut status

Cet attribut contient un des 3 textes suivants : - “fail” pour les codes réponse HTTP 5XX - “error” pour les codes réponse HTTP 4XX - “success” pour les codes réponse HTTP 1XX, 2XX et 3XX.

L’attribut message

Cet attribut est exclusivement présent lorsque le statut est “fail” ou “error”. Il contient la liste des messages d’erreur à prendre en compte par le client.

Un message est un objet avec deux attributs code et description.

Dans certains cas à la marge, il peut être utilise d’avoir des messages plus détaillés, auquel cas, il conviendra d’inclure un attribut details qui contient la liste des messages d’erreur détaillés.

L’attribut data

Contient le corps de la réponse. En cas d’erreur (status égal à “fail” ou “error”) il contient la liste des exceptions remontées.

Exemple 1: { "code":200, "status":"success", "data": [{"name":"Vincent"}] } Exemple 2: { "code":400, "status":"error", "message" : { "code": UserAbsent", "description" : "The request is invalid. Rent no more present" } "details" : [ { "code":"InvalidArgument", "description" :"Parameter ABC should be a valid date" }, { "code":"UnknownField", "description" :"Unknown Field XYZ" } ] }

Versioning de services


En utilisant la négotiation de contenu

Cette approhe est adaptée lorsque la version du service n’a pas changé mais qu’il existe plusieurs représentations possibles d’une ressource. Dans ce cas, l’entête HTTP “Accept” est utilisé. Dans l’exemple suivant, on demande la ressource au format JSON en version 1.

Accept: application/json; version=1

La réponse renvoie alors dans l’entête HTTP “Content-Type” le format et la version retournée. Dans l’exemple suivant, la ressource est renvoyée au format JSON en version 1. Content-Type: application/json; version=1

Si le client ne précise aucune version, le serveur doit renvoyer la plus ancienne version supportée par la ressource. Cette règle s’applique également lors de la création ou de la mise à jour d’une ressource. Les données fournies doivent être considérées comme étant dans la plus ancienne représentation supportée.

Lorsque la version ou le format demandé n’existent pas ou ne sont plus supportés, le code réponse HTTP 406 (NOT ACCEPTABLE) doit être renvoyé.

En utilisant l’URI

Dans ce cas on préfixe l’URI par un numéro de version vXXX ou XXX désigne le numéro de version. Dans l’exemple ci-dessous on adresse un service dans sa version 2: GET http://api.europcar.com/v2/customers/12345

Cela permet de placer des règles de routage d’URL assez facilement au niveau des serveurs HTTP intermédiaires.

Tip Le versioning via l’URI est à privilégier.

Conditions à la création d’une nouvelle version de l’API?

Dès que la compatibilité ascendante est cassée, il faut impérativement créer une nouvelle version. Cela concerne les cas suivants notamment :

  • Renommage d’un attribut ou d’une entité
  • La suppression d’un attribut ou d’une entité
  • La modification du type d’un attribut
  • La modification des règles de validation d’un attribut ou d’une entité qui rompt la compatibilité avec l’existant.

Par contre, dans les cas ci-dessous, il n’est pas nécessaire de créer une nouvelle version de l’API:

  • Ajout de nouveaux attributs à la réponse
  • Support de nouveaux formats
  • Support de nouvelles langues

Le support de la dépréciation d’un service

Si un service est déprécié mais qu’il continue d’être supporté, il faut l’indiquer dans l’entête “Deprecated” qui est de type boolean: Deprecated: true

Authentification pour l’accès aux services


Une authentification est nécessaire soit au niveau de l’utilisateur soit au niveau du tiers appelant (le partenaire).

Dans le premier cas, l’utilisateur s’appuie en général sur un couple identifiant/mot de passe. Dans le second, il s’agit d’un token qui permet d’identifier le tiers. Dans les deux cas, il est préconisé d’utiliser l’entête Basic Auth HTTP.

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Dans le cadre d’un token, ce même entête HTTP sera utilisé. Dans ce cas par contre, le user et password correspondent à une API Key dédiée partagée par plusieurs utilisateurs potentiellement.

La gestion du cache


Le cache est mis en oeuvre exclusivement pour les appels de type GET. Les entêtes suivants permettent de supporter les mécanismes de cache:

Entête Description Exemple
Date Date et heure à laquelle la ressource a été renvoyée (au format RFC 1123) Mon, 3 Aug 2015 09:26:12 GMT
Cache-Control Le nombre de secondes maximum pendant lesquelles la réponse peut être mise en cache. La valeur no-cache indique que la réponse ne doit pas être mise en cache. Cache-Control: 3600 ou Cache-Control: no-cache
Expires Date absolue au format RFC 1123 à laquelle la ressource expire. Mon, 3 Aug 2015 09:26:12 GMT
Pragma Si la ressource ne doit pas être mise en cache, alors il faut rajouter cet entête avec la valeur no-cache Pragma: no-cache
Last-Modified Date au format RFC 1123 de dernière mise à jour de la ressource Mon, 3 Aug 2015 09:26:12 GMT

TipLes règles de cache sont souvent exprimées au niveau des éléments d’infrastructure. Ils ne doivent généralement pas être positionnés par l’applicatif.

Gestion des sessions


ST dans REST signifie State Transfer, ce qui signifie que les informations de session doivent être transférées au client et non stockées sous la forme d’objets en session. La règle est la suivante : - Toute état spécifique à la session en cours dans être stockée côté client. - Tout état spécifique à un objet métier est géré côté serveur.

La session peut être utile dans certains cas à la marge : - Compatibilité avec une application antérieure - Préchargement de certaines ressources métiers volumineuses utiles au traitement

Dans ce cas, un identifiant de session est transmis au client au travers d’un cookie dans l’entête HTTP. Set-Cookie: jsessionid=ig2fac55; path=/; secure; HttpOnly

La présence des deux indicateurs secureet HttpOnly permet de sécuriser le cookie de la manière suivante :

  • L’indicateur secure empêche le cookie d’être envoyé en clair sur le canal HTTP et force l’utilisation du protocole HTTPS.
  • L’indicateur HttpOnlyempêche le code JavaScript d’accéder au cookie, pratique courante dans les attaques de type XSS (Cross site scripting).

Ces deux indicateurs sont à positionner par l’applicatif.

Les codes erreur HTTP et leur utilisation


La liste exhaustive des codes réponse HTTP REST est disponible à cette adresse : restapitutorial

Les 10 principaux codes HTTP REST sont les suivants.

Code Libellé Description
200 OK Code succès générique
201 CREATED Création avec succès via POST ou PUT avec mise à jour de l’entête Location qui référence la ressource ainsi créée.
204 NO CONTENT Quand le corps de la réponse est vide et que l’opération s’est exécutée avec succès.
304 NOT MODIFIED En réponse à la méthode GET conditionnelle pour indiquer que la ressource n’a pas été modifiée depuis la date indiquée en entête
400 BAD REQUEST Paramètres de la requête invalides
401 UNAUTHORIZED Absence du tocken d’authentification dans la requête
403 FORBIDDEN Utilisateur on autorisé à accéder à la ressource
404 NOT FOUND Quand la ressource demandée n’a pas été trouvée.
409 CONFLICT Quand la requête risque de créer des conflits tel que des doublons en création ou des suppressions en cascade non supportées
500 INTERNAL SERVER ERROR Erreur générique côté serveur

Annexe - HATEOAS


Le principe

L’objectif de HATEOAS est de permettre la découvrabilité de l’API REST. La ressource accédée renvoie en plus de la réponse, le moyen d’accéder aux services suivants. Il est ainsi possible de connaître quelles actions sont possibles sur la ressource accédée.