Robusta Web Library : Simplifiez REST et Ajax

Nicolas Zozol

Avec l'apparition d'Ajax puis des Mash-ups, la construction d'applications voire de plateformes Web s'est nettement compliquée. REST propose une architecture plus simple et logique que SOAP, et Robusta fournit un ensemble de classes utilitaires et d'interfaces pour booster la productivité dans la rédaction du code. Robusta est téléchargeable en version 0.9, avec une licence GPL et un code documenté.

La version 1.0 sortira vers octobre 2009 et sera GWT compatible, ce qui nécessitera un nombre important de changements. N'utilisez donc la version actuelle qu'en tests.

Lien vers le forum : Commentez

- Titre : Robusta Web Library : Simplifiez REST et Ajax

- Auteur : Nicolas Zozol

- Parution : 1 juillet 2009

- Mise à jour : 23 février 2020

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2009 Nicolas Zozol. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. REST et Robusta

- I-A. Qu'est-ce que REST ? Pourquoi REST ?

- I-A-1. Qu'est-ce que REST ?

- I-A-2. Pourquoi REST ?

- I-B. Pourquoi Robusta ?

- I-C. Framework, Toolkit ou Bibliothèque ?

- I-D. Les 3R : REST, Resource et Robusta

- I-E. Méthodes Http, Resource et ResourceController

- I-F. Sites web, Applications Web, Web Services

- I-G. Double discours de la Method

- I-H. Le Content-Type

- II. Service Web avec JAX-RS

- II-A. JAX-RS

- II-A-1. Installation

- II-A-2. Tester GET

- II-A-3. ResourceController fournit par Robusta

- III. JSP et JSF

- III-A. Sites web avec JSP et JSF

- III-A-1. Quand utiliser JSP plutôt que JAX-RS ?

- III-A-2. La structure avec JSP

- III-A-3. Le tag <robusta:request> et son JspResourceContoller

- III-A-4. Exemple avec un fragment HTML

- III-A-4-a. Le code Ajax

- III-A-4-b. La page JSP

- III-A-4-c. Le ResourceController

- III-A-4-d. Résultats

- IV. La Sécurité

- IV-A. Rappels sur la sécurité à travers le Web

- IV-A-1. HTTPS et le certificat

- IV-A-2. L'authentification BASIC

- IV-A-3. Avantages et défauts de la BASIC Authentication pour une application web

- IV-B. Les contraintes ergonomiques et sécuritaires d'un site web

- IV-C. Cryptage != Sécurité

- IV-D. Le protocole ArmyLazy

- IV-D-1. Un peu de vocabulaire

- IV-D-2. NONE

- IV-D-3. LAZY-INIT

- IV-D-3-a. Le navigateur envoie la requête

- IV-D-3-b. Le travail du Serveur

- IV-D-3-c. Le navigateur reçoit la requête

- IV-E. LAZY

- IV-E-1. Tag dans une page JSP

- IV-E-2. Fonctionnement interne

- IV-E-3. Résultat

- IV-E-4. Détection dans un Service Web

- IV-F. ARMY

- IV-G. Conclusions

- V. Les Exceptions

- V-A. Le Status Code d'une requête HTTP

- V-B. Les Exceptions dans les Webs Services

- V-C. La classe RestException et ses dérivés

- VI. Very Simple XML

- VI-A. Définition par l'exemple

- VI-B. SAX ou JDOM ?

- VI-C. L'interface VerySimpleXml

- VI-D. Quelques exemples

- VI-E. Conclusion

- VII. Les Clients HTTP

- VII-A. L'interface RestClient

- VII-B. Implémentation Apache ou Sun ?

- VII-C. Exemples

- Conclusion et Remerciements

I - REST et Robusta

I-A - Qu'est-ce que REST ? Pourquoi REST ?

I-A-1 - Qu'est-ce que REST ?

On construit un site ou un service web REST comme les autres, avec toutefois ces critères principaux :

- la communication entre modules se fait par HTTP ;

- la mémoire RAM du serveur ne mémorise pas de données dans une Session (Stateless) ;

- les méthodes des requêtes utilisent GET, POST, PUT et DELETE ;

- les URI des requêtes ont un sens très fort (GET /user/34 renvoie le 34e user) ;

- le sens du Status Code de la réponse est respecté.

Si ces contraintes (et quelques autres que vous trouverez dans ce livre) sont respectées, alors vous faites un site RESTFul.

I-A-2 - Pourquoi REST ?

REST n'est pas une technologie, mais un ensemble de (bons) procédés reposant fortement sur le protocole HTTP. Les contraintes ne sont pas très importantes, et évitent souvent de mauvaises surprises une fois que le site web (ou le service) évolue. REST est en fait le champion de la « Scalability ».

I-B - Pourquoi Robusta ?

Le Web 2.0 permet évidemment bien plus de choses que le web 1.0. Mais c'est au prix d'une plus grande complexité, principalement liée à Ajax et aux Mash-up. REST et Robusta aident à éviter la complexité exponentielle.

Robusta Web Library (Robusta) est une bibliothèque Java spécialement conçue pour les applications REST (plus ou moins 'ful'), et pour les sites web Ajax. Robusta n'est pas un framework : il ne guide pas le déroulement des opérations, mais donne un certain nombre de solutions déclinées en packages :

- rest : resource, ResourceController, RepresentationManager : toute la terminologie REST en trois interfaces ;

- security : simplifier l'ergonomie web avec le protocole ArmyLazy tout en sécurisant les données sensibles ;

- exceptions : gérer les Exceptions en facilitant l'envoi de Code Retour Http ;

- xml : écrire et analyser rapidement les petits documents XML envoyés par les requêtes ;

- i18n : internationaliser un service web ;

- j2ee : des tags pour vous simplifier JSP.

Tous ces packages sont indépendants (mais nécessitent le package 'commons') et la plupart n'utilisent pas la terminologie REST. Robusta peut donc être utilisé dans n'importe quel environnement Java, mais il prend naturellement tout son sens dans une application Web RESTful et Ajax.

Vous pouvez donc parfaitement utiliser Robusta dans un conteneur web (Tomcat, Glassfish, JBoss), avec Spring ou Restlet.

Notez également que les classes ou packages notés Impl ou implementation sont des solutions toutes faites et piochent dans les différents packages de Robusta. Vous pouvez éviter ces classes si vous souhaitez customiser de A à Z.

I-C - Framework, Toolkit ou Bibliothèque ?

Si l'on se réfère à Wikipédia, un framework est un guide de travail, ce qui suggère une tolérance plus ou moins grande à déborder de ce cadre. Un framework comme Struts a été très répandu dans l'industrie, et sa faible tolérance à l'excentricité a permis son utilisation à l'échelle industrielle, favorisant l'outsourcing.

La créativité étant source de richesse dans les hautes technologies, Spring s'est vite affirmé comme étant un Framework souple : on utilise ce que l'on veut. Restlet, quant à lui utilisé pour ses capacités REST, s'intègre avec les autres frameworks. GWT préfère parler de Toolkit pour contourner la sémantique de rigidité.

