• 50 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 29/07/2019

Une architecture, pas un protocole

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

REST, RESTful… vous avez sans doute déjà croisé ces termes. Si ce n'est pas le cas, je vous invite à faire un petit tour par le cours d'introduction aux API REST : Utilisez des API REST dans vos applications web.

Maintenant, vous allez construire votre API REST avec PHP et Symfony. Mais un petit instant ! Avant de se lancer tête la première dans le code, il est important d'en connaître les règles. Eh oui, tout cela est très codifié et pour arriver à produire une API de qualité, il faut en maîtriser tous les aspects. Commençons par le premier : l'architecture.

Architecture

Ne vous y méprenez pas ! REST n'est pas un protocole, mais une architecture. Créée par Roy Fielding en 2000, REST est un acronyme pour Representational State Transfer. Souvent associée à l'architecture orientée service (Service Oriented Architecture), cette architecture est un moyen de présenter et manipuler des ressources.

Quel est le rapport avec la notion d'architecture alors ?

Une API (Application Programming Interface) est une application, à laquelle on peut faire effectuer des actions via le protocole HTTP :

  • récupérer des données concernant des utilisateurs ;

  • ajouter des produits ;

  • supprimer l'auteur d'un article ;

  • rattacher un artiste à un spectacle…

Tout est possible !

Cela ressemble franchement à ce que nous pourrions retrouver dans un site web habituel… hormis le fait qu'il ne s'agit pas d'afficher de page web HTML.

Les utilisateurs d'API sont d'autres développeurs (ou vous-même), et ils s'attendent à ce que votre API REST soit architecturée d'une certaine manière. C'est une bonne nouvelle ! ^^  En effet, savoir à qui vous vous adressez et comment vous devez le faire facilite grandement le travail ! Plus besoin de réfléchir à "la meilleure façon de faire". Nous ne parlons pas de code ici, mais d'une structure d'application.

Concrètement, vous aurez à créer une liste d'actions possibles avec votre application (récupérer une liste d'utilisateurs, en ajouter, en supprimer…) et une manière d'effectuer ces actions grâce à HTTP. Cette manière de faire doit respecter les nombreuses contraintes de REST.

Commençons par passer en revue ces contraintes en rapport avec ce que votre API devra refléter.

Les six contraintes de REST

Contrainte n° 1 : Client/Server (Client / Serveur)

La première contrainte impose qu'il y ait une séparation des responsabilités entre le client et le serveur. Jusque là, nous avions vu lors des cours précédents qu'un client est un logiciel capable d'émettre une requête HTTP, et qu'un serveur est un logiciel capable de recevoir des requêtes HTTP pour finalement rendre une réponse HTTP. Complétons un peu ce paradigme : lorsque l'on fait appel à une API, en général c'est au travers d'une application. Ainsi, le client est en fait une autre application web capable d'émettre une requête HTTP (peu importe dans quel langage cette application est développée).

Voici un exemple en images :

Interactions avec une API
Interactions avec une API

Comme vous pouvez le voir, ici il y a 3 acteurs :

  1. le navigateur ;

  2. le frontend (application 1) ;

  3. l'API (application 2).

Dans ce type de configuration (très commune lorsqu'il s'agit de travailler avec des API), le frontend ne se charge que de la présentation des informations qu'il ira chercher ailleurs (ici, auprès de l'application 2).

