Initiation à la programmation Web avec java

Tomcat

Olivier Capuozzo

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

1. Introduction
2. Installation de Tomcat
3. Exemple d'application
3.1. points d'entrée de l'application ldvSimple (html et servlet)
3.2. Test de l'application ldvSimple
3.3. Explications
4. Déploiement d'une application web
5. Gestionnaire d'applications web sous Tomcat
5.1. Interface via une requête GET
5.2. Interface HTML
5.3. Travaux pratiques
6. Servlet
6.1. Servlet
6.2. Cycle de vie d'une servlet
6.3. Modifier le comportement de la servlet
6.4. Obtenir les données transmises par le client
6.5. Travaux pratiques
7. Mise au point et déploiement avec Ant
7.1. Travaux Pratiques : Eclispe + Ant
8. Introduction JSP MVC
8.1. Pages JSP
8.1.1. Quelques (mauvais) exemples de pages JSP
8.2. Une réalisation du modèle MVC avec JSP
8.3. Expression Language (JSP 2.0)
8.3.1. Introduction
8.3.2. Portée des variables
8.3.3. Structures de contrôles
8.4. Programe exemple JSP/JSTL/EL
8.4.1. Truc Helper
8.4.2. Servlet controleur
8.4.3. Vue d'un truc
8.4.4. Vue d'une liste de Trucs
8.4.5. Fichier de déploiement : web.xml
8.4.6. Exemple d'exécution
8.4.7. Un truc (http://localhost:8080/bonjour/truc?action=untruc)
8.4.8. Des trucs (http://localhost:8080/bonjour/truc?action=destrucs)
8.5. Travaux pratiques
8.6. Ressources
9. Suivi de session
9.1. Autorisation utilisateur
9.2. Champs cachés de formulaire
9.3. Réécriture de l'URL
9.4. Cookies persistants
9.5. API de suivi de session
9.6. Cycle de vie d'une session
10. Côté SGBD, communication avec JDBC (rappel)
10.1. Pilotes JDBC
10.2. I - Chargement du pilote dans la JVM (Java Virtual Machine)
10.3. II - Etablissement de la connexion
10.4. III - Exécution d'une requête SQL
10.5. IV - Analyse des résultats
10.5.1. Requête d'interrogation avec l'ordre SELECT
10.5.2. Requête de mise à jour (UPDATE, INSERT, DELETE)
10.6. Exemple de programme
10.7. JDBC et JNDI
11. Côté client : JavaScript (ECMAScript)
11.1. Contrôle de validité côté client avec JavaScript
11.2. Programmation évenementielle

1. Introduction

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.

2. Installation de Tomcat

  1. Télécharger le fichier tar.gz (UNIX) ou zip (traditionnellement pour Windows) ici : http://jakarta.apache.org/site/downloads/downloads_tomcat-5.cgi.

  2. Décompresser le fichier dans un répertoire (par exemple votre home directory /cirdan/home/login-name).

  3. 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.

  4. 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

  5. Vérifier l'installation en lancant Tomcat, puis lancer mozilla et entrer l'adresse : http://localhost:8080/.

    Figure 1. Tomcat : http://localhost:8080/

    Tomcat : http://localhost:8080/

    N'hésitez pas à consulter la documentation :

    Figure 2. Tomcat : documentation

    Tomcat : documentation

3. Exemple d'application

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.

Figure 3. Arborescence type d'une application web sous Tomcat

Arborescence type d'une application web sous Tomcat

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

  1. Récupérer le fichier ldvSimple.war.

  2. Placer ce fichier à la racine du répertoire TOMCAT_HOME/webapps

  3. (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.

3.1. points d'entrée de l'application ldvSimple (html et servlet)

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
    

3.2. Test de l'application ldvSimple

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

Figure 4. Test de l'application ldvSimple

Test de l'application ldvSimple

Cliquez sur le lien Exemple 1, vous devriez voir :

Figure 5. Résultat de l'appel de la servlet

Résultat de l'appel de la servlet

3.3. Explications

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.

4. Déploiement d'une application web

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).

    1. Se placer à la racine de l'application, par exemple : tomcat/webapps/ldvSimple

    2. 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).

5. Gestionnaire d'applications web sous Tomcat

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).

5.1. Interface via une requête GET

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.

5.2. Interface HTML

Une nouvelle interface HTML a vu le jour, pourquoi s'en priver ?

http://localhost:8080/manager/html/list

Figure 6. Gestionnaire d'applications. http://localhost:8080/manager/html/list

Gestionnaire d'applications. http://localhost:8080/manager/html/list

5.3. Travaux pratiques

  1. Modifier le fichier web.xml afin que l'utilisateur puisse déclencher la servlet avec l'url suivante : http://localhost:8080/ldvSimple/tp1

  2. Faire de même avec cette url : http://localhost:8080/ldvSimple/tp1/index.html.

6. Servlet

6.1. Servlet

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).

Figure 7. Cas d'utilisation système d'une servlet

