Version PDF

Transcription

Version PDF
Services REST en JEE (JAX-RS)
1
Présentation
Pour connecter un client à un serveur métier, nous avons déjà vu trois solutions : Spring, RMI, EJB. Dans les
trois cas, le client et le serveur doivent être développés sur la même plateforme (Java).
1.1
Les services WEB
Nous avons maintenant besoin de généraliser cette connexion et d’améliorer l’hétérogénéité de nos solutions.
Pour ce faire, nous allons introduire la notion de Services WEB 1 (Web Services) qui consiste à utiliser des
documents XML pour coder les requêtes et les réponses qui transitent entre le client et serveur via le protocole
HTTP :
+--------------------+
|
|
|
Client
|
|
|
+--------------------+
----> Requ^
etes ---->
<---- Réponses <----
+--------------------+
|
|
|
Serveur
|
|
|
+--------------------+
Ces WS peuvent publier des données et/ou permettre aux clients de modifier les données métiers. Ces WS
échangent souvent des documents XML sur la base du protocole Soap 2 . Le protocole SOAP est géré dans JEE
par l’API JAX-WS 3 .
1.2
Les services REST
Depuis quelques années, les WS se démocratisent et se simplifient. Afin d’éviter le recours à XML, des auteurs
ont suggéré d’utiliser toute la puissance du protocole HTTP pour coder les requêtes en utilisant à la fois :
• la méthode HTTP ( GET , POST , PUT , DELETE , ...) pour coder la nature de la requête,
• les paramètres HTTP placés dans l’entête ou même dans l’URI pour préciser les arguments de la requête.
Par exemple
GET http://monservice.fr/ws/produit/A255/prix/euros
pour obtenir le prix en euros du produit identifié par le code A255 . Le résultat peut être codé en mode
texte, en XML ou par le langage JSON 4 . Nous venons de définir les services REST 5 (representational
state transfer). Ils sont gérés dans JEE par l’API JAX-RS 6 .
1.3
Utilisation des Web Services REST/SOAP
Les API JAX-WS et JAX-RS s’utilisent à la fois dans
• un environnement JRE classique (hors JEE)
1.
2.
3.
4.
5.
6.
https://fr.wikipedia.org/wiki/Service web
https://fr.wikipedia.org/wiki/SOAP
https://docs.oracle.com/javaee/6/tutorial/doc/bnayl.html
https://fr.wikipedia.org/wiki/JavaScript Object Notation
https://fr.wikipedia.org/wiki/Representational State Transfer
https://docs.oracle.com/javaee/6/tutorial/doc/giepu.html
1
Extérieur
|
Intérieur
----------------------+------------------------------|
Client Ws -----> Application JRE/Rest/Soap
|
• un environnement JEE/EJB (sans couche Web)
Extérieur
|
Applicatif
|
Métier
--------------+--------------------------------------|
|
Utilisateur -----> App. client Ws -----> JEE/EJB/Rest/Soap
| (Natif/JSF/Servlet) |
• un environnement JEE/JSF ou JEE/Servlet (pas d’EJB)
Extérieur
|
Web & Métier
------------------+---------------------|
Client Web & Ws -----> JSF/JSP/Rest/Soap
|
• un environnement JEE/JSF/EJB ou JEE/Servlet/EJB
Extérieur
|
Web
|
Métier
------------------+--------------------------------------|
|
Client Web & Ws -----> PHP/.NET/JSF/JSP -----> EJB/Rest/Soap
|
|
1.4
Quelques liens
• Tutorial JAX-RS JEE˜6 7
• Une série d’exemples 8
2
Mise en place
• Préparez dans Eclipse JEE un projet de type Application WEB basé sur le serveur TomEE Plus.
• Nous allons utiliser la librairie Apache CXF 9 qui est intégrée à TomEE Plus. Elle est également utilisable
de manière autonome.
• Activez la fonction Maven de votre projet.
3
Un premier Service REST
• Comme habituellement dans JEE 6 et plus, les informations de déploiement sont gérées par l’utilisation des
annotations.
7. http://docs.oracle.com/javaee/6/tutorial/doc/giepu.html
8. http://www.mkyong.com/tutorials/jax-rs-tutorials/
9. https://cxf.apache.org/
2
• Préparez la classe ci-dessous :
package myrest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@Path("/ws")
public class MessageRestService {
/* Premier exemple avec un GET */
@GET()
@Path("hello")
public String hello() {
return "Hello";
}
}
• L’annotation @Path
• L’annotation @GET
10
11
indique le chemin d’accès à un service
indique la méthode HTTP qui doit être utilisée
Travail à faire :
• Observez le déploiement de ce service dans les traces du serveur TomEE.
• Essayez d’utiliser ce service avec un navigateur.
• Ajoutez les méthodes init et close classiques (annotées par @PostConstruct et @PreDestroy )
pour suivre l’instanciation de votre service.
• Ajoutez à votre application une page JSP qui va nous aider à coder la partie cliente :
<html>
<body>
<p><a target="_blank" href="ws/hello">Get Hello</a></p>
</body>
</html>
• Ajoutez une méthode helloPost() annotée @POST
dans votre page JSP pour tester ce WS.
12
qui traite le même chemin et prévoir un formulaire
• Modifiez la méthode hello pour qu’elle accepte un argument (annoté par @QueryParam
cette fonction avec votre page JSP.
@Path("hello")
@GET()
public String hello(@QueryParam("message") String message) {
if (message == null) {
return "Hello";
} else {
return "Hello " + message.toUpperCase();
}
}
10.
11.
12.
13.
https
https
https
https
://docs.oracle.com/javaee/6/api/javax/ws/rs/Path.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/GET.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/POST.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/QueryParam.html
3
13 )
et testez
• Ajoutez (grace à l’annotation @DefaultValue
14 )
une valeur par défaut et testez son fonctionnement.
@Path("hello")
@GET()
public String hello(
@QueryParam("message") @DefaultValue("bye") String message) {
...
}
• En étudiant la documentation de @QueryParam 15 , faites en sorte d’avoir une liste de messages en entrée
de ce WS. Comment fonctionne dans ce cas la valeur par défaut ?
• Modifiez la méthode helloPost() pour qu’elle accepte un paramètre annoté par @FormParam
ajoutez à votre formulaire un champ de saisie :
16
et
<form target="_blank" action="ws/hello" method="post">
<p>
<input type="text" name="message" />
<input type="submit" value="Submit" />
</p>
</form>
4
Traiter les URI avec paramètres
• Nous allons maintenant exploiter des paramètres placés directement dans l’URI et annotés par @PathParam
17 .
@GET()
@Path("hello/{message}")
public String hello(@PathParam("message") String message) {
return "Hello " + message;
}
• Nous pouvons également préciser par une expression régulière la nature du paramètre. Essayez l’exemple
suivant et vérifiez que la bonne version est appelée en fonction du paramètre.
@GET()
@Path("hello/{message:[0-9]+}")
public String hello(@PathParam("message") Integer number) {
return "Number " + (number);
}
• Finalement, nous pouvons récupérer la requête et traiter les paramètres de manière conventionnelle comme
le ferait une servlet :
14.
15.
16.
17.
https
https
https
https
://docs.oracle.com/javaee/6/api/javax/ws/rs/DefaultValue.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/QueryParam.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/FormParam.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/PathParam.html
4
@GET()
@Path("param")
public String param(@Context UriInfo uri) {
String result = "";
result += "path: " + uri.getPath();
for (Entry<String, List<String>> e : uri.getQueryParameters().entrySet()) {
for (String value : e.getValue()) {
result += " ";
result += e.getKey() + "=" + value;
}
}
return result;
}
Testez ce service avec la requête ws/param?a=1&b=2&c=3&a=4 . Étudiez ensuite l’annotation @Context
et la classe UriInfo 19 .
• Exploitez les PathSegment
5
20
18
(méthode uri.getPathSegments() ) sur l’exemple ws/param;a=1;b=2;c=3
Améliorer le résultat
Travail à faire :
• Vous pouvez piloter la production du résultat avec la classe Response 21 . A partir d’un ResponseBuilder 22
vous appelez différentes méthodes (qui renvoient toutes le même ResponseBuilder 23 ) pour construire
votre réponse :
@GET()
@Path("reponse/{m}")
public Response reponse(@PathParam("m") @DefaultValue("Salut") String m) {
String res = "Result : " + m;
return Response.ok().type(MediaType.TEXT_PLAIN).entity(res).build();
}
A faire : Si le message est vide, tentez une redirection vers un service existant (méthode seeOther ).
• Les services REST peuvent produire du XML (un arbre DOM dans l’exemple ci-dessous). L’annotation
@Produces 24 indique le type MIME de la donnée à produire.
18.
19.
20.
21.
22.
23.
24.
https
https
https
https
https
https
https
://docs.oracle.com/javaee/6/api/javax/ws/rs/core/Context.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/core/UriInfo.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/core/PathSegment.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/core/Response.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/core/ResponseBuilder.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/core/ResponseBuilder.html
://docs.oracle.com/javaee/6/api/javax/ws/rs/Produces.html
5
@GET()
@Path("xhello/{message}")
@Produces(MediaType.TEXT_XML)
public Document xhello(@PathParam("message") @DefaultValue("Salut") String message)
throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document doc = docBuilder.newDocument();
Element root = doc.createElement("hello");
root.appendChild(doc.createTextNode(message));
doc.appendChild(root);
return doc;
}
• Si vous enlevez l’annotation @Produces
25 ,
ce WS va produire du JSON.
• Un WS peut produire plusieurs types de résultat avec :
@Produces({ MediaType.TEXT_XML, MediaType.APPLICATION_JSON })
il va produire du XML par défaut, mais, si le client précise son choix, il peut recevoir du JSON :
wget -q --header "accept:application/json" \
-O - ’http://localhost:8080/votre-app/ws/xhello/Bye
6
Utiliser des JavaBeans
Travail à faire :
• Commencez par définir un javaBean pour représenter un message. Cette classe est annotée par des
déclarations JAXB (Java API for XML Binding) qui une technologie de mise en correspondance d’un
schéma XML et d’une classe Java (voir un exemple ici 26 ).
25. https ://docs.oracle.com/javaee/6/api/javax/ws/rs/Produces.html
26. http://www.mkyong.com/java/jaxb-hello-world-example/
6
package myrest;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Message {
Integer number;
String body;
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
@XmlElement(required = true)
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public Message() {
super();
}
public Message(Integer number, String body) {
super();
this.number = number;
this.body = body;
}
@Override
public String toString() {
return "Message [number=" + number + ", body=" + body + "]";
}
}
• Nous pouvons maintenant mettre en place un service de production de messages (au format XML) :
@GET()
@Path("msg/{number}")
@Produces(MediaType.TEXT_XML)
public Message msg(@PathParam("number") Integer number) {
return new Message(number, "texte " + number);
}
• Changer l’annotation @Produces pour produire du JSON.
• Nous pouvons également renvoyer un javaBean en indiquant que c’est un sous-ressource qui doit être
exploitée via des annotations. Pour ce faire,
. ajoutez les annotations suivantes à votre JavaBean
7
...
@XmlAttribute(name = "id", required = true)
@GET()
@Path("number")
public Integer getNumber() ...
@XmlElement(required = true)
@GET()
@Path("body")
public String getBody() ...
...
. ajoutez ensuite la méthode ci-dessous (notez l’absence de GET ou POST ) :
@Path("message/{number}")
public Message message(@PathParam("number") Integer number) {
return new Message(number, "texte " + number);
}
. Vous devez être capable de traiter la requête suivante :
GET http://localhost:8080/votre-app/ws/message/100/body
• Nous allons maintenant consommer (annotation @Consumes
le service :
27 )
du XML et produire du XML. Préparez
@POST()
@Path("addMsg")
@Produces({ MediaType.TEXT_XML, MediaType.APPLICATION_JSON })
@Consumes({ MediaType.TEXT_XML, MediaType.APPLICATION_JSON })
public Message addMessage(Message m) {
if (m == null) {
m = new Message(0, "NULL");
}
System.err.println(m);
m.setNumber(m.getNumber() + 1000);
m.setBody(m.getBody().toUpperCase());
return m;
}
• Nous allons tester ce service avec un client sous la forme d’un script bash :
27. https ://docs.oracle.com/javaee/6/api/javax/ws/rs/Consumes.html
8
#!/bin/bash
tmp=/tmp/tmp.$$
trap "rm -f ${tmp}*" EXIT
CONTENT="text/xml"
ACCEPT="text/xml"
MESSAGE="<message id=’10’><body>Bye</body></message>"
wget --quiet \
--header="Content-Type: $CONTENT" \
--header="accept: $ACCEPT" \
--post-data="$MESSAGE" \
"http://localhost:8080/votre-application/ws/addMsg" \
-O "$tmp.result"
cat $tmp.result
exit
• faites varier (dans le script) le type de codage d’entrée et de sortie.
• Si le client est basé sur Java, nous pouvons utiliser les API Client :
. Ajoutez les dépendances Maven pour jersey-client
. Ajoutez à votre projet le test unitaire suivant :
package myrest;
import org.junit.Assert;
import org.junit.Test;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
public class MyTest {
@Test
public void testMsg() {
Client client = Client.create();
WebResource webResource =
client.resource("http://localhost:8080/votre-app/ws/msg/200");
Message m = webResource.accept("text/xml").get(Message.class);
Assert.assertNotNull(m);
Assert.assertEquals(m.getNumber(), new Integer(200));
Assert.assertEquals(m.getBody(), "texte 200");
System.out.println(m);
}
}
9