Ainsi, le navigateur émet la première requête HTTP, parce qu'un utilisateur (un internaute) souhaite afficher une page, puis l'application 1 demande un certain nombre d'informations à l'API (ici il s'agit d'une liste d'utilisateurs). L'application 1 récupère ces informations brutes (au format JSON par exemple), les met en forme pour finalement rendre le tout bien ficelé, en HTML, au navigateur. Lorsque l'on travaille avec l'architecture REST, c'est ainsi que les demandes se passent. Il est tout à fait possible que plusieurs autres demandes émanent du frontend vers l'API. Il est même possible que le frontend fasse d'autres demandes à d'autres API en plus de ce qui est montré sur le schéma ci-dessus (comme aller chercher des informations en rapport avec la météo à l'API de Météo France par exemple). Tout est possible ! ;)

Donc, si on simplifie un peu les choses, la contrainte impose tout simplement que le client effectue une requête HTTP, le serveur reçoit la demande et doit renvoyer une réponse. Ça tombe bien ! Le protocole HTTP est exactement pensé ainsi ! Cette contrainte fait également mention du fait que le serveur doit être en mesure de gérer des requêtes provenant de plusieurs clients à la fois.

Contrainte n° 2 : Stateless (sans état)

Une API REST doit être sans état. Concrètement, d'une requête à une autre, l'API ne doit pas garder en mémoire ce qu'il s'est passé à la requête précédente. Vous voyez où je veux en venir ? Non ? Il faut oublier le principe de session côté serveur ! Et oui. C'est au client d'avoir une session. :waw:

C'est particulièrement utile lorsqu'il faut qu'un utilisateur soit authentifié pour obtenir une information. Si nous reprenons l'exemple que nous avons vu plus haut, l'authentification doit se passer sur le frontend. Ce qui sera communiqué à l'API sera donc les informations de l'utilisateur authentifié, et ce, à chaque requête. L'API ne doit en aucun cas garder un historique des requêtes précédentes.

Contrainte n° 3 : Cacheable (cachable)

Je vous ai dit un peu plus tôt qu'une application pouvait faire appel à une API pour récupérer des informations, et qu'il est même possible qu'elle le fasse plusieurs fois. Ceci dit, en multipliant les demandes, le temps de chargement de la page finale s'allonge… Et nous ne voulons pas cela, oh non non non ! :'(

Pour cela, nous pouvons faire appel au cache HTTP : il s'agit de réduire au minimum le temps de génération d'une réponse HTTP. Nous devons donc faire en sorte qu'une même réponse HTTP ne soit pas générée deux fois. La première fois, tous les calculs et traitements sont faits (ce qui peut prendre beaucoup de temps), puis cette réponse est "enregistrée" pour pouvoir être resservie les fois prochaines et ainsi éviter tout le temps de traitement à chaque requête.

Contrainte n° 4 : Layered system (système à plusieurs couches)

Lorsqu'un client émet une requête à une API, il ne doit pas savoir ce qui se passe pour obtenir une réponse. Le client ne doit donc pas se soucier de comment l'API renverra une réponse.

Contrainte n° 5 : Uniform interface (Interface uniforme)

Cette contrainte est complètement orientée vers les ressources. Rappelez-vous, nous en avons parlé un peu plus haut... Une ressource n'est en fait qu'un élément que l'on manipule en fonction du besoin que nous avons avec une API. Prenons un exemple : avec l'API d'Instagram, il est possible de manipuler des utilisateurs (récupérer des informations, peut-être en mettre à jour…), de manipuler des images également. Les images et les utilisateurs sont des ressources.

Et la contrainte n° 5 alors ?

Oui oui, je n'ai pas oublié. ;) Cette contrainte nous impose les choses suivantes, pour chaque ressource :

  1. a. doit posséder un identifiant unique ;

  2. b. doit avoir une représentation ;

  3. c. doit être auto-décrite.

    a. Contrainte n° 5 - Une ressource doit posséder un identifiant

Cette sous-contrainte signifie qu'un élément doit avoir un moyen d'être reconnaissable. En général, chaque élément a un  id , cela peut servir d'identifiant. Mais il est possible que ce soit unuuid, un slug ou tout autre attribut permettant de retrouver une ressource, que ce soit en base de données ou ailleurs. Pour accéder à cette ressource, il faut que cet identifiant se retrouve dans l'URI. Par exemple, pour accéder à un utilisateur, l'URI pourrait être  /users/1  ( 1 étant l'id de l'utilisateur).

    b. Contrainte n° 5 - Une ressource doit avoir une représentation

Il s'agit de choisir une manière d'afficher la ressource. La contrainte dit simplement qu'il faut choisir une manière de formater la réponse et s'y tenir. Prenons l'exemple d'un utilisateur : votre API offre la possibilité de consulter les informations d'un utilisateur. Après un peu de réflexion, et surtout parce que vous savez qu'il faut faire un choix, voici le squelette du JSON (cela aurait pu être du XML aussi) qui représentera les informations de l'utilisateur. Ainsi, si l'utilisateur de l'API effectue une requête GET sur l'url http://domain.name/users/ad70e3ea-e793-11e6-bf01-fe55135034f3 , voici le contenu de la réponse :

{
    "uuid" : "ad70e3ea-e793-11e6-bf01-fe55135034f3",
    "fullname" : "Sarah Khalil",
    "job" : "Auteur"
}

La contrainte dit simplement qu'une fois que vous avez choisi la manière dont vous allez représenter une ressource, il faut simplement s'y tenir.

    c. Contrainte n° 5 - Une ressource doit être auto-décrite

Il s'agit simplement d'indiquer quel est le format de votre réponse. En général, les API REST rendent un contenu en JSON ou en XML. Pour l'indiquer, il s'agit simplement de s'assurer que le header  Content-Type  est bien ajouté à la réponse HTTP.

Dans le cas où la réponse contient du JSON, le header serait  Content-Type: application/json .

Contrainte n° 6 : Code on demand (du code sur demande)

Celle-ci est facultative. Il s'agit de demander au serveur, donc à votre API, un morceau de code pour que celui-ci soit exécuté par le client.

En général, les traitements sont délégués à l'API, ou, en fonction des cas, au client (frontend par exemple). Si le cas se présente, Il faudra s'assurer que le code que l'on s'apprête à exécuter n'est pas malicieux…

Et voilà ! Nous avons fait le tour des 6 contraintes de l'architecture REST. Néanmoins, nous n'en avons pas fini avec les règles à suivre. Il nous reste encore à explorer la théorie, nous permettant de faire en sorte que vos API REST soient RESTFul (pleinement REST). Ces règles sont régies par le modèle de maturité de Richardson. Mais avant de le passer en revue, il faut avoir les idées bien claires concernant HTTP, le protocole sur lequel l'architecture REST se repose.

Une architecture basée sur un protocole que l'on connaît bien : HTTP

Nous allons faire quelques rappels concernant HTTP : il s'agit d'un protocole d'échange entre deux machines. Une API n'est rien d'autre qu'une application capable de recevoir une requête HTTP et rendre une réponse HTTP. Ce qui change d'un site web "classique" est le fait qu'il s'agit de faire des actions plus atomiques, contrairement à une page web HTML. Une API va se charger de la gestion des utilisateurs (ajout, suppression…) ou d'une gestion de produits, ou tout autre ressource.

Revoyons ce que contient une requête et une réponse HTTP.

Requête HTTP

Une requête HTTP émane d'un client (tout logiciel dans la capacité de forger une requête). Une requête est constituée des éléments suivants :

  1. La première ligne (request line) doit contenir :

    • la méthode HTTPGET ,  POST ,  PUT ,  PATCH ,  DELETE ,  OPTIONS ,  CONNECT ,  HEAD ou  TRACE )

    • l'URI, c'est-à -dire ce qu'il y a après le nom de domaine (exemple :  /users/1 )

    • la version du protocole (exemple :  HTTP/1.1 )

  2. Les entêtes (headers), une entête par ligne, chaque ligne finie par le caractère spécial "retour à la ligne" (CRLF)

  3. Le contenu de la requête (body), doit être séparé de deux caractères spéciaux "retour à la ligne" ( CRLF CRLF ) - optionnel

Voici un exemple de requête POST :

POST /users HTTP/1.1
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Content-Length: 28

name=Sarah Khalil&job=auteur
        Méthodes HTTP
  •  GET : utilisée pour récupérer des informations en rapport avec l'URI ; il ne faut en aucun cas modifier ces données au cours de cette requête. Cette méthode est dite safe (sécuritaire), puisqu'elle n'affecte pas les données du serveur. Elle est aussi dite idempotent, c'est-à-dire qu'une requête faite en GET doit toujours faire la même chose (comme, renvoyer une liste d'utilisateurs à chaque fois que la requête est faite - d'une requête à l'autre, on ne renverra pas des produits si le client s'attend à une liste d'utilisateurs !).

  •  POST : utilisée pour créer une ressource. Les informations (texte, fichier…) pour créer la ressource sont envoyées dans le contenu de la requête. Cette méthode n'est ni safe, ni idempotent.

  •  PUT : utilisée pour remplacer les informations d'une resource avec ce qui est envoyé dans le contenu de la requête. Cette méthode n'est ni safe, ni idempotent.

  •  PATCH : utilisée pour modifier une ressource. La différence avec une requête avec la méthode PUT est que l'action à effectuer sur la ressource est indiquée dans le contenu de la requête. Prenons un exemple : nous souhaitons rattacher un utilisateur à une organisation, dans le contenu de la requête, il sera indiqué qu'il s'agit d'un rattachement à une organisation en plus des informations à mettre à jour.

  •  DELETE : utilisée pour supprimer une ou plusieurs ressources. Les ressources à supprimer sont indiquées dans l'URI.

  •  OPTIONS : utilisée pour obtenir la liste des actions possibles pour une ressource donnée (suppression, ajout…).

  •  CONNECT : utilisée pour établir une première connexion avec le serveur pour une URI donnée.

  •  HEAD : même principe que pour la méthode GET, mais seules les entêtes devront être renvoyées en réponse.

  •  TRACE  : utilisée pour connaître le chemin parcouru par la requête à travers plusieurs serveurs. En réponse, une entêteviasera présente pour décrire tous les serveurs par lesquels la requête est passée.