Cas d'utilisation système d'une servlet

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).

Figure 8. Graphe d'héritage d'une servlet (diagramme de classes UML)

Graphe d'héritage d'une servlet (diagramme de classes UML)

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);
  }

6.2. Cycle de vie d'une servlet

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.

6.3. Modifier le comportement de la servlet

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.

Figure 9. ldvSimple nouvelle version

ldvSimple nouvelle version

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.

6.4. Obtenir les données transmises par le client

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");

6.5. Travaux pratiques

  1. 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

  2. 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.

7. Mise au point et déploiement avec Ant

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.

7.1. Travaux Pratiques : Eclispe + Ant

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.

8. Introduction JSP MVC

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.

[Note]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 :

[Note]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>

8.1. Pages JSP

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.

8.1.1. Quelques (mauvais) exemples de pages JSP

<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...

8.2. Une réalisation du modèle MVC avec JSP

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.

Figure 10. Exemple de mis en oeuvre d'une séparation fonctionnelle des responsabilités

Exemple de mis en oeuvre d'une séparation fonctionnelle des responsabilités

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.

Figure 11. Exemple de mis en oeuvre d'une séparation objet des responsabilités

Exemple de mis en oeuvre d'une séparation objet des responsabilités

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/.

8.3. Expression Language (JSP 2.0)

8.3.1. Introduction

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.

8.3.2. Portée des variables

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é.

8.3.3. Structures de contrôles

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 &euro;'/>
    </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).

8.4. Programe exemple JSP/JSTL/EL

8.4.1. Truc Helper

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;
  }
}
 

8.4.2. Servlet controleur

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);
 }	
}

8.4.3. Vue d'un truc

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>
  

8.4.4. Vue d'une liste de Trucs

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>
  

8.4.5. Fichier de déploiement : web.xml


<?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>

    

8.4.6. Exemple d'exécution

Figure 12. Index de l'application (http://localhost:8080/bonjour/index.html)

Index de l'application (http://localhost:8080/bonjour/index.html)

8.4.7. Un truc (http://localhost:8080/bonjour/truc?action=untruc)

Figure 13. Un truc

Un truc

8.4.8. Des trucs (http://localhost:8080/bonjour/truc?action=destrucs)

Figure 14. Liste de trucs

Liste de trucs

8.5. Travaux pratiques

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 !

8.6. Ressources

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

9. Suivi de session

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.

9.1. Autorisation utilisateur

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.

9.2. Champs cachés de formulaire

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.

9.3. Réécriture de l'URL

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.

9.4. Cookies persistants

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.

9.5. API de suivi de session

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.

9.6. Cycle de vie d'une 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>

10. Côté SGBD, communication avec JDBC (rappel)

10.1. Pilotes JDBC

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 :

  1. Chargement du pilote

  2. Etablissement de la connexion

  3. Exécution d'une requête

  4. 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.

10.2. I - Chargement du pilote dans la JVM (Java Virtual Machine)

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

10.3. II - Etablissement de la connexion

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);
   ...

10.4. III - Exécution d'une requête SQL

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%'");
    

10.5. IV - Analyse des résultats

10.5.1. Requête d'interrogation avec l'ordre SELECT

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.

10.5.2. Requête de mise à jour (UPDATE, INSERT, DELETE)

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.");

10.6. Exemple de programme

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.

10.7. JDBC et JNDI

Pour l'utilisation d'un pool de connexions, on consultera avec attention la partie consacrée à JDBC dans la doc de Tomcat.

11. Côté client : JavaScript (ECMAScript)

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.

11.1. Contrôle de validité côté client avec JavaScript

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>

11.2. Programmation évenementielle

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énementS'applique àEst déclenché quand
onAbort abandon Imagesl'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 formulaireLe curseur quitte (ou arrive sur) un contrôle. Lorsqu'un composant est (dés)activé (fenêtre, frame...).
onChangeliste, champ d'édition...sur changement de données
onClick, onDblClickboutons, case ou option à cocher, lienssur clic (ou double clic) de souris
onDragDropwindowcliquer/glisser
onErrorwindow, imagegestionnaire d'erreur JavaScript à redéfinir de façon globale (window.onError=maFonctionErreur()) ou local (<img src="..." onError=maFonction())
onKeyDown onKeyPress onKeyUpDocument, image, link, textAreaune action de l'utilisateur sur le clavier
onLoadImage, layer, windowEvénement déclenché lorsque le navigateur termine le chargement d'une page ou de ses frames
onMouseDown, onMouseMove, onMouseOut, onMouseOver, onMouseUpDocument, button, linkEvénements souris
onMovewindow, framedéplacement d'une fenêtre ou d'une frame.
onResetformen relation avec le bouton Reset d'un formulaire
onResizewindowLors d'un changement de taille de la fenêtre
onSelectText, TextareaLors de selection de texte dans une zone de texte
onSubmitformsur validation du formulaire
onUnLoadwindowlorsque 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'&eacute;v&eacute;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>