Architecture des SI - partie 2 - année 2014 Didier FERMENT - Université de Picardie REST "Representational State Transfer" est une architecture orienté ressource suggéré par Roy T. Fielding (co-auteur d'Apache) • donc basé sur HTTP, les URIs, les liens .... • c'est un mécanisme analogue à RPC (Remote Procedure Calls) et les Web Services (SOAP, WSDL, …) mais beaucoup plus simple. • Ce n'est pas un "standard W3C". • Roy T. Fielding a définit des règles à respecter pour une architecture RESTful • Règle : les ressources sont identifiées par URI Exemple : • http://nirvana.com/prophetes identifie la liste des prophètes • http://nirvana.com/prophetes/13 identifie le 13-ème prophète • http://nirvana.com/prophetes/brian identifie le prophète brian • http://nirvana.com/prophetes/13/propheties identifie la liste des prophéties du 13-ème prophète • Règle : les ressources sont manipulées au travers des représentations • une ressource peut avoir des représentations dans des formats divers : XML, CSV, JSON, HTML, CSV, .... • Le client peut définir le(les) format de réponse souhaitée via l’entête Accept. • Exemple : GET /prophetes/13 HTML/1.1 Host: nirvana.com Accept: application/xml • Règle : Utiliser les verbes HTTP comme identifiant des opérations • HTTP propose les verbes correspondant aux 4 opérations CRUD : • Créer (create) => POST
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Architecture des SI - partie 2 - année 2014
Didier FERMENT - Université de Picardie
REST
"Representational State Transfer" est une architecture orienté ressource suggéré par Roy T. Fielding (co-auteur d'Apache)
• donc basé sur HTTP, les URIs, les liens ....
• c'est un mécanisme analogue à RPC (Remote Procedure Calls) et les Web Services (SOAP, WSDL, …) mais beaucoup plus simple.
• Ce n'est pas un "standard W3C".
• Roy T. Fielding a définit des règles à respecter pour une architecture RESTful
• Règle : les ressources sont identifiées par URI Exemple :
• http://nirvana.com/prophetes identifie la liste des prophètes
• http://nirvana.com/prophetes/13 identifie le 13-ème prophète
• http://nirvana.com/prophetes/brian identifie le prophète brian
• http://nirvana.com/prophetes/13/propheties identifie la liste des prophéties du13-ème prophète
• Règle : les ressources sont manipulées au travers des représentations
• une ressource peut avoir des représentations dans des formats divers : XML,CSV, JSON, HTML, CSV, ....
• Le client peut définir le(les) format de réponse souhaitée via l’entête Accept.
• Exemple :
GET /prophetes/13 HTML/1.1 Host: nirvana.com Accept: application/xml
• Règle : Utiliser les verbes HTTP comme identifiant des opérations
• HTTP propose les verbes correspondant aux 4 opérations CRUD :
• Créer (create) => POST
• Afficher (read) => GET
• Mettre à jour (update) => PUT
• Supprimer (delete) => DELETE
• Exemple :
• GET http://nirvana.com/prophetes lit la liste des prophètes
• DELETE http://nirvana.com/prophetes/13 supprime le 13-ème prophète
• POST http://nirvana.com/prophetes + body des données pour créer un prophète
• PUT http://nirvana.com/prophetes/13 + body des données pour modifier le 13-ème prophète
• Règle : Stateless : Pas de gestion d'état
• Le serveur ne stocke jamais l’état des applications donc des requêtes.
• Simplifie le service mais augmente le volume de communication !
• L'alternative est fournie par la règle suivante : HATEOAS
• Règle : Hypermedia as the Engine of Application State (HATEOAS)
• Les liens hypermédia doivent permettre l’enchaînement des actions donc le changement d'état
• exemple : l'URI de suppression d'un prophète propose, en retour, un lien d'URI pour en faire un saint.
DELETE /prophetes/13 HTML/1.1
Host: nirvana.com Accept : application/xml HTTP/1.1 200 OK ContentType :application/xml <status>stopped as martyr</status> <link rel="beatification" method="post" href="prophetes/13/beatifier" />
web-ographie :
• http://opikanoba.org/tr/fielding/rest/chapitre 5 de la thèse de Roy T. Fielding sur REST
• JAX-RS = spécification par le Java Community Process pour les services Restful en Java
• décrit uniquement le coté serveur• javax.ws.rs.* = package de JAX-RS• implémentations : XCF d'Apache, RestEasy de Jboss, ...
• Jersey est l'implémentation d'Oracle pour cette spécification• fonctionne sur Tomcat, GlassFish, JBoss, ….• construit avec Apache Maven : ce qui simplifie la dépendance entre APIs• fournit une Api coté client.
téléchargez puis décompressez• Eclipse Standard/SDK Version: 4 .3.2 Kepler
http://www.eclipse.org/• téléchargez puis décompressez puis démarrez• Installez Web, XML, JavaEE and OSGi Entreprise Development :
Help → install new software → work with → Kepler - http://download.eclipse.org/releases/keplerCochez -> Web, XML, JavaEE and OSGi Entreprise Development → … finissez
• Installez Maven for Eclipse :Help → install new software → Add repository → name : M2Eclipse → location : http://download.eclipse.org/technology/m2e/releasesCochez → Maven for Eclipse→ … finissez
• Maven est un successeur de make d'Unix et d'Ant d'Apache pour gérer le cycle de vie d'un projet logiciel
• Le fichier de description du projet est pom.xml :project object model. Il contient des dépendances avec des ressources internes ou externes et les séquences de compilation, test, installation, …
• Maven est capable de récupérer des ressources via le réseau dans des dépôts, notamment suite à des dépendances transitives. C'est ce point fort que nous allons exploiter
• Un projet Maven a sa propre structure arborescente évidemment différente d'un projet Eclipse. Le plugin assure une assez bonne cohérence des 2.
• → pom.xml : ajoutez les dépendances Jersey nécessaires au projet <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jerseycontainerservlet</artifactId> <version>2.8</version> </dependency>
• → Java ressources -> src/main/java -> df.jersey.helloserver → new class Hello• rmq : les sources se trouvent dans src/main/java dans un projet Maven
• La servlet-class est celui de la servlet container Jersey• le param-value spécifie le package des classes POJO du service :
df.jersey.helloserver• l'url-pattern spécifie les patterns d'uri qui peuvent être "servi"• le servlet-name doit être identique dans les 2 sections : servlet et servlet-
mapping
• donc l'appel get http://localhost:8080/hello/rest/bonjour• comportant un Accept: text/html, ….
• déclenche la servlet hello• qui utilise la servlet container Jersey
• si l'uri est rest/*• qui recherche dans le package df.jersey.helloserver
• une classe et méthode pour un GET avec, dans l'uri, bonjour
• comme il y a plusieurs GET possible, celui qui produit du text Html est choisie :
• la réponse est :<html> <title>bonjour</title><body><h1>bonjour</h1></body></html>
• l’appel dans un Navigateur :http://localhost:8080/hello/rest/bonjour/telautre?prenom=bibi
bonjour bibi
Les annotations JAX-RS
• @Path :@Path("/bonjour") public class Hello {
@Path("/telautre") @GET public String sayPlainTextHello3( … …
@Path("/untel/{nom}") @GET public String sayPlainTextHello2 ...
• sur la classe, définit une ressource racine :http:hote:port/servlet/chemin/bonjour
• optionnelle sur une méthode, elle s'ajoute à l'URI :http:hote:port/servlet/chemin/bonjour/telautre
• optionnellement, le path peut comporter un template parameter comme { nom } :ainsi la requête d'URI http:hote:port/servlet/chemin/bonjour/bilou fournira la valeurbilou au paramètre nom de la méthode exécutée.
• indique que la valeur récupérée dans l'URI est passée à ce paramètre (qui peut n'a pas forcément le même identificateur) :ainsi la requête d'URI http:hote:port/servlet/chemin/bonjour/bilou fournira la valeurbilou au paramètre nom de la méthode exécutée.
• Les types JAVA qui peuvent être passés :1. Type primitif2. String3. Type/Classe ayant un constructeur qui a un seul argument String4. Type/Classe ayant une méthode statique valueOf(String) 5. ...6. ou de classe List<T>, Set<T> ou SortedSet<T>, où T satisfait la condition 2
• JAX-RS permet de passer des objets de type autre que ceux mentionnés plus haut,• en s'appuyant sur la marshall-isation/unmarshall-isation fourni par JAXB (Java
Architecture for XML Binding) : sérialisation d'un objet JAVA en document XML et réciproquement
• JAX-RS est capable aussi de sérialiser/dé-sérialiser en JSON.
• Créez un nouveau projet Maven :• maven-archtype-webapp
ArtifactId : delire0• créez un package df.prophete.delire0.model• créez une classe style "POJO" Prophete :
package df.prophete.delire0.model;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement public class Prophete {
public Prophete() { super(); this.nom = null; this.id = null;
• JAXB fournit des annotations qui, appliquées à un POJO, simplifient la transformation.• @XmlRootElement définit la racine du document XML généré à partir de cette
classe.
• Créez un package df.prophete.delire0• Créez une "root resource classe" PropheteRessource :
• Exécutez le client après avoir lancé le serveur :200 <?xml version="1.0" encoding="UTF-8" standalone="yes"?><prophete><id>0</id><nom>Zarathoustra</nom></prophete>
• Éditez la classe App : package testJersey.clientwadl_delire0;
import df.delire0.wadl.client.Localhost_Delire0Rest; import df.delire0.wadl.client.Prophete; public class App { public static void main(String [] args) {
@GET @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON}) public Prophete getProphete() { Prophete prophete = DaoOlympe.instance.getProphetes().get(id); if (prophete == null) throw new RuntimeException("Get: Prophete id " + id + " inexistant"); return prophete; }
@GET @Produces({MediaType.TEXT_XML}) public Prophete getPropheteBrowser() { Prophete prophete = DaoOlympe.instance.getProphetes().get(id); if (prophete == null) throw new RuntimeException("Get: Prophete id " + id + " inexistant"); return prophete; }
@PUT @Consumes(MediaType.APPLICATION_XML) public Response putProphete(JAXBElement<Prophete> propheteParam) { Prophete prophete = propheteParam.getValue(); Response res; if(DaoOlympe.instance.getProphetes().containsKey(prophete.getId())) { res = Response.noContent().build(); } else { res = Response.created(uriInfo.getAbsolutePath()).build(); } DaoOlympe.instance.getProphetes().put(prophete.getId(), prophete); return res; } @DELETE public void deleteProphete() { Prophete prophete = DaoOlympe.instance.getProphetes().remove(id); if (prophete == null) throw new RuntimeException("Delete: Prophete id " + id + " inexistant"); }}
• L'annotation @Context injecte une information de la requête HTTP dans la variable.
• L'annotation @Consumes spécifie le type de donnée transmise• La méthode noContent( ) crée une réponse vide• La méthode created( uri ) crée une réponse avec dans le header l'uri passée
• créez une classe ProphetesResourcepackage df.prophete.delire1;
• Au service Prophetes ci-dessus, ajoutez http://localhost:8080/delire1/rest/prophetes/recherche?nom=sus
qui recherche les prophètes dont le nom contient "sus"<links> <href>http://localhost:8080/delire1/rest/prophetes/recherche/3</href></links>
• Améliorez le service Prophetes ci-dessus, en ajoutant les …. prophéties :• chaque prophète aura une liste de prophéties• une prophétie comporte un texte … prophétique et un Id.
• Voici la classe Prophetie :package df.prophete.delire2.model;
public String getId() { return id; } public void setId( String id) { this.id = id; }}
• et la nouvelle classe Prophete :package df.prophete.delire2.model;
import java.util.HashMap;import java.util.Map;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElementpublic class Prophete { public Prophete() { super(); this.nom = null; this.id = null; } public Prophete(Integer id, String nom) { super(); this.nom = nom; this.id = id; } private String nom; private Integer id; private Map<Integer, Prophetie> listePropheties = new HashMap<Integer, Prophetie>(); public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Map<Integer, Prophetie> getListePropheties() { return listePropheties; } public void setListePropheties(Map<Integer, Prophetie> nouvelleListe) { this.listePropheties = nouvelleListe; }}
• et la classe DaoOlympe :package df.prophete.delire2.model;
import java.util.HashMap;
public enum DaoOlympe { instance; private Map<String, Prophete> contentProvider = new HashMap<String, Prophete>(); private DaoOlympe() { Prophete prophete = new Prophete("1", "Zarazoustra"); contentProvider.put(prophete.getId(),prophete); Prophetie prophetie = new Prophetie("1", "Dieu est mort"); prophete.getListePropheties().put(prophetie.getId(), prophetie); prophetie = new Prophetie("2", "Nietzsche est mort aussi"); prophete.getListePropheties().put(prophetie.getId(), prophetie);
prophetie = new Prophetie("3", "vive le surhumain"); prophete.getListePropheties().put(prophetie.getId(), prophetie); prophete = new Prophete("2", "Brian"); contentProvider.put(prophete.getId(),prophete); prophetie = new Prophetie("1", "Le sens de la vie"); prophete.getListePropheties().put(prophetie.getId(), prophetie); prophete = new Prophete("3", "Dac"); contentProvider.put(prophete.getId(),prophete); prophetie = new Prophetie("1", "Si la matière grise était plus rose, le monde aurait moins les idées noires."); prophete.getListePropheties().put(prophetie.getId(), prophetie); prophetie = new Prophetie("2", "Il faut une infinie patience pour attendre toujours ce qui n'arrive jamais."); prophete.getListePropheties().put(prophetie.getId(), prophetie); prophetie = new Prophetie("3", "S'il est bon de ne rien dire avant de parler il est encore plus utile de réfléchir avant de penser."); prophete.getListePropheties().put(prophetie.getId(), prophetie); } public Map<String, Prophete> getListeProphetes(){ return contentProvider; }
}
• Dans la classe PropheteRessource, ajoutez la sub-locator ressource :... @Path("/propheties") public ProphetiesRessource getPropheties() { return new ProphetiesRessource(uriInfo, request, id); }
• Voici le début de la classe ProphetiesRessource :...@Path("/")public class ProphetiesRessource { @Context UriInfo uriInfo; @Context Request request; Integer idProphete; public ProphetiesRessource(UriInfo uriInfo, Request request, Integer idProphete) { super(); this.uriInfo = uriInfo; this.request = request; this.idProphete = idProphete; }...
• Complétez la classe ProphetiesRessource pour satisfaire les requêtes :• GET prophetes/13/propheties donnera la liste des prophéties du 13-ème prophète
• POST prophetes/13/propheties + body comportant un id et un texte créera cette prophétie du 13-ème prophète
• Editez une classe ProphetieRessource pour satisfaire les requêtes :• GET prophetes/13/propheties/666 donnera la 666-iéme prophéties du 13-ème prophète
• DELETE prophetes/13/propheties/666 supprimera la 666-iéme prophétie du 13-ème prophète
• PUT prophetes/13/propheties/666 + body comportant un élément XML correspondant à un prophète mettra à jour la 666-iéme prophétie du 13-ème prophète avec cet item.