Olivier Gutknecht
Bien architecturer une application REST Avec la contribution de Jean Zundel Ce livre traite exactement du sujet suivant : comment faire pour que les services web et les programmes qui les utilisent aient la même souplesse de navigation parmi l’information que tout internaute dans son navigateur web ? Comment utiliser les mêmes principes ? On verra que les bonnes pratiques du Web « humain » doivent se retrouver lorsqu’on conçoit des services web en REST.
Prix : 16 E Code éditeur : G85015 ISBN : 978-2-212-85015-4 © Eyrolles 2009
Organisation de ce livre Ce livre présente un aperçu des services web que l’on peut concevoir dans le style d’architecture REST. Plutôt que de se focaliser sur un framework particulier, nous mettrons en lumière les principes de l’architecture, les bonnes pratiques associées et comment tirer parti au mieux des protocoles pour concevoir des applications et tenir compte de la latence, des caches, de la montée en charge, etc. Avertissement Ce livre n’a pas la prétention d’être une référence sur REST, ne serait-ce que par son format, mais il donne un tour d’horizon des concepts de base et des apports de ce style d’architecture. Le lecteur averti devra nous pardonner d’avoir simplifié légèrement certains concepts – le prix de la concision.
Après une introduction générale, nous verrons au chapitre 2, sur un exemple minimaliste comment concevoir une application selon les principes REST, et quel en est l’impact sur la structuration des données, sur la lecture ou la mise à jour des informations.
Avant-propos
Bien architecturer une application REST
Avant-propos
II
Au chapitre 3, nous reviendrons sur REST et sur certains points d’architecture spécifiques, en étudiant comment tirer parti au mieux de HTTP et des standards associés. Nous verrons comment une utilisation soigneuse du protocole permet de bénéficier d’une architecture de cache, de gestion des versions, et d’une meilleure montée en charge.
Au chapitre 5, nous explorerons une application REST existante, l'API Google Contacts, et nous analyserons comment les concepteurs de cet outil ont mis en oeuvre les concepts REST. Nous conclurons par une check-list méthodique, avant de proposer quelques pistes et références bibliographiques.
Remerciements Je tiens à remercier, pour leur relecture attentive et leurs conseils, Muriel Shan Sei Fan, Jean Zundel, Luc Heinrich, Sébastien Tanguy, Loïc Ségalou, et Véronique Heinrich.
Avant-propos
Bien architecturer une application REST
Nous verrons au chapitre 4 dans le détail quelques principes simples d’implémentation pour exploiter facilement les caches, la distribution, ou le contrôle de version. Bien sûr, nous y aborderons également les grands écueils classiques.
III
Ma journée démarre : j’ouvre mon navigateur web, je pars butiner quelques blogs du matin. Un billet de l’un de mes auteurs favoris suggère la lecture d’un autre billet d’un inconnu. Je file le lire, et commence à parcourir les archives du mois de ce nouveau blog. Je trouve un lien sur la page personnelle de l’auteur, je passe sur la liste de ses publications. Pendant ce temps, une application sur ma machine se connecte sur un site de photos, télécharge la liste des albums auxquels je suis abonné, récupère la liste de commentaires récents sur une de mes photos, trouve et charge dans un de ces commentaires une image que l’on me conseille d’aller voir, puis revient sur les informations de mon compte, vérifie mon quota, et met en ligne les dernières photos que j’ai stockées sur ma machine. Quoi de commun entre mon activité et celle de mon programme ? Superficiellement, pas grandchose. Je suis dans mon navigateur et je passe de page en page, et l’application travaille silencieusement à synchroniser des données assez hétérogènes. Et pourtant… Mon programme et moi sommes finalement en train de faire exactement la même chose : parcourir le Web, naviguer d’une information à l’autre en suivant des liens, en prenant des décisions à chaque étape sur quoi aller voir ensuite. C’est-à-dire présenter une requête à un serveur sur une URL donnée, attendre sa réponse. Examiner le contenu, y trouver des liens. Suivre un ou plusieurs de ces liens, c’est à dire refaire une requête sur une autre URL, peut-être sur un autre serveur, attendre la réponse, etc. Mon navigateur me présente des données dans différents formats : images, texte, et même d’autres sans que je m’en aperçoive, comme un peu de javascript pour me faciliter - ou compliquer - ma navigation. L’application navigue dans la même toile de liens, téléchargeant parfois du XML, parfois un peu de HTML ou de JSON, parfois des formats plus spécialisés comme ATOM ou GData.
Avant-propos
Bien architecturer une application REST
Le Web pour les humains – le Web pour les machines
IV
Table des matières
Les services web : appel de procédure ou exploration d’espace ? 2 REST, un style d'architecture 4 2. COMPRENDRE REST À TRAVERS UNE PREMIÈRE UTILISATION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 Modélisation des données 8 Identifier les ressources 8 Quelles URL pour donner l’accès à mes ressources ? 12 Représentations d’une ressource 15 Manipulation des ressources 18 Accéder à une ressource 19 Accès à une carte du carnet 19 Accès à un groupe de fiches 20 Créer et modifier une ressource 21 Créer une nouvelle carte du carnet 21 Modifier une fiche 23 Détruire une ressource 24 Enlever un groupe 24 Et si tout se passe mal ? 25 Enlever une carte… inexistante ! 25 Envoyer des données… incompréhensibles ! 25 Se heurter à une limitation du serveur 26
Table des matières
Bien architecturer une application REST
AVANT-PROPOS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . II 1. INTRODUCTION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
V
3. RETOUR SUR REST : MODÈLE ET PRINCIPES. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 Des ressources… 29 L’adressabilité 30 Des actions génériques et bien connues 31 Des représentations tout aussi génériques et bien connues 33 Un style d’architecture sans état 37 Que faire si l’on a vraiment besoin d’état ? 40 Un protocole de choix : HTTP 41 Petit rappel sur HTTP 42 Structure d’une requête 43 Structure d’une réponse 43 Utilisation des méthodes HTTP : sûreté et idempotence 44 Méthodes sûres 44 Méthodes idempotentes 44 Effet des méthodes HTTP 45 Méthode GET 46 Méthode POST 47 Méthode PUT 49 Méthode DELETE 50 Une architecture en couches 52 Une montée en charge naturelle 53 Une négociation possible entre client et service 53 4. BONNES PRATIQUES D’IMPLÉMENTATION REST . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Mise en cache et maîtrise de l’accès aux ressources 56 Accès conditionnel aux ressources 58 Last-Modified et ETag, quels problèmes potentiels ? 62 Last-Modified, ETags et modèle de données 63 Configuration de la mise en cache : Cache-Control, Expires… 65 Faut-il utiliser la négociation de type de contenu ? 67
Table des matières
Bien architecturer une application REST
En résumé… 27
VI
Comment émuler PUT et DELETE ? 68 5. UNE COURTE ÉTUDE D’UNE API EXISTANTE DE GOOGLE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 Obtenir la liste des contacts 74 Mettre à jour un contact 83 Détruire un contact 85 En résumé 86 A. BRÉVIAIRE DES CODES HTTP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 Les codes 200 91 Les codes 300 92 Les codes 400 94 Les codes 500 96 B. BIBLIOGRAPHIE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Table des matières
Bien architecturer une application REST
6. POUR CONCLURE : COMMENT RESTER REST ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
VII
Introduction
Ce livre traite exactement du sujet suivant : comment faire pour que les services web et les programmes qui les utilisent aient la même souplesse de navigation dans l’information que tout internaute dans son navigateur web ? Comment utiliser les mêmes principes ? On verra que les bonnes pratiques du web « humain » doivent se retrouver lorsqu’on conçoit des services web en REST.
1
Les services web : appel de procédure ou exploration d’espace ?
Quelle différence entre ces deux URL : http://carnet-rest.com/api?method=findcard&userid=aveen&sessionid=0679725229
et http://carnet-rest.com/cards/ada-veen
Du point de vue de l’implémenteur, probablement pas grand-chose. L’une comme l’autre se trouvent renvoyer la même information : une fiche dans deux services virtuels de carnet d’adresses. Du point de vue de l’architecte, la différence est déjà plus nette : la première semble exhiber directement un choix d’implémentation, en l’occurrence un appel de méthode sur un service distant. On imagine aisément être directement en train d’interroger un objet Java, C#, Ruby, etc. On utilise une technologie web, HTTP, comme simple transport pour interroger directement un programme. Dans le second cas, on voit bien que le concepteur du service a conçu son service un peu différemment : on a moins l’impression de parler directement à une machine, d’invoquer une action (rechercher une fiche). On est face à une URL qui semble traduire directement un concept : la fiche d’une personne nommée Ada Veen.
Chapitre 1 – Introduction
Bien architecturer une application REST
Imaginons un service web qui nous permette d’interroger ou de modifier un carnet d’adresses, sur le Web, mais aussi via un client spécialisé. Prenons un premier exemple en examinant deux façons d’écrire une URL pour accéder à la fiche d’une hypothétique amie, Ada Veen :
2
Dans ce dernier cas, l’implémentation sous-jacente est bien sûr moins apparente, mais la différence fondamentale est que dans le premier cas, on exprimait via cette URL une action, alors qu’ici on se focalise sur l’information, le concept.
L’une suppose que le client a une connaissance exacte de la structure des URL qu’il faudra demander au serveur – et probablement du format de réponse – en encapsulant un appel de procédure à distance dans un protocole qui se trouve être ici HTTP. L’autre donne un point d’entrée dans un espace que l’on partira explorer, en exploitant des réponses dans des formats bien connus. Ce principe d’exploration via des liens a un autre impact fondamental. Il permet de mettre en place une architecture où le serveur n’a pas à connaître le client spécifique auquel il s’adresse. Comme le client ne fait que suivre des liens, l’un après l’autre, la notion « d’état » du client n’a même pas à être connue par le serveur : c’est le client qui sait où il est, et où il va aller à l’étape suivante. REST (Representational State Transfer) définit ce style d’architecture logicielle. JARGON REST et ses acceptions Le terme REST est d’ailleurs parfois un peu dévoyé et, le succès aidant, certaines architectures sont un peu trop rapidement qualifiées de « REST » alors qu’elles se contentent d’adopter a minima tel ou tel point de ce style d’architecture. C’est au point où parfois, le qualificatif de « REST » n’est utilisé qu’en creux, par opposition à d’autres caractéristiques : « cet outil n’est basé ni sur SOAP, ni sur XML-RPC et comme il utilise HTTP, il est donc REST ! ». Je caricature, mais j’espère montrer dans les pages à venir que REST… c’est bien plus que cela.
Chapitre 1 – Introduction
Bien architecturer une application REST
Cette distinction est révélatrice de deux grandes approches pour concevoir un service web. L’une exprime un concept finalement assez ancien : l’appel de procédure à distance. L’autre se focalise sur des concepts à explorer.
3
REST, un style d'architecture
Les services web conformes à REST (RESTful) sont implémentés par dessus un jeu classique et connu de technologies : • des bibliothèques de code implémentant HTTP, • des serveurs web pour traiter des requêtes, • des frameworks pour faciliter l’écriture d’une application, • des systèmes de « templates » pour générer des vues XML, • des spécifications de types de contenus, • etc. L’examen des différentes constructions logicielles qu’on peut former avec ces différentes briques fait prendre conscience que, parmi toutes ces architectures possibles, certaines ont des propriétés vraiment intéressantes – qui s’inspirent des bonnes pratiques et de la philosophie générale du Web. Bref, REST n’est que cela : 1 une famille d’architecture de services web, 2 une façon de représenter des ressources web, 3 une certaine manière de les décliner, de les manipuler, 4 l’usage de quelques principes simples pour parcourir des données dans une application, 5 et, finalement, la décision de voir les services web – ou le Web d’une façon plus large – comme un style spécifique d’écriture d’applications, de structuration de l’information.
Chapitre 1 – Introduction
Bien architecturer une application REST
REST repose sur les standards qui fondent l’infrastructure du Web et c’est là son paradoxe : on ne peut pas pour autant en écrire de spécification précise. REST est un style d’architecture, pas une architecture bien précise et concrète.
4
Bien sûr, toute architecture vient avec ses présupposés, son domaine d’excellence : REST n’est pas la réponse absolue à tous les problèmes d’architectures de services web, et certains problèmes n’y trouvent pas toujours d’expression élégante (en particulier les questions de transactions, de time-out, de ressources critiques, etc.). Mais penser une application dans le style REST, c’est reconnaître qu’organiser l’information sur le Web est un travail à part entière ; c’est reconnaître que le Web est plus que la somme de ses parties, plus qu’une collection de standards et de spécifications.
Chapitre 1 – Introduction
Bien architecturer une application REST
REST est le retour aux fondamentaux du Web.
5
Comprendre REST à travers une première utilisation
Un livre technique n’est rien sans un bon « hello world ». Déroulons donc dans ce chapitre la découverte d’un service REST imaginaire proposé pour un carnet d’adresses « RESTful ».
2
Par client REST, nous entendons un programme, écrit dans un langage quelconque, qui interrogera des URL via le protocole HTTP pour accéder aux données du carnet d’adresses, dans un format à définir (HTML, XML…).
Bien architecturer une application REST
RAPPEL Client REST Un client REST peut aussi bien être un simple navigateur, qui navigue dans une version web de mon carnet d’adresses, qu’un client riche natif parfaitement intégré dans mon système d’exploitation… Voire des outils en ligne de commande accédant de la façon la plus directe possible aux données, par exemple dans des représentations XML. On verra au fil de l’ouvrage que ces principes simples sont la colonne vertébrale d’une bonne architecture REST.
Du côté des fonctionnalités, au strict minimum, il faudrait pouvoir lire une carte de visite qui y sera stockée, et bien évidemment en ajouter ou compléter les renseignements saisis. Il serait bon également de pouvoir regrouper les fiches par groupes (professionnel, amis, vie courante), y faire des recherches, exporter ou importer des fiches dans d’autres formats, etc. Posons quelques principes de base pour la construction de notre service. Les problèmes que je vais me poser sont les suivants : 1 Modéliser mon application, en identifier les concepts de base, ou « ressources » de mon modèle ; 2 Trouver un moyen d’identifier ces ressources, en les associant à des URL ; 3 Identifier les liens possibles entre ces ressources : – Poser la question des représentations possibles de ces ressources : dans quels formats puis-je « exporter » ou « importer » ces ressources ?
Chapitre 2 – Comprendre REST à travers une première utilisation
Notre application de carnet d’adresses va proposer un service que des clientsREST pourront utiliser.
7
– Définir une manière uniforme de manipuler ces ressources : en particulier, comment les créer, les mettre à jour ou les détruire ?
Bien architecturer une application REST
Modélisation des données En construisant cette application de carnet d’adresses, nous allons garder comme fil rouge l’idée que l’on veut fournir une façon d’explorer de l’information à l’utilisateur (qu’il soit homme ou machine). Par « explorer de l’information », j’entends que le plus important n’est pas de fournir des points d’entrée vers du code (qu’il soit Ruby, C++, Java, C#, Intercal…), mais une vision un tant soit peu organisée et générale des données qui forment mon carnet d’adresses : un « espace d’information », en quelque sorte.
Identifier les ressources Mais que sont ces ressources dont on parle depuis quelques pages ? Je vais construire une « architecture orientée ressource », un qualificatif que l’on rencontre de plus en plus fréquemment. Comme l’indique cet acronyme, la clé de ce style d’architecture est de se focaliser avant tout sur un modèle de données : quels concepts de base vais-je trouver dans mon application et comment vais-je les organiser et y accéder ? VOCABULAIRE ROA ou Resource Oriented Architecture On parle donc ici de ROA, ou Resource Oriented Architecture, pour insister sur l’importance du modèle de données.
Chapitre 2 – Comprendre REST à travers une première utilisation
Mais commençons par le commencement – à défaut du plus simple.
8
Tout de suite, un premier piège apparaît : il serait facile – et d’autant plus facile si l’on vient avec une culture d’implémenteur – de voir dans ces questions autant de requêtes de base de données sur un serveur, ou encore des appels de méthodes vers des objets implémentant le service. Ce serait aller bien trop vite en besogne, et c’est précisément ce que l’on veut éviter ! Car cela serait se détourner de l’objectif initial : se concentrer sur la définition de ce fameux espace d’information dans lequel on veut pouvoir naviguer à loisir. RÉFLEXION Génie logiciel : architecture REST vs architectures objet et non objet On n’est pas très loin, finalement, des problèmes de conception logicielle classique. Que sont les gros morceaux dans mon système ? En conception objet, on a l’habitude de chercher d’abord à identifier des objets, à en comprendre la spécialisation possible (« Une voiture est une sorte de véhicule ») et à y associer des comportements (« démarre! », « arrête-toi »). Dans des architectures non objet, on raisonnera davantage sur les actions possibles sur un système, sur les données à passer en paramètre (« ouvrir une connexion sur le port passé en paramètre et renvoyer un flux en valeur de retour»).
Chapitre 2 – Comprendre REST à travers une première utilisation
Bien architecturer une application REST
Voici de premiers exemples de « ressources » qui peuvent venir à l’esprit : • Les dernières cartes dans mon carnet • La carte correspondant à Ada Veen • Le carnet d’adresses d’Ada Veen • Toutes mes cartes « Vie Courante » • Tous les groupes de cartes que j’ai pu définir dans mon carnet • Les cartes de visite de mes collègues • Toutes les cartes de visite de tous les carnets • Ma propre carte • …
9
Bien architecturer une application REST
Démarche D’ailleurs, j’ai tendance à considérer qu’une bonne architecture REST doit pouvoir tenir debout si on l’examine simplement du point de vue de l’accès en lecture aux données – le problème de la modification des données mises à jour peut venir ensuite. Si l’organisation des ressources est bonne, déterminer les actions qui s’y appliqueront doit venir assez naturellement. Il m’est d’ailleurs arrivé plusieurs fois de commencer une implémentation par l’organisation des URL et les représentations en lecture, quitte à avoir quelque chose de temporaire et d’imparfait pour l’écriture avant de finaliser l’ensemble.
Partons des concepts qui semblent revenir dans ma liste précédente : la fiche, ou carte de visite, semble naturellement être une ressource primordiale ; le groupe de cartes également. Quant au résultat d’une recherche, il est plus problématique : il s’agit bien d’une collection de cartes, mais est-ce un concept à part entière, une opération ? Il apparaît ainsi que certaines ressources sont le résultat d’une opération : recherche, filtrage… Astuces de conception Il n’est pas inutile de garder en tête quelques trucs et astuces pendant cette phase. On peut se poser des questions de ce style : - « Si je concevais un site web et pas un service, quelles seraient les pages intéressantes, celles que je voudrais garder en signets ? » - « Comment pourrais-je rendre l’architecture de mon site la plus naturelle possible ? Est-ce que la structure de liens du site a du sens, semble-t-elle naturelle ? » - Et si plutôt que de « pages », je parle de « ressources », tout cela a-t-il encore du sens ?
Chapitre 2 – Comprendre REST à travers une première utilisation
Quoi de différent ici avec les architectures objet ? On ne s’intéresse pas à la transcription de tout cela dans un langage informatique (ou en tout cas, pas avant une phase bien ultérieure). En REST, on s’attardera sur les concepts et leurs relations, sur leurs représentations, et sur les moyens de les lire et d’agir sur eux.
10
Essayons de les catégoriser. Qu’avons-nous ? • des types de ressources élémentaires : ma propre carte, l’ensemble du carnet, la liste de mes groupes. Ces ressources sont importantes : elles vont me permettre d’avoir un point d’entrée dans les données, et donc permettre la navigation entre les ressources ; • des ressources identifiées et non ambiguës : la carte de Ada Veen, mon groupe « Vie courante » ; • des ressources qui sont manifestement le résultat d’une opération, ou qui sont un sousensemble naturel de mes données : les dernières cartes que j’ai entrées, toutes les cartes dans mon carnet de personnes travaillant dans la même entreprise que moi, les cartes de contacts prénommés Van…
Chapitre 2 – Comprendre REST à travers une première utilisation
Bien architecturer une application REST
Figure 2–1
11
Reprenons notre liste initiale. Je n’ai finalement que quatre types de ressources au lieu d’une dizaine : le carnet d’adresses, la carte, une collection de cartes, un groupe, et j’ai les idées plus claires sur leurs relations possibles.
Bien architecturer une application REST
Voyons maintenant comment identifier ces différentes ressources en leur associant une adresse, une URL. RAPPEL Pourquoi des URL ? Mais d’ailleurs… pourquoi utiliser quelque chose d’aussi long et compliqué qu’une URL pour identifier une fiche de carnet d’adresses ? Pourquoi pas « card :41a12 » ? La question est légitime. Il y a plusieurs raisons au choix d’URL. Tout d’abord une URL est universelle : on identifie quelque chose, mais dans le même temps, on indique où la trouver, via quel protocole, quelle machine, quel chemin. Deuxièmement, une URL a un caractère permanent. Par opposition, la valeur d’un pointeur dans le programme du serveur peut parfaitement convenir pour désigner quelque chose, mais n’aura plus de sens une fois l’exécution terminée. Quant à un identifiant dans une base de données, certes plus stable, il reste lié à un choix d’implémentation. Troisièmement, une URL est une simple chaîne de caractères, facile à transmettre. Enfin, une URL représente facilement une étape dans le parcours de mon carnet. Dans certaines réponses de mon service de carnet, on pourra trouver d’autres URL pour continuer l’exploration.
Voici les URL qu’une application client pourra utiliser pour accéder aux ressources de mon carnet : B http://carnet-rest.com/addressbook/olivier
Chapitre 2 – Comprendre REST à travers une première utilisation
Quelles URL pour donner l’accès à mes ressources ?
12
Bien architecturer une application REST
B http://carnet-rest.com/cards/recent
Cette ressource me donne accès à une collection, celle des dernières fiches rajoutées. Assez naturellement, cette liste donnera des liens vers chaque fiche, par exemple… B http://carnet-rest.com/cards/ada-veen
… une carte de visite en particulier. On peut s’attendre à y retrouver des liens vers les groupes de cartes que j’ai pu définir, comme… B http://carnet-rest.com/groups/friends
Là encore, c’est une collection de cartes de visite, avec des liens vers chaque carte. Mais une carte de visite peut aussi très bien se retrouver dans une autre collection, dynamique celle-ci, comme toutes les cartes comprenant le texte « bar»… B http://carnet-rest.com/addressbook /search?q=bar
Chapitre 2 – Comprendre REST à travers une première utilisation
D’abord, nous avons besoin d’un point d’entrée, par exemple ci-dessus une URL pour me représenter, moi, l’utilisateur du système. On aura envie d’y trouver des liens vers mon carnet complet, la liste des groupes de fiches, ou encore les dernières fiches entrées, ce que l’on trouvera sous l’URL suivante…
13
On retrouve le schéma guère organisé de la page précédente, mais exprimé ici sous forme d’une liste d’URL :
Rien de très étonnant pour quelqu’un qui aurait l’habitude de créer des sites web. Mais quelques propriétés intéressantes vont bientôt apparaître. Notons aussi qu’en soi, un style d’architecture REST n’impose aucunement une structure particulière sur la forme de l’URL elle-même : l’URL est quelque chose d’opaque que le client ne doit jamais chercher à « comprendre », forger ou manipuler. La navigation dans l’application ne doit se faire que via des liens entre ressources.
Chapitre 2 – Comprendre REST à travers une première utilisation
Bien architecturer une application REST
Figure 2–2
14
La question de l’identité est une question souvent bien embarrassante et dans laquelle on peut vite se noyer. Quand je définis l’identité d’une carte dans mon carnet comme étant « van-veen », je fais un choix fort. Pourquoi utiliser cela, plutôt que « Van%20Veen» ? Ou bien « 12 » (l’index dans ma base de données sous-jacente ? Ou bien « e00b0237627997b1c81c0510d68a187e » (un hash de la donnée elle-même) ? Il n’y a pas de réponse absolue, et le choix est souvent dicté par le pragmatisme et le compromis entre les contraintes suivantes : • ne pas trop exhiber de mécanique interne du serveur, • garder de la lisibilité dans l’URL, • avoir des identités stables pour les ressources, • éviter l’ambiguïté… Or, souvent, ces contraintes sont contradictoires… Au concepteur donc de faire le moins pire des choix !
Pourquoi insister sur ce côté « opaque » des URL ? C’est pour éviter qu’un client soit obligé de deviner ou connaître a priori comment construire une URL, alors qu’il devrait se contenter de naviguer de proche en proche via des URL renvoyées par le service : la structuration et la construction des URL sont de la responsabilité du serveur, non du client. Cela n’empêche, il n’est jamais mauvais, pour des raisons humaines, qu’une URL reste lisible, et qu’elle ne change jamais – un principe d’utilisabilité fondamental (on se réfèrera par exemple aux articles de J. Nielsen et T. Berners-Lee cités dans la bibliographie).
Représentations d’une ressource Jusqu’à maintenant, nous avions imaginé une fiche comme un concept finalement très abstrait. Mais comment peut-il se représenter concrètement ?
Chapitre 2 – Comprendre REST à travers une première utilisation
Bien architecturer une application REST
MÉTHODOLOGIE
15
Dans la figure ci-après, on trouve différentes représentations possibles pour cette ressource. Mais quel est le « meilleur » format pour cette fiche ? La question ne se pose pas vraiment : c’est au serveur de décider d’une implémentation possible (un enregistrement dans une base de données, un fichier texte de référence, etc.), et c’est cette implémentation serveur qui se chargera de renvoyer – si elle le peut – la représentation demandée par le client. Cela peut être le cas parce celui-ci aura demandé une URL explicitant le format (« http:// carnet-rest.com/cards/ada-veen.vcard ») ou bien parce qu’il aura indiqué dans des en-têtes HTTP la (ou les) représentation(s) qu’il aimerait recevoir (« Accept : text/plain, application/ xml, text/x-vcard » ). Nous verrons la mécanique de tout cela plus en détail au chapitre suivant. Notons au passage qu’il n’est nullement nécessaire d’avoir exactement les mêmes formats acceptés par mon implémentation pour toutes les URL. Si on peut imaginer tous les formats ci-dessous pour une carte de visite unique, dans le cas d’URL référençant par exemple l’ensemble de mon carnet d’adresses, il est possible que cette ressource ait pour seules représentations un format XML basique et un PDF complet du carnet, prêt pour impression. On voit aussi dans l’exemple que je n’ai finalement pas fait de distinction entre la représentation HTML et les autres types de représentations. La version HTML n’est qu’une représentation parmi d’autres ; il se trouve que c’est celle à laquelle on est le plus habitué en tant qu’utilisateurs quotidiens du Web. La force du style REST est bien de garder ces principes familiers pour d’autres usages.
Chapitre 2 – Comprendre REST à travers une première utilisation
Bien architecturer une application REST
En fait, l’avantage qu’il y a à raisonner en termes de ressources est qu’on garde une certaine liberté dans le mode de représentation : une carte a autant de sens qu’elle soit sous la forme d’un document PDF, d’une page HTML ou d’une VCard – voire d’un format XML ad hoc qui sera utilisé par mon client web services final pour naviguer dans mes données.
16
0%3 %%4 #%$% 50) %%/ % 0 6% 0 *
% %
Bien architecturer une application REST
!" #$% #& "&& '( # )*+,-'&.
Figure 2–3
(% %$%$ % % %
7 ( * $ ( * $ %
7 ! ! " * (%
! !!!"""# # $ %# & '% "(% # & ( %) * #%$%%% '% +'% + '% + % ,$ * ( '% %$'% %$ '% - '% - '% + '% ./012 % ,$ * ( %* %* $ * ( "(% # & '% ./012 (
Chapitre 2 – Comprendre REST à travers une première utilisation
!
17
Nous avons vu il y a un instant comment représenter, structurer et traduire des ressources : la fiche d’Ada Veen peut s’exprimer sous forme HTML, VCard, XML, etc. Reste maintenant l’étape tout aussi importante de la manipulation de ces ressources : quelles « actions » peut-on vouloir exprimer sur une ressource donnée ?
Bien architecturer une application REST
Le nombre d’actions à invoquer sur une ressource peut être assez limité en fait : modifier cette ressource, la détruire, ou en ajouter une nouvelle. Pour un service REST HTTP – ce qui nous intéresse ici – on peut parfaitement se limiter aux quatre « verbes » de base de HTTP pour agir sur les « noms » que sont mes fiches, mes groupes, etc. : • GET : permet d’accéder à une représentation de notre fiche ; • POST : permet de créer une nouvelle fiche ; • PUT : permet de mettre à jour une fiche ; • DELETE : permet de supprimer une fiche. C’est là un principe essentiel de REST : normaliser les actions possibles sur les ressources. En effet, quel besoin aurait-on de définir des points d’accès spécialisés – getCard,updateGroup, addNewCard comme pourrait instinctivement les imaginer un développeur ayant une culture d’implémenteur ? Il nous suffit d’utiliser les verbes HTTP tout à fait standard : envoyer une requête GET sur une fiche pour en retrouver une représentation, faire un POST dans une collection pour créer un nouveau groupe, envoyer un DELETE sur une ressource pour la retirer !
Chapitre 2 – Comprendre REST à travers une première utilisation
Manipulation des ressources
18
À RETENIR Normalisation des actions possibles
Cela a néanmoins une conséquence : la notion de ressource dans mon architecture est primordiale et il faudra veiller à ce que les ressources soient le moins équivoques possible : toutes les actions qui seront formulées par la suite vont les désigner. On voit là encore que les ressources sont les clés de mon architecture. Prenons à titre d’illustration quelques exemples de requêtes et leurs réponses.
Accéder à une ressource Peu de surprise ici, parce qu’il s’agit des exemples de requêtes que nous avons utilisés jusqu’ici.
Accès à une carte du carnet Demande du client Une requête HTTP
GET /cards/ada-veen
Réponse du serveur Un code de retour
200 (la ressource a été trouvée)
Chapitre 2 – Comprendre REST à travers une première utilisation
Bien architecturer une application REST
Nous découvrons donc là un nouveau principe fondamental de REST : l’« API » (Application Programming Interface) d’une ressource sera toujours la même, quelle que soit cette ressource. Les actions possibles sont uniformes et s’appliquent directement aux ressources.
19
Une représentation de la ressource, par exemple en XML :
Bien architecturer une application REST
Veen Ada
[email protected]
On notera que la réponse de notre serveur REST nous donne un lien intéressant dans la réponse, une URL du groupe auquel appartient cette fiche, ce qui nous permettra d’accéder à d’autres informations si nécessaire
Accès à un groupe de fiches Demande du client Une requête HTTP
GET /groups/friends
Réponse du serveur Un code de retour
200 (la ressource a été trouvée)
Corps de la réponse
Une représentation de la ressource, ici encore en XML : My Friends Quilty Clare
Chapitre 2 – Comprendre REST à travers une première utilisation
Corps de la réponse
20
Autre comportement intéressant de notre service de carnet d’adresses : il donne des informations de base dans la réponse à une collection, mais également des liens vers les ressources, ce qui peut permettre d’avoir l’intégralité d’une fiche via d’autres requêtes.
Créer et modifier une ressource Créer une nouvelle carte du carnet Créer une nouvelle carte de visite, c’est en substance la rajouter à une collection, et notre service de carnet nous permet tout simplement d’émettre une requête POST vers l’URL représentant une collection de cartes de visite. Demande du client Une requête HTTP
POST /cards
Corps de la requête
Une représentation de la ressource dans un format acceptable par le serveur, par exemple en XML comme précédemment :
Chapitre 2 – Comprendre REST à travers une première utilisation
Bien architecturer une application REST
Veen Ada
21
Veen Van
Un code de retour
201 (la ressource a correctement été créée)
Contenu de la réponse
Le serveur nous renvoie l’URL de la nouvelle ressource sous forme d’un en-tête. Nous pourrons donc l’utiliser par la suite pour y accéder ou la mettre à jour. Location : http://arch-rest.fr/cards/van-veen
Il est également possible que le serveur renvoie dans le corps de la réponse une représentation de la ressource nouvellement créée. Cela permet au client de valider la façon dont le serveur a compris les données, et de connaître des informations supplémentaires que le serveur a peut-être rajoutées au moment de la création de la ressource : 2009-02-26 23 :00 :51 Veen Van
Remarquons au passage que notre serveur REST nous renvoie également un lien vers la ressource nouvellement créée dans la réponse.
Chapitre 2 – Comprendre REST à travers une première utilisation
Bien architecturer une application REST
Réponse du serveur
22
Modifier une fiche
Bien architecturer une application REST
C’est assez simple, il me suffit de renvoyer une représentation de la carte modifiée. Par contre, comme je connais maintenant son URL précisément, il n’est plus nécessaire de demander à créer une nouvelle ressource dans la collection des cartes comme précédemment, il suffit d’envoyer directement la fiche modifiée sur l’URL, à l’aide d’une requête PUT. Demande du client Une requête HTTP
PUT /cards/van-veen
Corps de la requête
Une représentation de la ressource dans le même format que précédemment : Veen Van
[email protected]
Réponse du serveur Un code de retour
200 (la ressource a correctement été mise à jour)
Contenu de la réponse
Là aussi, il est également possible (mais non obligatoire) que le serveur renvoie dans le corps de la réponse une représentation de la ressource qui vient d’être mise à jour.
Chapitre 2 – Comprendre REST à travers une première utilisation
Ma nouvelle carte a été créée dans mon carnet, et j’en connais maintenant l’URL. Comment la modifier, si je veux y rajouter des informations ou en corriger d’autres ?
23
Bien architecturer une application REST
Détruire une ressource Enlever un groupe Le lecteur aura probablement deviné la technique à utiliser : si je veux faire disparaître une ressource, il me suffira d’en connaître l’URL et d’envoyer la requête idoine. Par exemple, si je veux arrêter de me servir d’un groupe de fiches donné dans mon carnet : Demande du client Une requête HTTP
DELETE /groups/school
Réponse du serveur Un code de retour
204 (la requête a été exécutée correctement, et il n’y a pas d’autre information retournée)
Chapitre 2 – Comprendre REST à travers une première utilisation
2009-02-26 23 :00 :51 2009-02-27 13 :10 :41 Veen Van
[email protected]
24
Et si tout se passe mal ?
Comme nous nous appuyons sur HTTP, nous pouvons tirer parti de la multitude de code de retours prévus par le protocole : 404, 415, 500... En voici quelques exemples.
Bien architecturer une application REST
Enlever une carte… inexistante ! Demande du client Une requête HTTP
DELETE /cards/roger-rabbit
Réponse du serveur Un code de retour
404 (la ressource n’a pas été trouvée : exactement le même code d’erreur que lorsque la requête a demandé une page qui n’existe pas sur un site)
Envoyer des données… incompréhensibles ! Imaginons que les données envoyées par le client soient incorrectes, parce qu’elles présentent des erreurs de syntaxe dans leur format, ou bien que, suite à une erreur de programmation, un morceau de texte sans rapport soit transmis : Demande du client Une requête HTTP
PUT /cards/van-veen.txt
Chapitre 2 – Comprendre REST à travers une première utilisation
Ne nous laissons pas abuser par la simplicité apparente de ces requêtes : il peut y avoir de multiples problèmes lors de l’utilisation réelle de notre carnet d’adresses. Comment faire pour traiter les cas d’erreurs ?
25
Corps de la requête
"kingdoms fell and dictatordoms rose, and republics half-sat, half-lay in various attitudes of discomfort"
Bien architecturer une application REST
Un code de retour
415 (le serveur ne sait comment interpréter pareil format)
Se heurter à une limitation du serveur Parfois, la faute est de l’autre côté : si le serveur implémentant mon carnet REST n’a plus de place pour stocker une nouvelle fiche, ou s’il est juste mal programmé… Demande du client Une requête HTTP
GET /cards/van-veen
Réponse du serveur Un code de retour
500 (erreur du serveur)
ERGONOMIE Préciser l’erreur ? Dans ces exemples de situation d’erreurs, le serveur fait le strict minimum, mais rien n’empêche de renvoyer des détails additionnels dans le corps de la réponse, par exemple un message plus explicite à l’attention de l’utilisateur final… Nous en verrons un exemple au chapitre suivant.
Chapitre 2 – Comprendre REST à travers une première utilisation
Réponse du serveur
26
Notre courte exploration de ce service de carnet d’adresses, aussi simpliste soit-il, nous aura bien servi. On peut retenir les principes suivants : • l’ensemble des informations du carnet sont représentables par des ressources ; • à chacune de ces ressources on associe une URL ; • à une ressource donnée peuvent correspondre de multiples représentations ; • l’utilisation de l’application consiste simplement à suivre des liens entre ressources (nous verrons d’ailleurs bientôt que c’est cette navigation qui va représenter l’état de l’application, un autre principe fondamental de REST… mais n’anticipons pas !) ; • l’utilisation de quelques actions simples sur toutes les ressources permet facilement et élégamment de fournir une interface uniforme à toute mon application pour modifier l’information. Avec cet exemple « RESTful » en tête, nous allons à présent pouvoir explorer en profondeur quelques points conceptuels et techniques.
Chapitre 2 – Comprendre REST à travers une première utilisation
Bien architecturer une application REST
En résumé…
27
Retour sur REST : Modèle et principes
Après avoir déroulé un premier exemple d’application « RESTful », nous avons vu qu’un des points marquants en était l’intégration naturelle entre les styles d’architecture REST et les bonnes pratiques du Web au sens large. Voyons maintenant comment reformuler de façon un peu plus générale ces principes et comment définir quelques bonnes pratiques.
3
Après cette exploration d’un premier exemple, revenons sur les caractéristiques fondamentales des architectures REST, telles qu’exprimées par Roy Fielding, et codifiées par la pratique, des ressources au protocole.
Roy Fielding est l’un des auteurs du standard HTTP – et l’un des pionniers des architectures web. En 2000, dans sa thèse de doctorat « Architectural Styles and the Design of Network-based Software Architectures » à l’Université de Californie, il a inventé le terme Representational State Transfer (REST). C’est lui qui a le premier formalisé cette approche des architectures web. Son travail montre comment, en analysant différents styles d’architectures possibles, on en vient naturellement à REST si l’on essaie d’exploiter au mieux les principes en germe dès le départ dans le Web.
Des ressources… Sur l’exemple minimaliste du carnet d’adresses, j’ai pu abstraire la fonctionnalité et l’état de mon application dans un jeu de ressources. Ces ressources partagent toutes des traits bien particuliers qu’il est important de rappeler : • l’adressabilité, • le partage d’une interface uniforme, • un maillage des ressources par les hyperliens.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
UN PEU D’HISTOIRE Roy Fielding
29
L’adressabilité Toutes les ressources utilisées dans l’exemple du carnet d’adresses étaient identifiées par une URL. C’est un point fondamental, parce qu’utiliser des URL ouvre toute une gamme de possibilités.
NORMALISATION URL et HTTP Imaginons un instant un navigateur qui ne comprendrait pas les URL – il ne permettrait pas d’ouvrir une page web. Cela obligerait à spécifier qu’on veut utiliser HTTP pour ouvrir une page, puis à préciser quel serveur contacter, avant d’indiquer à ce serveur quelle page on souhaite ouvrir… De quoi rappeler l’ère des réseaux propriétaires d’antan avec leurs interfaces graphiques bien spécifiques.
Manipuler des adresses URL est intéressant pour une autre raison qui peut sembler évidente : une URL n’est qu’une chaîne de caractères. Rien de plus simple que de l’inclure dans un document, de l’envoyer par mail, de l’écrire sur un morceau de papier, de l’envoyer par messagerie instantanée, etc. Une URL est bien une syntaxe universelle pour référencer une ressource, et permet de normaliser totalement l’écriture de liens hypertextes. Et surtout, dans le cas qui nous intéresse ici, la représentation d’une ressource (comme le contenu de l’ensemble mon carnet d’adresses) peut tout naturellement renvoyer une liste d’URL permettant de continuer l’exploration. C’est grâce aux URL que l’interconnexion de ressources dans REST est possible car en renvoyant des URL, on minimise ce qu’un client a besoin de savoir pour interroger un service. À l’inverse, renvoyer des identifiants ou des références d’objets, comme c’est le cas de services basés sur des modèles RPC (Remote Procedure Call), impliquerait de connaître à l’avance comment continuer l’exploration.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
En premier lieu, une URL permet de connaître le protocole à utiliser. Une fois le protocole connu, le reste de l’URL permet d’accéder pour de bon au contenu, si besoin est.
30
C’est donc bien par les URL, qui permettent d’identifier la représentation d’une ressource que je vais pouvoir agir sur la ressource.
Des actions génériques et bien connues L’adressabilité règle le premier problème : être universel quand il s’agit de savoir à quelle ressource on s’adresse. Il en reste un second : le choix des opérations qu’on peut appliquer sur une URL. Le parti pris des architectures REST est de considérer qu’un ensemble bien connu (et plutôt minimal) d’opérations peut être appliqué sur une ressource.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Figure 3–1
31
C’est somme toute assez logique : pourquoi l’action d’accéder à une fiche et celle d’accéder à un rendez-vous dans mon calendrier devraient-elles utiliser des opérations différentes ? Bien qu’il s’agisse de deux ressources très différentes, les requêtes nécessaires pour y accéder n’ont aucune raison d’être distinctes, pas plus que celles pour les mettre à jour – voire les détruire. Figure 3–2
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
La justification est assez similaire : si deux services différents (mon carnet d’adresses, mon calendrier) définissent chacun leur propre jeu d’opérations, mon client aura besoin d’une connaissance précise, et spécifique de chacun des services. En restreignant le nombre d’opérations possibles, on permet à un client de ne pas être juste le client d’un service unique, mais d’être complètement adaptable.
32
Une API REST ne devrait donc jamais avoir à définir de nouvelles opérations. Il est parfois nécessaire de contourner les zones d’ombres d’un protocole (et on le verra dans le cas de HTTP un peu plus loin), mais l’interface vers une ressource doit rester générique. À RETENIR
Des représentations tout aussi génériques et bien connues Quand nous avons construit le service de carnet d’adresses, nous avons décrit quelques représentations possibles d’une ressource « fiche » : en HTML, en PDF, en JSON, en XML, en RDF... C’est assez caractéristique des types de représentations – ou « ContentType », pour reprendre le vocable HTTP – évoqués au chapitre précédent que l’on peut croiser dans une application REST. Pourquoi donc ? Remarquons tout d’abord que la grande majorité de ces représentations permettent naturellement d’exprimer en hypertexte des liens entre ressources, et ce n’est pas un hasard : l’édifice reposant sur ces liens entre ressources, il est logique d’avoir dès le départ un format permettant d’exprimer cette connectivité. Nul besoin d’un service d’annuaire, ou d’un moyen extérieur pour trouver les ressources. Deuxième remarque : ce sont des représentations exprimées dans des formats standard. Pourquoi cela ? Rien n’empêche un service d’envoyer ses données dans une représentation complètement spécifique et propre à l’application, mais ce faisant, cela contraindrait le client à comprendre ce format de données. En faisant le choix de représentations bien connues et en nombre restreint, on maximise les possibilités d’avoir des clients génériques, capables de travailler avec de multiples services.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Finalement, de quoi ai-je vraiment besoin pour travailler sur une ressource ? D’accéder à une représentation, de la supprimer, d’en créer une nouvelle, voire de la modifier. C’est à peu près tout ce qui est nécessaire pour construire une architecture générique.
33
Figure 3–3
Pour finir, un détail important : la nature d’une représentation, c’est-à-dire le format utilisé, le type, est toujours une information explicite. Le client demande une représentation, le service la lui fournit, mais l’information de type est toujours véhiculée en tant que métadonnée, ce qui enlève toute ambiguïté. On verra plus tard que le choix d’une représentation peut être négocié entre client et serveur.
Dans une architecture de type REST, la combinaison d’un jeu d’opérations et d’un ensemble de représentations bien connus reposant sur des formats standardisés est une garantie d’interopérabilité entre client et serveur. Cela n’exclut pas des mécanismes d’une
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
34
extrême souplesse, par exemple le renvoi à la demande, par certaines ressources, de code Javascript s’exécutant sur le client. Pour schématiser REST, on peut voir ce style d’architecture comme un trio entre des ressources, des actions et des représentations. Les actions et les représentations se doivent d’être contraintes pour permettre l’interopérabilité, tandis que le concepteur a toute latitude pour définir son espace de ressources.
Le triangle REST : des ressources librement définies, mais des actions et représentations codifiées
1 Si dans une application on s’aperçoit qu’il est nécessaire de créer de nouveaux types de
représentations (ou de nouvelles ressources, ou – ce qui plus rare – de nouvelles actions), l’impact est nul sur les autres pointes de notre triangle. 2 Une interconnexion des données 3 En tant que concepteur d’un service, il peut être facile de négliger ce qu’apportent les liens hypertextes entre les représentations. La seule URL à connaître a priori est celle
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Figure 3–4
35
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
qui va donner un point d’accès initial au service, les autres devant pouvoir être découvertes dans les représentations des ressources renvoyées par le serveur. 4 Pourquoi est-ce important ? Prenons la situation inverse. Je pourrais construire mon service sans cette interconnexion entre mes données, définir mes ressources, y associer des représentations, des URL associées, et ne pas exprimer de liens entre elles. Mais ce faisant, je fais une hypothèse très forte : que le client aura précisément la connaissance de mon espace d’information, de la structure de mes URL. Et qu’il devra finalement forger les URL sur lesquelles il voudra travailler. On mettrait singulièrement à mal le principe d’opacité d’une URL que l’on a vu au chapitre 2. 5 En partant à l’opposé d’un principe de liens explicites entre mes ressources, je permets au client REST d’explorer l’univers des ressources que je lui propose. On pourrait être effrayé par la trop grande souplesse d’un tel mécanisme, mais si l’on se place dans le contexte d’un service web exploité par des programmes (et non une navigation web classique), rien ne nous empêche d’associer des métadonnées à ces différents liens, pour indiquer explicitement la nature de l’URL proposée. 6 Par exemple, dans le protocole et la syntaxe Atom, on peut indiquer explicitement la nature d’un lien, et le type de contenu qui y sera trouvé :
36
Un style d’architecture sans état
L’état de mon client web, c’est savoir là où j’en suis de ma navigation : je suis passé par un certain nombre de pages, je suis sur la page courante, j’irai suivre un lien de cette page pour continuer. Mon client maintient un historique de ce que j’ai consulté, je peux donc revenir en arrière, mettre un signet sur une page précédente, etc. Mon navigateur web maintient l’état de ma navigation. Par contre, et c’est le point très intéressant, le site que je consulte ne connaît pas ce même état : pour lui, des requêtes ont été émises, et il a répondu en transmettant des pages. Le serveur n’a pas de connaissance de l’état de mon navigateur web, de ce parcours de navigation, et n’a pas besoin de le savoir. Il est possible que certaines de mes actions changent une ressource sur le serveur (la création d’un nouveau commentaire, par exemple), mais cela est uniquement l’affaire du site. Si le serveur doit maintenir son propre état (par exemple, la page de commentaires est modifiée pour tenir compte de l’ajout), c’est de façon indépendante. Les anglophones utilisent parfois un acronyme particulièrement barbare pour décrire cela : HATEOS (Hypermedia As The Engine Of State) : l’hypermedia comme vrai moteur de l’état (du client).
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Qu’est-ce que l’état dans une architecture REST ? Prenons l’exemple de la navigation sur un site web. Quand je navigue sur Internet, j’effectue certaines actions : j’interroge un moteur de recherche, je cherche de la documentation, je suis un lien pour aller sur un blog qui parle du problème qui m’intéresse, je passe sur la page des commentaires, je crée un compte, je m’authentifie pour ajouter un commentaire, puis je continue sur une autre page, etc.
37
!
!
"# $
!
!
$
! ! %
"#
#
Figure 3–5 Evolution indépendante de l’état d’un client et de ressources côté serveur au fil des requêtes
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
38
Les conséquences sont multiples et très intéressantes : comme le serveur n’a aucune connaissance de l’état du client, cela implique que de son point de vue, toutes les requêtes sont indépendantes. Chaque requête doit contenir suffisamment d’informations pour pouvoir être traitée indépendamment de toutes les autres. Ou, exprimé un peu autrement, le serveur n’a pas à conserver le fait qu’il a parlé à un certain client une fois la requête traitée. Et le client n’a pas à se préoccuper du serveur auquel il s’est adressé : il a émis une requête, il a reçu une réponse, le dialogue est terminé. Tout serveur doit être capable de traiter toute requête sur une URL en provenance de n’importe quel client : nulle obligation qu’il s’agisse du même serveur auquel on s’est adressé l’instant d’avant… Dès lors, il est très facile d’utiliser plusieurs serveurs physiques pour faire du partage de charge sur un service, puisqu’il n’y a aucune notion de « session » à garder entre un client et un serveur donné. On pourrait se demander alors comment j’ai pu accéder à une ressource authentifiée, puisque je suis obligé de m’identifier. La réponse est assez logique : chaque requête devant contenir toute l’information pour y répondre, l’information d’authentification doit être fournie avec chaque demande.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
REST est un style d’architecture sans état, c’est à dire que l’on part du principe que le serveur n’a jamais à connaître l’état du client, et réciproquement. Le client maintient l’état de l’application de son point de vue, le serveur maintient l’état de ses ressources, mais cet état n’est jamais partagé. Tout changement d’état a lieu parce que des représentations sont transférées entre client et serveur, et que ces transferts vont changer quelque chose chez le client (« j’avance dans ma navigation ») ou dans le serveur (« une ressource a été modifiée »), mais jamais en commun. On a enfin là l’explication de ce sigle barbare : REST, Representational State Transfer.
39
Sites REST et sites non REST L’approche peut sembler assez naturelle, mais si l’on y prête attention, on notera que ce n’est pas toujours le cas sur le Web : on croise parfois des sites où la navigation dans l’historique ne fonctionne pas quand je reviens directement à une page précédente, ou bien des messages indiquant que ma « session est expirée ». Ce sont de bons indices que le service auquel je m’adressais n’était pas conçu selon une approche REST.
La solution à un problème consiste souvent en sa reformulation. Prenons l’exemple d’un panier d’achats. Le premier réflexe serait de stocker le panier comme un état du client particulier du côté du serveur, ce qui met à mal nos principes. En fait, on peut parfaitement considérer qu’un panier d’achats va être une ressource comme un autre, et avoir le scénario suivant : 1 Le client demande la création d’une nouvelle ressource panier ; 2 Le serveur crée cette ressource et y associe une URL (par exemple http:// example.org/panier/13489cx24) 3 Le client ajoute successivement, via des modifications de cette ressource, les produits qu’il veut inclure dans son panier 4 Le serveur prend en compte ces modifications en changeant l’état de la ressource. 5 Quand le client veut signaler que le panier est complet, il envoie une dernière modification à la ressource en y ajoutant une information comme quoi le panier est complet 6 Le serveur prend en compte cette dernière modification et redirige alors le client vers une nouvelle ressource, qui permettra par exemple d’exprimer le paiement du panier.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Que faire si l’on a vraiment besoin d’état ?
40
Diverses solutions existent néanmoins, de la définition d’une nouvelle action non standard à la création d’une ressource spécifique pour gérer le cas (comme plus haut dans notre exemple), l’utilisation raisonnée d’un cookie (qui peut d’ailleurs ne pas être lié à un état sur le serveur).
Un protocole de choix : HTTP Dans la section précédente, nous avons passé en revue les principes des modèles REST. Intentionnellement, je n’ai que très peu mentionné le protocole grâce auquel on va mettre en musique ce modèle : REST en tant que style d’architecture reste découplé du protocole concret que l’on va choisir. Ainsi on pourrait parfaitement imaginer d’écrire une application REST à l’aide d’un protocole de transfert autre que HTTP, mais les caractéristiques de ce dernier en font un substrat très naturel pour un modèle REST, et pour cause : REST a été modélisé à la suite des premières expériences acquises avec HTTP 1.0. Voyons maintenant comment se servir au mieux de HTTP pour architecturer une application REST.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
On touche ici aux limites de l’élégance des architectures REST : modéliser une suite d’opérations de façon transactionnelle n’est jamais trivial si l’on veut garder la contrainte d’être sans connaissance de l’état du client. Le panier d’achat en est un bon exemple, mais on peut en trouver bien d’autres : un « wizard » qui enrichit progressivement une configuration, la gestion d’une ressource partagée avec time-out entre plusieurs clients, une opération devant s’appliquer simultanément sur plusieurs ressources, etc.
41
Petit rappel sur HTTP Avant tout, voici un bref rappel sur les requêtes HTTP, que nous allons observer directement dans les pages à venir :
Bien architecturer une application REST
Un exemple de requête HTTP
!""#!$"%& ' () * + $ !"! (!%!# !"##$%# # $ #%# #
HTTP est un protocole client/serveur sans état : chaque interaction est indépendante des autres, et a toujours la même structure : une requête du client au serveur, une réponse du serveur au client. Le client se connecte au serveur (habituellement sur le port 80), envoie
Chapitre 3 – Retour sur REST : Modèle et principes
Figure 3–6
42
sa requête et attend la réponse. C’est bien un protocole synchrone, mais uniquement au niveau d’une seule interaction. C’est aussi un protocole non connecté : nul besoin d’établir d’une manière particulière la connexion avant d’envoyer une requête.
Une requête HTTP a toujours la forme suivante : • Une ligne initiale : on y trouve tout d’abord la méthode HTTP, puis le chemin auquel on veut accéder, suivi de la version du protocole HTTP utilisé (1.1 pour ce qui nous concerne). Le chemin n’est rien d’autre que la portion de l’URL qui suit la désignation du protocole et de la machine à contacter. • Suivent ensuite zéro ou plusieurs en-têtes. Fondamentaux, ils vont permettre au client d’exprimer des métadonnées sur la requête : identification des types de contenu préférés, contrôle des mécanismes de cache, etc. • Si la requête contient un body– c’est à dire des données à transmettre – typiquement pour les requêtes PUT ou POST, on trouvera une ligne vide suivi du contenu.
Structure d’une réponse La réponse du serveur a une structure très proche. On y trouvera : • Une ligne initiale : la version du protocole HTTP est indiquée, suivie immédiatement du code de retour. Fondamental, il nous permettra de savoir si la requête a été couronnée de succès ou non, et comment. En cas d’erreur, ce même code indiquera la cause de l’erreur d’une façon standardisée. Selon le code de retour, on saura que certains entêtes de la réponse seront présents et importants à consulter. • De la même façon, suivent ensuite zéro à plusieurs en-têtes. Tout aussi fondamentaux, ils donnent une liberté au serveur pour transmettre des métadonnées arbitraires
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Structure d’une requête
43
sur le traitement de la requête. Par exemple, on y trouvera le type de contenu renvoyé, l’URL à consulter ensuite si on a été redirigé, etc. • Si la réponse contient un corps (body) c’est-à-dire des données à transmettre – typiquement pour les requêtes PUT ou POST, on trouvera une ligne vide suivie du contenu.
Dans une utilisation REST de HTTP, on considère qu’il y a deux propriétés importantes pour décrire les méthodes HTTP, et que l’on croise souvent dans le monde REST : la sûreté et l’idempotence.
Méthodes sûres Une méthode sûre ne doit jamais modifier l’état de la ressource. C’est typiquement le cas de la méthode GET (ou aussi de la méthode HEAD). POST, DELETE et PUT ne le sont pas parce qu’elles modifient (ou détruisent) la ressource. Pour reprendre le vocabulaire que l’on a abordé plus haut, des méthodes non sûres modifient l’état de la ressource.
Méthodes idempotentes Le terme peut paraître un peu barbare. Il signifie simplement qu’une opération idempotente peut être répétée un nombre quelconque de fois, l’ensemble des ressources restant toujours dans le même état après l’opération. DÉFINITION Idempotence Le résultat d’une opération idempotente dans un contexte donné et avec des paramètres donnés est toujours identique.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Utilisation des méthodes HTTP : sûreté et idempotence
44
C’est le cas de la méthode GET, mais aussi des méthodes PUT et DELETE. Par contre, POST n’est pas forcément idempotente car cette méthode va créer une nouvelle ressource.
C’est la même chose avec la méthode DELETE : si je détruis une ressource avec cette méthode, toute autre requête exactement similaire aura le même effet : la ressource n’existe plus. Le serveur peut certes renvoyer un code d’erreur quand on émet un DELETE sur une ressource disparue, mais le résultat est stable : la ressource n’est plus présente. Méthode HTTP
Propriétés
GET, HEAD
Sûre, idempotente
POST
Non sûre, non idempotente
PUT
Non sûre, idempotente
DELETE
Non sûre, idempotente
Effet des méthodes HTTP Examinons maintenant l’effet d’une méthode HTTP sur les ressources d’un service, et commençons à aborder comment cela se traduit en terme de codes de retour ou d’en-têtes HTTP Le lecteur pourra se référer en annexe au petit mémento des codes de retour HTTP les plus classiques.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Le cas de PUT et DELETE peut paraître un peu surprenant, puisque la ressource est mise à jour. En effet, PUT modifie une ressource donnée en fonction du contenu de cette ressource : la réémission d’une méthode PUT avec le même contenu aboutit toujours au même nouvel état pour la ressource. Prenons l’exemple de la mise à jour d’un numéro de téléphone sur une fiche. Si l’opération de mise à jour est réémise, quel que soit le nombre de fois, la fiche garde le même état qu’après la première modification.
45
Méthode GET Figure 3–7
La méthode GET laisse inchangé l’état du serveur.
C’est le cas le plus simple : une méthode GET (ou HEAD) n’a aucun impact sur le monde des ressources du serveur. Si l’on résume, en REST, une requête GET ne doit jamais avoir d’effets de bord.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
46
La seule nuance possible est qu’une fois la première requête faite, des optimisations sont possibles (et même recommandées !) pour éviter d’aller rechercher l’intégralité du contenu les fois suivantes – nous le verrons un peu plus tard lorsque nous aborderons les caches et les GET conditionnels.
W3C On se réfèrera à la note du W3C « URIs, Addressability, and the use of HTTP GET and POST » pour plus de détails. B http://www.w3.org/2001/tag/doc/whenToUseGet.html
Méthode POST La méthode POST est aussi connue que le GET, car utilisée depuis très longtemps dans les navigateurs web pour envoyer des données quelconques vers un serveur. Dans le cadre de REST, son utilisation est un peu plus codifiée : on la réserve à la création d’une nouvelle ressource, c’est à dire que le client va envoyer un contenu dans une requête, et cette requête aura comme conséquence la création d’une nouvelle ressource, associée à une nouvelle URL. En réponse à la requête du client, le serveur renverra dans un en-tête HTTP Location l’URL correspondant à la nouvelle ressource (et éventuellement la représentation dans le corps de la réponse).
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Le fait que GET doive être sûr et idempotent n’est d’ailleurs en rien spécifique aux architectures REST : si le concepteur d’un site web quel qu’il soit ne prend pas garde à cette règle, il peut s’attendre à des surprises quand les moteurs de recherche et autres robots viendront explorer son site.
47
Figure 3–8 La méthode POST et son effet sur l’état du serveur.
Une façon simple de voir POST est d’imaginer une URL de « collection » (liste de mes fiches) qui va agir un peu comme un pattern de « Factory » pour créer à la demande une nouvelle ressource du même type. L’explication est un peu simpliste car le serveur reste toujours maître et a tout pouvoir pour décider s’il veut ou non créer cette nouvelle res-
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
48
source, sa nature, où la placer dans son espace de ressources et sous quelle URL. L’important est de noter que le client ne peut pas imposer l’URL de la future ressource. On voit notamment que la méthode n’est pas idempotente : si j’envoie plusieurs fois la même requête POST, cela a toute les chances de créer autant de nouvelles ressources.
Méthode PUT La méthode PUT modifie l’état du serveur, de façon idempotente.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Figure 3–9
49
Une précision s’impose : PUT n’est pas uniquement destinée à modifier une ressource existante. On peut également l’utiliser pour créer une nouvelle ressource. Quelle est alors la différence avec la méthode POST ? Dans le cas d’un POST, on envoie un POST à une ressource qui va créer la nouvelle ressource (et donc créer une nouvelle URL). Dans le cas d’un PUT, on envoie directement la requête sur l’URL de la ressource à modifier. Mais si la ressource n’existe pas encore, il est tout à fait licite de comprendre PUT comme la volonté du client de créer une nouvelle ressource sous cette URL précise. Cette subtilité se reflète dans les codes de retour HTTP possibles : • si une nouvelle ressource est créée, le serveur d’origine répondra par un code 201 « Created » ; • si c’est une modification, le serveur renverra plutôt un code 200 « OK ».
Méthode DELETE L’impact d’un DELETE est net sur le service : la ressource n’existe plus. Notons encore une fois que cela ne dit rien sur l’implémentation concrète dans le serveur. Dans certains services un DELETE pourrait effacer un fichier, dans d’autres supprimer un enregistrement dans la base de données, ou poser un simple marqueur indiquant que la ressource n’est plus accessible, etc. Le seul point important est que la ressource ne doit plus être accessible par un client. On retrouve au passage une notion évoquée plus haut : une ressource doit être non ambiguë. Lors d’un DELETE, le serveur n’a que l’URL de la ressource, sans aucun contexte pour savoir quelle ressource doit disparaître.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
La caractéristique d’idempotence apparaît bien ici : la nouvelle représentation est transmise lors de la requête, et on a modifié l’état de la requête.
50
Figure 3–10
La méthode DELETE modifie aussi l’état du serveur, toujours de façon idempotente...
Le code de retour HTTP est : • le plus souvent 204, si la réponse au DELETE n’a aucun contenu ; • parfois 200, si le service renvoie un contenu quelconque décrivant la bonne exécution de la requête.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
51
Une architecture en couches
Le trajet d’une requête n’est pas forcément une connexion directe d’un client à un serveur, mais peut tirer parti d’intermédiaires sur le chemin de la requête. De même, client et serveur peuvent se mettre d’accord, toujours via des en-têtes spécifiques, pour converser de la manière la plus efficace possible.
Figure 3–11 HTTP 1.1 autorise une architecture en couches avec des intermédiaires entre client et serveur.
De la même façon, il est possible de fractionner le traitement d’une requête. Par exemple, un serveur frontal peut s’occuper uniquement de l’authentification d’une demande, avant de passer le relais au serveur suivant qui supposera que ce travail aura été effectué.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Par conception, HTTP offre une souplesse notable dans la conception d’une infrastructure web. Dès la seconde mouture du protocole (1.1), des mécanismes sont fournis dans le protocole – via des en-têtes standardisés – pour mettre en œuvre des architectures complexes.
52
L’important est que ces intermédiaires n’imposent aucun changement du protocole ni de la désignation des ressources. L’architecture mettant en œuvre un service REST doit pouvoir croître de façon organique sans avoir d’impact sur la mise en œuvre finale du client ou du serveur.
Une montée en charge naturelle La capacité à monter en charge (scalability) repose sur plusieurs facteurs : • des intermédiaires qui nous permettront de répartir le trafic entre plusieurs serveurs. Les requêtes pourront être « routées » de façon transparente selon leur action HTTP, leur type de contenu, leur URL, ou toute autre métadonnée ; • le fait pour le protocole d’être « sans état » du point de vue d’une requête/réponse, qui donne une latitude extrême au concepteur de l’architecture pour la faire évoluer. Dans certains cas, les caches ou les accès conditionnels aux ressources que nous allons voir plus bas permettront de limiter drastiquement le nombre de requêtes qui traverseront notre architecture.
Une négociation possible entre client et service La négociation d’un type de contenu pour une ressource permet d’être le plus efficace possible et le plus proche des besoins d’un client :
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Un des avantages revendiqués par les partisans du style REST est qu’une application conçue selon ces principes exploitera de façon plus efficace les ressources réseau. L’ensemble de ces techniques permet à la fois de réduire la latence perçue par l’application cliente ou l’utilisateur final, et de minimiser la bande passante consommée.
53
• en validant un formulaire dès le navigateur web parce que l’on aura chargé une portion de code Javascript correspondant à la ressource ; • en ne renvoyant que le strict minimum pour mettre à jour les informations d’une application cliente ; • en se pliant aux contraintes d’une application sur téléphones mobiles ; • etc.
Chapitre 3 – Retour sur REST : Modèle et principes
Bien architecturer une application REST
Voyons maintenant plus en détail le fonctionnement de ces techniques.
54
Bonnes pratiques d’implémentation REST
Pour bien comprendre comment implémenter un service REST, il nous faut replonger au niveau HTTP pour comprendre les interactions entre client et service.
4
Mise en cache et maîtrise de l’accès aux ressources Dès qu’un service commence à atteindre une taille raisonnable, le problème de la performance (au sens large) a tendance à se poser.
Notre architecture étant orientée ressource, on peut assez facilement tirer parti de pratiques courantes dans les architectures web. En particulier, la meilleure façon d’améliorer la performance est… de ne rien faire. Ou plutôt, au moment où l’on demande l’accès à une ressource, de tirer parti des caches susceptibles d’être déjà en possession de la ressource.
Figure 4–1 L’intermédiaire qui possède un cache épargne le serveur d’origine.
À quoi bon rappeler ce qui peut sembler une lapalissade direz-vous ? Rappelons quand même que c’est bien l’idempotence de notre méthode GET, vue aux chapitres précédents, qui fonde le recours au cache : invoquée sur une ressource ou sur ses multiples représentations, une série de GET n’a aucune raison de changer le contenu transmis au(x) client(s).
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
La question des performances d’un service web est évidemment une boîte de Pandore, et le propos n’est pas ici d’entrer dans le détail des optimisations possibles dans une base de données ou dans un framework donné.
56
Un tel cache peut exister à de multiples niveaux dans l’architecture : • dans l’implémentation du service – on dispose maintenant de très bonnes briques de base pour implémenter des caches efficaces, par exemple l’excellent memcached ; • dans le client lui-même – c’est depuis longtemps le cas des navigateurs web. On peut à la fois jouer sur les caches (se passer complètement de la requête), ou jouer sur la requête elle-même : dans ce dernier cas, on essaie de tirer parti de possibilités fournies par HTTP pour ne pas transférer le contenu s’il n’a pas changé ;
Figure 4–2 La mise en cache peut se faire à tous les niveaux.
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
C’est l’évidence même. Si la génération de cette ressource est un tant soit peu dynamique et prend du temps – parce qu’on interroge une base, parce qu’on a des mises en forme lourdes, etc. – la première chose à faire est de garder le résultat calculé dans un cache pour l’obtenir immédiatement à la prochaine requête.
57
• entre le client et le serveur : comme le dialogue entre le client et le serveur se fait en HTTP, il est possible d’insérer sur le chemin un « proxy cache » HTTP qui servira de tampon entre les deux acteurs, de façon invisible(si tout va bien), avec des outils comme Squid ou Varnish.
Accès conditionnel aux ressources Mettre en cache des données est avant tout un problème d’accès en lecture à une ressource : il ne serait pas raisonnable de se poser la question pour des accès en écriture (mise à jour ou destruction d’une ressource). On verra néanmoins un peu plus tard qu’il peut y avoir un lien subtil entre caches et modifications. Si l’on reprend notre schéma maintenant habituel, un client va chercher à accéder à une donnée, via GET. Le dialogue classique sera : Requête du client GET /cards/van-veen HTTP/1.1 Host : carnet-rest.com Accept : application/xml
Réponse du serveur HTTP/1.1 200 OK Date : Sun, 01 Feb 2009 17 :30 :14 GMT Content-Type : application/xml …. (Le contenu suit)
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
Nous n’allons pas nous préoccuper du premier point, qui n’est qu’une question de choix d’implémentation du service. Le troisième cas est finalement une version un peu particulière du second. Voyons donc en détail comment un client peut tirer parti d’informations assez simples fournies par les requêtes passées pour éviter de transférer de nouveau des données.
58
Rien de bien étonnant ici. Si le serveur implémentant le service veut donner des indications au client sur ce qu’il devrait stocker en cache, il pourra envoyer en plus le header ETag (pour Entity Tag) qui donnera une information au client sur la version de la ressource, et/ou un header Last-Modified qui indiquera la dernière date de modification de cette ressource. On trouvera donc en plus de la réponse précédente les lignes suivantes :
Et l’on retrouve ici encore un de nos bons principes. Nul besoin de stocker des informations côté serveur pour savoir quel client a accédé à la ressource, ou si ce client a déjà cette information : on ne garde aucun état. HTTP Qu’est-ce qu’un ETag et comment le calculer ? Autant l’en-tête Last-Modified peut être clair, autant l’en-tête ETag peut sembler vraiment plus obscur. Un ETag est un en-tête prévu dans le protocole HTTP qui permet d’identifier le contenu présent à une URL donnée. Le fonctionnement est assez simple : le format d’un ETag est complètement libre, la seule contrainte est qu’un ETag soit unique pour une URL donnée et ne change pas si le contenu ne change pas. Si l’ETag change pour une même URL, c’est que le contenu a changé ! Cela permet donc aux clients d’optimiser l’accès à une ressource. Il existe différentes techniques pour calculer un ETag : on peut utiliser une checksum sur le contenu, ou bien se baser sur une combinaison de plusieurs paramètres (par exemple, si la ressource est un fichier sur le serveur, le forger à partir de la date de dernière modification, la taille du fichier, la valeur de l’inode sur le système de fichier, etc.). Il n’y a pas de « bonne » technique absolue, tout dépend de la nature du contenu : statique ou dynamique, de petite taille ou non, etc.
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
ETag : "6f4180-4bc-472bc358" Last-Modified : Sat, 03 Nov 2007 00 :39 :52
59
Si, plus tard, le client veut accéder à la même ressource, il pourra transmettre ces informations de date de modification ou d’ETag de l’entité, et le serveur pourra décider en toute connaissance de cause si il a besoin de renvoyer la ressource ou simplement s’arrêter là. Requête du client (en utilisant l’ETag)
Réponse du serveur HTTP/1.1 304 Not Modified Date : Sun, 01 Feb 2009 17 :36 :12 GMT ETag : "6f4180-4bc-472bc358"
Bingo ! 1 Le client demande l’accès à la ressource, sachant que si l’ETag de la ressource vaut 6f4180-4bc-472bc358, il la connaît déjà. 2 Après vérification de l’ETag, c’est bien toujours la version à jour de la ressource qu’il a, nul besoin de transmettre le contenu. 3 Le serveur, au lieu de l’habituel 200, renvoie alors le code HTTP 304 « Not Modified ». 4 Le client sait alors qu’il peut reprendre sa version précédente de façon sûre. Si le client utilise l’information de Last-Modified, le fonctionnement est très similaire, il fournira sa dernière date connue de modification dans un header If-Modified-Since.
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
GET /cards/van-veen HTTP/1.1 Host : carnet-rest.com Accept : application/xml If-None-Match : "6f4180-4bc-472bc358"
60
Requête du client (en utilisant le Last-Modified) GET /cards/van-veen HTTP/1.1 Host : carnet-rest.com Accept : application/xml If-Modified-Since : "Sat, 03 Nov 2007 00 :39 :52 GMT"
HTTP/1.1 304 Not Modified Date : Sun, 01 Feb 2009 17 :36 :12 GMT Last-Modified : "Sat, 03 Nov 2007 00 :39 :52 GMT"
Même réponse ici : 304 « Not Modified », et pas de transmission du contenu. Figure 4–3
En-têtes HTTP permettant d’éviter l’accès inutile à une ressource déjà disponible en cache
L’accès conditionnel est un mécanisme très puissant et robuste, et dont le bon fonctionnement est lié à une coopération entre le client et le serveur. On notera que si le client n’implémente pas ou ne garde pas trace systématiquement des Etaget Last-Modified, tout se passera bien : une requête « classique » aura lieu et le contenu de la ressource sera renvoyé. De
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
Réponse du serveur
61
la même manière un serveur ne fournissant pas d’ETag et de Last-Modified fonctionnera sans problème avec un client plus puissant, au prix de requêtes moins efficaces.
Malgré tout, l’utilisation du Last-Modified et de l’Etag peuvent parfois causer des problèmes subtils. C’est assez facile à imaginer dans le cas du Last-Modified, car on manipule des dates, qui peuvent introduire des complications variées : que se passe-t-il si la ressource change à une fréquence de quelques millisecondes, et que le Last-Modified n’a qu’une résolution à la seconde près ? L’ETag est exempt de ces difficultés, mais peut poser d’autres problèmes plus conceptuels. La valeur d’un ETag est avant tout un identifiant complètement opaque qui permet de désigner la « version » d’une ressource. Le concepteur du service a toute liberté pour le choisir parmi quelques techniques classiques. On peut utiliser pour un ETag : • la valeur d’un champ « version » de la ressource dans la base de donnée sous-jacente ; • un hash de la représentation de la ressource qui est retournée au client. Par exemple on prendra notre XML généré, on en calcule un MD5, qui va fournir une valeur hexadécimale assez courte à partir de l’ensemble des octets du contenu, et on le transmet comme valeur du header ETag ; • l’identifiant – au sens système de fichiers – d’une version « fichier » de la ressource, ou sa date de dernière modification (une technique que l’on trouve par exemple implémentée dans le serveur web Apache) ; etc. Attention, il faut donc garder en tête que l’ETag doit toujours être vu comme une information complètement opaque pour le client. Même si je décide d’utiliser une notion de « version » pour ma ressource, le client ne doit absolument pas s’en servir pour déduire qu’un document est plus ou moins vieux qu’un autre en comparant directement les
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
Last-Modified et ETag, quels problèmes potentiels ?
62
ETags, au risque de ne plus fonctionner correctement si le serveur décide d’utiliser un hash ou une autre technique, ce dont il a parfaitement le droit.
Last-Modified, ETags et modèle de données
Il en va différemment si ma ressource représente une collection d’informations, par exemple « l’ensemble de toutes les fiches du groupe Personnel ». Les choses deviennent alors un peu plus floues. Quelle est la date de modification d’une telle collection ? On pourrait choisir la date de la fiche la plus récente, mais dans ce cas, ce serait oublier le fait que je peux très bien avoir effacé une fiche de mon groupe. Il faudrait donc garder la date de la dernière action sur ma collection, ce que je n’ai peut-être pas prévu dans mon implémentation sous-jacente. Un ETag peut être plus adapté à ce cas, mais peut causer ses propres problèmes. Par exemple, si je décide de calculer l’ETag comme un hash de la représentation de ma ressource, cela m’oblige à extraire toutes les informations, générer la représentation pour en obtenir le hash et comparer à celui que m’a transmis le client, tout cela pour finalement ne rien envoyer ! Je vais économiser le temps du transfert par le client, mais si l’étape d’accès aux données et de génération est coûteuse dans mon implémentation, la technique est problématique. Pour éviter cela, une technique possible est de tirer parti des ETags à un niveau plus profond de mon architecture : si je connais la valeur des ETags (ou des versions) pour chacune des fiches qui constituent ma collection de fiches, je peux m’en servir pour calculer
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
Une question un peu subtile se pose parfois dans l’utilisation des ETag ou Last-Modified. Si ma ressource est une ressource « simple », comme par exemple une fiche de carnet d’adresses, sa notion de version ou de ETags est assez naturelle : la date de dernière modification de la fiche, le hash de sa représentation.
63
un ETag global. Imaginons un groupe contenant trois fiches, et que mon implémentation sous-jacente en connaisse les ID et versions dans la base de données : ID=12, Version : 5 • Olivier Gutknecht, • Ada Veen, ID=42, Version : 8 • Van Veen, ID=2, Version : 3 Je peux forger à partir de cela un ETag pour ma collection qui serait : Mon problème est quasiment réglé, sauf que si mon groupe contient un millier de fiches, le header ETag va devenir un rien pachydermique. Mais je peux m’en sortir à revenant à la technique du hash, que je ne vais plus appliquer à mon contenu final généré, mais simplement à ma liste précédente : ETag : 07140e63604c0c173c4e754bf2ed33d5
Je pourrais même imaginer d’avoir dans mon implémentation serveur un stockage rapide et très efficace qui associe ressource et ETag/Last-Modified pour répondre rapidement aux accès GET. Bref, il existe de multiples façons de résoudre ce problème, et l’utilisation « d’ETags profonds » comme l’exprime Joe Gregorio permet de traiter le problème de l’identité d’une ressource de façon aussi élégante qu’efficace. Référence B http://bitworking.org/news/150/REST-Tip-Deep-etags-give-you-more-benefits
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
ETag : 12/5,42/8,2/3
64
Configuration de la mise en cache : Cache-Control, Expires…
La politique la plus courante concernant les caches consiste à ne pas vouloir mettre en cache. C’est le rôle du header Cache-Control. Certaines ressources ne sont en effet jamais cachables, parce qu’elles dépendent de l’instant présent, parce qu’elles sont totalement dynamiques ou imprévisibles. Cache-Control : no-cache
Un cas un peu plus subtil consiste à demander que seul le client final puisse mettre en cache les ressources – et qu’aucun proxy ni passerelle ne le fasse sur le chemin. Cache-Control : private
Plus finement, on veut souvent donner au client, ou aux caches qui peuvent agir sur le chemin une indication sur le temps maximal de mise en cache d’une ressource, et donc ne provoquer aucune requête (conditionnelle ou non) pour y accéder. Deux moyens existent : • là encore l’utilisation du Cache-control pour donner la durée que le serveur estime pertinente pour garder en cache une information : Cache-Control : max-age=86400
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
Nous avons vu en détail comment une interaction directe entre client et serveur peut s’appuyer sur un accès conditionnel (conditional GET) pour éviter des transferts ou calculs trop coûteux. Néanmoins, ce n’était qu’une partie de l’histoire : en HTTP, les GET sont par définition cachables, et il peut y avoir de multiples acteurs sur le chemin (proxies, gateways, etc.), avec chacun sa propre politique de gestion des cache. Néanmoins, quelques headers HTTP nous permettent de donner quelques indications aux différentes couches traversées.
65
• ou une date explicite d’expiration pour cette ressource, avec le header Expires
:
Expires : Sun, 01 Nov 2009 06 :30 :00 GMT
De plus, en l’absence de tout header explicite, le client final a toujours la possibilité de décider de sa propre politique de gestion du cache. EN PRATIQUE Exemple de problème de cache HTTP Un exemple de problème non trivial que l’on rencontre parfois lorsqu’on utilise un cache HTTP entre un client et un serveur : le problème de la mise en cache d’une représentation. Ma ressource /cards/ olg – grâce à la négociation d’une représentation particulière avec le header Accept – peut être transmise sous forme d’un contenu HTML, d’un fichier VCard, d’une représentation XML ou d’un fragment JSON pour une application web Ajax. Si mon cache intermédiaire ne prête pas attention au type de contenu échangé entre le client et le serveur, il peut très bien mettre en cache le contenu la première requête faite : par exemple, une représentation JSON correspondant à un appel Ajax. Si, lors de la seconde requête sur cette même ressource, un contenu HTML est cette fois-ci demandé, un cache pas suffisamment malin ne se réfèrera qu’à l’URL demandée, et renverra le contenu du cache, et donc un contenu JSON, et non pas le HTML demandé, à la grande surprise du client ! Il convient donc de vérifier soigneusement les fonctionnalités de l’ensemble des logiciels sur le chemin d’une requête.
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
La gestion des caches est en réalité beaucoup plus complexe que cela. En particulier, même si le cache est censé ne s’appliquer qu’aux requêtes en GET ou HEAD (et uniquement dans le cas d’une réussite de la requête), un PUT, POST, ou DELETE sur la même ressource devraient immédiatement marquer le contenu du cache comme étant périmé.
66
RÉFÉRENCE On pourra se référer à un ouvrage plus général sur le problème de la mise en cache et la performance web, tel l’excellent High Performance Web Sites de Steve Souders. R High Performance Web Sites – Essential Knowledge for Front-End Engineers, Steve Souders, O’Reilly, sept. 2007
Comme on vient de le voir, utiliser la négociation de contenu peut causer certains problèmes subtils. On pourrait donc se demander s’il faut recourir à la négociation de type de contenu ou plutôt exhiber systématiquement les différentes représentations d’une ressource. C’est-à-dire qu’au lieu de me reposer uniquement sur les en-têtes comme Accept-Language pour l’URL suivante : • http://carnet-rest.com/cards/olivier-gutknecht
Accept
et
Je peux définir pour chaque représentation une URL explicite : • http://carnet-rest.com/cards/olivier-gutknecht.xml • http://carnet-rest.com/cards/olivier-gutknecht.json • http://carnet-rest.com/cards/olivier-gutknecht.html • http://carnet-rest.com/cards/olivier-gutknecht.vcard Il n’y a pas de réponse absolue à ce problème, mais notons que la seconde solution présente des avantages certains : • La façon d’accéder à un type de contenu est explicite dans l’URL et ne dépend pas d’en-têtes HTTP. Il est donc facile de transmettre une URL référençant une représentation sans ambiguïté.
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
Faut-il utiliser la négociation de type de contenu ?
67
• Utiliser une URL explicite n’est pas incompatible avec le fait d’être explicite sur le type de contenu à attendre, il suffit de l’indiquer comme métadonnées dans la représentation. Pensons à l’exemple des link dans Atom que nous avions aperçu au chapitre précédent. • Des traitements spécifiques (authentification, caches…) peuvent être plus facilement appliqués par type de contenu par des intermédiaires sur le chemin de la requête.
Comment émuler PUT et DELETE ? Les architectures REST font un usage assez général des différents verbes HTTP : HEAD, GET, POST, PUT, DELETE – voire d’autres parfois, à travers des raffinements du protocole HTTP tels que WebDAV, ou des propositions en cours d’étude comme PATCH. Néanmoins, la réalité des clients HTTP est parfois moins idéale, et s’il est facile d’utiliser n’importe laquelle de ces actions HTTP quand on dispose d’une bibliothèque ou d’un framework bas niveau pour forger les requêtes d’un client, ce n’est pas systématiquement le cas. En particulier, l’usage standard de HTML a conduit les navigateurs web classiques à ne donner accès qu’à GET et POST… Notez que des problèmes peuvent survenir avec PUT ou DELETE, même si le client et le serveur les comprennent parfaitement : il suffit qu’une passerelle ou un cache intermédiaires n’implémentent pas complètement les actions HTTP et perturbent le cheminement des requêtes.
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
Rien n’empêche en outre, même si l’on utilise des URL explicites, d’indiquer quelle URL est considérée comme l’URL canonique, par exemple via un en-tête Content-Location.
68
Une technique maintenant courante et admise face à de telles contraintes est de ruser en envoyant le contenu de la requête via un POST, mais en précisant dans une métadonnée additionnelle quelle était la « vraie » action HTTP voulue.
À la réception, le serveur verra une requête POST vers /cards/olivier-gutknecht, mais avec la valeur put dans le champ caché _method. Il saura alors que l’intention était bien un PUT, donc une modification de la ressource. On peut aussi imaginer d’envoyer un GET plutôt qu’un POST, mais en transmettant la méthode HTTP réelle sous la forme d’un paramètre de requête, par exemple : /cards/olivier-gutknecht ?_method=PUT
IMPLÉMENTATION Frameworks Ruby on Rails et Java Restlet Cette technique est utilisée par plusieurs frameworks mettant en œuvre des techniques REST, comme Ruby on Rails et Java Restlet.
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
Une solution possible, quand on est dans le cadre d’un navigateur web, est d’utiliser un champ caché dans un formulaire web.
69
Autre façon de résoudre le problème dans un cadre plus large que celui d’un formulaire web : transmettre la véritable action HTTP sous forme d’un en-tête supplémentaire : XHTTP-Method-Override :
BONNE PRATIQUE Déguiser des PUT et DELETE en POST, plutôt que GET On préféra donc souvent déguiser les PUT ou DELETE derrière un POST plutôt qu’un GET pour éviter tout problème de cache ou pour se préserver de robots web qui suivraient des liens et s’amuseraient à déclencher des DELETE en rafale… En effet, il faut nous en tenir aux propriétés de base de GET – sûre, cachable et idempotente – et se garder de trop en redéfinir le sens.
Chapitre 4 – Bonnes pratiques d’implémentation REST
Bien architecturer une application REST
POST /cards/van-veen HTTP/1.1 Host : carnet-rest.com X-HTTP-Method-Override : PUT .....
70
Une courte étude d’une API existante de Google
Nous avons vu jusqu’ici comment s’architecture une API REST sur des exemples simples. Prenons maintenant un exemple réel et examinons comment une « vraie » API met en œuvre ces techniques.
5
Le carnet d’adresses de Google est utilisé par plusieurs de leurs applications en ligne : Google Mail, Google Calendar, etc. Il se trouve que Google fournit depuis plusieurs années un ensemble d’API, toutes basées sur le même modèle, pour accéder à ses données personnelles. Ils ont baptisé ce modèle GData (ou Google Data), et on y retrouve des principes que nous avons maintenant croisés plusieurs fois : • une architecture REST : la base de l’API est un jeu d’URL liées les unes aux autres ; • une représentation standardisée et bien connue, ATOM ; • des extensions intégrées à ATOM quand il faut représenter des types de données étrangères à ce modèle ; • des actions simples, basées sur les verbes HTTP standard et les URL représentant les données, pour accéder en lecture et modification. Le choix fait par Google a été de ne pas chercher à inventer un nouveau format ou un nouveau modèle, mais de tirer parti de ce que connaissaient le plus les développeurs web. Et dans ce domaine, les « flux » ATOM ou RSS, popularisés par les blogs, sont maintenant universels, et apparaissent même dans les produits grand public, visibles directement par l’utilisateur. Pourquoi utiliser un modèle de données de blog pour une application de contacts (et pour leurs autres applications) ? N’est-il pas inefficace de réutiliser quelque chose qui ne semblait pas prévu pour cet usage précis ? Le raisonnement suivi par Google est qu’une liste de contacts, des évènements dans un calendrier, des photos dans un albums, une liste de documents, tout ceci peut finalement se représenter comme des listes de « quelque chose ». De plus, il est souvent possible d’imaginer
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
Pour rester dans le même domaine, nous allons jeter un œil à l’API de gestion de contacts proposée par Google.
72
Initialement, ATOM cherchait à définir un format pour représenter une liste de billets dans un blog, mais quand on lit plus en détail la spécification, on se rend compte rapidement que le modèle est bien plus générique. Un « flux » est une succession « d’entrées » qui vont chacune avoir un titre, un identifiant, une date de création ou de publication en ligne. Rien de plus facile donc que de partir de ce principe pour avoir un « flux » de « groupes », et des « flux » de « cartes de visite ». Le titre de l’entrée pouvant être le nom du groupe, ou le nom de famille sur la carte. L’utilisation d’ATOM dans ce contexte n’est donc pas si baroque. On commence à se douter de la façon dont nous allons récupérer nos groupes et nos fiches dans l’API Google : envoyer un GET sur l’URL des contacts, récupérer l’URL d’un contact, connaître l’URL d’un groupe, faire un GET sur celui-ci pour obtenir des détails, etc. Reste à savoir comment va se faire la modification ou la destruction des fiches : de la même manière ! Si l’on connaît l’URL d’un groupe, assez logiquement, un DELETE sur cette ressource la détruira et un PUT la modifiera. Là encore, Google s’est appuyé sur un modèle préexistant. Tout comme ATOM standardisait un format pour représenter des billets de blogs, une spécification associée, AtomPub, décrit une façon standard de poster un nouveau billet sur un blog ou de l’éditer, et le tout sans avoir à implémenter une multitude d’API propriétaires pour tel ou tel logiciel de blogs. Google Data ne fait que réutiliser AtomPub, en l’enrichissant par endroits là où c’est nécessaire (authentification, recherche, etc.). Le lecteur nous pardonnera cette introduction un rien longuette. Mettons maintenant les mains dans le cambouis de Google Contacts.
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
une imbrication, une hiérarchie de ces différentes listes. Par exemple, une liste d’albums, chaque album étant une liste de photos, une liste de groupes, une liste de contacts.
73
REMARQUE PRÉLIMINAIRE
Obtenir la liste des contacts Comme il se doit dans une API REST, nous avons d’abord besoin d’un point d’entrée, une URL connue au préalable. Dans Google Contacts, on peut en trouver deux : • L’URL représentant un flux de contacts : • http://www.google.com/m8/feeds/contacts/olivierg%40gmail.com/full • L’URL représentant un flux de groupes : http://www.google.com/m8/feeds/groups/olivierg%40gmail.com/full
Deux choses intéressantes sont à noter sur ces URL. Une URL est bien sûr normalement à considérer comme opaque : il ne faut pas faire d’hypothèse sur la façon dont elle est construite… sauf si le fournisseur du service nous en dit un peu plus, ce qui est le cas de Google Contacts. Dans la référence du protocole, on lit que ces URL sont à construire de la façon suivante : • http://www.google.com/m8/feeds/contacts/userID/projection • http://www.google.com/m8/feeds/groups/userID/projection La section userID permet de savoir pour quel utilisateur on veut obtenir les contacts ou les groupes. Bien sûr, comme ces informations sont très personnelles, il faudra s’authentifier
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
Nous allons examiner en détail le protocole défini par Google Contacts. Bien sûr, dans un cas réel, ce protocole serait caché par l’utilisation d’une bibliothèque du langage de programmation choisi pour permettre au développeur de travailler à un niveau d’abstraction plus familier, objets ou fonctions. Google fournit d’ailleurs des bibliothèques de base pour ses API en .NET, Java, Javascript ou Python ; et des développeurs indépendants proposent leurs propres couches d’adaptation pour Objective-C, Ruby, C, etc.
74
L’autre section intéressante est cette notion de projection. À quoi cela correspond-il ? Une fiche de carnet d’adresse peut avoir des informations très complètes (appartenance de la fiche à d’autres groupes, relation à d’autres fiches, propriétés spécifiques à telle ou telle application). Selon l’application que je code en utilisant Google Contacts, je peux ou non avoir besoin de toutes ces informations. La projection indique simplement si je veux recevoir l’intégralité des informations connues par Google Contacts, ou si un sousensemble simplifié me suffit : selon les cas, j’utiliserai full ou thin comme valeur de projection pour construire l’URL de mon flux. Observons maintenant les échanges entre un client REST et le serveur Google Contacts. Les exemples de ce chapitre comportent quelques simplifications destinées à les rendre plus concis : j’ai en effet très légèrement changé les exemples d’interactions HTTP, pour enlever quelques lignes sans importance réelle (des identifiants de versions d’outils, des propriétés non-standard et non utilisées, etc.) pour pouvoir garder des exemples suffisamment concis pour un livre, fût-il électronique.
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
comme étant cet utilisateur pour accéder à ces flux – nous y reviendrons plus loin. De plus, étant authentifié, la référence GData m’indique que l’on peut remplacer ce userID par default pour indiquer implicitement l’utilisateur – puisqu’il est identifié. Cet utilisateur est le compte Google, dans mon cas
[email protected]. Comme j’exprime ceci dans une URL, où le caractère « @ » n’est pas autorisé, il faut l’encoder de la façon idoine, d’où le « %40 » un rien barbare.
75
Requête du client
Bien architecturer une application REST
Réponse du serveur (en-têtes) HTTP/1.1 200 OK Content-Type: application/atom+xml; charset=UTF-8; type=feed Expires: Sun, 10 May 2009 20:12:09 GMT Cache-Control: private, max-age=0, must-revalidate, no-transform ETag: W/"DkQERn89fSp7ImA9WxJSGUQ." Last-Modified: Sun, 10 May 2009 20:12:09 GMT
Une première remarque sur ces en-têtes avant d’aborder la réponse en elle-même : dans ma requête est présent un en-tête Authorization, qui me permet de m’identifier à chaque requête grâce à un jeton ou un cookie. Nous ne décrirons pas ici le mécanisme d’authentification en lui-même, il est spécifique à Google (même si l’adoption de standards ouverts comme OAuth est en court). En deux mots, l’authentification consiste à transmettre à Google Contacts le login et mot de passe de l’utilisateur, d’indiquer le serveur auquel on veut accéder (les contacts), et Google nous renvoie en retour un token que nous utiliserons dans cet en-tête. On ne sera pas trop surpris de ce premier échange : • la nature de la représentation est renvoyée via l’en-tête Content-Type, • les en-têtes ETag, Last-Modified et Cache-Control permettent au client de savoir si il serait de bon ton de mettre en cache le résultats (et le serveur indique que non).
Chapitre 5 – Une courte étude d’une API existante de Google
GET /m8/feeds/contacts/default/full HTTP/1.1 Host: www.google.com GData-Version: 2.0 Accept: */* Authorization: GoogleLogin auth=xxxxxxx
76
Bref, que des éléments que nous avons croisé dans les chapitres précédents.
Bien architecturer une application REST
Le début de la réponse montre un document XML assez classique : après l’indicateur XML arrive le premier élément feed, qui indique le namespace principal du document (ATOM), ainsi que quelques autres qui vont être utilisés dans ce qui suit. L’un, openSearch, est un schéma utilisé assez largement – et par d’autres que Google – nous allons y revenir. Les autres, batch, gd, gcontact sont des extensions définies par Google. Plus intéressant est l’attribut gd:etag (une extension Google), qui reprend la valeur de l’ETag renvoyé en en-tête HTTP. Pourquoi cette redite ? Par exemple parce que certaines bibliothèques construites au-dessus de Google Contacts pourraient ne pas transmettre les headers HTTP à l’application, mais uniquement les données. Cette redite permet à l’application de disposer de ces informations importantes. D’entrée de jeu, on devine ce qui va se passer : une réponse renvoyée dans le format ATOM, c’est à dire un flux – feed – contenant des entrées – entry – mais qui insèrera dans le document des extensions quand il s’agira de préciser des informations sortant de la stricte application d’ATOM.
[email protected] Olivier Gutknecht's Contacts 2009-05-10T20:13:43.678Z
Chapitre 5 – Une courte étude d’une API existante de Google
Réponse du serveur (Corps de la réponse – commenté au fur et à mesure)
77
Bien architecturer une application REST
Voici les premiers éléments d’information sur ce flux de fiches. Rien d’étonnant dans le titre, ni dans la présence d’un identifiant (id – que le format ATOM impose). L’élément category est plus intéressant. Initialement, cet élément dans ATOM avait été conçu pour indiquer dans quelles rubriques classer un billet de blog. Ici, Google Contacts l’utilise pour préciser le type de ce qui va suivre : le type (kind) du flux est un flux de contacts (contact).
La suite était encore plus intéressante (même si on peut avoir un doute au premier regard). Google Contacts nous donne ici des informations fondamentales. ATOM donne la possibilité d’inclure des liens dans un flux ou une entrée, via l’élément link, en précisant la nature de ce lien (l’attribut rel). Certains types de liens sont définis par ATOM comme self, next ou alternate. D’autres types – non formalisés – peuvent être rajoutés, comme ici par Google Contacts.
Chapitre 5 – Une courte étude d’une API existante de Google
Olivier Gutknecht
[email protected]
78
Le second, self, indique l’URL de la ressource même que nous sommes en train de consulter : l’URL de référence pour cette ressource. Notons la présence d’un paramètre, max-results, qui était complètement implicite dans notre requête. Le troisième lien, next nous permettra d’obtenir des résultats complémentaires, car notre carnet est trop gros pour que Google Contacts nous le renvoie en une seule réponse, nous verrons cela dans un instant. Le lien suivant, dont le type finit en #post est spécifique à Google Contacts. Il nous donne une information très intéressante : l’URL à utiliser pour créer un nouveau contact, via un POST. Il se trouve qu’il s’agit de la même URL que celle sur laquelle nous avons envoyé GET, mais cette mention indique que nous sommes dans une mise en œuvre de REST très satisfaisante, puisque cette ressource nous indique explicitement, via un hyperlien, comment créer une nouvelle ressource. 324 1 25
Une dernière information dans le flux avant d’aborder les informations de contact. On se souviendra de l’extension openSearch que nous avons vue au début du document. La voici en application : elle nous indique combien de contacts au total sont disponibles, et quel intervalle du total représente la réponse. Dans le cas d’une liste de plusieurs centaines d’entrées comme ici, il n’est pas raisonnable de penser obtenir l’intégralité des informations en un seul passage. Ces informations,
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
Le premier, alternate, indique où l’on peut trouver la même information dans un type de représentation différent (ici en HTML). Google Contacts ne fait guère d’effort et nous conseille de retourner sur www.google.com !
79
couplées avec le lien next que l’on a vu plus haut, vont nous permettre de parcourir page par page, 25 par 25, l’ensemble de nos fiches.
Là encore, on est dans une mise en œuvre REST : la représentation de la ressource contient des hyperliens explicites pour continuer la navigation dans l’information, et nul besoin d’un état côté serveur pour aller de page en page, les URL distinctes suffisent. http://www.google.com/m8/feeds/contacts/olivierg%40gmail.com/base/1234 2009-05-04T17:04:39.858Z Ada Veen +33 1 23 45 67 89 1, Abraham Milton Road Ardis Hall Amerussia Federassion
Voici enfin les entrées correspondant à nos contacts. Ayant vu les éléments au niveau du flux, nous ne serons guère surpris par ceux que nous rencontrons au niveau de l’entrée. Les classiques id, category, updated sont présents ; title nous indique le nom du contact.
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
Voici pourquoi le lien de référence d’il y a quelques instants finissait en ?max-results=25. Et pour accéder à la page d’après, il me suffira de suivre le lien next (qui a logiquement comme paramètres ?start-index=26&max-results=25). Je connaîtrai alors les 25 entrées suivantes, avec un nouveau lien next, et ainsi de suite.
80
Et là encore, les éléments link nous apportent des informations fondamentales. Le premier link indique où trouver la ressource décrite par l’entrée elle-même (la fiche d’Ada Veen), sous l’URL : http://www.google.com/m8/feeds/contacts/olivierg%40gmail.com/full/1234
La fin de l’URL est intéressante : par rapport à celle que nous avons utilisée pour obtenir la liste des contacts, il y a une information en plus, le 1234 qui indique manifestement une identification de la ressource « Ada Veen ». C’est cette URL que nous pourrions interroger en GET pour avoir des informations uniquement sur ce contact. La seconde URL est également intéressante, c’est celle qui nous permet d’éditer la ressource décrite par cette entrée (le groupe Amis), par exemple pour en changer le nom, via une action PUT. Il s’agit de la même URL.
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
On voit maintenant à quoi servent ces extensions gd aperçues au début du document : rajouter des éléments XML spécifiques aux données d’un carnet d’adresses, en les insérant à l’intérieur d’une entrée ATOM tout à fait licite. Cette solution est intéressante : un client qui ne connaîtrait que ATOM pourrait avoir accès à l’essentiel (le nom du contact). Un client connaissant les extensions Google Contacts obtiendra toute l’information.
81
VERSION Goole Contacts v 2.0 Nos exemples se basent sur la version actuelle (2.0 à ce jour) du protocole Google Contacts. Dans la version précédente, l’URL d’édition était différente : Pourquoi ce nombre en plus à la fin de l’URL ? Dans la version 1 du protocole, la notion d’ETag n’était pas utilisée, donc un client émettant un PUT pour modifier un groupe aurait pu écraser un changement plus récent fait par un autre client. Les développeurs de Google Contacts avaient résolu ceci d’une manière astucieuse : l’URL d’édition était différente, et incluait la version du groupe (le fameux chiffre final). Lors d’une requête en PUT pour modifier un groupe, le serveur de Google pouvait donc détecter si le client se basait sur une version à jour pour envoyer sa modification, ou si la ressource avait déjà changé – auquel cas la modification était refusée. Bref, ce mécanisme jouait le rôle de « l’Etag du pauvre ». La version 2.0 de Google Contacts implémente maintenant les en-têtes ETag et If-Match., rendant ce mécanisme superflu.
Et pour finir cette fiche, une autre extension Google Contacts nous donne un lien vers la ressource d’un groupe. Un simple GET sur cette URL, et j’obtiendrai une nouvelle entrée ATOM dans la category « groups » et dont le titre m’indiquerait qu’il s’agit du groupe de mes amis dans mon carnet d’adresse… … 24 autres contacts non recopiés ici…
Oups, nous allions oublier la fin du flux… Nous épargnerons au lecteur les 24 contacts suivants.
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
B http://www.google.com/m8/feeds/groups/olivierg%40gmail.com/full/4321/092348123498
82
Mettre à jour un contact Ayant obtenu cette liste, comment changer l’adresse email d’Ada Veen ? La réponse précédente a indiqué exactement comment faire, à savoir utiliser le lien suivant :
Bien architecturer une application REST
Qu’envoyer au serveur pour mettre à jour le contact ? Bien sûr une entrée ATOM, construite exactement de la même façon que ce qu’il m’a transmis dans le flux, ou que j’aurais pu retrouver directement via un GET précédent. Dans cette entrée, il suffira de modifier l’information désirée (par exemple le numéro de téléphone), et de ne pas oublier d’indiquer l’ETag dans les en-têtes, pour que Google Contacts puisse vérifier que je n’écrase pas une version plus récente (comme nous l’avons évoqué dans l’encadré plus haut). Observons l’échange : Requête du client PUT /m8/feeds/contacts/olivierg%40gmail.com/full/1234 HTTP/1.1 Host: www.google.com GData-Version: 2.0 Content-Type: application/atom+xml Authorization: GoogleLogin auth=xxxxxxx If-Match: "SHY-cDVSLyp7ImA9WxJSFEgMQQM." Content-Length: 1523
Chapitre 5 – Une courte étude d’une API existante de Google
http://www.google.com/m8/feeds/contacts/olivierg%40gmail.com/full/1234
83
Réponse du serveur (en-têtes) HTTP/1.1 200 OK Content-Type: application/atom+xml; charset=UTF-8; type=entry GData-Version: 2.0 ETag: "Q309fDVSLyp7ImA9WxJSGUQCRw0." Date: Sun, 10 May 2009 23:38:12 GMT Expires: Sun, 10 May 2009 23:38:12 GMT Cache-Control: private, max-age=0 … Le serveur renvoie alors le contact dans le même format …
Et le contact est mis à jour ! Rien de particulier à noter dans cet échange, sinon que le serveur, en cas de succès renvoie de nouveau l’entrée. Cela peut sembler superflu, mais cela me permet de connaître, en tant que client, ce que le serveur a compris de ma réponse. Il
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
http://www.google.com/m8/feeds/contacts/olivierg%40gmail.com/base/1234 2009-05-04T17:04:39.858Z Ada Veen +33 9 87 65 43 32 1, Abraham Milton Road Ardis Hall Amerussia Federassion
84
peut par exemple avoir procédé à de légères adaptations de formats, etc. Il me transmet également un nouvel ETag pour cette nouvelle version de la ressource.
C’est encore plus simple : en bon protocole REST, Google Contacts demande une action HTTP DELETE sur la même URL que celle utilisée pour la mise à jour. Exactement comme pour une mise à jour, un en-tête If-Match doit obligatoirement être transmis avec l’Etag connu du client, pour être certain de ne pas effacer un contact qui aurait pu être mis à jour récemment. http://www.google.com/m8/feeds/contacts/olivierg%40gmail.com/full/1234
Voici l’échange : Requête du client DELETE /m8/feeds/contacts/olivierg%40gmail.com/full/1234 HTTP/1.1 Host: www.google.com GData-Version: 2.0 Authorization: GoogleLogin auth=xxxxxxx If-Match: "Q309fDVSLyp7ImA9WxJSGUQCRw0"
Réponse du serveur (en-têtes) HTTP/1.1 200 OK Content-Lenght: 0 GData-Version: 2.0 Date: Sun, 10 May 2009 23:45:12 GMT Expires: Sun, 10 May 2009 23:45:12 GMT Cache-Control: private, max-age=0
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
Détruire un contact
85
Comme on l’a découvert par ces quelques scénarios, Google Contacts est un bon exemple d’API REST, d’autant plus intéressant que ce protocole est une extension d’un protocole REST existant, AtomPub. Nous avons pris l’API de Contacts comme sujet puisque c’est un peu notre fil rouge dans cet ouvrage, mais toutes les autres API de Google, à de très rares exceptions près, sont conçues sur la même base : Picasa, Google Calendar, Blogger, Google Docs, YouTube… Un développeur maîtrisant l’une de ces API n’aura aucun souci à extrapoler. On ne saurait trop conseiller à un développeur envisageant de concevoir son propre protocole d’adopter la même technique que Google : réinventer la roue est coûteux, et la difficulté se niche dans la multitude de détails qu’il faut prendre en compte : versioning, batches, recherches, relations, etc. Autant de problèmes classiques que les concepteurs de protocoles comme AtomPub ou GData ont déjà exploré, parfois dans la douleur. Autant profiter de leur travail !
Chapitre 5 – Une courte étude d’une API existante de Google
Bien architecturer une application REST
En résumé
86
Pour conclure : comment rester REST ?
Essayons de dégager quelques points clés des chapitres précédents sous forme d’une courte check-list de pratiques à garder en tête lorsqu’on essaie de définir une architecture REST.
6
Chapitre 6 – Pour conclure : comment rester REST ?
Bien architecturer une application REST
• Utiliser GET à bon escient pour accéder à une ressource et non pour invoquer une action. Si je vois une URL du style http://example.com/cards?method=deleteCard&id=veen cela devrait me mettre la puce à l’oreille. Une URL, dans une architecture REST, est censée décrire une ressource (un nom), pas une action (un verbe). • Tirer parti des fonctionnalités fournies par HTTP, en particulier la mise en cache. Les en-têtes ETag, Cache-Control, Last-Modified sont des outils formidables… si on les utilise ! • Les codes de retour HTTP ont leur signification. S’appuyer sur l’existant facilitera la vie du concepteur (qui aura moins de questions à se poser) et celle des clients (qui pourront s’appuyer sur des pratiques codifiées). En particulier, une situation d’erreur ne devrait jamais revenir avec un code 20x… • Utiliser les cookies avec discernement. Utiliser un cookie ne veut pas forcément dire que l’on s’écarte de REST : si on utilise des cookies mais que le serveur n’a pas besoin de connaître l’état du client, tout va bien. Si on les utilise pour retrouver une session sur le serveur, c’est mauvais signe. • Nulle ressource n’est une île : ce sont les liens entre les ressources qui forment la trame du Web. Les utiliser dans un service web veut dire que le client n’a pas à connaître a priori (ou à forger) les URL pour obtenir d’autres ressouces. • Respecter les types de données. Les types Mime (Mime-Types) sont une excellente manière de « typer » les représentations et d’apporter de la flexibilité – y compris par la négociation entre client et serveur. C’est aussi ce qui permet de mêler au sein d’un même service des représentations qui seront destinées aux humains (HTML, images) et aux machines (XML, JSON…).
88
Chapitre 6 – Pour conclure : comment rester REST ?
Bien architecturer une application REST
• Eviter de réinventer le monde… sauf si c’est vraiment nécessaire. Avant de définir votre propre architecture REST, le type de donnée spécifique à votre application, il est toujours bon de vérifier s’il n’y aurait pas moyen d’étendre un existant. Le foisonnement de protocoles REST utilisant ATOM comme base est un excellent exemple de réutilisation.
89
Bréviaire des codes HTTP
Un court aide-mémoire des codes de retour HTTP les plus utilisés dans les clients et services REST : codes en 200, 300, 400, 500...
A
Les codes 200 Tous les codes de retour dans la gamme des 2xx indiquent un bon déroulement de la requête. Nous citons les plus courants.
La requête a réussi. Le contenu de la réponse dépend de la méthode HTTP utilisée. Dans un GET, on trouvera la représentation. Pour un POST, cela peut être un contenu décrivant le bon fonctionnement, ou bien directement une représentation de la ressource nouvellement créée (même si 201 ou 202 est probablement plus approprié).
201 : « Created » La requête a réussi et une nouvelle ressource a été créée sur le serveur. C’est typiquement le retour d’une action POST avec l’URL de la représentation canonique de la ressource nouvelle créée, et peut-être la représentation elle-même dans la réponse.
202 : « Accepted » Un code de retour moins fréquent, mais qui peut arriver quand le serveur accepte la création de la ressource, mais ne peut pas immédiatement renvoyer une URL ou une représentation, typiquement parce qu’un traitement (asynchrone) est nécessaire avant que la ressource soit visible.
Annexe A – Bréviaire des codes HTTP
Bien architecturer une application REST
200 : « OK »
91
204 : « No Content » Nettement plus fréquent, ce code de retour est l’équivalent du code 200, mais indique que le serveur n’a rien à transmettre dans le corps de la réponse. Cela peut aussi signifier que la représentation de la ressource est vide.
Moins fréquent, mais qu’on rencontre dans la pratique quand on manipule des représentations de taille importante (des fichiers binaires, de grosses photos, des vidéos…). Via l’utilisation d’un en-tête Content-Range, le client peut ne demander qu’une portion de la ressource, le serveur répondra alors avec un code 206 et la section demandée.
Les codes 300 Les codes de retour dans les 3xx indiquent des redirections, c’est-à-dire que le client ayant demandé l’accès à une URL recevra une réponse en 3xx avec l’indication de la nouvelle URL à utiliser.
301 : « Moved Permanently» Le serveur indique dans un en-tête Location la nouvelle URL à utiliser. Il est de bon ton d’indiquer dans le corps de la réponse quelques lignes en HTML avec un lien vers la nouvelle URL pour les navigateurs web. Le code de retour 301 indique que du point de vue du serveur, cette URL renvoie désormais définitivement sur une autre ; un client recevant un 301 devrait considérer que l’URL qu’il a utilisée est obsolète et n’utiliser que la nouvelle dans ses futures requêtes.
Annexe A – Bréviaire des codes HTTP
Bien architecturer une application REST
206 : « Partial Content »
92
303 : « See other » Tout comme pour le 301, le serveur indique dans un en-tête Location une nouvelle URL à utiliser – avec, là aussi, un petit fragment HTML. Mais à la différence du 301, le code 303 est juste une indication du serveur, sur quelle est, de son point de vue, l’URL recommandée pour accéder à une ressource. C’est utile pour proposer une URL canonique.
http://bibliotheque.example.org/auteur/Nabokov/livre/Ada répondrait bibliotheque.example.org/auteur/Nabokov/livre/Ada.en.html.
Ou bien une URL http://blog.example.org/user/olg/posts/latest http://blog.example.org/user/olg/posts/2009/03/07.
un code 303 vers http://
renverrait via un 303 vers un
Dans le cast d’un PUT, POST ou DELETE, le comportement est un peu plus subtil. Il indique que la requête a réussi, mais que la représentation est à aller chercher à l’URL renvoyée dans l’URL renseignée dans Location.
307 : « Temporary Redirect » 307 est un autre code de retour important. Il fonctionne de la même manière que le code 301, mais indique une redirection temporaire : le client doit continuer à utiliser l’URL originale. Lors d’un GET, cela peut permettre de renvoyer un client sur un serveur moins chargé. Sur POST, PUT ou DELETE, le client doit considérer que le serveur n’a pas pris en compte du tout la demande et qu’il faut bien utiliser la nouvelle URL renvoyée dans l’entête Location pour réémettre le POST, PUT ou DELETE.
Annexe A – Bréviaire des codes HTTP
Bien architecturer une application REST
Par exemple, un service pourrait choisir que pour l’URL générale du livre Ada ou l’ardeur, la représentation canonique serait celle du texte anglais :
93
Les codes 400 Les codes 4xx (et les 5xx qui vont suivre) indiquent que la requête n’a pu être traitée par le serveur. Dans le cas des 400, il s’agit d’une erreur dans la requête émise par le client : le serveur n’est pas en faute, mais ne peut répondre.
C’est un peu le code attrape-tout : le serveur n’a rien compris à la requête du client, et aucun autre code d’erreur 4xx n’est adapté. C’est aussi classiquement le code utilisé lors d’une requête POST ou PUT syntaxiquement correcte, mais où le contenu est sémantiquement invalide.
401 : « Unauthorized » et 403 « Forbidden » Un code fondamental : il permet d’indiquer au client que l’URL accédée est protégée et qu’il n’a pas fourni d’information d’authentification (ou bien que l’information était incorrecte). Le serveur répondra habituellement avec un en-tête WWW-Authenticate pour préciser le mécanisme d’authentification que le client devrait utiliser pour réémettre la requête. On trouve aussi parfois le code 403 « Forbidden » un peu plus général, qui indique que la ressource n’est pas accessible au client, sans plus de précision.
404 : « Not found » et 410 À tout seigneur, tout honneur. Le plus visible de tous les codes de retour HTTP, signifiant que l’URL est introuvable. Il existe quelques variations sur le 404, en particulier le code 410, qui indique que la ressource existait bien… mais n’est plus disponible.
Annexe A – Bréviaire des codes HTTP
Bien architecturer une application REST
400 : « Bad Request »
94
405 : « Method Not Allowed » On commence ici à aborder quelques codes d’erreur assez rarement vus dans le Web « classique », mais souvent rencontrés dès que l’on programme des clients REST spécifiques.
406 : « Not acceptable » et 415 « Unsupported Media Type » C’est une peu une variante du précédent : là ce n’est pas la méthode HTTP qui ne convient pas, mais la représentation demandée par le client. Imaginons un client qui me demande via l’en-tête Accept une représentation sous forme de PDF d’un livre, alors que le serveur n’est prêt à lui fournir que du HTML ou du XML (le serveur pourrait aussi passer outre la demande et renvoyer une des représentations disponibles). En répondant un 406, le serveur pourrait également renvoyer dans le corps de la réponse une liste de types de contenus acceptés (Content-Type). Il existe quelques autres codes de retour assez similaires, comme le 415 « Unsupported Media Type » pour être plus précis sur la cause de l’erreur.
409 : « Conflict » C’est un code de retour assez important pour les POST ou les PUT. Imaginons que j’envoie un POST pour ajouter un livre dans http://bibliotheque.example.org/livres mais que suite à une faute de frappe, je transmette un ISBN déjà utilisé. Le serveur serait en droit de me répondre 409 : une ressource existe déjà et entrerait en conflit avec la requête.
Annexe A – Bréviaire des codes HTTP
Bien architecturer une application REST
Le code 405 indique qu’on a essayé d’utiliser une méthode HTTP qui n’est pas permise sur la ressource, comme un PUT sur une ressource accessible uniquement en lecture via un GET, ou bien un GET sur une URL représentant une collection dans laquelle je me contente d’émettre des POST pour créer de nouvelles ressources.
95
412 : « Precondition Failed »
Si cette condition If-Unmodified-Since est fausse, cela voudra dire que quelqu’un a mis à jour la ressource entre temps, et le serveur renverra un code 412. Au client d’aller de nouveau accéder en GET à la ressource, et de mettre à jour sa représentation (éventuellement en fusionnant ses changements) pour réémettre un PUT.
Les codes 500 Souvent plus désagréables, les codes 5xx indiquent que la requête n’a pu être traitée à cause d’une erreur du service.
500 : « Internal Server Error » C’est un peu l’équivalent du code 400 que nous avons croisé un peu plus tôt : c’est le code attrape-tout pour signaler un problème du côté du service, sans autre précision. Cela peut être un souci d’infrastructure, un problème dans le cœur de l’implémentation du service, etc.
Annexe A – Bréviaire des codes HTTP
Bien architecturer une application REST
Ce code commence à prendre un peu d’importance, il est à rapprocher du code 409 précédent. Reprenons l’exemple précédent : via un PUT, je mets à jour des informations au sujet d’un livre, mais dans le même temps un autre client a également envoyé des modifications. Je risque donc de les écraser. Pour éviter cela, le client peut utiliser un en-tête IfUnmodified-Since en se basant sur la date de la ressource telle qu’elle lui a été renvoyée dans un GET un peu plus tôt.
96
503 : « Service Unavailable» - et 502 « Bad Gateway » et 504 « Gateway timeout »
Annexe A – Bréviaire des codes HTTP
Bien architecturer une application REST
C’est un code de retour un peu paradoxal : le service répond pour dire qu’il ne peut pas répondre ! En fait, cela correspond à la situation classique ou le « vrai » service est situé derrière un serveur proxy, et que suite à une requête, ce serveur proxy ne peut contacter le service. Il renverra donc un code 503. On trouve aussi parfois le code 502 « Bad Gateway », ou 504 « Gateway timeout » pour ce type de problème.
97
Bibliographie
Quelques références, URL et outils pour aller plus loin dans le monde REST...
B
REST n’étant qu’un style d’architecture, il n’en existe pas de spécification ; mais pour certaines briques de base comme HTTP et la syntaxe des URI/URL, quelques références sont utiles : « RFC 2616 – Hypertext Transfer Protocol – HTTP/1.1 » R. Fielding, J. Gettys et al. http://tools.ietf.org/html/rfc2616
Le cœur de REST est décrit dans la thèse de Roy Fielding. « Architectural Styles and the Design of Network-based Software Architectures » Roy Fielding Doctoral dissertation, University of California, Irvine. http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
Un des rares livres à traiter purement de REST est l’ouvrage de Sam Ruby et Leonard Richardson. C’est un panorama très complet des problématiques que l’on a traitées ici. Ils présentent dans le détail la construction d’API web « restful » et les problèmes qui peuvent se poser. RESTful Web Services, Leonard Richardson & Sam Ruby. O’Reilly (May 8, 2007), ISBN 0596529260 La page Wikipedia REST donne une brève définition de REST, et la section d’ « external links » donne des références vers de nombreux articles, documents et billets de blogs décrivant REST. Parmi ceux-ci, on peut citer : « Comment j’ai expliqué REST à ma femme », par Ryan Tomayko, traduit par Karl Dubost http://www.pompage.net/pompe/comment-j-ai-explique-rest-a-ma-femme/
Annexe B – Bibliographie
Bien architecturer une application REST
« RFC 3986 – Uniform Resource Identifier (URI) : Generic Syntax » T. Berners-Lee, R. Fielding, L. Masinter. http://tools.ietf.org/html/rfc3986
99
Le REST wiki, bien qu’un peu désorganisé, contient plusieurs articles sur des questions précises de conception. http://rest.blueoxen.net/
Paul Prescod et Joe Gregorio publient régulièrement des billets sur REST :
Une note du W3 étudiant le problème du choix entre GET et POST et les bonnes pratiques à retenir : « URIs, Addressability, and the use of HTTP GET and POST » http://www.w3.org/2001/tag/doc/whenToUseGet.html
Trois articles sur l’importance des URL/URI, stabilité, lisibilité, négociation de contenu, types de représentations. « URL as UI » de Jakob Nielson http://www.useit.com/alertbox/990321.html « Cool URIs for the Semantic Web » W3C Interest Group Note. http://www.w3.org/TR/ cooluris
« Cool URIs don’t change », Tim Berners-Lee. http://www.w3.org/Provider/Style/URI ATOM est documenté dans les RFC 4287 (pour le format en lui-même) et 5023 (pour le protocole REST AtomPub). L’API Google Contacts que nous avons pris en exemple est décrite en détails sur le site Google Code, sous http://code.google.com/apis/contacts/
Annexe B – Bibliographie
Bien architecturer une application REST
http://www.prescod.net/rest/ http://bitworking.org/
100
Le protocole GData qui sous-tend la quasi-totalité des API disponibles chez Google est expliquée en détails sur http://code.google.com/apis/gdata/overview.html Pour finir, quelques outils sont intéressants à avoir dans sa trousse de développeur REST : Les plus “geeks” de mes lecteurs trouveront leur bonheur dans la lecture de ce document qui explique comment utiliser la ligne de commande et cURL pour explorer une API REST : « Using cURL to interact with Google data services »
Pour ceux qui préfèrent des outils plus graphiques, je ne peux que conseiller les “Web Debugging Proxies”, des logiciels qui prennent la place d’un proxy Web classique, mais qui interceptent les requêtes, les analysent, mesurent, etc. Deux outils intéressants parmi d’autres : Charles, une application multi plate-forme : http://www.charlesproxy.com/ Fiddler, un outil Microsoft : http://www.fiddler2.com/
Annexe B – Bibliographie
Bien architecturer une application REST
http://code.google.com/support/bin/answer.py?answer=78451
101