Réponse HTTP

Une réponse HTTP émane d'un serveur (tout logiciel dans la capacité de forger une réponse HTTP). Une réponse est constituée des éléments suivants :

  1. La première ligne (status line) doit contenir :

    • la version du protocole utilisée

    • le code status

    • l'équivalent textuel du code status

  2. Les entêtes (headers), une entête par ligne, chaque ligne finie par le caractère spécial "retour à la ligne" (CRLF)

  3. Le contenu de la réponse (body), doit être séparé de deux caractères spéciaux "retour à la ligne (CRLFCRLF) - optionnel.

Voici un exemple de réponse :

HTTP/1.1 200 OK
Date:Tue, 31 Jan 2017 13:18:38 GMT
Content-Type: application/json

{
    "current status" : "Everything is ok!"
}
       Code status

Il est important d'accorder une attention toute particulière aux codes status qui doivent être choisis avec sagesse lorsqu'il vous faudra rendre une réponse. Il existe cinq catégories de code status, les voici :

Catégorie

Description

1xx : les informations

Une réponse doit contenir ce type de code status lorsqu'il s'agit d'informer le client de l'état de la demande. C'est utile pour indiquer que, par exemple, la requête a bien été reçue et que le traitement vient de commencer, dans le cas de traitements asynchrones par exemple.

 2xx : les succès

 Tout s'est bien passé sur le serveur.

 3xx : les redirections

 Une redirection est sur le point d'être effectuée.

 4xx : les erreurs client

 La requête contient une erreur et ne peut pas être traitée.

 5xx : les erreurs serveur

 Le serveur vient de rencontrer un problème empêchant le traitement de la requête