Robusta est bien une bibliothèque : un ensemble de classes et d'interfaces utilisables à n'importe quel moment de votre code. S'il s'intègre parfaitement à un contexte REST et Ajax, seul ce tutoriel propose un guide pour votre architecture. Robusta est donc une collection de raccourcis que vous allez, je l'espère, vite apprivoiser pour l'utiliser dans tout type de projets.

I-D - Les 3R : REST, Resource et Robusta

REST utilise beaucoup le concept de Resource, et HTTP aussi : URI signifie Universal Resource Identifier. D'après ce nom, une URI n'est pas une « adresse web » au sens géographique, mais c'est la technologie qui a imposé ce dernier sens. Inutile de revenir en arrière, et considérons qu'une Resource est un concept manipulable par des requêtes émises à destination d'une adresse.

En langage objet, ces concepts sont des classes. En UML, ce sont des éléments du Modèle du Domaine. On ne fait pas une classe pour chaque concept, aussi on ne formalise pas une Resource, en créant son URI, pour chaque concept. Mais dans le doute, cela ne fait pas de mal de créer une Resource.

Robusta définit l'interface Resource de façon à interagir avec les bases de données. Le « I » de URI signifie Identifier, et un outil de Netbeans générait automatiquement l'élément id comme étant une URI. Il est à mon sens préférable de décrire, dans une classe Java, cet identifiant comme étant une clé primaire de la base de données, et donc un Long.

public interface Resource {

public static final long UNKNOWN_ID = -1995;
public long getId();
public void setId(long id);
public RepresentationManager getRepresentationManager();
public String getRepresentation();
}

Comme précisé, Robusta n'est pas un framework : utilisez votre définition de Resource si vous le préférez. Robusta sera toujours utilisable, car très peu de classes utilisent robusta.rest.Resource.

I-E - Méthodes Http, Resource et ResourceController

Souvenez-vous de vos premiers formulaires en HTML et PHP :

<FORM action="newuser.php" method="POST">
<input name="username" type ="text">
<input name="password" type ="password">
<input type ="SUBMIT" value="Login">
</FORM>

Vous envoyiez alors de façon mécanique une requête HTTP vers la page newuser.php. Et à chaque action, une page PHP était créée, à chaque fois en utilisant $_POST[« inputName »]. Il fallait aussi créer une page deleteUser.php, readUser.php, updateUser.php, ou passer par l'url user.php?iWantTo=delete et accumuler les isset().

Avec REST, vous feriez une page userController.php, sur laquelle on envoie une requête :

- GET pour lire le User ;;

- POST pour créer un User ;

- PUT pour modifier le User ;

- DELETE supprimer le User.

User est une Resource. La page userController.php est un ResourceController.

Eh oui, si $_GET et $_POST existent en PHP, $_PUT et $_DELETE n'existent toujours pas. Les FORM HTTP ne gèrent eux aussi que GET et POST, alors que PUT, DELETE et bien d'autres méthodes sont définies dans le protocole HTTP depuis toujours. Lisez également attentivement le paragraphe « Double-discours de la Method ».

I-F - Sites web, Applications Web, Web Services

« Attention, je ne fais pas un Site Web, je fais une application Web » est une citation de plus en plus fréquente. Il est sous-entendu qu'un Site Web est un graphe de plusieurs pages reliées par des liens GET, alors qu'une Application Web tient en une seule page grâce à Ajax. GMail en est l'exemple le plus illustre.

Si la distinction permet de faire augmenter le tarif de la prestation, je doute qu'elle soit techniquement bien fondée. Pour fonctionner correctement, une application web n'a pas pour contrainte vitale de tenir en une page. D'ailleurs Gmail s'intègre très bien à l'Agenda, qui est situé à une autre adresse. Une Application Web est plutôt une ou plusieurs pages faisant appel à un Service Web. Si l'application est RESTful, elle utilisera, par Ajax, des méthodes PUT et DELETE que ne peut utiliser un graphe de pages.

Alors qu'une page web affiche quelque chose, un Web Service est un système client-serveur échangeant des données. À charge du client d'afficher ou non ces données.

Site web, Application web, Web service fonctionnent avec HTTP, éventuellement avec Ajax et Java et peuvent être RESTful. Je parlerais en général de Site Web, expression fondatrice, pour faire référence à ces trois concepts : vos Resources lues ou modifiées sont bien situées sur un Site identifié par une URI.

I-G - Double discours de la Method

Selon les versions, les navigateurs Konqueror et surtout Safari ne gèrent pas ou mal les méthodes PUT et DELETE, ce qui n'est pourtant pas trop demander ! En conséquence, certains frameworks Prototype ou Rails utilisent POST avec le paramètre _method pour désigner la véritable méthode :

PUT /myUri/24
devient
POST /myUri/24
_method:put

Pire encore ! Avec Prototype, insérer du XML dans l'option postBody de la requête va effacer ce paramètre _method. On ne peut pas penser à tout ! Il faut donc penser à écrire soi-même dans l'url le paramètre de la méthod.

Le framework ExtJs envoie lui sur demande des requêtes PUT et DELETE. Et s'il est assez facile de modifier le code source de Prototype.js pour forcer la requête PUT ou DELETE, cela ne le fera pas fonctionner ces requêtes dans les navigateurs défectueux. Prudence et tests sont de mise.

La bibliothèque Robusta permet de s'en sortir assez facilement avec l'attribut _method. Comme le fait Ruby on Rails, le tag JSP <robusta:request> détectera la méthode correcte, et les fonctions isPut() et isDelete() jouent ce rôle avec JAX-RS.

I-H - Le Content-Type

Sachez que par défaut, le navigateur enverra souvent une requête avec 'application/x-www-form-urlencoded' : c'est le content-type logique d'une balise <FORM>. Avec Ajax, il faudra changer par 'application/xml' ou 'application/json'.

En principe, la réponse sera soit 'text/html' pour du HTML, soit 'application/xml' pour du XML, soit 'application/json' pour JSON.

JSON est une syntaxe presque équivalente du xml, sans attributs ni namespace. Comme l'écriture est plus condensée, c'est par exemple une très bonne solution pour transmettre des larges listes de données. Très populaire avec Ruby, son support est assez restreint en Java et Robusta ne fait malheureusement pas exception.

II - Service Web avec JAX-RS

II-A - JAX-RS

JAX-RS est une spécification définie par Sun pour simplifier l'utilisation de Services Web RESTful en Java. Et le moins que l'on puisse dire est que cela est très réussi. Il est également possible d'utiliser JAX-RS comme une Servlet pour faire une page Web classique.

L'utilisation de JAX-RS se fait en quatre parties.

II-A-1 - Installation

Le serveur Glassfish v2 fournit directement Jersey et ses dépendances. Jersey est le mécanisme gérant JAX-RS développé par Sun Microsystem, JBOSS va utiliser RestEasy. Il vous faut aussi l'API proprement dite de JAX-RS dans le ClassPath.

