Services REST - Plateforme e-learning Moodle de l`INSA de Rouen
Transcription
Services REST - Plateforme e-learning Moodle de l`INSA de Rouen
INSA - ASI InfoRep : Services REST Informatique Répartie Services REST Alexandre Pauchet INSA Rouen - Département ASI BO.B.RC.18, [email protected] 1/36 INSA - ASI InfoRep : Services REST Plan 1 Introduction 2 Les Services Web de type REST 3 JAX-RS 4 Gestion des exceptions 5 Arguments et valeur en retour 6 EndPoint : bonnes pratiques 7 Références 2/36 INSA - ASI InfoRep : Services REST Introduction à REST (1/4) Historique (rappel) Années 70 : architectures Mainframe (1 tier) Années 80 : architectures 2 tiers (BD) Fin des années 80 : architectures 3 tiers (RPC) Années 90 : architectures 3 tiers Objet (RMI/Corba) Années 00 : architectures orientées services (Web Services) Fin des années 00 : architectures orientées ressources (RESTful) 3/36 INSA - ASI Introduction à REST InfoRep : Services REST 4/36 (2/4) Architecture 2 tiers et 3 tiers BdD Primergy Ressources Système de Fichiers INSA - ASI Introduction à REST Ressources sur Serveur Web InfoRep : Services REST (3/4) 5/36 INSA - ASI InfoRep : Services REST Introduction à REST 6/36 (4/4) Caractéristiques de REST REST : REpresentational State Transfert REST est une méthodologie pour construire une application distribuée un type d’architecture orientée Client/Serveur Web, Les Services Web REST permettent de construire des Architectures Orientées Ressources (ROA). Une telle application est dite RESTful. INSA - ASI InfoRep : Services REST Les Services Web de type REST 7/36 (1/5) Description de REST Les services REST sont des Services Web Les services REST sont sans état : l’état est maintenu par la représentation de la ressource Les services REST utilisent comme protocole de transport HTTP, et exclusivement 4 méthodes : GET, POST, PUT et DELETE Une architecture orientée REST est définie par un ensemble de ressources identifiées par leur URI. Une ressource (un “document”) peut être représentée dans divers formats (XML, JSON, etc.) La création d’une ressource est réalisée par la combinaison d’une méthode POST et d’un document au bon format INSA - ASI InfoRep : Services REST Les Services Web de type REST (2/5) Requête REST Ressource (identifiant) Identifiée par une URI Ex : http://localhost:8080/SecretariatREST/plan Méthode Elles permettent de manipuler les identifiants Méthodes HTTP : GET, POST, PUT et DELETE Représentation Vue sur l’état (requête client/serveur) Ex : XML, JSON 8/36 INSA - ASI InfoRep : Services REST Les Services Web de type REST (3/5) Ressource et URI Une URI identifie une ressource de manière unique Une ressource peut avoir plusieurs URI et la représentation de la ressource peut évoluer avec le temps Les ressources sont hiérarchiques : une ressource peut être une collection de ressources Exemples http://localhost:8080/banque/comptes/BobLeponge/PEL http://localhost:8080/banque/comptes/BobLeponge/1 /PEL et /1 sont les identifiants primaires d’une même ressource /banque/comptes/BobLeponge est une ressource de type collection : tous les comptes de BobLeponge 9/36 INSA - ASI InfoRep : Services REST Les Services Web de type REST (4/5) Ressource, opération et méthode HTTP Opérations Une ressource peut subir 4 opérations (CRUD) : Create (Création), Retrieve (Lecture), Update (Mise à jour), Delete (Suppression) Méthodes HTTP Chaque opération correspond à une méthode HTTP : Create → POST Retrieve → GET Update → PUT Delete → DELETE 10/36 INSA - ASI InfoRep : Services REST Les Services Web de type REST (5/5) Représentations Objectif : fournir les données suivant une représentation pour le client (GET) le serveur (PUT et POST) Les données peuvent être retournées dans différents formats (XML, HTML, JSON, etc.) Les formats des requête et réponse peuvent être différents 11/36 INSA - ASI InfoRep : Services REST JAX-RS 12/36 (1/10) Description JAX-RS : Java API for RESTful Web Services Spécification : JSR311 (jcp.org/en/jsr/summary?id=311) JAX-RS est intégré à Java à partir de Java EE 6 Le développement de Services Web REST avec JAX-RS s’appuie sur les annotations Java Plusieurs implémentations existes : JERSEY (Oracle, référence), CXF (Apache), RESTEasy (JBoss/WildFly), RESTlet La spécification ne décrit que la partie serveur, la partie client dépend de chaque implémentation INSA - ASI InfoRep : Services REST JAX-RS (2/10) Annotations principales En serveur (Producer) Chemin vers la ressource : @Path("/..."), adossée à la classe Chemin vers la ressource : @Path("/...{...}"), adossée à la méthode, avec d’éventuels paramètres associés Méthode HTTP GET : @GET, adossée à la méthode Paramètre : @PathParam("..."), adossée au paramètre En “client” pour le serveur (Consumer) Chemin vers la ressource : @Path("/..."), adossée à la classe Méthode HTTP POST : @POST, adossée à la méthode 13/36 INSA - ASI InfoRep : Services REST JAX-RS (3/10) JAX-RS sur JBoss/WildFly : RESTEasy Respecte la spécification JSR311 Propose une gestion de projets par Maven Gestion des bibliothèques nécessaires au fonctionnement de REST Intégration des tests unitaires Commandes : mvn clean, mvn install, mvn test, etc. Peut être utilisée dans n’importe quelle application J2EE Intégration facilitée : détection automatique des services Propose une API pour la conception de clients Installation : ajout de modules dans $JBOSS HOME/modules 14/36 INSA - ASI JAX-RS InfoRep : Services REST (4/10) Servlet dédiée : web.xml <? xml v e r s i o n = " 1.0 " ? > <!DOCTYPE web - app PUBLIC " -// Sun Microsystems , Inc .// DTD Web Application 2.3// EN " " http: // java . sun . com / dtd / web - app_2_3 . dtd " > <web - app > < display - name > HelloWorld REST </ display - name > < context - param > < param - name > resteasy . guice . modules </ param - name > < param - value > helloRest . HelloModule </ param - value > </ context - param > < listener > < listener - class > org . jboss . resteasy . plugins . guice . GuiceResteasyBootstrapServletContextListener </ listener - class > </ listener > < servlet > < servlet - name > Resteasy </ servlet - name > < servlet - class > org . jboss . resteasy . plugins . server . servlet . H t t p S e r v l e t D i s p a t c h e r </ servlet - class > </ servlet > < servlet - mapping > < servlet - name > Resteasy </ servlet - name > <url - pattern > /* </ url - pattern > </ servlet - mapping > </ web - app > 15/36 INSA - ASI JAX-RS InfoRep : Services REST (5/10) Exemple 1 : HelloWorld REST (à vite oublier) HelloModule.java package helloRest ; i m p o r t com . google . inject . Binder ; i m p o r t com . google . inject . Module ; p u b l i c c l a s s HelloModule i m p l e m e n t s Module { p u b l i c v o i d configure ( f i n a l Binder binder ) { binder . bind ( helloRest . HelloRessource . c l a s s ) ; } } 16/36 INSA - ASI JAX-RS InfoRep : Services REST (6/10) Exemple 1 : HelloWorld REST (à vite oublier) HelloRessource.java package helloRest ; i m p o r t com . google . inject . Inject ; i m p o r t javax . ws . rs . GET ; i m p o r t javax . ws . rs . Path ; i m p o r t javax . ws . rs . PathParam ; @ Path ("/ hello ") public c l a s s HelloRessource { @ Path ("/{ webname }") @ GET p u b l i c String hello ( @ PathParam (" webname ") f i n a l String name ) { r e t u r n " Hello " + name + " ! " ; } } 17/36 INSA - ASI JAX-RS InfoRep : Services REST (7/10) Exemple 1 : HelloWorld REST (à vite oublier) HelloTest.java i m p o r t org . junit . Assert ; i m p o r t org . junit . Test ; i m p o r t java . io . BufferedReader ; i m p o r t java . io . In put Str eamR ead er ; i m p o r t java . net . URL ; p u b l i c c l a s s HelloTest { @ Test p u b l i c v o i d test () t h r o w s Exception { f i n a l URL url = new URL ( " http :// localhost :9095/ hello / world " ) ; f i n a l BufferedReader reader = new BufferedReader (new In put Stre amR ead er ( url . openStream () ) ) ; try { Assert . assertEquals ( " Hello world ! " , reader . readLine () ) ; Assert . assertNull ( reader . readLine () ) ; } finally { reader . close () ; } } } 18/36 INSA - ASI InfoRep : Services REST JAX-RS 19/36 (8/10) Exemple 1 : HelloWorld REST (à vite oublier) Application Web HelloWorldRest-1.0.war |_WEB-INF | |_lib | | |_scannotation-1.0.3.jar, | | |_resteasy-jaxrs-3.0.6.Final.jar, ... | |_classes | | |_helloRest | | |_HelloModule.Class, HelloRessource.class | |_web.xml |_META-INF Compilation et déploiement mvn clean install cp target/HelloWorldRest-1.0.war $JBOSS HOME/standalone/deployments INSA - ASI JAX-RS InfoRep : Services REST (9/10) Requête depuis un navigateur 20/36 INSA - ASI InfoRep : Services REST JAX-RS 21/36 (10/10) client.Client.java package client ; import import import import import import java . io . BufferedReader ; java . io . IOException ; java . io . In put Str eamR ead er ; java . io . StringReader ; java . net . URL ; java . net . H ttp URLC onn ect ion ; p u b l i c c l a s s Client { p u b l i c s t a t i c v o i d main ( String [] args ) { try { URL url = new URL ( " http :// localhost :8080/ HelloWorldRest -1.0/ hello / " + args [0]) ; H t t p U R L Co nne cti on conn = ( H ttp URL Con nect ion ) url . openConnection () ; conn . s e tRequestMethod ( " GET " ) ; conn . s e t Re qu e st P ro pe r ty ( " Accept " , " text / plain " ) ; i f ( conn . getResponseCode () != 200) { throw new RuntimeException ( " Failed : HTTP error code : " + conn . getResponseCode () ) ; } Buffe redReader br = new BufferedReader (new In put Str eamR ead er (( conn . getInputStream () ) ) ) ; String apiOutput = br . readLine () ; System . out . println ( apiOutput ) ; conn . disconnect () ; } c a t c h ( Exception e ) { e . pr i nt StackTrace () ; } } } INSA - ASI InfoRep : Services REST Exemple 2 : Dictionnaire REST DictionaryResource.java package dicoRest ; i m p o r t javax . ws . rs .*; i m p o r t javax . ws . rs . core . Response ; i m p o r t java . util . HashMap ; i m p o r t dicoRest . Term ; @ Path (" dictionary ") public c l a s s D ic t io na r yR e so ur c e { p r i v a t e s t a t i c HashMap < String , String > listOfTerms = new HashMap < String , String >() ; @ GET @ Path ("/ terms /{ term }") @ Produces (" text / plain ") p u b l i c String definition ( @ PathParam (" term ") f i n a l String term ) { r e t u r n term + " : " + D ic t io n ar yR e so u rc e . listOfTerms . get ( term ) ; } @ GET @ Path ("/ size ") @ Produces (" text / plain ") p u b l i c i n t size () { r e t u r n D ic ti o na r yR es o u r c e . listOfTerms . size () ; } 22/36 INSA - ASI InfoRep : Services REST Exemple 2 : Dictionnaire REST DictionaryResource.java @ POST @ Path ("/ terms ") @ Consumes (" application / xml ") p u b l i c Response addTerm ( Term term ) { D i c t io na r yR e so ur c e . listOfTerms . put ( term . getName () , term . getDefinition () ) ; r e t u r n Response . status (200) . entity ( term . toString () ) . build () ; } @ PUT @ Path ("/ terms ") @ Consumes (" application / xml ") p u b l i c Response updateTerm ( Term term ) { D i c t io na r yR e so ur c e . listOfTerms . remove ( term . getName () ) ; D i c t io na r yR e so ur c e . listOfTerms . put ( term . getName () , term . getDefinition () ) ; r e t u r n Response . status (200) . entity ( term . toString () ) . build () ; } @ DELETE @ Path ("/ terms /{ term }") p u b l i c Response removeTerm ( @ PathParam (" term ") f i n a l String term ) { D i c t io na r yR e so ur c e . listOfTerms . remove ( term ) ; r e t u r n Response . status (200) . entity ( term . toString () ) . build () ; } } 23/36 INSA - ASI InfoRep : Services REST Exemple 2 : Dictionnaire REST Term.java package dicoRest ; i m p o r t javax . xml . bind . annotation . XmlElement ; i m p o r t javax . xml . bind . annotation . XmlRootElement ; @ Xml RootEle ment ( name =" term ") public c l a s s Term { p r i v a t e String name , definition ; p u b l i c Term () {} p u b l i c Term ( String t , String def ) { t h i s . name = t ; t h i s . definition = def ; } p u b l i c String getName () { r e t u r n t h i s . name ; } @ XmlElement p u b l i c v o i d setName ( String t ) { t h i s . name = t ; } p u b l i c String getDefinition () { r e t u r n t h i s . definition ; } @ XmlElement p u b l i c v o i d setDefinition ( String def ) { t h i s . definition = def ; } @ Override p u b l i c String toString () { r e t u r n t h i s . name + " : " + t h i s . definition ; } } 24/36 INSA - ASI InfoRep : Services REST 25/36 Exemple 2 : Dictionnaire REST Client.java package client ; import import import import import org . jboss . resteasy . client . jaxrs . ResteasyClient ; org . jboss . resteasy . client . jaxrs . R e s t e a s y C l i e n t B u i l d e r ; org . jboss . resteasy . client . jaxrs . Rest eas yWe bTa rget ; javax . ws . rs . core . Response ; javax . ws . rs . client . Entity ; i m p o r t dicoRest . Term ; p u b l i c c l a s s DictionaryClient { p u b l i c s t a t i c v o i d main ( String [] args ) { Rest easyClient client = new R e s t e a s y C l i e n t B u i l d e r () . build () ; R e s t e a s y W ebT arg et target ; Response response ; Term asi = new Term ( " ASI " , " Architecture des Systemes d ’ Information " ) , chat = new Term ( " chat " , " Petit animal à poils " ) ; String baseURL = " http :// localhost :8080/ DictionnaireRest -1.0/ " ; target = client . target ( baseURL + " dictionary / size " ) ; response = target . request () . get () ; System . out . println ( " Taille du dictionnaire : " + response . readEntity ( String . c l a s s ) ) ; response . close () ; target = client . target ( baseURL + " dictionary / terms " ) ; response = target . request () . post ( Entity . entity ( asi , " application / xml ; charset = UTF -8 " )); System . out . println ( " POST ( ASI ) : " + response . getStatus () ) ; response . close () ; INSA - ASI InfoRep : Services REST 26/36 Exemple 2 : Dictionnaire REST Client.java response = target . request () . post ( Entity . entity ( chat , " application / xml ; charset = UTF -8 ")); System . out . println ( " POST ( chat ) : " + response . getStatus () ) ; response . close () ; target = client . target ( baseURL + " dictionary / size " ) ; response = target . request () . get () ; System . out . println ( " Taille du dictionnaire : " + response . readEntity ( String . c l a s s ) ) ; response . close () ; target = client . target ( baseURL + " dictionary / terms / chat " ) ; response = target . request () . get () ; System . out . println ( response . readEntity ( String . c l a s s ) ) ; response . close () ; chat . setDefinition ( " Petit animal fourbe à poils " ) ; target = client . target ( baseURL + " dictionary / terms " ) ; response = target . request () . put ( Entity . entity ( chat , " application / xml ; charset = UTF -8 " )); System . out . println ( " UPDATE ( chat ) : " + response . getStatus () ) ; response . close () ; target = client . target ( baseURL + " dictionary / terms / chat " ) ; response = target . request () . get () ; System . out . println ( response . readEntity ( String . c l a s s ) ) ; response . close () ; target = client . target ( baseURL + " dictionary / terms / ASI " ) ; response = target . request () . delete () ; System . out . println ( " DELETE ( ASI ) : " + response . getStatus () ) ; response . close () ; INSA - ASI InfoRep : Services REST 27/36 Exemple 2 : Dictionnaire REST Client.java target = client . target ( baseURL + " dictionary / size " ) ; response = target . request () . get () ; System . out . println ( " Taille du dictionnaire : " + response . readEntity ( String . c l a s s ) ) ; response . close () ; } } Classpath client à l’exécution commons-io-2.3.jar commons-logging-1.1.1.jar httpclient-4.2.3.jar httpcore-4.2.3.jar jaxrs-api-3.0.9.Final.jar resteasy-client-3.0.9.Final.jar resteasy-jaxb-provider-3.0.9.Final.jar resteasy-jaxrs-3.0.9.Final.jar INSA - ASI InfoRep : Services REST Gestion des exceptions (1/3) Principe Rappel Le protocole HTTP ne permet pas de remonter des exceptions ! Solution Le code Status HTTP peut être utilisé comme information Architecture orientée Ressources ⇒ favoriser la remonter de document pour une consultation par un client web Exemple 404 : ressource non trouvée, valeur de paramètre erronée, ... 500 : erreur interne du serveur 28/36 INSA - ASI Gestion des exceptions InfoRep : Services REST (2/3) Exemple TestExceptions.java package exceptions ; i m p o r t com . google . inject . Inject ; i m p o r t javax . ws . rs .*; i m p o r t javax . ws . rs . core . Response ; @ Path ("/ test ") public c l a s s TestExceptions { @ GET @ Path ("/{ web - param }") @ Produces (" text / plain ") p u b l i c Response test ( @ PathParam (" web - param ") f i n a l String param ) { i f ( param . equals ( " division " ) ) r e t u r n Response . status (500) . entity ( " " + 1/0) . build () ; e l s e i f ( param . equals ( " null " ) ) r e t u r n Response . status (404) . entity ( " Paramètre attendu " ) . build () ; else r e t u r n Response . status (200) . entity ( " Reçu : " + param ) . build () ; } } 29/36 INSA - ASI InfoRep : Services REST Gestion des exceptions (3/3) Exemple Client.java package client ; import import import import import org . jboss . resteasy . client . jaxrs . ResteasyClient ; org . jboss . resteasy . client . jaxrs . R e s t e a s y C l i e n t B u i l d e r ; org . jboss . resteasy . client . jaxrs . Rest eas yWe bTa rget ; javax . ws . rs . core . Response ; javax . ws . rs . client . Entity ; p u b l i c c l a s s Client { p u b l i c s t a t i c v o i d main ( String [] args ) { Rest easyClient client = new R e s t e a s y C l i e n t B u i l d e r () . build () ; R e s t e a s y W ebT arg et target ; Response response ; String baseURL = " http :// localhost :8080/ ExceptionsRest -1.0/ test / " ; i f ( args . length >0) target = client . target ( baseURL + args [0]) ; else target = client . target ( baseURL ) ; response = target . request () . get () ; System . out . println ( response . getStatus () ) ; System . out . println ( response . readEntity ( String . c l a s s ) ) ; response . close () ; } } 30/36 INSA - ASI InfoRep : Services REST Arguments et valeur en retour 31/36 (1/4) Principe Rappel Le serveur est en mode Consommateur ou Producteur en fonction de la méthode HTTP ! Le passage par référence et donc les Callback ne sont pas supportés par les Services Web (et donc par les Services REST) Arguments GET : les arguments sont passés directement dans la requête POST, PUT et DELETE : les arguments sont consommés par le serveur (annotation @Consumes) Valeur en retour GET/POST : une valeur en retour est attendue ; l’annotation @Produces précise le type MIME de la valeur en retour Le code Status doit être utilisé INSA - ASI InfoRep : Services REST Arguments et valeur en retour (2/4) Exemple ServiceDeNommage.java package arguments ; i m p o r t com . google . inject . Inject ; i m p o r t javax . ws . rs .*; i m p o r t javax . ws . rs . core . Response ; @ Path (" personnes ") public c l a s s ServiceDeNommage { @ POST @ Path ("/{ web - param }") @ Consumes (" application / xml ") @ Produces (" application / xml ") p u b l i c Response renomme ( @ PathParam (" web - param ") f i n a l String nouveauNom , Personne aRenommer ) { aRenommer . setNom ( nouveauNom ) ; r e t u r n Response . status (200) . entity ( aRenommer ) . build () ; } } 32/36 INSA - ASI InfoRep : Services REST Arguments et valeur en retour (3/4) Exemple Personne.java package arguments ; i m p o r t javax . xml . bind . annotation . XmlElement ; i m p o r t javax . xml . bind . annotation . XmlRootElement ; @ XmlRootEle ment ( name =" personne ") public c l a s s Personne { p r i v a t e String nom ; p u b l i c Personne () {} p u b l i c Personne ( String n ) { t h i s . nom = n ; } p u b l i c String getNom () { r e t u r n t h i s . nom ; } @ XmlElement p u b l i c v o i d setNom ( String n ) { t h i s . nom = n ; } @ Override p u b l i c String toString () { r e t u r n t h i s . nom ; } } 33/36 INSA - ASI InfoRep : Services REST Arguments et valeur en retour (4/4) Exemple Client.java package client ; import import import import import org . jboss . resteasy . client . jaxrs . ResteasyClient ; org . jboss . resteasy . client . jaxrs . R e s t e a s y C l i e n t B u i l d e r ; org . jboss . resteasy . client . jaxrs . Rest eas yWe bTa rget ; javax . ws . rs . core . Response ; javax . ws . rs . client . Entity ; i m p o r t arguments . Personne ; p u b l i c c l a s s Client { p u b l i c s t a t i c v o i d main ( String [] args ) { Rest easyClient client = new R e s t e a s y C l i e n t B u i l d e r () . build () ; R e s t e a s y W ebT arg et target ; Response response ; Personne personne = new Personne ( args [0]) ; String baseURL = " http :// localhost :8080/ ArgumentsRest -1.0/ personnes / " ; target = client . target ( baseURL + args [1]) ; response = target . request () . post ( Entity . entity ( personne , " application / xml ; charset = UTF -8 " ) ) ; personne = response . readEntity ( Personne . c l a s s ) ; System . out . println ( " POST : " + response . getStatus () + " ; received : " + personne ) ; response . close () ; } } 34/36 INSA - ASI InfoRep : Services REST Nommage des EndPoints : bonnes pratiques Recommandations Serveur orienté Ressources Utilisation possible de collections Privilégier l’utilisation de collections Ex : (GET) annuaire/Bob ⇒ (GET) annuaire/personnes/Bob Éviter les arguments explicites Ex : (GET) annuaire/personne/param=Bob ⇒ (GET) annuaire/personnes/Bob Éviter les noms de type “fonction” Ex : (POST) annuaire/renommer/Bob ⇒ (POST) annuaire/personnes/Bob 35/36 INSA - ASI InfoRep : Services REST Références Livres RESTful Java (Bill Burke), Oreilly, 2009 RESTful Java Web Services (Jose Sandoval), PACKT, 2009 Sites jcp.org/en/jsr/summary?id=311 Cours Sun http://miageprojet2.unice.fr/@api/deki/files/ 99/=FY09TechDays_REST_Carol_(1).odp Cours M. Baron http: //miageprojet2.unice.fr/@api/deki/files/2109/=REST.pdf Cours M. Baron http://mbaron.developpez.com/soa/jaxrs/ jersey.java.net/nonav/documentation/latest/user-guide. html 36/36