Voyons maintenant comment une application REST devient RESTFul, c'est à dire pleinement REST.

Modèle de maturité de Richardson

Le modèle de Richardson est aussi connu que la reine d'Angleterre, toutes proportions gardées, dans le monde des APIs REST. :D Ce modèle donne un moyen d'évaluer son API. Plus on monte dans les niveaux, plus notre API est considérée RESTFul (pleinement REST autrement dit). Plus votre API adhère aux contraintes REST, plus elle est RESTFul, ce qui est plutôt bien du point de vue des bonnes pratiques : en effet, étant donné que rien n'est imposé, le fait de respecter les règles permettent de faire que ceux qui connaissent et aiment utiliser les API REST et/ou développer des API REST adopteront plus facilement votre travail.

Passons en revue ce modèle ensemble. Il existe 4 niveaux, de 0 à 3 :

Niveaux de respects du modèle de maturation de Richardson (crédit : martinfowler.com)
Niveaux de respects du modèle de maturité de Richardson (crédit : martinfowler.com)

Je vous invite à lire les explications en anglais de Martin Fowler (Steps toward the glory of REST). Je vous propose tout de même une explication de chacun des niveaux dans la suite de ce cours :

Level 0 : The Swamp of POX (Un marécage de bon vieux XML)

Une API qui ne fait que respecter le niveau 0 n'est pas une API REST. Ce niveau ressemble plus à ce que l'on peut retrouver dans une API SOAP (type d'API plutôt old school).

Il s'agit de :

  • n'utiliser qu'un seul point d'entrée pour communiquer avec l'API, c'est-à-dire qu'une seule URI, comme par exemple  /api  ;

  • n'utiliser qu'une seule méthode HTTP pour effectuer ses demandes à l'API, avec POST.

Ces deux règles ne sont pas à suivre selon les contraintes de REST énoncées : en effet, chaque ressource devrait avoir son point d'entrée. Si par exemple notre API gère des utilisateurs et des produits, il devrait y avoir au moins deux URI différentes pour récupérer ces listes,  /users  et  /products . Par ailleurs, l'utilisation de la méthode HTTP  POST  pour toutes les actions que nous souhaiterions effectuer sur l'API n'est vraiment pas une bonne idée : comme expliqué plus haut, la méthode  POST  n'est réservée qu'à la création de ressource. Si nous souhaitions récupérer une liste d'utilisateurs, il faudrait utiliser la méthode  GET pour la requête.

Du coup, ce niveau 0 n'a rien de bon a priori. Passons au suivant.

Level 1 : Les ressources

Le niveau 1 concerne les ressources et demande dans un premier temps que chaque ressource puisse être distinguée séparément. Cela ne vous rappelle rien ? Mais si, la contrainte n° 5 ! Je ne vais pas me répéter du coup. ;) Simplement, rappelez-vous qu'il faut que vos URIs doivent correspondre à la ressource que le client de votre API souhaite manipuler.