Jersey in Netbeans

Il faut ensuite installer la Servlet de Jersey qui prendra en charge les requêtes. Cette servlet fait partie de la bibliothèque Jersey 1.0 montrée plus haut.

<servlet>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ServletAdaptor</servlet-name>
<url-pattern>/myjax/*</url-pattern>
</servlet-mapping>

Si le context de la Web Application est « mywebapp », toutes les requêtes allant vers /mywebapp/myjax seront destinées à Jersey.

II-A-2 - Tester GET

Créons donc une classe dans le package destiné à recevoir vos requêtes. Mon package s'appelle demo.myjax , et la classe EvenBondGetREST.

package demo.myjax;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

/**
* First tests with JAX-RS
* @author Nicolas Zozol for Robusta Web Library Tutorial
*/
@Path("/bond")//Full path is so /mywebapp/myjax/bond
public class EvenBondGetREST {

@GET
@Produces("text/html")
public Response getBond(){

Response r = Response.status(Status.OK).entity("<html><body>Bond, James Bond</body></html>").build();
return r;

}
}

Nous déployons la Web Application avec le serveur Glassfish (ou Tomcat), et lançons Firefox. Le port sera 8080.
Tout se passe comme prévu. La balise <head/> est ici automatiquement rajoutée par le processing de Firefox. Rappelons au passage que taper une URL dans le navigateur appelle une requête Http GET vers l'url citée.

II-A-3 - ResourceController fournit par Robusta

James Bond envoie une requête POST à notre ResourceController pour ajouter une donnée. Nous allons valider cette requête en vérifiant sommairement l'identification. Voici le code Ajax, fait à partir de prototype.js et robusta.js pour le traitement XML de la réponse.

<!--
POST /mywebapp/myjax/bond
Authorization : James Bond
<root><ennemy>Le Chiffre</ennemy></root>
-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Test Post with Ajax</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="/mywebapp/lib/js/prototype.js"></script>
<script type="text/javascript" src="/mywebapp/lib/js/robusta.js"></script>
</head>
<body>
<div id="message" style="background-color:yellow;font-weight:bold"></div>
</body>
<script type="text/javascript">
var url ="/mywebapp/myjax/bond/";
var postBody="<root><ennemy>Le Chiffre</ennemy></root>";
new Ajax.Request(url, {
method: 'POST',
contentType :'application/xml;charset=UTF-8',
postBody : postBody,
requestHeaders:$H({Authorization:"James Bond"}),
onSuccess: function(transport) {
var result = robusta.VerySimpleXml.getValue(transport,"message");
$("message").update(result);
}
});
</script>
</html>

La requête Ajax arrive vers la fonction annotée par POST :

@Path("/bond")//Full path is so /mywebapp/myjax/bond
public class EvenBondGetREST extends JaxRsResourceController {

@POST
@Produces("application/xml")
public Response ajaxPost(String content) {//Content corresponds to the postBody of the request
try {
String userString = getAuthorizationValue();// The ResourceController finds the Authorization header of the request

if (userString.contains("Bond")) {//light authentication :)
String ennemy = new VsxSax().getValue(content, "ennemy"); // VsxSax signifie "VerySimpleXml, Sax implementation"
String message = userString+" has a new Ennemy :" + ennemy;
return classicSuccess(message);
} else {
throw new UnauthorizedException("User " + userString + " is not allowed");
}
} catch (Exception ex) {
return classicFailure(ex);
}
}
}//End of class

Le résultat est un succès :

Si toutefois un utilisateur perfide tentait de se connecter avec une mauvaise autorisation, une UnauthorizedException serait lancée, aussitôt 'catchée' et une réponse adéquate serait renvoyée. Cette « identification » n'est évidemment pas suffisante, et nous développerons une stratégie acceptable au chapitre « Sécurité »

III - JSP et JSF

III-A - Sites web avec JSP et JSF

S'il est parfaitement possible de travailler avec des JSP pour contrôler les requêtes, utiliser JAX-RS est à mon avis largement préférable. Cependant on n'a pas toujours le choix, et JSP permet également d'écrire plus proprement du xml. La construction de tags spécialisés est de plus un outil puissant.

Notons que les outils de ce chapitre sont également valables pour JSF. Si vous souhaitez utiliser des composants JSF pour la vue, alors votre site web peut être RESTful et utiliser Robusta.

III-A-1 - Quand utiliser JSP plutôt que JAX-RS ?

JSP a été créé pour éviter que les servlets n'envoient trop souvent out.println(« some html with \ » everywhere"). JSF en est une évolution majeure. On utilisera donc JSP par ordre de préférence pour :

- afficher une page Web, sans Ajax. La méthode HTTP sera toujours GET ;

- afficher le résultat d'un formulaire HTML sans Ajax. La méthode sera en général POST ;

- afficher un fragment de page web par Ajax, pour gérer l'affichage par Java plutôt que par Javascript ;

- faire un service Web : JAX-RS est interdit dans le projet ou le XML produit est complexe.

J'utilise par exemple JSP pour un service Web sur Google App Engine, car le Container fourni par Google a des restrictions incompatibles avec Jersey. Il existe un « patch » non officiel qui fonctionne, mais avec des exceptions par moment.

III-A-2 - La structure avec JSP

L'utilisateur envoie une requête en tapant une adresse dans son navigateur, ou par Ajax. Cette requête arrive dans une page JSP spécifique, comportant un javabean comme ResourceController et le tag <robusta:request/> . Ce javabean doit implémenter l'interface ResourceController, et peut étendre la classe JspResourceController.

Structure générale d'une page ou d'un service REST par JSP

III-A-3 - Le tag <robusta:request> et son JspResourceContoller

Supposons que notre Espion ait supprimé l'ennemi numéro 1. Place au suivant, une mise à jour est nécessaire en faisant une requête PUT vers une page jsp.

<!--/*
PUT /mywebapp/resources-jsp/ennemi1.jsp
Authorization : James Bond
<idEnnemyNumero1>14</idEnnemyNumero1>
*/-->
<html>
<head>
<title>Test PUT avec Ajax vers JSP</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="/mywebapp/lib/js/prototype.js"></script>
<script type="text/javascript" src="/mywebapp/lib/js/robusta.js"></script>
</head>
<body>
<div id="message" style="background-color:yellow;font-weight:bold"></div>
</body>
<script type="text/javascript">
var url ="/mywebapp/resources-jsp/ennemi1.jsp?_method=PUT";//à cause du postBody, le param est obligatoire.
var postBody="<idEnnemyNumero1>14</idEnnemyNumero1>";
new Ajax.Request(url, {
method: 'PUT',
contentType :'application/xml;charset=UTF-8',
postBody : postBody,
requestHeaders:$H({Authorization:"James Bond"}),
onSuccess: function(transport) {
//dépend de ce que renvoie le serveur : xml ou html ?
}
});
</script>
</html>

