02 septembre 2009
Résumé
Ce document a été réalisé sous GNU/Linux avec vim, au format docbook , mis en page avec le processeur XSLT saxon développé par Michael Kay et les feuilles de styles de Norman Walsh.
Ce document est placé sous la Licence GNU Free Documentation License, Version 1.1 ou ultérieure publiée par la Free Software Foundation.
Table des matières
On classe généralement, d'un point de vue "physique", les architectures d'applications en trois catgories :
Autonome
C'est le cas d'applications qui ne dépendent pas de systèmes tiers, autres que ceux généralement offerts par un système d'exploitatioin.
Client/Serveur
Modèle phare des années 80, où le système d'information est centré sur les données. La logique métier est répartie, plus ou moins, entre le client (qui détient les formulaires) et le serveur (SGBDR).
Architecture 3 tiers (et plus)
Tiers veut dire parties. Alors que les applications C/S sont de types 2 parties (le client IHM et le SGBDR), les architectures n tiers (pour n > 2) font intervenir un middleware applicatif responsable de la logique applicative. Le terme middleware est à prendre dans le sens intermédiaire de communication entre 2 niveaux.
Cette vue par "parties" (tiers) correspond à « un changement de niveau dès qu'un module logiciel doit passer par un intermédiaire de communication (middleware) pour en invoquer un autre » - ref : www.octo.com.
Le modèle n tiers le plus répandu des années 2000 est le modèle 3 tiers Web, typiquement représenté par :
Navigateur <----> Serveur d'applications <-----------> SGBDR
présentation http pages dynamiques middleware
coordination composants logiciels SGBD
C'est ce modèle que nous vous proposons d'exploiter, en environnement Java pour la partie application.
Pour cela, nous utiliserons une technologie issue des projets Apache. Tomcat est un serveur HTTP capable de traiter certaines requêtes pointant indirectement vers des applications Java spécifiques nommées servlet. Tomcat se présente comme un conteneur de servlet/JSP (dont Catalina fait partie).
Une servlet est un programme Java qui s'exécute côté serveur. La technologie servlet n'impacte donc pas le client (navigateur). Une page JSP (Java Server Page) est une page dynamique interprétée par le serveur (et traduite par une servlet par celui-ci). Les spécifications Servlet/JSP sont accessibles sur le site de Sun http://java.sun.com/products/servlet/ et http://java.sun.com/products/jsp/.
Dans ce qui suit, vous allez devoir installer et administrer une architecture 3 tiers Web, en mode développement d'applications. Le plus souple est de monter cette architecture sur un même poste.
En phase de test, vous devriez toujours avoir à l'esprit que votre poste joue réellement plusieurs rôles : il est courant que le poste du développeur supporte les trois niveaux (présentationn, application, sgbd).
En production, ces rôles sont bien entendu répartis sur des machines distinctes.
La difficulté réside donc dans la détection d'erreur. En cas de problème, il est essentiel de déterminer le bon niveau à incriminer.
Exemple de problèmes
Côté client
La page active du navigateur a-t-elle été rafraichie ?
Interprète-il bien le code HTML qu'il reçoit ?
Côté serveur d'applications
Le serveur délivre t-il du code HTML valide au client ?
Le programme a-t-il été recompilé et déployé après compilation ?
Côté SGBD
Les paramètres de connexion à la base sont-ils acceptés ?
La requête SQL envoyée au SGBDR est-elle correcte ?
Il est grand temps de passer à l'installation du système.
Télécharger le fichier tar.gz (UNIX) ou zip (traditionnellement pour Windows) ici : http://jakarta.apache.org/site/downloads/downloads_tomcat-5.cgi.
Décompresser le fichier dans un répertoire (par exemple votre home directory /cirdan/home/login-name).
Si ce n'est déjà fait, préparer la variable d'environnement JAVA_HOME.
JAVA_HOME désigne le répertoire racine d'un jdk installé sur la machine.
Ajouter en fin du fichier .bashrc la commande : export JAVA_HOME=racine du chemin local du jdk. Faire de même avec CATALINA_HOME qui référence lechemin racine de tomcat.
Les commandes pour lancer et arrêter Tomcat se situent dans le répertoire bin de tomcat (tomcat/bin) :
Démarrer Tomcat : ./startup.sh
Arrêter Tomcat : ./shutdown.sh
Vérifier l'installation en lancant Tomcat, puis lancer mozilla et entrer l'adresse : http://localhost:8080/.
N'hésitez pas à consulter la documentation :
A chaque application web correspond une arborescence bien précise de répertoires.
D'origine, il y a autant d'applications qu'il y a de sous-répertoires directs de <TOMCAT_HOME>/webapps. Dans la figure ci-dessous, extraite de l'ouvrage JSP de Fields et Kolb (Eyrolles), "Archive web" correspond à un répertoire racine de <TOMCAT_HOME>/webapps.
Une application est livrée sous la forme d'un fichier compressé (compression zip) ayant l'extension .war.
Nous vous proposons ici une application exemple très simplifiée, qui vous servira de base de TP. Nous l'avons appelée ldvSimple, voici le contenu de l'archive :
$ jar -tf ldvSimple.war META-INF/ index.html WEB-INF/ WEB-INF/classes/ WEB-INF/classes/org/ WEB-INF/classes/org/vincimelun/ WEB-INF/classes/org/vincimelun/servlet/ WEB-INF/classes/org/vincimelun/servlet/LdvSimpleServlet.class WEB-INF/web.xml WEB-INF/src/ WEB-INF/src/LdvSimpleServlet.java
Installation de l'application exemple
Récupérer le fichier ldvSimple.war.
Placer ce fichier à la racine du répertoire TOMCAT_HOME/webapps
(attendre quelques secondes que Tomcat déploie automatiquement l'application)
Le répertoire ldvSimple sera créé pour l'application du même nom ldvSimple.
TOMCAT_HOME
-> webapps
-> ldvSimple
Puis, à partir de ldvSimple :
ldvSimple
-> WEB-INF
-> classes
Le répertoire classes contient les fichiers .class. En effet, tomcat recherchera automatiquement les .class dans ce dossier.
Par défaut, c'est le fichier index.html qui est retourné au client. C'est ici une page statique.
fichier index.html.
Le fichier index.html est placé dans le répertoire racine de l'application.
webapps
-> ldvSimple
-> index.html
Dans sa version actuelle, ce fichier contient le lien suivant : <a href="servlet/ldv?action=Hello"/a>Hello World, qui fait référence à la servlet (programme java côté serveur), selon les instructions déclarées dans web.xml (voir section suivante).
Servlet : une application java (une classe Java qui hérite de GenericServlet) destinée à être exécutée côté serveur.
Son emplacement est :
ldvSimple
-> index.html
-> WEB-INF
-> classes
-> org/vincimelun/servlet/LdvSimpleServlet.class
Par défaut, le nom de l'application est le nom de son répertoire sous webapps, ici donc ce sera ldvSimple (qui est également le nom de l'archive .war).
Lancement de l'application (par défault tomcat écoute sur le port 8080) :
http://localhost:8080/ldvSimple
Cliquez sur le lien Exemple 1, vous devriez voir :
La page HTML propose deux façons de soliciter le programme java côté serveur (la servlet LdvSimpleServlet) :
Appel d'une servlet côté client
Via un lien hypertexte (méthode GET)
<p style="text-align: center;">Tester la servlet ldvSimpleServlet :
<a href="ldv?action=Hello"> Exemple 1 </a></p>
Via un formulaire (méthode POST)
<form action="ldv" method="post"> Tester la servlet ldvSimpleServlet : <input type="submit" name="actionHello" value="Exemple 1bis"> <input type="hidden" name="action" value="Hello"> </form>
Dans les deux cas, le client fait référence au modèle URL (url-pattern) défini dans le fichier web.xml, voir plus loin (Fichier de déploiement web.xml).
L'URL pointe donc indirectement vers un programme java, nommé servlet, que nous étudierons prochainement.
Votre application doit contenir un fichier de déploiement (web.xml) spécifiant au minimum le nom des servlets et leurs paramètres éventuels, leur nom public et l'URL associée.
Exemple de fichier de déploiement de ldsSimple :
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>C/S Web avec Tomcat</display-name>
<description>
Exemple de programme avec Tomcat. STS IG, Lycée Léonard de Vinci, Melun.
</description>
<servlet>
<!-- le nom public de la servlet -->
<servlet-name>Ldv</servlet-name>
<!-- le nom réel de la servlet -->
<servlet-class>LdvSimpleServlet</servlet-class>
</servlet>
<servlet-mapping>
<!-- reprise du nom public de la servlet -->
<servlet-name>Ldv</servlet-name>
<!-- pour l'associer à -->
<!-- l'url-pattern qui permet au serveur de construire l'URL qui -->
<!-- référencera la servlet, par exemple :
http://localhost:8080/ldvSimple/ldv
-->
<url-pattern>/ldv</url-pattern>
<!-- autre exemple : (en commentaire)
<url-pattern>/servlet/tp1.exo1</url-pattern>
l'url d'acces à LdvSimpleServlet sur votre poste serait alors :
http://localhost:8080/ldvSimple/servlet/tp1.exo1
-->
</servlet-mapping>
</web-app>
Ce qui suit explique comment simplement déployer une application d'un conteneur de servlet à un autre (Tomcat, WebSphere, WebLogic, JRUN, iPlanet...).
Construire un fichier .war (fichier compressé avec jar).
Se placer à la racine de l'application, par exemple : tomcat/webapps/ldvSimple
Executer la commande : jar -cvfM ldvSimple.war . (ne pas oublier l'"espace point" à la fin).
Placer ce fichier war dans le répertoire webapps du moteur de servlet cible (par exemple lorsque vous rapatriez la première fois votre application chez vous, dans un Tomcat fraichement installé) .
Informer le conteneur du nouvel arrivant (voir documentation du conteneur).
Tomcat propose une interface http de gestion des applications. L'utilisateur doit être connu de Tomcat pour y accéder.
Par défaut, les utilisateurs sont définis dans un fichier XML (tomcat-users.xml), lu au démarrage de Tomcat. C'est sur la base de ce fichier que Tomcat, par défaut, authentifie les utilisateurs.
Nous allons ajouter un utilisateur (admin), lui fournir un mot de passe (pwachanger - en clair dans le fichier), et lui associé un ou plusieurs roles (l'association application - rôle est défini dans le web.xml de l'application).
Pour cela nous devons modifier les fichier <TOMCAT_HOME/conf/tomcat-users.xml, en y ajoutant une entrée :
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
<role rolename="tomcat"/>
<tole rolename="role1"/>
<role rolename="manager"/>
<user username="tomcat" password="tomcat" roles="tomcat"/>
<user username="both" password="tomcat" roles="tomcat,role1"/>
<user username="admin" password="pwachanger" roles="manager"/>
</tomcat-users>
Le rôle manager est déjà défini et associé à l'application manager.
Vous devez arrêter Tomcat et le redémarrer, afin qu'il prenne en compte les modifications. En effet, par défaut Tomcat utilise un objet MemoryRealm pour gérer l'authentification HTTP, mais d'autres solutions sont possibles et préférables, en utilisant par exemple un SGBDR (dans ce cas, le schéma de la base doit être conforme à celui attendu par Tomcat, voir la documentation pour cela).
Demander la liste des applications
http://localhost:8080/manager/list
Retour :
OK - Applications listées pour l'hôte virtuel (virtual host) localhost /admin:running:0:../server/webapps/admin /examples:running:0:examples /tomcat-docs:running:0:/home/kpu/tomcat41/webapps/tomcat-docs /ldvSimple:running:0:/home/kpu/tomcat41/webapps/ldvSimple
Nous constatons que 4 applications sont actives.
Recharger une application
http://localhost:8080/manager/reload?path=/ldvSimple
Retour
OK - Application rechargée au chemin de contexte /ldvSimple
Attention, le fichier web.xml de l'application n'est pas relu dans ce cas là.
D'autre fonctions sont disponibles, et qui s'utilisent avec la même syntaxe que reload, telles que stop, start. Voir la documentation en ligne pour plus d'information.
Une nouvelle interface HTML a vu le jour, pourquoi s'en priver ?
http://localhost:8080/manager/html/list
Une servlet est une classe Java particulière qui hérite d'une classe spécialisée (HttpServlet) elle même un composant du paquetage livré avec Tomcat : <TOMCAT_HOME>/common/lib/servlet-api.jar.
Les servlets utilisent les classes et interfaces de deux packages :
Javax.servlet // support de servlets génériques Javax.servlet.http // extension avec fonctionnalités spécifiques http
Le x de javax : Une extension standard.
Une servlet n'a pas de main, mais une méthode nommée service qui appelle (automatiquement) une méthode particulière (doGet, doPost, doPut, doDelete,...), selon la nature de la requête (HTTP GET, HTTP POST, HTTP PUT, HTTP DELETE).
Le développeur doit donc redéfinir ces méthodes. Typiquement, il peut se contenter de redéfinir les méthodes doGet et doPost (comme dans l'exemple ci-dessous).
La méthode service transmet aux méthodes doXXXX deux paramètres : un objet de type HttpServletRequest qui détient des informations précieuses sur le client émetteur de la requête, et un objet de type HttpServletResponse qui lui permet de construire dynamiquement une réponse (HTML ou autres) qui sera retournée au client.
Ci-dessous, la servlet récupère la donnée transmise par le client à l'aide de l'objet HttpServletRequest et une de ses méthodes (getParameter) :
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import java.util.*; public class LdvSimpleServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); String action = request.getParameter("action"); // récupère la valeur du paramètre // nommé "action" transmise par le client if (action == null) action = ""; if (action.equals("Hello")) { String title = "Lycee Leonard de Vinci - 77 Melun "; String hello = "Hello world !"; out.println("<html>"); out.println("<head> <title>" + title + "</title> </head>"); out.println("<body><center>"); out.println("<h1>" + hello + "</h1><br>"); out.println("<h2>Vous me sollicitez via un " + request.getMethod()+"</h2>"); out.println("</center></body>"); out.println("</html>"); } else { out.println("<html><head></head><body>ERREUR action : >" +action+"< </body></html>"); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doGet(request, response); }
Une servlet est similaire à une extension du serveur Web, et s'exécute dans machine virtuelle Java (JVM). Elle est donc sûre et portable. Généralement les serveurs Web ont un moteur (conteneur) de servlets qui exécute toutes les servlets dans la même JVM.
Un moteur gère le cycle de vie d'une servlet :
1 Crée et Initialise la servlet - méthode init()
n fois Lui fait traiter les services demandés par les clients,
par exemple doGet( ) et doPost( )
n+1 Détruit la servlet et la passe au ramasse-miettes destroy ( )
Une servlet persiste entre les requêtes (les threads éventuellement crées par la servlet aussi).
Tomcat peut créer plusieurs instances d'une servlet. Dans ce cas, si des données doivent être partagées entre les différentes requêtes, elles devront être logées dans un contexte particulier appelé ServletContext, un contexte unique pour une application Web.
L'objet ServletContext est accessible via la methode getServletConfig(), ou via le paramètre config de la méthode init.
Le contexte de servlet se comporte comme un dictionnaire (setAttribute, getAttribute, removeAttribute...) et permet à l'application de communiquer avec son conteneur, par exemple Tomcat (getRealPath, log...), voir l'API pour plus de détails (tomcat est supposé lancé) : http://localhost:8080/docs/api/index.html.
Nous allons intervenir sur le code source de la servlet (LdvSimpleServlet.java) afin qu'elle affiche le contenu des cookies gérés par l'application.
Nous éditons le fichier <TOMCAT_HOME>/webapps/ldvSimple/WEB-INF/src/LdvSimpleServlet.java:
...
out.println("<body><center>");
out.println("<h1>" + hello + "</h1><br/>");
out.println("<h2>Vous me sollicitez via un "
+ request.getMethod()+"</h2>");
out.println("Votre adresse IP : ");
out.println("<h2>" + request.getRemoteAddr()+"</h2>");
out.println("</center></body>");
...
Après l'avoir enregistré, nous le compilons. Voici ce que cela donnerait en ligne de commande :
$ javac LdvSimpleServlet.java LdvSimpleServlet.java:2: package javax.servlet does not exist import javax.servlet.*; ^
Le paquetage servlet.jar n'est pas inclus dans JSE (java standard edition). Il est livré avec Tomcat, et disponible ici : <TOMCAT_HOME>/common/lib.
Nous informons le classpath à la compilation, par un chemin relatif :
$ javac -classpath ../../../../lib/servlet-api.jar
LdvSimpleServlet.java
-d ../classes/
$
Malheureusement, Tomcat a gardé en mémoire la précédente version de la servlet. Pour que soit prise en compte la nouvelle version nous devons demander à tomcat de recharger notre application. Si on arrête Tomcat pour le relancer ensuite cela fonctionnerait, mais nous arrêterions alors toutes les applications à la charge de Tomcat :-(.
Pour recharger votre application, aller sur l'interface d'administration : http://localhost:8080/manager/html/list.
L'adresse identifie la machine locale. Demander à un de vos collègues de se connecter à votre application. Votre adresse IP réseau est consultable par la commande ifconfig, exemple :
$ /sbin/ifconfig
eth0 Lien encap:Ethernet HWaddr 00:09:6B:5F:B5:A8
inet adr:192.168.0.120 Bcast:192.168.0.255 Masque:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
A partir d'un autre poste, votre collègue entrera comme adresse dans son navigateur :
http://192.168.0.120:8080/ldvSimple
Et recevra en retour un message "Hello World" et son adresse IP.
Il se peut que vous ayez commis une faute dans votre programme qui ne se révellera qu'à l'exécution. Tomcat renvoie alors au client un message d'erreur signalant une erreur interne. Dans ce cas, pour en savoir plus, vous (le développeur) devez consulter le fichier catalina.out. Son adresse exacte est : [TOMCAT_HOME]/logs/catalina.out. A titre d'exemple, en voici un extrait :
[user@localhost logs]$ less catalina.out 3 oct. 2003 14:16:27 org.apache.commons.modeler.Registry loadRegistry INFO: Loading registry information 3 oct. 2003 14:16:27 org.apache.commons.modeler.Registry getRegistry INFO: Creating new Registry instance 3 oct. 2003 14:16:28 org.apache.commons.modeler.Registry getServer INFO: Creating MBeanServer 3 oct. 2003 14:16:29 org.apache.coyote.http11.Http11Protocol init INFO: Initialisation de Coyote HTTP/1.1 sur le port 8080 Démarrage du service Tomcat-Standalone Apache Tomcat/4.1.27-LE-jdk14 ...
Les 10 dernières lignes de ce fichier sont accessibles directement par la commande tail. Exemple :
[user@localhost logs]$ tail catalina.out
3 oct. 2003 14:16:32 org.apache.struts.util.PropertyMessageResources <init>
INFO: Initializing, config='org.apache.struts.action.ActionResources',
returnNull=true
3 oct. 2003 14:16:33 org.apache.struts.util.PropertyMessageResources <init>
INFO: Initializing, config='org.apache.webapp.admin.ApplicationResources',
returnNull=true
3 oct. 2003 14:16:39 org.apache.coyote.http11.Http11Protocol start
INFO: Démarrage de Coyote HTTP/1.1 sur le port 8080
3 oct. 2003 14:16:39 org.apache.jk.common.ChannelSocket init
INFO: JK2: ajp13 listening on /0.0.0.0:8009
3 oct. 2003 14:16:39 org.apache.jk.server.JkMain start
INFO: Jk running ID=0 time=1/47 config=/home/kpu/tomcat41/conf/jk2.properties
[user@localhost logs]$
Ici tout va bien, les services sont montés. Si votre application provoque une erreur à l'exécution, vous trouverez alors les explications ici. Il se peut également que Tomcat ne puisse démarrer votre application parce que le fichier web.xml est mal formé ou non valide (ne correspondant pas à la DTD référencée). Vous trouverez alors, dans catalina.out, le numéro de la ligne erronée rendu par le parser xml utilisé par Tomcat.
Les données transmises par le client sont de type String. L'interface ServletRequest expose les méthodes getParameter et getParameterValues :
java.lang.String getParameter(java.lang.String name)
Retourne une référence à une String représentant la valeur associée à name. Rend null si l'entrée (le paramètre) n'existe pas.
java.lang.String[] getParameterValues (java.lang.String name)
Comme précédemment mais retourne une référence à un objet tableau de String.
Exemple :
String nom = request.getParameter("nom");
String[] choix = request.getParameterValues("choix");
Modifier l'application LdvSimple de sorte que, si la valeur du paramètre action est "bonjour", le programme retourne un message de bienvenue personnalisé. Dans ce cas, le programme s'attend à recevoir un deuxième argument nommé nom. Exemple de scénarii :
//sénario n°1 http://localhost:8080/ldvSimple/ldv?action=bonjour&nom=titi
Le programme devra rendre : Bonjou titi (au format HTML)
//sénario n°2 http://localhost:8080/ldvSimple/ldv?action=bonjour&nom=
Le programme devra rendre : Bonjou inconnu
//sénario n°3 http://localhost:8080/ldvSimple/ldv?action=bonjour
Le programme devra rendre : Bonjou inconnu
Concevoir une page HTML statique invitant l'utilisateur à saisir son nom, prénom ainsi que deux nombres x et y.
Sous l'action SUBMIT du formulaire, ces quatre informations sont transmises à la servlet LdvSimpleServlet pour traitement. En retour, l'utilisateur reçoit une page HTML contenant un message personnalisé de bienvenue acompagné d'un message du type :
Bonjour <prenom> <nom>
<x> / <y> = <z>
Où <prenom>, <nom>, <x> et <y> représentent les valeurs transmises par l'utilisateur à la servlet et <z> une donnée calculée par l'application côté serveur.
On prendra un soin particulier à la robustesse de l'application : toute erreur d'exécution (due principalement à de mauvaises valeurs d'entrée fournies par l'utilisateur) devra faire l'objet d'un message clair, à l'intention de l'utilisateur.
Comme nous l'avons constaté, à chaque modification du code le développeur doit sauvegarder, compiler, corriger les erreurs, itérativement, puis déployer l'application.
Nous allons nous appuyer sur ANT pour automatiser (une partie) de ces tâches (on pourrait en réalité tout automatiser).
ANT est un projet de la fondation APACHE, disponible ici : http://ant.apache.org/. Son objectif est de faciliter l'exécution de taches courantes, comme la compilation, l'exécution, le déploiement etc. Ant est maintenant intégré à Eclipse et ne cécessite donc plus une installation à part.
Vous trouverez ici : ldvSimple.zip, une version projet eclipse du programme exemple. Particularité de ce projet : le "source folder" est WEB-INF/src et le "bin folder" WEB-INF/classes. Ce projet contient, à sa racine, un fichier pour ANT (par défault il se nomme build.xml)
Un fichier build.xml contient des instructions (en XML) concernant les tâches à faire exécuter par l'outil ANT. En voici le contenu :
<?xml version="1.0"?>
<!-- ===================================================================
13 nov. 06 11:49:49
project ldvSimple
=================================================================== -->
<project name="ldvSimple" default="compile" basedir=".">
<description>
Application Web avec Tomcat
</description>
<property name="catalina.home" location="/home/kpu/tomcat5520" />
<property name="app.name" value="ldvSimple" />
<property name="webapps" location="${catalina.home}/webapps" />
<property name="sources" location="WEB-INF/src" />
<property name="classes" location="WEB-INF/classes" />
<!--determine les librairies pour le classpath -->
<path id="projet.class.path">
<fileset dir="${catalina.home}/common/lib">
<include name="*.jar"/>
</fileset>
</path>
<!-- =================================
target: compile
================================= -->
<target name="compile" description="--> compilation">
<javac srcdir="${sources}" destdir="${classes}">
<classpath refid="projet.class.path"/>
</javac>
</target>
<!-- =================================
target: deploy
================================= -->
<target name="deploy" depends="compile">
<description>
Deploiement de l'application
</description>
<echo>=> suppression du l'application dans Tomcat </echo>
<delete quiet="true" dir="${webapps}/${app.name}" />
<echo>=> regeneration du war</echo>
<jar basedir="." destfile="${webapps}/${app.name}.war" />
</target>
</project>
Ce fichier contient des déclarations d'entités (property ou élément XML) comme 'webapps', 'sources', 'classes' ou 'projet.class.path', qui sont exploités par les différentes taches décrites plus loin. On en compte deux : 'compile' et 'deploy'
Vous devez modifier la valeur de la propriété catalina.home
Pour lancer l'exécution d'une 'cible' (target), vous passer son nom en ligne de commande, ou directement sous eclispe ! :
A titre d'information seulement, l'exemple ci-dessous ne fonctionne que si ANT est installé sur votre machine et accessible via le path.
$ cd dans_le_workspace_ldvSimple $ ant compile Buildfile: build.xml compile: BUILD SUCCESSFUL Total time: 1 second $
Par défaut, ANT exploite le fichier build.xml placé dans le répertoire courant. L'argument fournit à ANT au lancement, correspond à un nom de tache à exécuter. En absence d'argument, la tache par défaut est exécutée.
Installer, si ce n'est déjà fait, ce projet dans Eclipse : ldvSimple.zip. Une façon simple de procéder consiste à copier ce fichier compressé (inutile de le décompresser) dans votre espace de travail d'Eclipse, puis, sous Eclipse, vous l'importez (import->existing project into workspace->archive), et le tour est joué.
Voici une illustration de la procédure :
video d'importation du projet ldvSimple sous Eclipse
Sur la base de l'exercice précédent, concevoir une application qui permet à un utilisateur de connaître ses chances de réussite à l'examen du BTS Informatique de Gestion, option Développeur d'Applications.
Le dictionnaire ci-dessous représente les coefficients de chacune des épreuves. Un étudiant ayant obtenu une moyenne supérieure ou égale à 10 est garanti d'obtenir son diplôme.
Vous utiliserez ce dictionnaire dans la conception de votre solution au problème.
// le terme 'static' signifie que l'objet (référencé par 'matiereCoef')
// est partagé par toutes les instances de la classe (portée de classe)
// le terme 'final' signifie que la référence à l'objet ne peut pas
// faire l'objet d'une affectation après son initialisation.
// Un objet 'static' sera initialisé dans un bloc nommé 'static'
// situé hors d'un contexte de méthode ou de constructeur. Un
// tel bloc est parfois appelé "construteur static".
private final static Map<String, Integer>matiereCoef;
static {
matiereCoef = new Hashtable<String, Integer>();
matiereCoef.put("Francais", new Integer(2));
matiereCoef.put("AnglaisE", new Integer(2));
matiereCoef.put("AnglaisO", new Integer(1));
matiereCoef.put("Math", new Integer(2));
matiereCoef.put("Eco-Droit", new Integer(3));
matiereCoef.put("EtudeDeCas",new Integer(5));
matiereCoef.put("Pratique-TI", new Integer(3));
matiereCoef.put("SoutenanceProjet", new Integer(4));
}
A savoir : Eclipse dispose d'une vue dédiée à ANT afin de lancer les taches à partir de l'éditeur, avec echo dans la vue console.
A savoir, le projet Tomcat est livré avec des tâches ANT (des classes java) dédiées à son pilotage. Ainsi Tomcat peut être piloté « à distance » via des cibles Ant.
Actuellement nous avons "embarqué" du code HTML dans des classes Java. Exemple :
...
if (action.equals("Hello")) {
String title = "Lycee Leonard de Vinci - 77 Melun ";
String hello = "Hello world !";
out.println("<html>");
out.println("<head> <title>" + title + "</title> </head>");
out.println("<body><center>");
out.println("<h1>" + hello + "</h1><br>");
out.println("Votre adresse IP");
out.println("<h2>" + request.getRemoteAddr()+"</h2>");
out.println("</center></body>");
out.println("</html>");
}
...
Cette façon de faire n'est pas vraiment des plus pratique : des out.println en cascade propre à Java, jusqu'au code (X)HTML et ses exigences, en passant par l'écriture de code ECMAScript et du SQL pour des accès aux bases de données...
Bref, dans notre cas, le langage hôte (ici Java) embarque d'autres langages : (X)HTML, ECMAScript et SQL. Chacun de ces langages est spécialisé :
Java : Responsable de la logique de l'application côté serveur.
(X)HTML : Responsable de la logique de présentation côté client.
ECMAScript : Responsable de la logique de l'application côté client.
SQL : Responsable de la logique de gestion côté SGBDR.
En concevant une application en direct avec la technologie Servlet, nous donnons la primeur à une logique de traitement.
![]() | Primeur à la logique de traitement |
|---|---|
Le code de la logiqe métier embarque du code de la logique de présentation. |
Or, il existe une alternative à cette approche, qui consiste à donner la primeur à la logique de présentation. En deux mots :
![]() | Primeur à la logique de présentation |
|---|---|
Le code de présentation embarque du code de la logique métier. |
Anticipons, voici ce que donnerez le même exemple avec cette logique :
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=iso-8859-1">
<title>Hello</title>
</head>
<body><center>
<h1>Hello world !</h1><br>
Votre adresse IP
<h2>${pageContext.request.remoteAddr}</h2>
</center></body>
</html>
JSP (JavaServer Pages) est une technologie introduite par Sun (http://java.sun.com/products/jsp/ pour simplifier la conception de contenu web en java.
Dit simplement, peut être vu comme un fichier HTML pouvant contenir des instructions Java ou EL (Expression Language).
Ce type de fichier n'est pas communiqué tel quel au client. Il est d'abord interprété par le serveur Tomcat.
C'est le résultat de l'interprétation qui est communiqué au client.
Les exemples ci-dessous, vous montre "ce qu'il ne faut pas faire".
Il n'y a plus de servlet contrôleur, la logique de présentation est reine, elle contrôle toute la logique :-(.
Ce n'est plus la logique de l'application qui embarque la logique de présentation, mais la logique de présentation qui embarque la logique de l'application : Finalement le problème a été déplacé, mais n'est pas résolu pour autant ! Un infographiste n'est pas un professionnel du développement, nous devrions lui cacher le plus possible toute logique de traitement, ce que nous ferons très prochainement, mais avant présentons quelques contre-exemples.
<html>
<body>
<% String visiteur = request.getParameter("nom");
if (visitor == null) {
out.println("<center><h1>Hello inconnu</h1></center>");
}
else
out.println("<cener><h1>Hello " + visiteur + "</h1></center>");
%>
</body>
<html>
}
Le même exemple, plus dans l'esprit :
<html>
<body>
<% String visiteur = request.getParameter("nom");
if (visiteur == null) visiteur = "inconnu";
%>
<center><h1>Hello <%= visiteur %> </h1></center>
</body>
<html>
Le risque, avec cette approche, est de simplement déplacer le problème des séparations de responsabilités. C'est ce qui arrive lorsque l'on met de la logique applicative (traitements métier, accès aux bases de données, etc., ) dans la couche présentation (view), comme ici.
Pour illustrer notre propos, nous prendrons comme exemple un programme qui produit une facture.
En toute logique, l'objet qui calcule le montant de la facture ne devrait pas se charger de la représentation visuelle de cette même facture, comme c'est le cas ici :
<html>
<body>
<% String idclient = request.getParameter("idClient");
int qte = Integer.parseInt(request.getParameter("qte"));
double pu = Double.parseDouble(request.getParameter("pu"));
double pht = qte*pu;
Client client = db.getClient(idclient);
if (client.getProfile().getPayeur() == Client.MAUVAISPAYEUR))
...
// détermination ou non d'une ristourne...
...
%>
<center><h1>Montant HT à payer <%= (pht - ristourne) %> </h1></center>
</body>
<html>
Cela ne parait pas, mais si vous deviez, après avoir calculé le montant HT, déterminer la remise en fonction du pht, du CA et du profil du client, ajouter à cela un peu de JavaScript et vous obtiendrez un boulgi-boulga pas facile à maintenir...
Nous proposons de réaliser une séparation des responsabilité : La servlet se charge de contrôler les requêtes entrantes, et sous-traite le traitement à une méthode ou à un autre objet, puis la réponse à un fichier JSP.
Pour pour de petites applications, une décomposition par méthodes peut suffire, comme présenté dans l'exemple des Trucs à la fin de cette série.
Dans le cas d'une séparation des responsabilités par classe d'objets, on introduit une classe tierce qui sera chargé de construire (factory) ces fameux objets.
Cette façon de faire a été popularisée par le framework struts.
Le but de ce design est de séparer le traitement de la réponse. Cette façon de concevoir est connue sous le terme de paradigme MVC (model view controller). Pour cela, l'interface de IFCommnad présente une opération nommée traiter() qui sera chargée du traitement (par exemple détermination d'une ristourne) et qui retournera le nom de la vue à appliquer (une chaîne de caractères).
Dans tous les cas, l'objectif est de distinguer la logique métier côté serveur, par exemple calcul de ristournes, logique de persistance (accès au SGBDR) et logique de présentation qui pourra être sous-traiter à un infographiste, professionnel du look and feel. En effet ces professionnels du design ont des préocupations très éloignées des problèmes de syntaxe d'un langage de programmation comme Java par exemple.
Exemple de servlet contrôleur se chargeant « d'aiguiller la réponse » vers un fichier JSP. Extrait :
public class LdvSimpleServlet extends HttpServlet {
...
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
...
Traitement de la logique métier
//
// IFCommand cmd = CommandFactory.getInstance(action);
// String vueJSP = cmd.traiter(request, response);
// ou
// String vueJSP = traiterFacture(request, response);
appliquerLaVue(request, response, vueJSP);
...
}
...
private void appliquerLaVue(HttpServletRequest request,
HttpServletResponse response, String laVue) {
getServletConfig().getServletContext()
.getRequestDispatcher("/jsp/"+laVue).forward(request, response);
}
}
Les fichier JSP sont supposés être localisés dans un dossier nommé /jsp
Exemple de logique de présentation d'une facteur avec le langage EL (Expression Language) qui simplifie l'utilisation des objets accessibles par une page JSP.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body bgcolor="white">
<h1>
<table bgcolor="tan" border=1 align="center" cellpadding="10">
<tr><th colspan="2">FACTURE</th></tr>
<tr><td> <c:out value="${facture.nom}"/> </td>
<td> <c:out value="${facture.prenom}"/> </td>
</tr>
<tr><td> MONTANT </td>
<td><c:out value="${facture.montant}"/> </td>
</tr>
</table>
</h1>
</body>
</html>
Avec JSP 2.0, pris en compte par Tomcat 5, c'est encore plus simple.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<body bgcolor="white">
<h1>
<table bgcolor="tan" border=1 align="center" cellpadding="10">
<tr><th colspan="2">FACTURE</th></tr>
<tr><td> ${facture.nom} </td>
<td> ${facture.prenom} </td>
</tr>
<tr><td> MONTANT </td>
<td>${facture.montant}</td>
</tr>
</table>
</h1>
</body>
</html>
Pour bénéficier de ce langage d'expression nous devons placer dans WEB-INF/lib les fichiers jstl.jar, standard.jar. Vous trouverez ces fichiers dans le répertoire [CATALINA_HOME]/webapps/jsp-examples/WEB-INF/lib/.
Sun, dans le but de toujours simplifier le travail des personnes responsables de la couche présentation des applications Web, propose un langage d'expression simplifié (EL).
Les structures de contrôles, standardisées par JSTL, sont au format XML.
JSTL, permet au développeur de créer, stocker, retrouver et supprimer des variables placées dans un contexte donné. Tout identificateur apparaissant dans une expression EL, autre qu'un objet implicite EL (présenté plus loin), sont assumés référencer un object stocké dans une de ces quatre portée JSP :
Page scope
Les objets stockés dans un contexte de page, ne sont accessibles que dans la page pour une requête donnée.
Request scope
Les objets stockés dans une portée de requête peuvent être retrouvés durant tout le processus de traitement de la requête (pouvant mettre en jeu plusieurs pages via par exemple <jsp:include> ou <jsp:forward>).
Session scope
Un objet stocké dans une portée de session, peut être retrouvé dans n'importe qu'elle page accédée par un utilisateur, durant une interaction avec l'application Web (ou jusqu'à ce l'objet HttpSession associé à l'utilisateur soit invalidé).
Application scope
Un object stocké dans une portée d'application est accessible de n'importe qu'elle page et par tout utilisateur, jusqu'à ce que l'application soit arrêtée par le conteneur JSP (tomcat dans notre cas).
Exemple de déclaration d'attribut
Syntaxe 1: Sans corps:
<c:set var="varName" value="value"
[scope= "{page|request|session|application}" ]/>
Syntaxe 2: Avec un corps
<c:set var="varName"
[scope = "{page|request|session|application}" ]>
body content
</c:set>
Un objet est toujours associé à une des quatre portées JSP (scopes). La recherche s'effectue d'abord au niveau page scope, puis request scope, session scope, et pour finir application scope. Le premier identificateur trouvé est retourné.
L'exemple suivant soumet l'évaluation du contenu de la base <c:if> à la valeur de l'expression du test bean1.getA() < 3:
<c:if test="${bean1.a < 3}">
...
<c:if>
Pour concevoir des instructions conditionnelles mutuellement exclusives :
<c:choose>
<c:when test="${user.category == 'trial'}">
...
</c:when>
<c:when test="${user.category == 'member'}">
...
</c:when>
<c:when test="${user.category == 'vip'}">
...
</c:when>
<c:otherwise>
...
</c:otherwise>
</c:choose>
Itération sur une collection :
<c:forEach[var="varName"] items="collection" [varStatus="varStatusName"] [begin="begin"] [end="end"] [step="step"]> body content </c:forEach>
Cet autre extrait itère sur tous les éléments d'une collection d'objets Livre (placée ici par une servlet contrôleur), et insère une image (HTML) uniquement sur les articles de moins de 30 € :
<c:forEach items="${requestScope['livres']}" var="livre">
<li> ${livre.titre} ${livre.prix} (${livre.prix] $euro;)
<c:if test="${livre.prix < 20.0}">
<img src='livre_petit_prix.png' alt='Moins de 20 €'/>
</c:if>
</li>
</c:forEach>
Vous trouverez ici (developpez.com) une version plus évoluée de cet exemple, spécifiant une classe CSS différente selon les lignes (pair et impair).
Une classe pour représenter des trucs issus d'une couche métier.
Remarque : ses instances sont en lecture seule (read object).
public class Truc {
private String nom;
private double prix;
public Truc(String nom, double prix) {
this.nom=nom;
this.prix=prix;
}
public String getNom(){
return this.nom;
}
public double getPrix(){
return this.prix;
}
}
En contact avec des objets de type application.
Remarque : Ici la servlet fait également office de contrôleur d'application. A la place de CommandFactory et d'IFCommand nous avons ici de simples méthodes, suffisant pour notre cas ici.
/**
Exemple d'un modele MVC simple
*/
package org.vincimelun.premierservlet;
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class CTruc extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
String vue = "";
String para = request.getParameter("action");
if (para==null) para = "";
if (para.equals("untruc")) {
vue = traiterUnTruc(request, response);
appliquerLaVue(request, response, vue);
}
else if (para.equals("destrucs")) {
vue = traiterDesTrucs(request, response);
appliquerLaVue(request, response, vue);
}
else {
response.sendError(400, "Où est le truc ?");
}
} // doPost
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
doPost(request, response);
}
private String traiterUnTruc(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
Truc truc1 = new Truc("nomTruc1", 12);
request.setAttribute ("truc", truc1);
return "vueUnTruc.jsp";
}
private String traiterDesTrucs(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
List desTrucs = new ArrayList();
desTrucs.add(new Truc("nomTruc1", 12));
desTrucs.add(new Truc("nomTruc2", 2.1));
desTrucs.add(new Truc("nomTruc3", 1.2));
request.setAttribute ("trucs", desTrucs);
return "vueDesTrucs.jsp";
}
private void appliquerLaVue(HttpServletRequest request,
HttpServletResponse response, String laVue)
throws IOException, ServletException {
getServletConfig().getServletContext()
.getRequestDispatcher("/jsp/"+laVue).forward(request, response);
}
}
Fichier : vueTruc.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta name="GENERATOR" content="Mozilla/4.77 [fr] (X11; U; Linux 2.4.3-20mdk i686) [Netscape]">
<title> Exemple JSTL </title>
</head>
<body>
<center>Test JSTL </center>
<center><i>DA - ldv</i></center>
<p />
<hr width="100%" />
<center><table border="1" >
<tr><th>NOM</th><th>Prix</th></tr>
<tr><td> ${truc.nom} </td>
<td> ${truc.prix} </td>
</tr>
</table></center>
</body>
</html>
Fichier : vueDesTrucs.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title> Exemple JSTL </title>
</head>
<body>
<center>Test JSTL </center>
<center><i>DA - ldv</i></center>
<p />
<hr width="100%" />
<center><table border="1" >
<tr><th>NOM</th><th>Prix</th></tr>
<c:forEach var="truc" items="${trucs}">
<tr><td> ${truc.nom} </td>
<td> ${truc.prix} </td>
</tr>
</c:forEach>
</table></center>
</body>
</html>
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name>Premier Servlet</display-name>
<description>
Premiers pas avec Tomvat
</description>
<servlet>
<servlet-name>PremierServlet</servlet-name>
<servlet-class>org.vincimelun.premierservlet.Premier</servlet-class>
</servlet>
<servlet>
<servlet-name>SecondServlet</servlet-name>
<servlet-class>org.vincimelun.premierservlet.VotreIP</servlet-class>
</servlet>
<servlet>
<servlet-name>TroisiemeServlet</servlet-name>
<servlet-class>org.vincimelun.premierservlet.CTruc</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PremierServlet</servlet-name>
<url-pattern>/s1.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>SecondServlet</servlet-name>
<url-pattern>/vip</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>TroisiemeServlet</servlet-name>
<url-pattern>/truc</url-pattern>
</servlet-mapping>
</web-app>
Le code source de l'exemple : premiermvc.war
Intégrer le projet exemple en tant que projet Eclipse :
video d'une procédure d'intégration d'un .war comme projet Eclipse
Etendre l'exemple des trucs en concevant une action permettant à l'utilisateur de naviguer séquentiellement parmi les trucs.
Exemple de commande : http://localhost:8080/bonjour/navigtruc?indice=1 - l'application affiche le deuxième truc de la liste et présente une fléche dirigée vers la gauche pour visualiser le premier truc et une autre vers la droite pour le suivant etc.
Reprendre l'exercice précédent mais ne pas proposer à l'utilisateur la possibilité de naviguer avant le premier et après le dernier truc affiché.
Indication : Conformément au pardigme MVC, on veillera à ne pas charger la vue en responsabilité (métier/technique).
Reprendre l'exercice de détermination du résultat au BTS selon le paradigme MVC. Le nom des champs du formulaire de saisie des notes devra directement être issu des clés du dictionnaire (une page JSP s'impose donc).
Une idée d'application : Concevoir une application (type MVC) qui donne la valeur des amortissements constants pour un bien acheté. On suppose que le bien est acheté en début de période (pas de prorata temporis) donc que la première annuité est complète.
Données d'entrée : valeur du bien et durée d'utilisation en année pleine.
Etendre l'exercice en proposant, au choix, 2 types de calcul : amortissement constant et dégressif.
Remarque : Afin de limiter (?) la mise en cache du navigateur, vous pouvez insérer dans les vues les méta données suivantes :
<meta HTTP-EQUIV="Pragma" content="no-cahe"> <meta HTTP-EQUIV="Expires" content="-1">
Bonne programmation !
Une présentation en français de EL et JSTL chez developpez.com
Vous devez prendre connaissance des spécifications JSTL 1.0
Pour bien démarrer : http://www-106.ibm.com/developerworks/java/library/j-jstl0211.html
De multiples ressources sur JSTL chez Sun : Sun's official JSTL
Rappel : HTTP est un protocole sans état.
Le protocole HTTP ne fournit pas au serveur un moyen de reconnaître une séquence de requêtes provenant toutes d'un même client. L'adresse IP n'est souvent pas suffisante pour identifier un client parce qu'elle peut faire référence à un serveur proxy ou un serveur hébergeant de multiples utilisateurs.
Problème : Beaucoup d'applications Web doivent gérées des états. Exemple : formulaire multipages comme la gestion d'un caddie.
Solution : Le client doit s'identifier à chaque requête. Pour cela, plusieurs techniques sont disponibles : Autorisation de l'utilisateur, Champs cachés de formulaire, réécriture de l'URL et Cookies persistants.
Cette autorisation se fait en premier lieu au niveau du serveur Web. C'est un service proposé par presque tous les serveurs. Le client, la première fois qu'il se connecte, doit fournir son nom et mot de passe. Le nom de l'utilisateur peut être récupéré par la méthode :
classe : HttpServletRequest public String getRemoteUser()
Le type d'autorisation est connu par :
classe : HttpServletRequest public String getAuthType()
La valeur de getAuthtype() est la valeur de la variable CGI AUTH_TYPE, soit une valeur parmi {null, "BASIC", "DIGEST"}.
Une servlet peut utiliser le nom de l'utilisateur pour suivre les sessions en enregistrant les données en mémoire (classe partagée) ou dans une base de données. Exemple :
String nomUser = req.getRemoteUser();
if (nomUser==null) {
// erreur protection attendue
}
else {
String[] items = BigCaddie.getItemsOf(nomUser);
if (items != null) {
for (int i=0; i < items.length; i++) {
// faire quelque chose avec les items
}
}
}
Avantages
Suivi de session simple à mettre en oeuvre.
L'utilisateur peut changer de machine ou de navigateur sans perdre sa session.
Sécurité au niveau du serveur.
Inconvénients
L'utilisateur doit s'enregistrer à chaque fois qu'il se connecte au site.
Trop astreignant pour des connections anonymes.
Pas plus d'une session par utilisateur sur le même site.
L'utilisation de champs cachés de formulaire permet au serveur de transmettre des informations au client qu'il retransmet au serveur de façon transparente. Exemple.
<form name="formCaddie1" onSubmit="return checkData()"> <input type="hidden" name="code" value=3> <input type="hidden" name="niveau" vamlue=expert> ... <input type="text" name="quantiteProdA" size=3> <p><input type="submit" value="Valider " name="Valider"> </form>
Deux types d'informations peuvent être placés dans les champs cachés.
Soit toutes les informations sur les différents choix retenus par l'utilisateur depuis le premier formulaire.
Soit des informations permettant d'identifier l'utilisateur, les autres informations étant retenues par le serveur. Pour identifier une session on peut utiliser la méthode suivante :
private static String generateSessionID() {
// la methode UID() de rmi nous garantit le caractère
// unique de uid
String uid = new java.rmi.server.UID().toString();
// puis codons les caractères spéciaux avant de
// transmettre le resultat
return java.net.URLEncoder.encode(uid, "UTF-8");
}
Avantages
Portabilité : les champs cachés sont supportés par tous les navigateurs.
Anonymat, l'utilisateur ne se fera connaître qu'au moment de la vente.
Inconvénients
Nécessite toujours une construction dynamique des pages (pas de page statique).
Problème côté client lorsque le navigateur s'arrête, qu'une page est mise dans un signet ou stockée par le client, etc.
L'idée consiste à placer des paramètres dans l'URL construite et renvoyée à l'utilisateur dans la balise <form>.
Par exemple, ici nous plaçons en paramètre (id) l'identifiant de la session sessionid obtenu par le serveur en invoquant la méthode generateSessionID().
out.println("<form action='/Shopping?id=sessionid' method='post'>");
Les informations sont ensuite retrouvées par une méthode genre :
private static String[] getItemsFromCaddie(String sessionId) { }
Avantages
Portabilité, car supporté par tous les navigateurs.
Anonymat, l'utilisateur ne se fera connaître qu'au moment de la vente.
N'est pas tenu d'utiliser la balise <form>
Inconvénients
Nécessite toujours une construction dynamique (certains serveurs peuvent toute fois réécrire des URL sur la base de documents statiques)
Peut être fastidieux à mettre en place et maintenir.
Un cookie, invention de Netscape, est un fichier sur le poste client qui contient des informations transmises à un navigateur par un serveur web.
Lorsqu'un navigateur reçoit un cookie, il le sauve sur disque et le renvoie au serveur en principe chaque fois que l'utilisateur se connecte au site à l'origine du cookie.
Pour utiliser les cookies l'API Servlet propose la classe javax.servlet.http.Cookie.
// Constructeur : public Cookie(String name, String value); // crée un cookie portant les infos nom=valeur
Pour communiquer un cookie au client :
// classe HttpServletResponse public void addCookie(Cookie cookie)
Limitations côté navigateur: Pas plus de 20 cookies par site, pas plus de 300 cookies par utilisateur. La taille d'un cookie peut être limitée à 4096 octets (4ko).
Avantages
Facilité et cohérence de mise en oeuvre (le client détient les informations le concernant).
Bonne capacité de personnalisation.
Inconvénients
Pas toujours accepté par les navigateurs, souvent un choix du client.
Il existe un sous-ensemble de l'API Servlet dédié au suivi de session : API Session Tracking.
L'API Session Tracking devrait être supportée par tout serveur Web qui supporte les servlets. L'implémentation minimale de cette API s'appuie sur les cookies persistants.
Principe de l'API : A un utilisateur du site correspond un objet de session. Un objet de session est de type javax.servlet.http.HttpSession.
Il fait office de conteneur d'objets. On peut y stocker toute sorte d'information utile. Pour obtenir l'objet de la session courante, on utilise la méthode :
// classe HttpServletRequest public HttpSession getSession() //ou public HttpSession getSession(boolean create)
L'objet HttpSession dispose de deux méthodes bien pratiques :
setAttribute(un nom, une valeur)
// et
getAttribute(un nom)
// rend la valeur associée à un nom, ou null
Exemple :
HttpSession session = request.getSession();
...
Integer niveau = new Integer(3);
session.setAttribute("niveau", niveau);
// si niveau était précédemment lié à un autre objet,
// par exemple ("niveau", 2)
// alors l'association est remplacée par la nouvelle ("niveau", 3)
// sinon il y a création du couple ("niveau", 3)
... quelques méthodes de Session ...
// Pour retrouver un objet associé à un nom
public Object getValue(String name)
// Pour retrouver tous les objets de la session courante
// Il faut obtenir la liste (peut-être vide) des noms
public String[] getValueNames()
// puis appeler getValues() pour chaque poste
// Pour supprimer une association
public void removeValue(String name)
// il ne se passe rien si l'association n'existe pas
// Pour connaître l'identifiant d'une session
java.lang.String getId()
// Retourne une String contenant l'identifiant
// unique attribué à la session.
La durée de vie est déterminée par le serveur Web (30 mn sur Java Web Server de Sun).
La date de création est accessible par la méthode getCreationTime().
La date du dernier accès est connu par la méthode getLastAccessTime().
Est-ce une nouvelle session ? : isNew()
Suppression d'une session : invalidate()
Exemple, un programme qui termine et réinitialise une session d'utilisateur au bout d'une (!) minute d'inactivité :
...
HttpSession session = request.getSession();
if (!session.isNew()) {
Date minutePrecedente = new Date(System.currentTimeMillis() - 60*1000);
Date dernierAcces = new Date(session.getLastAccessedTime());
if (dernierAcces.before(minutePrecedente)) {
//plus d'une minute d'inactivité
session.invalidate();
session = request.getSession(true);
}
}
out.println("<br>session id : " + session.getId() + "<br>");
...
Notez que d'ordinaire, on laisse au serveur Web le soin de régler le problème de durée d'une session. La durée maximale d'une session est paramétrable via un fichier de configuration situé sur le serveur (par exemple conf/web.xml pour tomcat. Exemple de valeur par défaut :
<!-- temps en minutes -->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
L'API JDBC se trouve dans java.sql et les pilotes doivent implémenter l'interface java.sql.Driver.
L'interaction à un système de gestion de base de données réquiert en général au moins quatre étapes :
Chargement du pilote
Etablissement de la connexion
Exécution d'une requête
Analyse des résultats
Les étapes 1 et 2 le sont pour un ensemble d'opérations (étapes 3 et 4). Nous présentons ci-dessous chacune de ces étapes.
On charge généralement le pilote par son nom. Ci-dessous, un exemple de programme chargeant un des deux pilotes définis sous forme de chaînes de caractères.
final String driverPostgreSql = "jdbc.postgresql.Driver";
// driver PostgreSql
final String driverOdbc = "sun.jdbc.odbc.JdbcOdbcDriver";
// driver odbc inclus dans le jdk (pratique sous Windows pour SQL Server, Access...)
final String driverHsql = "org.hsqldb.jdbcDriver";
// driver Hypersonic SQL
String driver = driverHsql;
Class.forName(driver).newInstance(); // Autochargement du driver
Une fois le driver chargé en mémoire, nous pouvons obtenir une connexion via la méthode de classe getConnection() de la classe DriverManager
Connection con = DriverManager.getConnection(URL, "user", "passwd"); // URL : url de connexion de la forme jdbc:sous-protocole:sous-nom // sous-protocole:identification du pilote // sous-nom :informations nécessaire au pilote pour la connexion (chemin, port, nom) // "passwd" : Mot de passe // "user" : Nom de l'utilisateur référencé par la base
Exemple
final String driver = "org.hsqldb.jdbcDriver";
final String url = "jdbc:hsqldb:file:/home/kpu/java/hsql/refuge/refuge";
final String user="sa";
final String password="";
Connection con = null;
try {
Class.forName(driver).newInstance();
con = DriverManager.getConnection(url, user, password);
...
L'exécution d'une requête SQL s'effectue via un objet de la classe java.sql.Statement. C'est l'objet Connection qui nous fournira une référence d'objet Statement (à ne pas instancier directement). Exemple :
Statement stat = con.createStatement();
On distingue deux types de requêtes : requête d'interrogation et de mise à jour.
Requête d'interrogation avec l'ordre SELECT
Nous utiliserons la méthode de Statement executeQuery( ) qui retourne un objet java.sql.ResultSet.
ResultSet rs = stat.executeQuery("SELECT * FROM ANIMAL");
Requête de mise à jour avec les ordres UPDATE, INSERT, DELETE
On utilisera la méthode executeUpdate( ) de Statement.
Cet exemple supprime de la table ENTREPRISES toutes les entreprises de Seine et Marne.
stat.executeUpdate( "DELETE FROM ENTREPRISES WHERE CODEPOST LIKE '77%'");
Le retour d'un ordre executeQuery(...) est un objet de type ResultSet, une collection de lignes constituées de 1 à n attributs (colonnes).
Pour accéder à la première ligne du résultat, il est nécessaire d'appeler la méthode next(), pour passer à la ligne suivante, il suffit d'appeler de nouveau cette méthode, etc.
ResultSet rs = stat.executeQuery("SELECT * FROM ANIMAL"); // Pour accéder à chacun des tuples du résultat de la requête : while (rs.next()) { String nom = rs.getString("nom"); java.sql.Date date_nais = rs.getDate("date_nais"); int id = rs.getInt(1); ... }
Remarque 1 : L'appel à la méthode next() de l'objet Statement est obligatoire avant tout appel aux méthodes permettant d'accéder à une valeur d'un attribut de la ligne courante.
Remarque 2 : Il y a deux façons d'accéder à une valeur d'un attribut (colonne) : 1/ soit par le nom de la colonne, comme par exemple les deux premiers appels de l'exemple. 2/ soit par position, qui commence à la position 1 (et non 0 comme avec les collections), comme le montre le troisième appel.
La méthode executeUpdate( ) de Statement, ne retourne pas un objet java.sql.ResultSet mais retourne le nombre de lignes impactées par l'instruction.
Cet exemple supprime de la table ENTREPRISES toutes les entreprises de Seine et Marne.
int count = stat.executeUpdate( "DELETE FROM ENTREPRISES WHERE CODEPOST LIKE '77%'");
System.out.println("Il y a eu " + count + " lignes supprimées.");
Le programme ci-dessous utilise une base nommée Refugedb.
Cette base de données contient une table nommée ANIMAL; voici un script de création :
CREATE TABLE ANIMAL (
id INTEGER PRIMARY KEY,
categorie VARCHAR NOT NULL,
nom VARCHAR, race VARCHAR,
sexe CHAR,
date_nais DATE,
id_proprio INTEGER,
present BIT
)
INSERT INTO ANIMAL VALUES (1,'CRM', 'kiki','berger','M','2000-2-21',21,false)
INSERT INTO ANIMAL VALUES (2,'CRM','rex','caniche','M','1996-12-2',11,true)
...
Lorsque l'on accède à une base de données, une gestion des exceptions s'avère nécessaire car de multiples problèmes peuvent survenir : le pilote ne peut être chargé (introuvable ?), connexion refusée, requête SQL mal formée... Voici l'exemple complet.
1 import javax.servlet.*;
2 import javax.servlet.http.*;
3 import java.util.*;
4 import java.sql.*;
5 import java.io.*;
6
7 public class TestDBAnimalServlet extends HttpServlet {
8 static final String driver = "org.hsqldb.jdbcDriver";
9 static final String url = "jdbc:hsqldb:hsql://localhost/dbTest";
10 // connexion au serveur HypersonicSql lancé préalablement
11 static final String user = "sa";
12 static final String password ="";
13 private Connection con = null;
14 // une solution plus efficace consiste a utiliser
15 // un pool de connexion
// voir tomcat-docs/jndi-datasource-examples-howto.html
16 /**
17 * Initialisation de la servlet. <br>
18 *
19 * @throws ServletException si une erreur intervient
20 */
21 public void init() throws ServletException {
22 try {
23 Class.forName(driver).newInstance();
24 con = DriverManager.getConnection(url, user, password);
25 }
26 catch (Exception e) {
27 throw new ServletException(e);
28 }
29 }
30
31 /**
32 * Destruction de la servlet. <br>
33 */
34 public void destroy() {
35 super.destroy();
36 try {
37 if (con != null) {
38 con.close();
39 }
40 }
41 catch (Exception e) { /* muet */ }
42 }
43
44 /**
45 * méthode répondant aux requêtes GET HTTP
46 */
47 public void doGet(HttpServletRequest request,
48 HttpServletResponse response)
49 throws IOException, ServletException
50 {
51 response.setContentType("text/html");
52 PrintWriter out = response.getWriter();
53 Statement st = null;
54 ResultSet rs = null;
55 String sql = "";
56 try {
57 st = con.createStatement();
58 sql = "SELECT * FROM ANIMAL";
59 rs = st.executeQuery(sql);
60 out.println("<html><head></head><body>");
61 out.println("ID --- TYPE --- NOM ---- RACE<br/>");
62 while (rs.next()) {
63 out.print(rs.getInt(1)+" -- ");
64 // ATTENTION, les indices commencent à 1.
65 out.print(rs.getString(2)+" -- ");
66 out.print(rs.getString("nom")+" ---- ");
67 out.println(rs.getString("race") + "<br/>");
68 }//while
69 out.println("</body></html>");
70 }
71 catch (SQLException e) {
72 out.println("SQL erreur : "+ sql + " " + e.getMessage());
73 }
74 catch (Exception e) {
75 out.println("Erreur : "+ e);
76 }
77 }
78 }
Quelques commentaires :
Ligne 23 : Chargement du pilote Hypersonic SQL.
Ligne 24 : Etablissement d'une connexion à la base.
Ligne 57 : Création d'un objet Statement en préparation à l'exécution d'une requête SQL.
Lignes 59 - 69 : Selection de toutes les lignes de la table ANIMAL.
Affichage de quelques attributs (ID, TYPE, NOM et RACE). La condition de poursuite dans le while permet d'avancer à la prochaine ligne et de tester si la fin n'est pas atteinte (rend false alors).
Ligne 38 : A ne pas oublier, fermeture de la connexion.
Le langage JavaScript a été inventé par Brendan Eich (chez Netscape) et fit sa première apparition dans le navigateur Netscape Navigator 2.0. Depuis il est présent dans la majorité des navigateurs.
La version standard de JavaScript est ECMAScript (1997, puis ECMA-262 1998). Initialement, ECMA veut dire European Computer Manufacturers Association, mais depuis l'arrivée de nouveaux membres venus d'Asie et d'Amérique du nord, ECMA est devenu ECMA International.
JavaScript se présente commet un langage de script "basé objet", développé pour les applications Web côté client (Netscape propose également des versions pour le côté serveur).
Dans une application côté client (pour un navigateur compatible), les instructions JavaScript logées directement dans une page HTML permettent de répondre aux événements déclenchés par l'utilisateur tels que des clics de souris, entrées de données et changements de page.
Une des applications caractéristiques de JavaScript est la vérification de la validation des données que l'utilisateur souhaite transmettre au serveur. L'objectif est de réduire ainsi le trafic sur le réseau en cas d'erreur.
Par exemple, une page HTML, avec du JavaScript embarqué, peut interpréter le texte entré par l'utilisateur et l'alerter par un message dans une boite de dialogue que les données sont invalides et l'interdire ainsi de transmettre des données invalides. Vous pouvez également utiliser JavaScript pour réaliser une action (telle que jouer un fichier audio, exécuter une applet, ou communiquer avec un plug-in) en réponse à l'ouverture ou sortie d'une page.
Le langage JavaScript ressemble à Java, mais sans les types static de Java et sans son côté fortement typé.
JavaScript est basé sur un petit nombre de types de base pour représenter les nombres, booléen et chaînes de caractères. JavaScript supporte les fonctions. Les fonctions peuvent être des méthodes d'objets.
Une page HTML est composée d'un certain nombre d'objets pré-définis, directement accessibles par une syntaxe objet.
Voici une graphe de la hiérarchie des classes d'objets présents dans le Navigateur :
window
|
+--parent, frames, self, top
|
+--location
|
+--history
|
+--document
|
+--forms
| |
| +--elements (text fields, textarea, checkbox, password
| radio, button, submit, reset, select)
| |
+--links +--options
|
+--anchors
|
+--applets
|
+--URL
|
...
Le Modèle Objet de Document, ou DOM (Document Object Model), est une interface définie par le W3C(World Wide Web Consortium)), une consortium d'éditeurs régissant les normes du Web et de l'Internet. DOM fournit au développeur une interface commune permettant, via un implémentation fournit par le navigateur, de manipuler les structures et le contenu d'un document et d'interagir avec le navigateur.
Les objets sont accessibles par leur nom (valeur de l'attribut name). Exemple :
... <form name="monformulaire"> <input type="text" name="nom" value="anonymus"> </form> ...
La valeur du texte entré est accessible par l'expression : document.monformulaire.nom.value.
Si on ne connait pas le nom du formulaire, on peut y accéder via un tableau. En effet, le premier formulaire est stocké dans document.forms[0], le deuxième dans document.forms[1] etc.
Considérant que notre formulaire (<form name="monformulaire">... soit le premier formulaire d'une page HTML, nous vous présentons ci-dessous, différentes syntaxes pour y accéder:
document.forms[0] document.forms["monformulaire"] document.monformulaire
Il en va de même pour les autres composants pluriels. Par exemple :
window.document.forms[0].elements
Contient tous les éléments du premier formulaire. La propriété elements.length donne le nombre d'éléments. Chaque élément du tableau elements est un objet de type "Objet de formulaire" ayant des propriétés particulières.
Voir la doc en ligne pour plus d'information sur ce sujet.
Exemple de code JavaScript :
<html>
<head>
<script language="JavaScript">
document.write("Hello net.")
</script>
</head>
<body>
That's all, folks.
</body>
</html>
Affichage par le navigateur :
Hello net. That's all folks.
Présentation de techniques permettant de contrôler la validité des données avant de transmettre leur valeur au serveur.
Exemple 1 : Utilisation du gestionnaire d'événement onSubmit de l'objet Form avec un input type submit standard.
<html> <head>
<title> L'objet Form et son gestionnaire d'événement onSubmit </title>
</head>
<script>
var dataOK=false
function checkData (){
if (document.form1.threeChar.value.length == 3){
return true
}
else {
alert("Enter exactly three characters. " +
document.form1.threeChar.value +
" is not valid.");
return false;
}
}
</script>
<body>
<form name="form1" onSubmit="return checkData()">
<b>Enter 3 characters:</b> <input type="text" name="threeChar" size=3>
<p><input type="submit" value="done" name="submit1"
onClick="document.form1.threeChar.value=
document.form1.threeChar.value.toUpperCase()">
</form>
</body>
</html>
Exemple 2 : Similaire au précédent mais utilise la méthode submit de l'objet Form au lieu de l'objet submit. Le formulaire n'a pas de bouton submit, mais se sert de l'evénement onClick d'un simple bouton pour contrôler l'envoi des données.
<html> <head>
<title> Exemple de méthode submit de l'objet Form </title>
</head>
<script>
function checkData (){
if (document.form1.threeChar.value.length == 3){
document.form1.submit();
}
else {
alert("Enter exactly three characters. " +
document.form1.threeChar.value +
" is not valid.");
return false;
}
}
</script>
<body>
<form name="form1" onSubmit="alert('Le formulaire est en train d'être validé.')">
<b>Enter 3 characters:</b> <input type="text" name="threeChar" size=3>
<p><input type="button" value="done" name="bouton"
onClick="checkData()">
</form>
</body>
</html>
Boîtes de dialogue :
alert Affiche une boîte de dialogue d'avertissement, contenant un message et un bouton OK. Exemple : alert("message");
confirm A utiliser lorsqu'une prise de décision de type Oui/Non, Valider/Abandonner doit être prise par l'utilisateur. Exemple : if (confirm("Confirmez-vous la suppression ?") { delete(); }
prompt Affiche une boîte de dialogue avec un champ de saisie. Syntaxe : prompt(message, [inputDefault]) -- la valeur par défaut est optionnelle. Exemple : var age = prompt("Quel est votre âge ?", 17);
La plupart des composants ECMAScript sont sensibles aux événements. Voici la liste globale des évenenemts :
Tableau 1. Evénements ECMAScript
| Gestionnaire d'événement | S'applique à | Est déclenché quand |
|---|---|---|
| onAbort abandon | Images | l'utilisateur abandone le chargement d'une image, clique sur le bouton Stop du navigateur, clique sur un autre lien alors qu'une image est en chargement. |
| onBlur, on focus (perte ou prise de focus) | Fenêtre, cadres et éléments de formulaire | Le curseur quitte (ou arrive sur) un contrôle. Lorsqu'un composant est (dés)activé (fenêtre, frame...). |
| onChange | liste, champ d'édition... | sur changement de données |
| onClick, onDblClick | boutons, case ou option à cocher, liens | sur clic (ou double clic) de souris |
| onDragDrop | window | cliquer/glisser |
| onError | window, image | gestionnaire d'erreur JavaScript à redéfinir de façon globale (window.onError=maFonctionErreur()) ou local (<img src="..." onError=maFonction()) |
| onKeyDown onKeyPress onKeyUp | Document, image, link, textArea | une action de l'utilisateur sur le clavier |
| onLoad | Image, layer, window | Evénement déclenché lorsque le navigateur termine le chargement d'une page ou de ses frames |
| onMouseDown, onMouseMove, onMouseOut, onMouseOver, onMouseUp | Document, button, link | Evénements souris |
| onMove | window, frame | déplacement d'une fenêtre ou d'une frame. |
| onReset | form | en relation avec le bouton Reset d'un formulaire |
| onResize | window | Lors d'un changement de taille de la fenêtre |
| onSelect | Text, Textarea | Lors de selection de texte dans une zone de texte |
| onSubmit | form | sur validation du formulaire |
| onUnLoad | window | lorsque l'utilisateur quitte un document |
Exemple 3 : Gestion d'événements avec l'objet Select :
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<script type="text/javascript" language="JavaScript">
function test(){
var age = prompt("Quel est votre âge ?", 17);
document.myForm.myText.value = age;
}
function testChoix() {
var i = document.myForm.choix.selectedIndex;
document.myForm.myText.value =
document.myForm.choix.options[i].value;
}
</script>
</head>
<body>
Exemple d'une gestion d'événements :
<form action="" method="post" name="myForm">
<select name="choix" onchange="testChoix()">
<option value="choix1">A</option>
<option value="choix2">B</option>
<option value="choix3" selected="selected">C</option>
<option value="choix4">D</option>
</select>
<br>
<input type="text" name="myText" onselect="test()" value="coucou"/>
</form>
</body>
</html>