J'ajouterai encore un petit quelque chose, en prenant un exemple concret : une API permettant de manipuler des articles. Disons que nous souhaitions développer un CRUD (CRUD est un acronyme anglais pour Create, Read, Update et Delete, soit Créer, Lire, Mettre à jour et Supprimer). La première question qu'il faut se se poser est la suivante :

Quelles vont être les URIs par lesquelles les utilisateurs de mon API vont pouvoir effectuer ces actions ?

Nous devons réfléchir aux points d'entrée dès le début. D'après ce que dit le niveau 1 du modèle de Richardson, les solutions possibles devraient être :

  • Pour la création d'articles,  /articles/create  ;

  • Pour la lecture d'articles, nous aurions  /articles  (liste des articles) et  /articles/{identifiant-unique}  (un seul article) ;

  • Pour la mise à jour,/articles/{identifiant-unique}/update;

  • Et enfin, pour la suppression, ce serait plutôt  /articles/{identifiant-unique}/delete .

Le niveau 1 ne prévoit pas de faire usage des différentes méthodes HTTP. Toutes les requêtes se font enPOSTici. C'est dommage ! :( Mais, bonne nouvelle, le niveau 2 vient à la rescousse pour pallier ce problème ! :D

Level 2 : HTTP Verbs (Méthodes HTTP)