La requête doit être analysée, et un tag JSP fourni par Robusta permet de décortiquer cette requête. Essayons tout d'abord la requête PUT vers ce tag associé au JspResourceController :

<?xml version="1.0" encoding="UTF-8"?>

<%@page contentType="application/xml" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.robustaweb.com"  prefix="robusta" %>

<jsp:useBean id="controller" class="robusta.rest.implementations.JspResourceController" scope="request" />
<robusta:request controller="${controller}"/>
<root>
<method>${controller.method}</method>
<uri>${controller.uri}</uri>
<authorizationValue>${controller.authorizationValue}</authorizationValue>
<postBody>${controller.postBody}</postBody>
<remoteIp> ${controller.remoteIpAdress}</remoteIp>
<isGet>${isGet}</isGet>
<isPost>${isPost}</isPost>
<isPut>${isPut}</isPut>
<isDelete>${isDelete}</isDelete>
</root>
<robusta:response controller="${controller}"/>

Si on exécute la requête Ajax dans cette page web vide, Firebug nous informe :

La console de Glassfish (ou Tomcat, JBOSS…) va également nous aider lorsque le Tag est mis en mode debug.

III-A-4 - Exemple avec un fragment HTML

Envoyons la requête PUT sur la page /mywebapp/resources-jsp/ennemi1.jsp. Cette page JSP va appeler un ResourceController spécifique que nous appellerons ici Ennemi1Controller.

III-A-4-a - Le code Ajax

On reprend la même page HTML précédente :demoPUTversJSP.html. Le code va insérer le fragment HTML dans le div message.

onComplete: function(transport) {
$("message").update(transport.responseText);
}

III-A-4-b - La page JSP

Voici le code qui comporte quatre parties : l'entête, le travail sur le ResourceController, l'affichage du résultat selon la méthode, et le statusCode de la réponse.

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/xml" prefix="x"%>
<%@ taglib uri="http://www.robustaweb.com"  prefix="robusta" %>

<jsp:useBean id="controller" class="jspcontrollers.Ennemi1Controller" scope="request" />
<jsp:setProperty name="controller" property="*" />
<robusta:request controller="${controller}"/>

<%-- GET : --%>
<c:if test="${isGet}"><!-- Nothing --></c:if>

<%-- POST : --%>
<c:if test="${isPost}"><!-- Nothing --></c:if>

<%-- PUT : --%>
<c:if test="${isPut}">
<c:choose>
<c:when test="${controller.updateOK}">
<div> Some HTML saying next <a href="http://www.007.com">James Bond</a> Movie will show a new Ennemy with codeName:${controller.nextVillain} </div>
</c:when>
<c:otherwise>
<x:parse var="error" doc="${controller.classicFailure}"/>
<div>Error : <x:out select="$error//message"></x:out> </div>
</c:otherwise>
</c:choose>
</c:if>

<%-- DELETE : --%>
<c:if test="${isDelete}"><!-- Nothing --></c:if>

<robusta:response controller="${controller}"/><!-- Send the status from the controller -->

III-A-4-c - Le ResourceController

Le ResourceController dérive de JspResourceController et implémente une des quatre fonctions de contrôle parmi isReadOK, isCreateOK, isUpdateOK et isDeleteOK.

L'interaction avec JSP suppose la création de fonctions getter/setter. Les setters seront utiles si la requête contient des paramètres url classiques.

public class Ennemi1Controller extends JspResourceController {

Integer nextVillain;

@Override
public boolean isUpdateOK() {
try {
if (getAuthorizationValue().contains("Bond")) {
//Business Code
nextVillain=new VsxSax().getSimpleInteger(getPostBody(),"idEnnemyNumero1");

} else {
throw new UnauthorizedException("User :" + getAuthorizationValue() + " is not accepted");
}

return true;
} catch (Exception ex) {
setClassicFailure(ex);
return false;
}
}

public Integer getNextVillain() {
return nextVillain;
}
}

III-A-4-d - Résultats

Si l'utilisateur utilise dans sa requête le header Authorization : « James Bond », alors cette requête Ajax aura grâce à transport.responseText à disposition le fragment HTML.

Sinon ce sera le fragment :

<div>Error : http statusCode : 401 ; message :User : S.M.E.R.S.H is not accepted </div>

IV - La Sécurité

IV-A - Rappels sur la sécurité à travers le Web

IV-A-1 - HTTPS et le certificat

Le protocole HTTPS a pour fonction de crypter les données entre le client et le serveur. Si vous vous connectez sur https://www.mi5.co.uk/home, aucun employé de votre FAI ne pourra lire vos données.

Sauf si cet employé a intercalé un serveur s'appelant également mi5.co.uk, à l'insu du site officiel. Le certificat a donc pour objet de s'assurer que le site mi5.co.uk est bien le site officiel, et non un site fourni par un escroc.

Une authentification (presque) parfaitement certifiée

Ces deux outils sont indispensables à un site sécurisé, mais ne seront cependant pas le sujet de ce chapitre : ce n'est pas à Java de s'occuper de cela. Je signalerais quand il sera simulé une requête cryptée.

IV-A-2 - L'authentification BASIC

Nous avons déjà abordé la notion d'authentification, en remplissant le champ Authorization par 'James Bond' et en l'envoyant à travers une requête non cryptée. Bien que très peu de gens savent lire ou créer eux-mêmes une requête HTTP, il y a peu de chance que cette méthode satisfasse les normes du MI5.

Le protocole dit BASIC sur Internet envoie une autorisation contenant l'identifiant (le terme anglais username est moins ambigu) et le mot de passe (password est plus pratique). Si l'on envoie à travers une requête cryptée :

HTTPS 1.1
POST /mi5/login
Authorization: BASIC "jamesbond:howisyourblanquette"
<root>It's good</root>

Alors seuls le serveur et son administrateur sont capables de connaître le couple username:password. Vous avez surement déjà rencontré une telle demande d'identification reconnaissable par un pop-up :

Il est recommandé d'utiliser des caractères spéciaux comme ' ' ou '%' dans un password, malheureusement le protocole HTTP(s) n'aime pas ces caractères. On utilise donc une fonction particulière nommée Base64 afin d'envoyer le couple ; cette fonction se trouve dans la classe robusta.utils.Codec. Codec.encodeBase64(« jamesbond:howisyourblanquette »)=amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=

On utilisera donc :

HTTPS 1.1
POST /mi5/login
Authorization:BASIC amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=
<root>It's good</root>

Base64 se décode aussi facilement par Codec.decode64(). De plus une chaîne Base64 se remarque très facilement puisqu'elle finit souvent par un ou deux caractères '='. Il est donc EXTRÊMEMENT important d'utiliser HTTPS pour ce protocole, sinon n'importe quel hacker de votre FAI ou dans votre entreprise trouverait le mot de passe.

