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

Documents pareils