Nous ajoutons maintenant l'utilisation des méthodes HTTP en fonction de l'action qu'il faut effectuer sur l'API. Reprenons donc notre exemple d'API permettant un CRUD sur des articles. Il va falloir changer un peu les URIs et se servir des méthodes HTTP pour indiquer nos intentions. Voyons comment nos requêtes devraient être formulées :

  • Création         •  POST /articles 

  • Lecture           •  GET /articles  ou  GET /articles/{identifiant-unique} 

  • Mise à jour    •  PUT /articles/{identifiant-unique} 

  • Suppression •  DELETE /articles/{identifiant-unique} 

Comme vous pouvez le voir, les actions ne font plus partie de l'URI. C'est primordial de les retirer. L'information concernant l'action est désormais contenue dans la méthode HTTP. Nous utilisons la pleine capacité du protocole HTTP !

Autre point abordé par le niveau 2 : le code status. Si vous décidez de respecter le niveau 2 du modèle de Richardson, il faut choisir avec sagesse le code retour HTTP dans vos réponses. Voici un petit tour des codes status les plus courants que vous serez amené à retourner en fonction des situations :

  • 200 OK • Tout s'est bien passé ;

  • 201 Created • La création de la ressource s'est bien passée (en général le contenu de la nouvelle ressource est aussi renvoyée dans la réponse, mais ce n'est pas obligatoire - on ajoute aussi un header Locationavec l'URL de la nouvelle ressource) ;

  • 204 No content • Même principe que pour la 201, sauf que cette fois-ci, le contenu de la ressource nouvellement créée ou modifiée n'est pas renvoyée en réponse ;

  • 304 Not modified • Le contenu n'a pas été modifié depuis la dernière fois qu'elle a été mise en cache ;

  • 400 Bad request • La demande n'a pas pu être traitée correctement ;

  • 401 Unauthorized • L'authentification a échoué ;

  • 403 Forbidden • L'accès à cette ressource n'est pas autorisé ;

  • 404 Not found • La ressource n'existe pas ;

  • 405 Method not allowed • La méthode HTTP utilisée n'est pas traitable par l'API ;

  • 406 Not acceptable • Le serveur n'est pas en mesure de répondre aux attentes des entêtes  Accept . En clair, le client demande un format (XML par exemple) et l'API n'est pas prévue pour générer du XML ;

  • 500 Server error • Le serveur a rencontré un problème.

Retenez simplement que plus vous serez explicite dans la réponse générée, meilleure sera la qualité de votre API.

Level 3 : Hypermedia controls

Et enfin le niveau 3, The glory of REST ! Le niveau 3 est simplement l'idée de rendre votre API auto découvrable, en imitant les liens hypertextes dans une page web classique. Concrètement, nous devons indiquer au client de votre API ce qu'il est possible de faire à partir d'une ressource.

Reprenons l'exemple de l'API de gestion d'articles : si le client demande un article, non seulement il obtiendra les informations de l'article en question (titre, contenu…), mais aussi la liste des liens (URL) pour effectuer d'autres actions sur cette ressource, comme la mettre à jour ou un article associé par exemple.

Voici un exemple de contenu de réponse offrant une auto découverte de l'API :

{
    "id" : 1,
    "title" : "Le titre de l'article",
    "content" : "<p> Le contenu de l'article.</p>",
    "links" : {
        "update" : "http://domain.name/article/1",
        "associated" : "http://domain.name/article/16"
    }
}

Bien évidemment, d'autres développeurs se sont intéressés à la question et ont proposé plusieurs solutions pour présenter ces liens. Nous aborderons le sujet plus en détail plus tard dans ce cours, et cela promet d'être une (auto) découverte ! :lol:

Nous en avons fini avec ce premier chapitre d'introduction à REST. Voyons comment Symfony va pouvoir nous aider à suivre toutes ces règles et contraintes aisément.

Exemple de certificat de réussite
Exemple de certificat de réussite