Le navigateur mémorise le champ Authorization, et chaque future requête vers le site web incorporera cette autorisation. En général, votre navigateur arrêtera d'envoyer ce champ à son prochain redémarrage. Il est possible que votre navigateur vous demande d'enregistrer le mot de passe, et il n'existe pas de bouton logout.

IV-A-3 - Avantages et défauts de la BASIC Authentication pour une application web

Il s'agit ici de s'intéresser aux contraintes d'un site qui veut faire de l'argent en attirant des clients : design, ergonomie et sécurité sont les critères. On exclut cependant les applications hautement sensibles comme les banques, voire les emails en Saas.

Défauts

- Le « pop-up style » est ringard, et non personnalisable.

- Pas de log-out : si plusieurs personnes utilisent le même PC, il y a danger.

- Il faut être sûr que HTTPS est bien utilisé, sur toutes les pages du site (ou du « realm »).

- HTTPS consomme des ressources : faut-il l'utiliser partout ?

- Contrairement aux cookies, le temps de sauvegarde du mot de passe par les Managers de sécurité du navigateur est illimité.

Avantages

- C'est techniquement parfaitement sécurisé, tant qu'il y a HTTPS et qu'une seule personne utilise le PC.

- C'est simple à implémenter.

- Tous les navigateurs gèrent un minimum l'authentification BASIC.

Pour pallier ce défaut de la nécessité de garantir HTTPS, il existe le mode d'authentification DIGEST, mais les autres défauts perdurent.

Ces deux paragraphes montrent la nécessité de créer son propre système de login, comme le font tous les sites web. Robusta propose le mode ArmyLazy, proche du mode BASIC.

IV-B - Les contraintes ergonomiques et sécuritaires d'un site web

Le protocole ArmyLazy a été inventé pour répondre aux contraintes du site Edupassion.com. Il s'agit d'un logiciel de bulletin de notes et de QCM en ligne et doit se plier aux contraintes suivantes :

- les élèves ont accès aux mêmes ordinateurs que les professeurs ;

- certains élèves sont fils de professeurs dans la même école, et ont accès à la maison aux mêmes ordinateurs ;

- il est illusoire de penser qu'un professeur cliquera toujours sur log-out ;

- il est complètement inacceptable qu'un élève modifie les notes ;

- il est extrêmement gênant qu'un élève voie les notes des autres élèves ;

- il est gênant qu'un élève voie le sujet du prochain QCM ;

- si le professeur doit rentrer son mot de passe à chaque page, il n'utilisera pas le logiciel ;

- le professeur ne va pas laisser le logiciel de notes ouvert en pleine classe, mais peut laisser une page de présentation ou de QCM.

Il apparait logique de créer des zones ultrasécurisées, et d'autres moins sécurisées, mais moins contraignantes. Voici les zones :

- NONE : accès direct pour les personnes non inscrites : présentation, tutoriels, etc. ;

- LAZY-INIT : cette connexion cryptée, sous HTTPS, permet de se logguer et de créer le cookie ;

- LAZY : on se log, puis on navigue normalement. Si on quitte le site web, on revient sans se relogguer (style forum, facebook, email) ;

- ARMY : on doit taper le mot de passe, même si on s'est déjà loggué. Army est entièrement sous HTTPS.

La plupart des sites peuvent se passer du mode ARMY !

Par conséquent, le mot de passe ne peut être enregistré ad vitam aeternam, et doit être retapé pour passer à certaines pages. Voici quelques exemples en images.

Présentation (None) vers Dashboard (Lazy) : un cookie suffit

Dashboard (Lazy) vers vue des notes (Army ou Lazy ?): à priori taper le password, dépendra des retours d'utilisation

Dashboard (Lazy) vers bulletin de notes (Army) : No way ! Il faut rentrer le Password

Quizz (Army) vers Modification des notes (autre zone Army) : il faut rentrer Password

Le dernier exemple montre que deux zones Army se comportent comme deux « realm » différents de la Basic Authentication.

IV-C - Cryptage != Sécurité

Si HTTPS n'existait pas, les escrocs n'auraient aucun mal à pirater vos mots de passe et leur business serait juteux. Puisque HTTPS existe, le crime ne paie pas et peu d'escrocs s'amusent à renifler vos requêtes donc paradoxalement crypter ou pas un site web a finalement peu d'influence sur la sécurité.

Le crime est dans la tentation ! Comptez davantage sur votre conjoint et surtout votre collègue pour vous piéger - éventuellement des élèves dans une école. C'est donc l'accès aux mots de passe qu'il faut protéger, et on en est bien loin. Faites un tour dans l'onglet Outils -> Options -> Mots De passe -> Mots de passe enregistrés du Firefox à votre travail.

Ces mots de passe sont tout autant lisibles dans les autres navigateurs s’ils ont été sauvés, et si l'on cherche un peu. Il ne faut donc jamais sauver son mot de passe d'email ou de banque sur son lieu de travail, et ne pas utiliser un mot de passe générique (c.-à-d. celui utilisé pour developpez.com, hattrick, labrute, etc. ) pour l'email, la banque ou d'autres applications web sensibles, comme Facebook.

Plus subtile est la récupération du cookie. Supposons que vous vous connectiez à Facebook au boulot, sans explicitement sauver le mot de passe. Un intrus peut copier le cookie (en 20 secondes avec une clé USB, ou par réseau) puis se connecter tranquillement sur son PC avec votre compte. Heureusement, le cookie contient rarement le mot de passe en clair.

Supposons maintenant que tous les sites web gèrent leurs cookies de la même façon, avec des utilisateurs utilisant le même mot de passe sur la plupart des sites. En récupérant un cookie de Facebook, vous pourriez vous connecter sur le compte Gmail. Le plus sûr est donc de faire intervenir une partie aléatoire dans la génération du cookie.

Dernier point, encore plus pernicieux. Vous faîtes un site web innocent jaimelesoranges.com, sauvez les mots de passe en clair, et un collègue mal intentionné a accès à la base de données. On peut coder le password en MD5, ou encore rajouter un grain de sel, ou « salt » dans la terminologie. À partir du mot de passe, vous sauvez dans la base de données MD5 (salt+password). Pour le décoder, le collègue aura besoin de l'accès à la base de données ET au code, ce qui diminue un peu les risques. Par contre, si un hacker extérieur accède à la basse de données, il n'aura pas accès au salt, et ne pourra pas décoder le mot de passe.

La seule réelle protection des utilisateurs est d'avoir un mot de passe différent pour chaque application sensible.

IV-D - Le protocole ArmyLazy

ArmyLazy est un protocole de sécurité inventé de toute pièce pour Robusta, mais fortement inspirée de BASIC Authentication. Il n'y a rien de révolutionnaire - et c'est une bonne chose. Comme c'est un protocole, ArmyLazy est indépendant de Java et donc de Robusta, bien que Robusta propose une interface et un Enum pour en faciliter l'implémentation.

Comme dit précédemment, il y a quatre zones à définir : NONE, LAZY-INIT, LAZY et ARMY. Sachant que la plupart des sites pourront se passer d'ARMY.

IV-D-1 - Un peu de vocabulaire

Dans le header Authorization : LAZY-INIT amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=

- L'ensemble « LAZY-INIT amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU= » est appelé AuthorizationValue

- La partie codée « amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU= » est appelé Credential

- Le prefix « LAZY-INIT » est appelé mode

IV-D-2 - NONE

Il n'y a rien à faire, si ce n'est le préciser. Ça y est, c'est fait !

IV-D-3 - LAZY-INIT

Par contre la partie LAZY-INIT est de loin la plus compliquée :

- le navigateur envoie une requête HTTPS ;

- le serveur décode le header Authorization ;

- le serveur valide le couple username/password ;

- le serveur crée ou update dans la base de données le lazyToken ;

- le serveur renvoie des données ;

- le navigateur analyse les données ;

- le navigateur crée les cookies ;

- le navigateur affiche la suite.

IV-D-3-a - Le navigateur envoie la requête

La bonne vieille boite Login

Le formulaire peut-être un FORM avec un bouton SUBMIT, ou une série de champs : le bouton envoie une requête Ajax. Quoi qu'il en soit, la page web montrant la boite doit être sous HTTPS !

Pour une requête Ajax, un username « jamesbond » et un password « howisyourblanquette », le header Autorization sera :
Authorization : LAZY-INIT Base64(« jamesbond: »+MD5(« howisyourblanquette »))
Ce qui se code en utilisant Prototype.js et robusta.js :

var username=$F("username"), password=$F("password"); //$F() est une fonction de Prototype.js lisant les champs.
var credential = robusta.Codec.encodeB64( username+":"+robusta.Codec.md5(password) );
var authorizationValue = "LAZY-INIT " + credential;

new Ajax.Request("/mywebapp/myjax/login", {
method: 'POST', //eventuellement GET, c'est pareil
contentType :'application/xml;charset=UTF-8',
requestHeaders:$H({Authorization:authorizationValue}),
onSuccess: function(transport) {
//analyse de la requête
//Création des cookies
//affichage de la suite sur la même page ou redirection
}
});

Pour des raisons de licence (GPL vs BSD), robusta.js n'implémente pas encore directement de fonction MD5. Vous devez joindre ce fichier md5.js source créé par Paul Andrew Johnston : http://pajhome.org.uk/crypt/md5/

IV-D-3-b - Le travail du Serveur

Il va de soi que tout ce travail peut-être fait en PHP, ruby ou autre. L'implémentation est assez longue, donc très partiellement écrite ici, et je fournis le code source de la démo. Nous allons nous concentrer sur les bénéfices apportés par Robusta. Voici la table DbUser utilisée pour stocker les comptes.

id

username

passwordMD5

email

lazytoken

Nous allons créer un ResourceController JAX-RS généraliste, pour tout le Web Service, implémentant l'interface ArmyLazyController. Le LoginController n'aura plus qu'à étendre ce SecuredController. Nous avons aussi besoin d'un objet implémentant robusta.security.User, qui est simple, mais suffisant. Robusta propose un objet UserImpl.

Le code Java ci-dessous récupère les credentials et extrait le username et password grâce aux fonctions Codec.getUsername(b64string) et son équivalent getPassord().

import robusta.commons.codec.Codec;
import robusta.security.User;
import robusta.security.UserImpl;
import robusta.security.ArmyLazyController;
import robusta.security.ArmyLazy;//Plus quelques imports

class SecuredController extends JaxRsResourceController implements ArmyLazyController{

User validateLazyInitAuthorization() throws UnknownUserException, ServerException, AuthenticationException{
ArmyLazy mode = ArmyLazy.getArmyLazyMode(authorizationValue);

if (mode != ArmyLazy.LAZY_INIT) {
throw new AuthenticationException("Illegal Authentication mode :" + mode);
}

String credentials, username, passwordMD5;
try {
credentials = mode.retrieveCredentials(authorizationValue);
username = Codec.getUsername(credentials);
passwordMD5 = Codec.getPassword(credentials); /* password should be in MD5 ; javascript is able to do it */
} catch (Exception ex) {
throw new AuthenticationException("Authorization Value :" + authorizationValue + " - is probably not correct");
}

this.user = fetchUserWithPassword(username, passwordMD5);// recherche dans la database
updateLazyToken(user.getId());// insertion du lazytoken  (MD5(randomString)) dans la database.
return user;
}

User validateLazyAuthorization() throws UnknownUserException, ServerException, AuthenticationException{}
User validateArmyAuthorization() throws UnknownUserException, ServerException, AuthenticationException{}
public String getStoredLazyToken(){}

}

Ce controller pourra être utilisé dans tous les controllers de votre application. Vous validerez la sécurité et récupérerez votre User en une ligne. Voici le LoginController que tapera la requête avec le path /mywebapp/myjax/login

@Path("/login")
public class LoginController extends SecuredController {

@POST
@Produces("application/xml")
/**
* The user must send something like :
* POST /xseditor/login
* Authorization:LAZY-INIT amFtZXNib25kOmhvd2lzeW91cmJsYW5xdWV0dGU=
* @return xml with idUser, username, lazyToken and email nodes and values
*/
public Response loginUser() {

try {
System.out.println("login authorization :"+getAuthorizationValue());
User user = validateLazyInitAuthorization();
String lazyToken = getStoredLazyToken();
if (!StringUtilities.validateMD5(lazyToken))//check lazyToken ressemble à du MD5
throw new ServerException("LazyToken is not correctly created");
// magic Xml function :)
String xml =  new VsxSax().buildVerySimpleXml("idUser", user.getId(),
"username", user.getUsername(), "email", user.getEmail(), "lazyToken",lazyToken );
return getResponse(xml);

} catch (Exception ex) {
MyLog.sendLog(ex, "");
return classicFailure(ex);
}

}

}

IV-D-3-c - Le navigateur reçoit la requête

Le navigateur reçoit le LazyToken sous forme de MD5, comme '9386f6cde6a557f23eeafa70c6e8e9d3'. Il y a très peu de chances que cela arrive, mais théoriquement, ce token peut être en double pour deux utilisateurs différents. Le cookie envoyé pour l'identification doit donc incorporer le username, car il est marqué Unique dans la database.

Autre problème, un cookie ne peut contenir d'espace : 'LAZY xyz=' n'est pas valable. Le cookie aura pour nom : lazy et valeur : xyz=. Je conseille fortement le nom lazy, mais la classe robusta.MyRobusta permet de renommer le nom du cookie qui sera détecté par les tags. Reprenons la fonction onSuccess () de la requête Ajax :

onSuccess: function(transport) {
//analyse de la requête : username est déjà connu
var lazyToken = robusta.VerySimpleXml.getValue(transport, "lazyToken");
var idUser = robusta.VerySimpleXml.getValue(transport, "idUser");
var email = robusta.VerySimpleXml.getValue(transport, "email");
//Création des cookies
document.cookie="idUser="+idUser+";path=/";
document.cookie="email="+email+";path=/";
document.cookie="username="+username+";path=/";
var lazyCredentials = Base64.encode(username+":"+lazyToken);
document.cookie="lazy="+lazyCredentials+";path=/";

//affichage de la suite : dans l'exemple on affiche idUser
}

Ceci conclut le travail sur LAZY-INIT. Heureusement, le travail sur LAZY et ARMY sera quasiment identique.

IV-E - LAZY

Le mode LAZY permet de se connecter aux zones nécessitant une identification, mais en général non cryptées et non stratégiques. L'authentification se fait en principe par cookie, mais on peut également utiliser le header Authorization ce qui est pratique pour les tests unitaires.

IV-E-1 - Tag dans une page JSP

Robusta fournit un tag <robusta:authentication/> qui permet de valider le cookie (ou le header Authorization), et fournit ensuite à la page l'attribut ${user} implémentant l'interface robusta.security.User. Selon votre implémentation, vous aurez accès à ce que vous voudrez de cet utilisateur. Vous avez également accès à ${authorizationValue}.

Voici un exemple :

<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.robustaweb.com" prefix="robusta" %>

<jsp:useBean id="controller" class="demo.myjax.SecuredController" scope="request" />
<robusta:authentication controller="${controller}" />

<html><body>
User Id : ${user.id}<br/>
Email : ${user.email}
</body></html>

Notre page JSP utilise comme bean un JaxRSResourceController, sans utiliser les capacités liées à @Context. Cela montre la versatilité de la spécification JAX-RS. Notez aussi que JspResourceContoller aurait très bien pu être utilisé, en y implémentant l'interface ArmyLazyController.

IV-E-2 - Fonctionnement interne

Le fonctionnement est simple :

- la requête arrive sur la page JSP avec un cookie lazy ;

- un javabean SecuredController est créé ;

- le tag <robusta:authentication/> signale l'authorizationValue (la valeur du cookie) au controller ;

- le tag appelle la fonction validateLazyAuthentication() du controller ;

- le tag crée l'attribut ${user}.

Notons d'une part que l'implémentation de validateLazyAuthentication() est très proche de celle du mode LAZY-INIT, et d'autre part que rien dans la page jsp ne gère automatiquement une exception, notamment en cas d'absence de cookie.

IV-E-3 - Résultat

Voici le résultat lors de l'appel d'une page nécessitant une identification par cookie.

IV-E-4 - Détection dans un Service Web

Nous avons utilisé le tag <robusta:authentication/>, mais JAX-RS a accès aux données de la requête (HttpServletRequest) grâce à l'utilisation de l'annotation @Context. JaxRsResourceController utilise ce système pour détecter le cookie et le header de la requête. Si votre ResourceController hérite de JaxRsResourceController, vous n'avez plus qu'à utiliser getAuthorizationValue().

IV-F - ARMY

Après avoir sécurisé votre domaine par HTTPS, le mode ARMY est extrêmement simple à implémenter une fois les autres modes assimilés. Le problème est de récupérer les valeurs du username et du password. En effet, une page web ne gère aucun état, excepté le cookie qui n'est pas sécurisé.

La solution consiste donc à passer par une page de Login, avec la fameuse boite demandant le couple username/pasword. Puis on fait la suite par Ajax. Ajax est en effet ici la solution de tous les problèmes d'état, car l'application peut se comporter comme une application Desktop.

Autrement, passer d'une page (ou URI) à une autre n'est pas aisé. Est-ce vraiment nécessaire ? Posez-vous bien la question, car cela augmente aussi les risques de faille de sécurité. Si votre réponse est affirmative, et si les exigences en ergonomie ne permettent pas de redemander le password, il existe heureusement des solutions. Vous pouvez faire passer la forme MD5 du password en paramètre de requête dans l'URL - c'est crypté ! Plus discret, il est possible d'utiliser le header « WWW-Authenticate » de la réponse pour faire passer ce password ; c'est ce qu'utilise l'authentification DIGEST.

IV-G - Conclusions

Il existe bien d'autres solutions de sécurité : JAAS, Acegi (Spring Security), etc. Le protocole ArmyLazy ne repose pas sur Java, et peut être aussi bien utilisé en PHP qu'en Ruby. Par extension, il n'y a pas non plus de dépendance à un serveur particulier. Cependant il vous faudra implémenter l'ensemble, car Robusta ne fournit qu'une interface et un Enum.

ArmyLazy ne fonctionne pas sur le principe d'Inversion de Controle, ni d'Interceptor. Côté serveur, cela se passe dans le code, et l'interface ArmyLazyController fournit à chaque fois un objet User, tout de suite réutilisable. Vous pouvez cependant utiliser le protocole ArmyLazy et vous passer sans difficulté de ce Controller, ou écrire le vôtre.

De plus, ArmyLazy ne protège pas votre application contre tout type d'injection de code. La classe robusta.utils.StringUtilities propose quelques fonctions efficaces, mais vous devrez pointer à chaque requête quelles sont les dangers potentiels, ainsi que définir le type de données acceptables.

Enfin, ne vous reposez pas sur la technologie. Si votre utilisateur ne voit pas de données sensibles à l'écran ou s'il ne s'est pas loggué le matin, il sera moins vigilant et un intrus pourra le surprendre. Ergonomie = Sécurité.

V - Les Exceptions

V-A - Le Status Code d'une requête HTTP

Le protocole HTTP fournit une liste de codes déclinés en quatre catégories :

- 2xx : succès de la requête ;

- 3xx : redirection de la requête ;

- 4xx : la requête envoyée pose problème ;

- 5xx : le serveur a rencontré un problème, alors que la requête est sans doute valable.

Beaucoup de codes semblent obsolètes, cependant la panoplie des erreurs 4xx doit être pleinement utilisée afin de renseigner l'utilisateur sur les corrections qu'il peut apporter à sa requête.

Évitez simplement de toujours renvoyer 400 ou 500. Il est également possible de spécifier soi-même ses status codes si cela vous semble nécessaire, en utilisant par exemple la partie 550-580 pour exprimer les erreurs envisageables dans votre code.

V-B - Les Exceptions dans les Webs Services

Vous avez pu remarquer que mon code dans les fonctions du Web Service est toujours similaire :

@METHOD
@Produces("application/xml")
public Response function() {

try {
//Appel vers les classes Business, qui lancent des exceptions
return classicSuccess();//ou return getResponse(xml) si du XML spécifique est produit

} catch (Exception ex) {
//Renvoi de l'erreur - Il est également intéressant de logger cette erreur
return classicFailure(ex);
}

}

}

C'est la partie Business qui lance l'exception, la partie JAX-RS ne fait que mettre en forme. La fonction classicFailure() va donc chercher le meilleur message et code correspondant à l'exception lancée. Cela sera plus précis si vous avez lancé une RestException.

V-C - La classe RestException et ses dérivés

Les RestException sont des classes dérivant de Exception et ayant les méthodes :

public int getStatusCode();
public void setStatusCode(int statusCode);
public String getClassicResponse();
public String getClassicResponse(String message);

Les RestExceptions permettent donc d'automatiser la réponse.

VI - Very Simple XML

VI-A - Définition par l'exemple

Le code xml suivant permet de créer un nouvel utilisateur :

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:user='http://user.com' xmlns:datas='http://www.exemple.com/datas'>

Posting new User
<user:firstname>Penny</user:firstname>
<user:lastname lastname="Money"/>
<datas:email where="JustAfter"/>
moneypenny@mi5.co.uk
</root>

Il y a quand même plus de chances que la requête envoie :

<root>
<firstname>Penny</firstname>
<lastname>Money</lastname>
<email>moneypenny@mi5.co.uk</email>
</root>

Le dernier exemple est appelé VerySimpleXml et 95 % de vos requêtes seront similaires. Robusta fournit des outils pour créer et lire très efficacement ces « documents ».

VI-B - SAX ou JDOM ?

Robusta propose une implémentation avec Sax (VsxSax), et une implémentation avec Jdom (VsxJdom). Sax est fourni dans la majorité des JVM, alors qu'il vous faudra télécharger Jdom et le mettre dans le ClassPath. Par contre VsxJdom fournit des méthodes supplémentaires pour obtenir un objet de type org.jdom.Document.

Chaque appel à VsxSax nécessite également de parser à nouveau le document. Il n'y a pas encore de Benchmarks, mais il semble logique que VsxJdom sera plus compétitif si vous faites plusieurs travaux sur le même document. Mais ce sera toujours dérisoire face aux autres travaux du serveur.

VI-C - L'interface VerySimpleXml

Voici quelques méthodes parmi les plus utiles.

public String getValue(String xml, String nodeName) throws XmlException;
public Long getSimpleNumber(String xml, String nodeName) throws XmlException;
public String getPossibleValue(String xml, String nodeName) throws XmlException;
public  String buildVerySimpleXml(Object  ... elements)  throws XmlException;

VI-D - Quelques exemples

Voici un exemple avec Jdom, analysant une requête pour enregistrer un utilisateur.

@POST
public Response registerUser(String content) {

VsxJdom vsx=new VsxJdom();

try {
org.jdom.Document doc = vsx.getDocument(content);
String email = vsx.getPossibleValue(doc, "email");
String passwordMD5 =vsx.getPossibleValue(doc, "passwordMD5")

if (!StringUtilities.validateEmail(email) || !StringUtilities.validateMD5(passwordMD5)) {
throw new IllegalArgumentException("email or password is not valid");
}
//other code
} catch (Exception ex) {
return classicFailure(ex);
}
}

Nous avions déjà vu l'utilisation de buildVerySimpleXml() :

String xml =  new VsxSax().buildVerySimpleXml("idUser", user.getId(),
"username", user.getUsername(), "email", user.getEmail(), "lazyToken",lazyToken );

produira :

<root>
<idUser>254</idUser>
<username>jamesbond</username>
<email>jamesbond@mi5.co.uk</email>
<lazyToken>abcdefghi==</lazyToken>
</root>

Enfin un exemple avec du XML un peu plus complexe, mais avec toujours une structure simple. La méthode getCouples est très utile pour analyser une liste de Resources.
Soit la variable String xml =

<root>
<object><data>Jambalaya</data><beans>75</beans></object>
<object><data>Cassoulet</data><beans>150</beans></object>
<object><data>Garbure</data><beans>15</beans></object>
</root>

L'appel getCouples(xml, « data », « beans ») renverra le CoupleList [(« Jambalaya », « 75 »), (« Cassoulet », « 150 »), (« Garbure », « 15 »)].

VI-E - Conclusion

VerySimpleXml peut être utilisé dans tout type de projet Java utilisant des structures XML simples. C'est pourquoi Robusta n'est pas un framework, mais une bibliothèque qui peut accompagner tout type de projets.

VII - Les Clients HTTP

Robusta définit une interface RestClient avec des fonctionnalités simplifiées, mais suffisantes et bien organisées pour une architecture REST. Comme les implémentations SunRestClient et ApacheRestClient sont des wrappers pour les HttpClient Sun et Apache, il est possible d'accéder également à l'ensemble de leurs fonctionnalités.

Notons que la version 1.0 de Robusta implémentera un GwtRestClient avec une interface similaire, mais asynchrone.

VII-A - L'interface RestClient

//Utiliser une fois suffit
public void setApplicationUri(String uri); //ApplicationUri est également défini par le constructeur
public void setAuthorizationValue(String authorizationValue);
public void setContentType(String contentType);

//Optionnel
public void setPostBodyToNextRequest(String postBody, boolean contentTypeIsXml);

//Pour chaque requête
public String executeGETmethod(String relativeFileWithNoParam, CoupleList<String, Object> parameters) throws HttpException, RestException;
public String executePOSTmethod(String relativeFileWithNoParam, CoupleList<String, Object> parameters) throws HttpException, RestException;
public String executePUTmethod(String relativeFileWithNoParam, CoupleList<String, Object> parameters) throws HttpException, RestException;
public String executeDELETEmethod(String relativeFileWithNoParam, CoupleList<String, Object> parameters) throws HttpException, RestException;

public int getLastStatusCode() ;

VII-B - Implémentation Apache ou Sun ?

Le client d'Apache est présumé plus stable et plus efficace que la version Sun. Cependant le HttpClient d'Apache nécessite l'installation dans le ClassPath des trois bibliothèques d'Apache. Apache est proscrit pour les applets. D'une part le temps de chargement est assez long, mais surtout les bibliothèques annexes tentent un chargement sur le disque local, ce qui est interdit.

Par souci de simplicité, il me semble donc préférable de se tourner vers le SunRestClient qui est immédiatement utilisable.

VII-C - Exemples

Voici un accès GET exécuté par le SunRestClient, utilisant deux paramètres de requête :

RestClientSun client = new RestClientSun("http://www.mi5.co.uk/);
String result = client.executeGETmethod("/people", CoupleList.builder("firstname", "James","city","London") );
//result contient la réponse

Voici un exemple avec la méthode POST.

String xml = "<root><user>Shark</user><ennemy>true</ennemy></root>";
RestClientSun client = new RestClientSun("http://www.mi5.co.uk/);
client.setPostBodyToNextRequest(xml, true);
String result = client.executePOSTmethod("/people", null );

Conclusion et Remerciements

Ce tutoriel est long et n'aborde qu'une partie des éléments d'un site web basé sur Java. Mais ce n'est pas Java qui est complexe, c'est la création d'un site web performant supportant une forte montée en charge. Robusta et REST font partie des outils efficaces pour simplifier votre tâche.

Tout programme commence par du café. Je remercie donc les planteurs et cueilleurs de café.

Merci l'Équipe de Netbeans de fournir mon principal outil de travail gratuitement.

Et un grand merci à Ricky81 pour la longue relecture.