Skip to content

3. Introduction aux servlets Java et pages JSP

On trouvera dans ce chapitre divers exemples de servlets et pages JSP. Ils ont été testés avec le serveur Tomcat. Celui-ci travaille sur le port 8080. En suivant les liens de la page d'accueil, on a accès à des exemples de servlets et pages JSP. Les exemples ci-dessous sont pour la plupart tirés des exemples de Tomcat. Pour les tester, il suffit de lancer Tomcat, de demander l'URL http://localhost:8080 avec un navigateur et de suivre le lien des servlets.

Image Image

3.1. Servlets Java

3.1.1. Envoyer un contenu HTML à un client Web

Nous examinons l'exemple Hello World ci-dessus. La servlet est la suivante :


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");
    }
}

A l'exécution de cette servlet, on obtient l'affichage suivant :

Image

On notera les points suivants :

  • il faut importer des classes spéciales pour les servlets :

import javax.servlet.*;
import javax.servlet.http.*;

La bibliothèque javax.servlet n'est pas toujours livrée en standard avec le jdk. Dans ce cas, on peut la récupérer directement sur le site de Sun.

  • une servlet dérive de la classe HttpServlet

public class HelloWorld extends HttpServlet {
  • Une requête GET faite à la servlet est traitée par la méthode doGet

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • De même une requête POST faite à la servlet est traitée par la méthode doPost

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
  • L'objet HttpServletRequest request est l'objet qui nous donne accès à la requête faite par le client Web. La réponse de la servlet sera faite via l'objet HttpServletResponse response

  • L'objet response nous permet de fixer les entêtes http qui seront envoyés au client. Par exemple l'entête Content-type: text/html est ici fixé par :

        response.setContentType("text/html");
  • Pour envoyer la réponse au client, la servlet utilise un flux de sortie que lui délivre l'objet response :
        PrintWriter out = response.getWriter();
  • Une foix ce flux de sortie obtenu, le code HTML est écrit dedans et donc envoyé au client :
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello World!</h1>");
        out.println("</body>");
        out.println("</html>");

3.1.2. Récupérer les paramètres envoyés par un client web

L'exemple suivant montre comment une servlet peut récupérer des paramètres envoyés par le client web. Un formulaire de saisie :

Image

La réponse envoyée par la servlet :

Image

Le code source de la servlet est le suivant :


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class myRequestParamExample extends HttpServlet {

    String title="Récupération des paramètres d'un formulaire";

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");
        if (firstName != null || lastName != null) {
            out.println("firstname= " + firstName + "<br>");
            out.println("lastname= " + lastName);
        } else {
            out.println("pas de paramètres");
        }
        out.println("<P>");
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
        out.println("firstname= <input type=text size=20 name=firstname>");
        out.println("<br>");
        out.println("lastname= <input type=text size=20 name=lastname>");
        out.println("<br>");
        out.println("<input type=submit>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }

    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }

}

On notera les nouveautés suivantes par rapport à l'exemple précédent :

  • Les paramètres envoyés par le navigateur sont récupérés de la façon suivante :

        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");

La méthode request.getParameter("nomParamètre") rend le pointeur null, si le paramètre nomParamètre ne fait pas partie des paramètres envoyés par le client web.

  • Le formulaire précise que le navigateur doit envoyer les paramètres par la méthode POST
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
  • Les paramètres reçus seront traités par la méthode doPost de la servlet. Ici, cette méthode se contente d'appeler la méthode doGet. Ainsi cette servlet traite les valeurs du formulaire qu'elles soient envoyées par un GET ou un POST.

    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }

3.1.3. Récupérer les entêtes http envoyés par un client web

La servlet qui suit montre comment récupérer les entêtes http envoyés par le client web :

Image

Le code source de la servlet est le suivant :


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class RequestHeaderExample extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        Enumeration e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String)e.nextElement();
            String value = request.getHeader(name);
            out.println(name + " = " + value);
        }
    }
}

Points à noter :

  • C'est l'objet request et sa méthode getHeaderNames qui nous donne accès aux entêtes http envoyés par le navigateur sous la forme d'une énumération :
        Enumeration e = request.getHeaderNames();
  • La méthode request.getHeader("entête") permet d'obtenir un entête http précis. L'exemple ci-dessus nous donne quelques-uns d'entre-eux. On se souviendra que les entêtes présentés ici sont envoyés par le navigateur. Le serveur a lui aussi ses propres entêtes http qui reprennent parfois ceux du navigateur. Les entêtes http envoyés par le navigateur ont pour objectif de renseigner le serveur sur les capacités du navigateur.

Entête

Signification


User-Agent

identité du navigateur


Accept

les formats MIME acceptés par le navigateur. Ainsi image/gif signifie que le navigateur sait traiter les images au format GIF


Host

au format hote:port. Indique quelle machine et quel port le navigateur veut contacter.


Accept-Encoding

format d'encodage accepté par le navigateur pour les documents envoyés par le serveur. Ainsi si un serveur a un document sous une forme normale non compressée et une au format compressé gzip et que le navigateur a indiqué qu'il savait traiter le format gzip, alors le serveur pourra envoyer le document au format gzip pour économiser de la bande passante.


Accept-language

langues acceptées par le navigateur. Si un serveur dispose d'un même document en plusieurs langues, il en enverra un dont la langue est acceptée par le navigateur.


Referer

l'URL qui a été demandée par le navigateur


Connection

le mode de connexion demandée par le navigateur. Keep-alive veut dire que le serveur ne doir pas couper la connexion après avoir servi la page demandée au navigateur. Si ce dernier découvre que la page reçue contient des liens sur des images par exemples, il pourra faire de nouvelles requêtes au serveur pour les demander sans avoir besoin de créer une nouvelle connexion. C'est le navigateur qui prendra alors l'initiative de fermer la connexion lorsqu'il aura reçu tous les éléments de la page.

3.1.4. Récupérer des informations d'environnement

La servlet qui suit montre comment accéder à des informations d'environnement d'exécution de la servlet. Certaines de celles-ci sont envoyées sous forme d'entêtes http par le navigateur et sont donc récupérables par la méthode précédente.

Image

Le code de la servlet est le suivant :


import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class RequestInfo extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>Request Information Example</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h3>Request Information Example</h3>");
        out.println("Method: " + request.getMethod());
        out.println("Request URI: " + request.getRequestURI());
        out.println("Protocol: " + request.getProtocol());
        out.println("PathInfo: " + request.getPathInfo());
        out.println("Remote Address: " + request.getRemoteAddr());
        out.println("</body>");
        out.println("</html>");
    }

  
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
        doGet(request, response);
    }
}

Les informations sont ici obtenues par diverses méthodes :

        out.println("Method: " + request.getMethod());
        out.println("Request URI: " + request.getRequestURI());
        out.println("Protocol: " + request.getProtocol());
        out.println("PathInfo: " + request.getPathInfo());
        out.println("Remote Address: " + request.getRemoteAddr());

Une liste de certaines des méthodes disponibles et leur signification est la suivante :

méthode

signification


getServerName()

le nom du serveur Web


getServerPort()

le port de travail du serveur web


getMethod()

la méthode GET ou POST utilisée par le navigateur pour faire sa requête


getRemoteHost()

le nom de la machine client à partir de laquelle le navigateur a fait sa requête


getRemoteAddr()

l'adresse IP de cette même machine


getContentType()

le type de contenu envoyé par le navigateur (entête http Content-type)


getContentLength()

le nombre de caractères envoyés par le navigateur (entête http Content-length)


getProtocol()

la version du protocole http demandée par le navigateur


getRequestURI()

l'URI demandée par le navigateur. Correspond à la partie de l'URL placée après l'identification hote:port dans http://hote:port/URI

3.1.5. Créer une servlet avec JBuilder, la déployer avec Tomcat

Nous décrivons maintenant comment créer et exécuter une servlet Java. On utilisera deux outils : Jbuilder pour compiler la servlet et Tomcat pour l'exécuter. Tomcat pourrait seul suffire. Néanmoins il offre des capacités de débogage limitées. Nous reprenons l'exemple développé précédemment qui affiche les paramètres reçus par le serveur. La servlet envoie tout d'abord le formulaire de saisie suivant :

Image

La réponse envoyée par la servlet :

Image

Le code source de la servlet est le suivant :


import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class myRequestParamExample extends HttpServlet {

    String title="Récupération des paramètres d'un formulaire";

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        String firstName = request.getParameter("firstname");
        String lastName = request.getParameter("lastname");
        if (firstName != null || lastName != null) {
            out.println("firstname= " + firstName + "<br>");
            out.println("lastname= " + lastName);
        } else {
            out.println("pas de paramètres");
        }
        out.println("<P>");
        out.print("<form action=\"RequestParamExample\" method=\"POST\">");
        out.println("firstname= <input type=text size=20 name=firstname>");
        out.println("<br>");
        out.println("lastname= <input type=text size=20 name=lastname>");
        out.println("<br>");
        out.println("<input type=submit>");
        out.println("</form>");
        out.println("</body>");
        out.println("</html>");
    }

    public void doPost(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException
    {
        doGet(request, response);
    }

}
  • créez un projet myRequestParamExample avec Jbuilder et y inclure le programme myRequestParamExample.java précédent.

  • à la compilation, on peut rencontrer le problème suivant : votre JBuilder n'a pas forcément la bibliothèque javax.servlet nécessaire à la compilation des servlets. Dans ce cas, il vous faut configurer Jbuilder pour qu'il utilise des bibliothèques de classes supplémentaires. La méthode est décrite dans les annexes de ce document pour JBuilder 7. Nous la reprenons partiellement ici :

  • activer l'option Tools/Configure JDKs ou (Options/Configurer les JDK)

Image

Dans la partie JDK Settings ci-dessus, on a normalement dans le champ Name un JDK 1.3.1. Si vous avez un JDK plus récent, utilisez le bouton Change pour désigner le répertoire d'installation de ce dernier. Ci-dessus, on a désigné le répertoire E:\Program Files\jdk14 où était installé un JDK 1.4. Désormais, JBuilder utilisera ce JDK pour ses compilations et exécutions. Dans la partie (Class, Source, Documentation) on a la liste de toutes les bibliothèques de classes qui seront explorées par JBuilder, ici les classes du JDK 1.4. Les classes de celui-ci ne suffisent pas pour faire du développement web en Java. Pour ajouter d'autres bibliothèques de classes on utilise le bouton Add et on désigne les fichiers .jar supplémentaires que l'on veut utiliser. Les fichiers .jar sont des bibliothèques de classes. Tomcat 4.x amène avec lui toutes les bibliothèques de classes nécessaires au développement web. Elles se trouvent dans <tomcat>\common\lib<tomcat> est le répertoire d'installation de Tomcat :

Image

Avec le bouton Add, on va ajouter ces bibliothèques, une à une, à la liste des bibliothèques explorées par JBuilder :

Image

A partir de maintenant, on peut compiler des programmes java conformes à la norme J2EE, notamment les servlets Java. Jbuilder ne sert qu'à la compilation, l'exécution étant ultérieurement assurée par Tomcat.

  • maintenant vous pouvez compiler le programme myRequestParamExample.java et produire la servlet myRequestParamExample.class. Où placer cette servlet ? Si la configuration initiale de Tomcat n'a pas été changée, les .class des servlets doivent être placés dans <tomcat>\webapps\examples\WEB-INF\classes (Tomcat 4.x).

  • vérifiez que Tomcat est lancé et avec un navigateur demandez l'URL http://localhost:8080/examples/servlet/myRequestParamExample :

Image

3.1.6. Exemples

Pour les exemples qui suivent, nous avons utilisé la méthode décrite précédemment :

  • compilation du source XX.java de la servlet avec Jbuilder
  • déploiement de la servlet XX.class dans <tomcat>\webapps\examples\WEB-INF\classes
  • Tomcat lancé, demander avec un navigateur l'URL http://localhost:8080/examples/servlet/XX

3.1.6.1. Génération dynamique de formulaire - 1

Nous prenons comme exemple la génération d'un formulaire n'ayant qu'un contrôle : une liste. Le contenu de cette liste est construit dynamiquement avec des valeurs prises dans un tableau. Dans la réalité, elles sont souvent prises dans une base de données. Le formulaire est le suivant :

Image

Si sur l'exemple ci-dessus, on fait Envoyer, on obtient la réponse suivante :

Image

On remarquera que l'URL qui fait la réponse est la même que celle qui affiche le formulaire. Ici on a une servlet qui traite elle-même la réponse au formulaire qu'elle a envoyé. C'est un cas courant. Le code HTML du formulaire est le suivant :

<html>
    <head><title>Génération de formulaire</title></head>
    <body>
    <h3>Choississez un nombre</h3><hr>
    <form method="POST">
      <select name="cmbValeurs" size="1">
        <option>zéro</option>
        <option>un</option>
        <option>deux</option>
        <option>trois</option>
        <option>quatre</option>
        <option>cinq</option>
        <option>six</option>
        <option>sept</option>
        <option>huit</option>
        <option>neuf</option>
      </select>
      <input type="submit" value="Envoyer">
    </form>
    </body>
</html>

On notera que les valeurs envoyées par le formulaire le sont par la méthode POST. Le code HTML de la réponse :

<html>
    <head><title>Voici ma réponse</title></head>
  <body>
    Vous avez choisi le nombre<h2>neuf</h2>
  </body>
</html>

Le code de la servlet qui génère ce formulaire et cette réponse est le suivant :

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class gener1 extends HttpServlet{
    // variables d'instance
    private String title="Génération d'un formulaire";
    private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
      "sept","huit","neuf"};
    private final String HTML1=
                "<html>" +
                  "<head>" +
                    "<title>Génération de formulaire</title>"+
                    "</head>" +
                  "<body>" +
                     "<h3>Choississez un nombre</h3>"+
                     "<hr>" +
                     "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\">";
        private final String HTML3="</form>\n</body>\n</html>";

    // GET
    public void doGet(HttpServletRequest request,HttpServletResponse response)
          throws IOException, ServletException{

      // on indique au client le type de document envoyé
      response.setContentType("text/html");
      // on envoie le formulaire
      PrintWriter out=response.getWriter();
            // début
      out.println(HTML1);
            // combo
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            for (int i=0;i<valeurs.length;i++){
                out.println("<option>"+valeurs[i]+"</option>");
            }//for
            out.println("</select>");
            // fin formulaire
            out.println(HTML2+HTML3);
    }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
          throws IOException, ServletException{

            // on récupère le choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) doGet(request,response);

            // on prépare la réponse
            String réponse="<html><head><title>Voici ma réponse</title></head>";
            réponse+="<body>Vous avez choisi le nombre <h2>"+choix+"</h2></body></html>";
            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // on envoie le formulaire
            PrintWriter out=response.getWriter();
            out.println(réponse);
    }//POST
    }//classe

La méthode doGet sert à générer le formulaire. Il y a une partie dynamique qui est le contenu de la liste, contenu provenant ici d'un tableau. La méthode doPost sert à générer la réponse. Ici la seule partie dynamique est la valeur du choix fait par l'utilisateur dans la liste du formulaire. Cette valeur est obtenue par request.getParameter("cmbValeurs")cmbValeurs est le nom HTML de la liste :

      <select name="cmbValeurs" size="1">

On notera pour terminer les points suivants :

  • le navigateur envoie les valeurs du formulaire à la servlet qui a généré le formulaire parce que la balise <form> n'a pas d'attribut <action>. Dans ce cas, le navigateur envoie les données saisies dans le formulaire à l'URL qui l'a fourni.
  • la balise <form> précise que les données du formulaire doivent être envoyées par la méthode POST. C'est pour cela que ces valeurs sont récupérées par la méthode doPost de la servlet.

3.1.6.2. Génération dynamique de formulaire - 2

Nous reprenons l'exemple précédent en le modifiant de la façon suivante. Le formulaire proposé est toujours le même :

Image

La réponse est elle différente :

Image

Dans la réponse, on renvoie le formulaire, le nombre choisi par l'utilisateur étant indiqué dessous. Par ailleurs, ce nombre est celui qui apparaît comme sélectionné lorsque la liste est affichée. L'utilisateur peut alors choisir un autre nombre :

Image

puis faire Envoyer. Il obtient la réponse suivante :

Image

Le code la servlet appelée gener2.java est la suivante :

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class gener2 extends HttpServlet{
        // variables d'instance
        private String title="Génération d'un formulaire";
        private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
            "sept","huit","neuf"};
        private final String HTML1=
                "<html>" +
                    "<head>" +
                        "<title>Génération de formulaire</title>"+
                    "</head>" +
                    "<body>" +
                         "<h3>Choisissez un nombre</h3>"+
                         "<hr>" +
                         "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\"></form>\n";
        private final String HTML3="</body>\n</html>";

        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on récupère l'éventuel choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // on envoie le formulaire
            PrintWriter out=response.getWriter();
            // début
            out.println(HTML1);
            // combo
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            String selected="";
            for (int i=0;i<valeurs.length;i++){
                if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                out.println("<option "+selected+">"+valeurs[i]+"</option>");
            }//for
            out.println("</select>");
            // suite formulaire
            out.println(HTML2);
            if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//if
            // fin du formulaire
            out.println(HTML3);
        }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on renvoie sur GET
            doGet(request,response);
        }//POST
    }//classe

La méthode doGet fait tout : elle élabore le formulaire qu'elle envoie au client et traite les valeurs que celui-ci renvoie. Les points à noter sont les suivants :

  • on vérifie si le paramètre cmbValeurs a une valeur.
  • si c'est le cas, lorsqu'on élabore le contenu de la liste, on compare chaque élément de celle-ci au choix de l'utilisateur pour mettre l'attribut selected à l'élément choisi par l'utilisateur : <option selected>élément</option>. Par ailleurs, on affiche sous le formulaire la valeur du choix.

3.1.6.3. Génération dynamique de formulaire - 3

Nous reprenons le même problème que précédemment mais cette fois-ci les valeurs sont prises dans une base de données. Celle-ci est dans notre exemple une base MySQL :

  • la base s'appelle dbValeurs
  • son propriétaire est admDbValeurs ayant le mot de passe mdpDbValeurs
  • la base a une unique table appelée tvaleurs
  • cette table n'a qu'un champ entier appelé valeur
E:\Program Files\EasyPHP\mysql\bin>mysql --database=dbValeurs --user=admDbValeurs --password=mdpDbVa
leurs

mysql> show tables;
+---------------------+
| Tables_in_dbValeurs |
+---------------------+
| tvaleurs            |
+---------------------+
1 row in set (0.00 sec)

mysql> describe tvaleurs;
+--------+---------+------+-----+---------+-------+
| Field  | Type    | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| valeur | int(11) |      |     | 0       |       |
+--------+---------+------+-----+---------+-------+

mysql> select * from tvaleurs;
+--------+
| valeur |
+--------+
|      0 |
|      1 |
|      2 |
|      3 |
|      4 |
|      6 |
|      5 |
|      7 |
|      8 |
|      9 |
+--------+
10 rows in set (0.00 sec)

La base MySQL dbValeurs a été rendu accessible par un pilote ODBC pour MySQL. Son nom DSN (Data Source Name) est odbc-valeurs. Le code de la servlet est le suivant :

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;

public class gener3 extends HttpServlet{
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
        // valeurs de liste
        private String[] valeurs=null;
        // msg d'erreur
        private String msgErreur=null;
        // code HTML
        private final String HTML1=
                "<html>" +
                    "<head>" +
                        "<title>Génération de formulaire</title>"+
                    "</head>" +
                    "<body>" +
                         "<h3>Choisissez un nombre</h3>"+
                         "<hr>" +
                         "<form method=\"POST\">";
        private final String HTML2="<input type=\"submit\" value=\"Envoyer\"></form>\n";
        private final String HTML3="</body>\n</html>";

        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on indique au client le type de document envoyé
            response.setContentType("text/html");
            // flux de sortie
            PrintWriter out=response.getWriter();

            // l'initialisation de la servlet s'est-elle bien passée ?
            if (msgErreur!=null){
                // il y a eu une erreur - on génère une page d'erreur
                out.println("<html><head><title>"+title+"</title></head>");
                out.println("<body><h3>Application indisponible ("+msgErreur+
                                        ")</h3></body></html>");
                return;
            }//if

            // on récupère l'éventuel choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";

            // on envoie le formulaire
            // début
            out.println(HTML1);
            // combo
            out.println("<select name=\"cmbValeurs\" size=\"1\">");
            String selected="";
            for (int i=0;i<valeurs.length;i++){
                if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                out.println("<option "+selected+">"+valeurs[i]+"</option>");
            }//for
            out.println("</select>");
            // suite formulaire
            out.println(HTML2);
            if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                out.println("<hr>Vous avez choisi le nombre <h2>"+choix+"</h2>");
            }//if
            // fin du formulaire
            out.println(HTML3);
        }//GET

        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on renvoie sur GET
            doGet(request,response);
        }//POST

        // initialisation de la servlet
        public void init(){
            // remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                // connexion à la base ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                // objet Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                rs=st.executeQuery("select valeur from Tvaleurs");
                // les valeurs sont récupérées et mises dans un tableau dynamique
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // on enregistre la valeur dans la liste
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                // transformation liste --> tableau
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                // problème
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
    }//classe

Les points importants à noter sont les suivants :

  1. Une servlet peut être initialisée par une méthode dont la signature doit être public void init(). Cette méthode n'est exécutée qu'au chargement initial de la servlet
  2. Une fois chargée, une servlet reste en mémoire tout le temps. Cela signifie que lorsqu'elle a servi un client, elle n'est pas déchargée. Elle répond ainsi plus vite aux requêtes des clients.
  3. Dans notre servlet, une liste de valeurs doit être cherchée dans une base de données. Cette liste ne changeant pas au cours du temps, la méthode init est le moment idéal pour la récupérer. La base n'est ainsi accédée qu'une fois par la servlet, au moment du chargement initial de celle-ci, et non pas à chaque requête d'un client.
  4. L'accès à une base de données peut échouer. La méthode init de notre servlet positionne un message d'erreur msgErreur en cas d'échec. Ce message est testé dans la méthode doGet et s'il y a eu erreur doGet génère une page la signalant.
  5. L'écriture de la méthode init utilise un accès classique à une base de données avec les pilotes Odbc-Jdbc. Si besoin est, le lecteur est invité à revoir les méthodes d'accès aux bases de données JDBC.

Lorsqu'on exécute la servlet et que le serveur MySQL n'a pas été lancé, on a la page d'erreur suivante :

Image

Si maintenant, on lance le serveur MySQL, on obtient la page :

Image

Si on choisit le nombre 6 et qu'on "envoie" :

Image

3.1.6.4. Récupérer les valeurs d'un formulaire

Nous reprenons un exemple déjà rencontré, celui du formulaire web suivant :

Image

Le code HTML du formulaire balises2.htm est le suivant :

<html>

  <head>
    <title>balises</title>
    <script language="JavaScript">
        function effacer(){
        alert("Vous avez cliqué sur le bouton Effacer");
      }//effacer
        </script>
  </head>

  <body background="/images/standard.jpg">
...

    <form method="POST" action="http://localhost:8080/examples/servlet/parameters">

      <table border="0">
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
            <input type="radio" value="Oui" name="R1">Oui
            <input type="radio" name="R1" value="non" checked>Non
          </td>
        </tr>
        <tr>
          <td>Cases à cocher</td>
          <td>
            <input type="checkbox" name="C1" value="un">1
            <input type="checkbox" name="C2" value="deux" checked>2
            <input type="checkbox" name="C3" value="trois">3
          </td>
        </tr>
        <tr>
          <td>Champ de saisie</td>
          <td>
            <input type="text" name="txtSaisie" size="20" value="qqs mots">
          </td>
        </tr>
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="txtMdp" size="20" value="unMotDePasse">
          </td>
        </tr>
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea rows="2" name="areaSaisie" cols="20">
ligne1
ligne2
ligne3
</textarea>
          </td>
        </tr>
        <tr>
          <td>combo</td>
          <td>
              <select size="1" name="cmbValeurs">
                <option>choix1</option>
                <option selected>choix2</option>
                <option>choix3</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>liste à choix simple</td>
          <td>
              <select size="3" name="lst1">
                <option selected>liste1</option>
                <option>liste2</option>
                <option>liste3</option>
                <option>liste4</option>
                <option>liste5</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>liste à choix multiple</td>
          <td>
              <select size="3" name="lst2" multiple>
                <option selected>liste1</option>
                <option>liste2</option>
                <option selected>liste3</option>
                <option>liste4</option>
                <option>liste5</option>
              </select>
          </td>
        </tr>
        <tr>
          <td>bouton</td>
          <td>
              <input type="button" value="Effacer" name="cmdEffacer" onclick="effacer()">
          </td>
        </tr>
        <tr>
          <td>envoyer</td>
          <td>
              <input type="submit" value="Envoyer" name="cmdRenvoyer">
          </td>
        </tr>
        <tr>
          <td>rétablir</td>
          <td>
              <input type="reset" value="Rétablir" name="cmdRétablir">
          </td>
        </tr>
      </table>
      <input type="hidden" name="secret" value="uneValeur">
    </form>
  </body>
</html>

La balise <form> du formulaire a été définie comme suit :

    <form method="POST" action="http://localhost:8080/examples/servlet/parameters">

Le navigateur "postera" les valeurs du formulaire à l'URL http://localhost:8080/examples/servlet/parameters qui est l'URL d'une servlet gérée par Tomcat et qui affiche les valeurs du formulaire précédent. Si on appelle la servlet parameters directement, on a les résultats suivants :

Image

Si le formulaire balises2.htm saisi est celui-ci :

Image

et qu'on appuie sur le bouton Envoyer (de type submit), la servlet parameters est cette fois appelée avec des paramètres. Elle renvoie alors la réponse suivante :

Image

On retrouve bien dans cette réponse, les valeurs saisies dans le formulaire. Le code de la servlet est la suivante :

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class parameters extends HttpServlet{
    // variables d'instance
    String title="Récupération des paramètres d'un formulaire";

    private String getParameter(HttpServletRequest request, String contrôle){
      // rend la valeur request.getParameter(contrôle) ou "" si elle n'existe pas
      String valeur=request.getParameter(contrôle);
      if(valeur==null) return ""; else return valeur;
    }//getParameter

    // GET
    public void doGet(HttpServletRequest request,HttpServletResponse response)
        throws IOException, ServletException
    {
      // on commence par récupérer les paramètres du formulaire
      String R1=getParameter(request,"R1");
      String C1=getParameter(request,"C1");
      String C2=getParameter(request,"C2");
      String C3=getParameter(request,"C3");
      String txtSaisie=getParameter(request,"txtSaisie");
      String txtMdp=getParameter(request,"txtMdp");
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");
      String cmbValeurs=getParameter(request,"cmbValeurs");
      String lst1=getParameter(request,"lst1");
      String[] lst2=request.getParameterValues("lst2");
      String secret=getParameter(request,"secret");

      // on indique le contenu du document
        response.setContentType("text/html");
      // on envoie le document
        PrintWriter out = response.getWriter();
        out.println("<html>");
        out.println("<body>");
        out.println("<head>");
        out.println("<title>" + title + "</title>");
        out.println("</head>");
        out.println("<body bgcolor=\"white\">");
        out.println("<h3>" + title + "</h3>");
        out.println("<hr>");
        out.println("<table border=\"1\">");
        out.println("<tr><td>R1</td><td>"+R1+"</td></tr>");
        out.println("<tr><td>C1</td><td>"+C1+"</td></tr>");
        out.println("<tr><td>C2</td><td>"+C2+"</td></tr>");
        out.println("<tr><td>C3</td><td>"+C3+"</td></tr>");
        out.println("<tr><td>txtSaisie</td><td>"+txtSaisie+"</td></tr>");
        out.println("<tr><td>txtMdp</td><td>"+txtMdp+"</td></tr>");
        for(int i=0;i<lignes.length;i++)
          out.println("<tr><td>areaSaisie["+i+"]</td><td>"+lignes[i]+"</td></tr>");
        out.println("<tr><td>cmbValeurs</td><td>"+cmbValeurs+"</td></tr>");
        out.println("<tr><td>lst1</td><td>"+lst1+"</td></tr>");
        if(lst2==null)
          out.println("<tr><td>lst2</td><td></td></tr>");
        else
          for(int i=0;i<lst2.length;i++)
            out.println("<tr><td>lst2</td><td>"+lst2[i]+"</td></tr>");
        out.println("<tr><td>secret</td><td>"+secret+"</td></tr>");
        out.println("</body>");
        out.println("</html>");
    }

    // POST
    public void doPost(HttpServletRequest request,HttpServletResponse response)
        throws IOException, ServletException
    {
      // renvoie sur GET
      doGet(request,response);
    }
}

On retrouve dans ce code les techniques présentées précédemment dans un autre exemple. On notera deux points :

  1. le contrôle lst2 est une liste à sélection multiple et donc plusieurs éléments peuvent être sélectionnés. C'est le cas dans notre exemple où les éléments liste1 et liste3 ont été sélectionnés. Les valeurs de lst2 ont été transmises par le navigateur au serveur sous la forme lst2=liste1&lst2=liste3. La servlet Java peut récupérer ces valeurs dans un tableau avec la méthode getParameterValues : ici request.getParameterValues("lst2") fournit un tableau de 2 chaînes de caractères ["liste1","liste3"].
  2. le contrôle areaSaisie est un champ de saisie multilignes. request.getParameter("areaSaisie") donne le contenu du champ sous la forme d'une unique chaîne de caractères. Si dans celle-ci, on veut récupérer les différentes lignes qui la forment on pourra utiliser la fonction split de la classe String. Le code suivant
      String areaSaisie=getParameter(request,"areaSaisie");
      String[] lignes=areaSaisie.split("\\r\\n");

récupère les lignes du champ de saisie. Ces lignes sont terminées par les caractères \r\n (0D0A).

Pour faire les tests on a :

  • construit et compilé la servlet parameters avec JBuilder comme il a été expliqué précédemment
  • placé la classe générée dans <tomcat>\webapps\examples\WEB-INF\classes<tomcat> est le répertoire d'installation de Tomcat.
  • demandé l'URL http://localhost:81/html/balises2.htm dont le code a été présenté plus haut
  • rempli le formulaire et appuyé sur le bouton Envoyer.

3.1.6.5. Récupérer les entêtes HTTP d'un client web

Nous reprenons le même exemple que précédemment mais en réponse au client web qui a envoyé les valeurs du formulaire, nous lui envoyons les entêtes HTTP qu'il a envoyés en même temps. Nous introduisons un seul changement dans notre formulaire :

    <form method="GET" action="http://localhost:8080/examples/servlet/headers">

Les valeurs du formulaires seront envoyées par la méthode GET à une servlet java appelée headers placée dans <tomcat>\webapps\examples\WEB-INF\classes. La servlet headers a été construite et compilée avec JBuilder :

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class headers extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
      // on fixe la nature du document
        response.setContentType("text/html");
      // on obtient un flux d'écriture
        PrintWriter out = response.getWriter();
      // affichage liste des entêtes HTTP
        Enumeration e = request.getHeaderNames();
        while (e.hasMoreElements()) {
            String name = (String)e.nextElement();
            String value = request.getHeader(name);
            out.println("<b>"+name + "</b> = " + value + "<br>");
        }
    }//GET

    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException
    {
      //GET
      doGet(request,response);
    }//POST
}

On demande l'URL http://localhost:81/html/balises2.htm et on fait Envoyer sans modifier le formulaire. On obtient la réponse suivante :

Image

On remarquera l'URL paramétrée présente dans le champ Address du navigateur qui montre la façon (GET) utilisée pour transmettre les paramètres. Nous reprenons le même exemple mais en modifiant la façon d'envoyer les paramètres (POST) :

    <form method="POST" action="http://localhost:8080/examples/servlet/headers">

On obtient la nouvelle réponse suivante :

Image

On remarquera les entêtes HTTP content-type et content-length caractéristiques d'un envoi par POST. Par ailleurs, on notera que dans le champ Address du navigateur, les valeurs du formulaire n'apparaissent plus.

3.2. Pages JSP

Les pages JSP (Java Server Pages) sont une autre façon d'écrire des applications serveurs web. En fait ces pages JSP sont traduites en servlets avant d'être exécutées et on retrouve alors la technologie des servlets. Les pages JSP permettent de mieux mettre en relief la structure des pages HTML générées. Nous présentons ci-dessous des exemples dont certains sont accessibles en suivant le lien JSP de la page d'accueil de Tomcat :

Image Image

3.2.1. Récupérer des informations d'environnement

Nous reprenons ici un exemple déjà traité avec une servlet : afficher les variables d'environnement d'une servlet. C'est l'exemple snoop des exemples JSP :

Image

Le code source de la page JSP se trouve dans <tomcat>\jakarta-tomcat\examples\jsp\snp\snoop.jsp (Tomcat 3.x) ou <tomcat>\examples\jsp\snp\snoop.jsp (Tomcat 4.x)


<html>
<!--
  Copyright (c) 1999 The Apache Software Foundation.  All rights 
  reserved.
-->

  <body bgcolor="white">
    <h1> Request Information </h1>
    <font size="4">
      JSP Request Method: <%= request.getMethod() %>
      <br>
      Request URI: <%= request.getRequestURI() %>
      <br>
      Request Protocol: <%= request.getProtocol() %>
      <br>
      Servlet path: <%= request.getServletPath() %>
      <br>
      Path info: <%= request.getPathInfo() %>
      <br>
      Path translated: <%= request.getPathTranslated() %>
      <br>
      Query string: <%= request.getQueryString() %>
      <br>
      Content length: <%= request.getContentLength() %>
      <br>
      Content type: <%= request.getContentType() %>
      <br>
      Server name: <%= request.getServerName() %>
      <br>
      Server port: <%= request.getServerPort() %>
      <br>
      Remote user: <%= request.getRemoteUser() %>
      <br>
      Remote address: <%= request.getRemoteAddr() %>
      <br>
      Remote host: <%= request.getRemoteHost() %>
      <br>
      Authorization scheme: <%= request.getAuthType() %> 
      <hr>
      The browser you are using is <%= request.getHeader("User-Agent") %>
      <hr>
    </font>
  </body>
</html>

On note les points suivants :

  • on a là un code qui ressemble fort à du HTML. On y trouve cependant des balises <%= expression %> qui sont propres au langage JSP. Le compilateur JSP remplace dans le texte HTML l'intégralité de la balise par la valeur de expression.
  • cet exemple utilise les méthodes de l'objet Java request qui est l'objet request déjà rencontré dans l'étude des servlets. C'est donc un objet HttpServletRequest. Ainsi la balise <%= request.getRemoteHost() %> sera remplacée dans le code HTML par le nom de la machine du client web qui a fait la requête.
  • on peut arriver au même résultat avec une servlet mais ici la structure de la page web est plus évidente.

3.2.2. Récupérer les paramètres envoyés par le client web

Nous reprenons ici l'exemple déjà étudié avec une servlet. Il est présenté un formulaire au navigateur :

Image

En réponse à la requête ci-dessus, le navigateur reçoit la page suivante :

Image

Le code de la page JSP est le suivant :


<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

<!-- code HTML -->
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body bgcolor="white">
    <h3><%= title %></h3>
    <%
      if (firstName != null || lastName != null) {
        out.println("firstname= " + firstName + "<br>");
        out.println("lastname= " + lastName);
      } else {
        out.println("pas de paramètres");
      }
    %>
    <P>
    <form method="POST">
      firstname= <input type="text" size="20" name="firstname">
      <br>
      lastname= <input type="text" size="20" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>
  • Si on retrouve la balise <%= expression %> déjà rencontrée dans l'exemple précédent, une nouvelle balise apparaît <% instructions Java; %>. La balise <% introduit du code Java. Ce code se termine à la rencontre de la balise de fermeture de code %>.

  • L'ensemble du code précédent (HTML + JSP) va faire l'objet d'une conversion en servlet Java. Il sera enfermé dans une unique méthode, appelée méthode principale de la page JSP. C'est pourquoi, les variables java déclarées au début de la page JSP sont connues dans les autres portions de code JSP qui parsèment le code HTML : ces variables et portions de code feront partie de la même méthode Java. Mais si notre code JSP devait contenir des méthodes, les variables title, firstname et lastname n'y seraient pas connues à cause de l'étanchéité entre méthodes. Il faudrait en faire des variables globales ou les passer en paramètres aux méthodes. Nous y reviendrons.

  • Pour inclure des parties dynamiques dans le code HTML deux méthodes sont possibles : <%= expression %> ou out.println(expression). L'objet out est un flux de sortie analogue à celui de même nom rencontré dans les exemples de servlets mais pas du même type : c'est un objet JspWriter et non un PrintWriter. Il permet d'écrire dans le flux HTML avec le méthodes print et println.

  • La page JSP reflète mieux la structure de la page HTML générée que la servlet équivalente.

3.2.3. Les balises JSP

Voici une liste de balises qu'on peut rencontrer dans une page JSP et leur signification.

balise

signification


<!-- commentaire -->

commentaire HTML. Est envoyé au client.


<%-- commentaire --%>

commentaire JSP. N'est pas envoyé au client.


<%! déclarations, méthodes %>

déclare des variables globales et méthodes. Les variables seront connues dans toutes les méthodes


<%= expression %>

la valeur de expression sera intégrée dans la page HTML à la place de la balise


<% code Java %>

contient du code Java qui fera partie de la méthode principale de la page JSP


<%@ page attribut1=valeur1
attribut2=valeur2 … %>

fixe des attributs pour la page JSP. Par exemple :

import="java.util.*,java.sql.*" pour préciser les bibliothèques nécessaires à la page JSP

extends="uneClasseParent" pour faire dériver la page JSP d'une autre classe

3.2.4. Les objets implicites JSP

Dans les exemples précédents, nous avons rencontré deux objets non déclarés : request et out. Ce sont deux des objets qui sont automatiquement définis dans la servlet dans laquelle est convertie la page JSP. On les appelle des objets implicites ou prédéfinis. Il en existe d'autres mais ce sont les plus utilisés avec l'obet response :

objet

signification


HttpServletRequest request

l'objet à partir duquel on a accès à la requête du client Web (getParameter, getParameterNames, getParameterValues)


HttpServletResponse response

l'objet avec lequel on peut construire la réponse du serveur Web à son client. Permet de fixer les entêtes http à envoyer au client Web.


JspWriter out

le flux de sortie qui nous permet d'envoyer du code HTML au client (print,println)

3.2.5. La transformation d'une page JSP en servlet

Reprenons le code JSP de myRequestParamExample.jsp :


<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

<!-- code HTML -->
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body bgcolor="white">
    <h3><%= title %></h3>
    <%
      if (firstName != null || lastName != null) {
        out.println("firstname= " + firstName + "<br>");
        out.println("lastname= " + lastName);
      } else {
        out.println("pas de paramètres");
      }
    %>
    <P>
    <form method="POST">
      firstname= <input type="text" size="20" name="firstname">
      <br>
      lastname= <input type="text" size="20" name="lastname">
      <br>
      <input type="submit">
    </form>
  </body>
</html>

Lorsque le navigateur demande cette page JSP au serveur Tomcat, celui-ci va la transformer en servlet. Si l'URL demandée est

http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp, Tomcat 4.x va placer la servlet générée dans le répertoire <tomcat>\work\localhost\examples\jsp\perso\intro :

Image

On retrouve dans ce nom l'URL http://localhost:8080/examples/jsp/perso/intro/myRequestParamExample.jsp de la page JSP. On voit ci-dessus qu'on a accès au code java de la servlet générée pour la page JSP. Dans notre exemple, c'est le suivant :

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class myRequestParamExample$jsp extends HttpJspBase {


    static {
    }
    public myRequestParamExample$jsp( ) {
    }

    private static boolean _jspx_inited = false;

    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {

            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;charset=ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);

            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();


                  // variables locales à la procédure principale
                  String title="Récupération des paramètres d'un formulaire";
                  String firstName = request.getParameter("firstname");
                  String lastName = request.getParameter("lastname");

                out.write("\r\n\r\n<!-- code HTML -->\r\n<html>\r\n  <head>\r\n    <title>");
                out.print( title );
                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
                out.print( title );
                out.write("</h3>\r\n    ");

                      if (firstName != null || lastName != null) {
                        out.println("firstname= " + firstName + "<br>");
                        out.println("lastname= " + lastName);
                      } else {
                        out.println("pas de paramètres");
                      }

                out.write("\r\n    <P>\r\n    <form method=\"POST\">\r\n      firstname= <input type=\"text\" size=\"20\" name=\"firstname\">\r\n      <br>\r\n      lastname= <input type=\"text\" size=\"20\" name=\"lastname\">\r\n      <br>\r\n      <input type=\"submit\">\r\n    </form>\r\n  </body>\r\n</html>\r\n");

        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}

Le code généré est assez complexe. Nous ne retiendrons que les points suivants :

  • La méthode principale de la servlet est la suivante :
    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

C'est cette méthode qui est lancée au départ de la servlet. On voit qu'elle reçoit deux paramètres : la requête request du client et un objet response pour générer sa réponse au client web.

  • Dans la méthode principale un objet JspWriter out est déclaré puis initialisé. C'est lui qui va permettre d'envoyer du code HTML au client par des instructions out.print("codeHTML").
        JspWriter out = null;
...
            out = pageContext.getOut();
  • Le code Java

<%
  // variables locales à la procédure principale
  String title="Récupération des paramètres d'un formulaire";
  String firstName = request.getParameter("firstname");
  String lastName = request.getParameter("lastname");
%>

a été repris intégralement dans la méthode principale _jspService de la servlet. Il en est de même pour tout code situé dans les balises <%… %>

  • Le code HTML de la page JSP fait l'objet d'instructions out.print("codeHTML") ou out.write(...). Par exemple

                out.write("</title>\r\n  </head>\r\n  <body bgcolor=\"white\">\r\n    <h3>");
  • Dans cet exemple, il n'y a pas d'autres méthodes que la méthode principale _jspService.

3.2.6. Les méthodes et variables globales d'une page JSP

Considérons la page JSP suivante :


<%!
  // la balise précédente démarre la partie variables et méthodes globales
  // cette partie sera reprise sans modification dans la servlet
  
  // une variabe globale
  String prenom="inconnu";

  // une méthode  
  private String sonChien(){
    return "milou";
  }//sonChien

  // une autre méthode
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi

  // fin de la partie globale de la servlet
%>  

<%
  // la balise précédente indique que le code qui suit sera enregistré
  // dans la méthode principale de la servlet
  
  // variable locale à la méthode principale
  String nom="tintin";
%>


<%-- code HTML --%>
<html>
  <head>
    <title>Page JSP</title>
  </head>
  <body>
    <center>
      <h2>Page JSP</h2>
      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
        // le nom de son ami
        afficheAmi(out);
      %>
    </center>
  </body>
</html>

Cette page JSP génère la page Web suivante :

Image

Intéressons-nous à la façon dont sont générées les quatre lignes ci-dessus :


      <p>Son nom est <%= nom %></p>
      <p>Son prénom est <%= prenom %></p>
      <p>Son chien s'appelle <%= sonChien() %></p>
      <%
        // le nom de son ami
        afficheAmi(out);
      %>

Les lignes ci-dessus sont dans une balise <%..%> et feront donc partie de la méthode principale _jspService de la servlet qui sera générée. Comment ont-elles accès aux variables nom, prenom et méthodes sonChien et afficheAmi ?


nom (tintin)

est une variable locale à la méthode principale de la page JSP et donc connue dans celle-ci


prenom (inconnu)

est une variable globale de la page JSP et donc connue dans la méthode principale


sonChien (milou)

est une méthode publique de la page JSP et donc accessible de la méthode principale


afficheAmi (Haddock)

est une méthode publique de la page JSP et donc accessible de la méthode principale. On remarquera qu'on passe l'objet out en paramètre à la méthode. C'est ici obligatoire. En effet, l'objet out est déclaré et initialisé dans la méthode principale de la servlet et n'est pas une variable globale.

Voyons maintenant le code de la servlet java générée à partir de cette page JSP, une fois débarassé du code inutile :

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class tintin$jsp extends HttpJspBase {

          // la balise précédente démarre la partie variables et méthodes globales
          // cette partie sera reprise sans modification dans la servlet

          // une variabe globale
          String prenom="inconnu";

          // une méthode  
          private String sonChien(){
            return "milou";
          }//sonChien

          // une autre méthode
          private void afficheAmi(JspWriter out) throws Exception{
            out.println("<p>Son ami s'appelle Haddock</p>");
          }//afficheAmi

          // fin de la partie globale de la servlet

    static {
    }
    public tintin$jsp( ) {
    }

    private static boolean _jspx_inited = false;

    public final void _jspx_init() throws org.apache.jasper.runtime.JspException {
    }

    public void _jspService(HttpServletRequest request, HttpServletResponse  response)
        throws java.io.IOException, ServletException {

        JspFactory _jspxFactory = null;
        PageContext pageContext = null;
        HttpSession session = null;
        ServletContext application = null;
        ServletConfig config = null;
        JspWriter out = null;
        Object page = this;
        String  _value = null;
        try {

            if (_jspx_inited == false) {
                synchronized (this) {
                    if (_jspx_inited == false) {
                        _jspx_init();
                        _jspx_inited = true;
                    }
                }
            }
            _jspxFactory = JspFactory.getDefaultFactory();
            response.setContentType("text/html;charset=ISO-8859-1");
            pageContext = _jspxFactory.getPageContext(this, request, response,
                        "", true, 8192, true);

            application = pageContext.getServletContext();
            config = pageContext.getServletConfig();
            session = pageContext.getSession();
            out = pageContext.getOut();

                out.write("  \r\n\r\n");
                  // la balise précédente indique que le code qui suit sera enregistré
                  // dans la méthode principale de la servlet

                  // variable locale à la méthode principale
                  String nom="tintin";

                out.write("\r\n\r\n\r\n");
                out.write("\r\n<html>\r\n  <head>\r\n    <title>Page JSP</title>\r\n  </head>\r\n  <body>\r\n    <center>\r\n      <h2>Page JSP</h2>\r\n      <p>Son nom est ");
                out.print( nom );
                out.write("</p>\r\n      <p>Son prénom est ");
                out.print( prenom );
                out.write("</p>\r\n      <p>Son chien s'appelle ");
                out.print( sonChien() );
                out.write("</p>\r\n      ");

                        // le nom de son ami
                        afficheAmi(out);

                out.write("\r\n    </center>\r\n  </body>\r\n</html>\r\n");
        } catch (Throwable t) {
            if (out != null && out.getBufferSize() != 0)
                out.clearBuffer();
            if (pageContext != null) pageContext.handlePageException(t);
        } finally {
            if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
        }
    }
}

On voit ci-dessus que le code Java qui était situé entre les balise JSP <%! .. %> a été repris intégralement et ne fait pas partie de la méthode principale _jspService de la servlet. Les variables déclarées dans cette partie sont alors des variables d'instance donc globales aux méthodes et c'est également là qu'on peut définir des méthodes autres que _jspService.


  // cette partie sera reprise sans modification dans la servlet

  // une variabe globale
  String prenom="inconnu";

  // une méthode
  private String sonChien(){
    return "milou";
  }//sonChien

  // une autre méthode
  private void afficheAmi(JspWriter out) throws Exception{
    out.println("<p>Son ami s'appelle Haddock</p>");
  }//afficheAmi

  // fin de la partie globale de la servlet

3.2.7. Déploiement et débogage des pages JSP au sein du serveur Tomcat

Lorsqu'on veut construire une page JSP et l'utiliser avec le serveur Tomcat, se pose la question de l'endroit où placer la page dans l'arborescence du serveur. Il y a différentes façons de faire sur lesquelles nous reviendrons. Pour l'instant, la plus simple est de placer la page JSP dans un dossier de l'arborescence <tomcat>\webapps\examples\jsp (Tomcat 4.x) où <tomcat> est le répertoire d'installation de Tomcat. Ainsi l'URL de l'exemple précédent était http://localhost:8080/examples/jsp/perso/tintin/tintin.jsp. Cela signifie que la page tintin.jsp était dans le dossier <tomcat>\webapps\examples\jsp\perso\tintin.

Une page JSP est traduite en fichier source java, fichier ensuite compilé par Tomcat lorsque l'URL de la page JSP est demandée par un navigateur. Des erreurs de compilation peuvent apparaître. Tomcat 4.x les signale dans sa réponse au navigateur. Il indique notamment les lignes du fichier .java qui sont erronées. Les erreurs peuvent avoir diverses sources :

  1. le code JSP de la page est erroné (erreurs dans les balises jsp utilisées par exemple)
  2. le code Java inclus dans la page JSP est erroné

La première cause peut être éliminée en vérifiant le code JSP de la page. La seconde peut l'être en vérifiant le code Java. On pourra le faire en compilant directement le fichier .java généré pour la page JSP avec un outil tel que JBuilder qui offre des possibilités de débogage plus évoluées que celles de Tomcat.

3.2.8. Exemples

Nous reprenons l'exemple déjà traité avec une servlet où un utilisateur choisit un nombre dans une liste et le serveur lui dit quel nombre il a choisi tout en lui renvoyant la même liste avec comme élément sélectionné l'élément choisi par l'utilisateur :

Image

Pour construire cette page, on a récupéré le code de la servlet qu'on a modifié de la façon suivante :

  • on a conservé tel quel le code Java qui ne produisait pas du code HTML
  • le code Java qui produisait du code HTML a été transformé en un mix code HTML, code JSP

On obtient alors la page JSP suivante :

<%@ page import="java.sql.*, java.util.*" %>

<%!

        // variables globales de l'application
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
        // valeurs de liste
        private String[] valeurs=null;
        // msg d'erreur
        private String msgErreur=null;

        // initialisation de la page JSP - n'est exécutée qu'une seule fois
        public void jspInit(){
            // remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                // connexion à la base ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                // objet Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                rs=st.executeQuery("select valeur from Tvaleurs");
                // les valeurs sont récupérées et mises dans un tableau dynamique
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // on enregistre la valeur dans la liste
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                // transformation liste --> tableau
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                // problème
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init

%>

<%
    // code de _jspService exécuté à chaque requête cliente
  // y-at-il eu une erreur lors de l'initialisation de la page JSP ?
  if(msgErreur!=null){
%>
    <!-- code HTML -->
    <html>
        <head>
        <title>Erreur</title>
      </head>
      <body>
        <h3>Application indisponible (<%= msgErreur %></h3>
      </body>
    </html>
<%
    // fin de jspService
    return;
  }//if

    // on récupère l'éventuel choix de l'utilisateur
    String choix=request.getParameter("cmbValeurs");
    if(choix==null) choix="";
%>

  <%-- pas d'erreur - code HTML de la page normale --%>
    <html>
        <head>
        <title><%= title %></title>
      </head>
      <body>
        <h3>Choisissez une valeur</h3>
        <form method="POST">
            <select name="cmbValeurs">
            <%
                // affichage dynamique des valeurs
                        String selected="";
                        for (int i=0;i<valeurs.length;i++){
                            if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                            out.println("<option "+selected+">"+valeurs[i]+"</option>");
                        }//for
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
<%
        // y-avait-il une valeur choisie ?
                if(! choix.equals("")){
        // on affiche le choix de l'utilisateur
%>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
<%        
                }//if
%>        
      </body>
    </html>

Notons les points suivants :

  • les instructions import de la servlet ont fait l'objet d'une directive <% page import="..." %>
  • la balise <%! ... %> encadrent les variables globales et les méthodes java de l'aplication
  • la méthode init de la servlet qui est exécutée une seule fois, au moment du chargement de la servlet, s'appelle pour une page JSP : jspInit. Ces deux méthodes ont le même rôle. On a donc repris ici intégralement le code de la méthode init de la servlet.
  • les variables d'instance de la servlet, celles qui doivent être connues dans plusieurs méthodes ont été reprises à l'identique. Il s'agit essentiellement des variables title, valeurs et msgErreur qui sont ensuite utilisées dans le code JSP.
  • les balises <% ... %> encadrent du code Java qui sera inclus dans la méthode _jspService exécutée au moment d'une requête d'un client.
  • comme pour la servlet, la méthode _jspService commencera par vérifier la valeur de la variable msgErreur pour savoir si elle doit générer une page d'erreur. S'il y a erreur, elle génère la page d'erreur et s'arrête (return).
  • s'il n'y a pas d'erreur, elle génère le formulaire avec la liste de valeurs
  • ceci fait, elle vérifie si l'utilisateur avait choisi un nombre, auquel cas elle affiche ce nombre dans la page générée

Qu'a-t-on gagné vis à vis de la servlet ? Sans doute une meilleure vue du code HTML généré. Mais il reste encore beaucoup de code Java qui "pollue" cette vue. Nous verrons ultérieurement, une autre méthode appelée délégation où on pourra mettre l'essentiel du code Java dans une servlet, la page JSP ne conservant elle que le code HTML et JSP. On sépare alors nettement la partie traitement de la partie présentation.

3.3. Déploiement d'une application web au sein du serveur Tomcat

Nous présentons maintenant la façon de déployer des applications web Java avec le serveur Tomcat. Si ce qui va être dit est propre à ce serveur, le déploiement d'une application web Java au sein d'un autre conteneur J2EE présentera des caractéristiques proches de celles qui vont être décrites maintenant.

3.3.1. Les fichiers de configuration server.xml et web.xml

Jusqu'à maintenant, pour tester nos servlets et pages JSP, nous avons placé

  • les servlets dans le dossier <tomcat>\webapps\examples\WEB-INF\classes. Elles étaient alors accessibles via l'URL http://localhost:8080/examples/servlet/nomServlet
  • les pages JSP dans l'arborescence <tomcat>\webapps\examples\jsp. Elles étaient alors accessibles via l'URL http://localhost:8080/examples/jsp/nomPageJSP

Nous n'avons jamais expliqué pourquoi c'était ainsi. La configuration du serveur Tomcat est faite dans un fichier texte appelé server.xml qui se trouve dans le dossier <tomcat>\conf :

Image

Ce fichier texte est en fait un fichier XML (eXtended Markup Language). Un document XML est un document texte contenant des balises comme l'est un document HTML. Cependant alors que les balises du langage HTML sont bien définies, celles du langage XML ne le sont pas. Ainsi le document suivant est un document XML :

<personne>
    <prenom>Pierre</prenom>
  <nom>Lucas</nom>
  <age>28</age>
</personne>

Un document XML est simplement un document "balisé" et qui suit certaines règles de balisage :

  • un texte balisé à la forme <xx att1="val1" att2="val2" ....>texte</xx>
  • une balise peut être seule et avoir la forme <xx att1="val1" att2="val2" ..../>

Les champs atti sont appelés attributs de la balise xx et les champs vali sont les valeurs associées à ces attributs. Certains documents HTML ne sont pas des documents XML valides. Par exemple la balise HTML <br> n'est pas une balise XML valide. Elle devrait s'écrire <br/> pour en être une afin de respecter la règle qui veut que toute balise XML doit être fermée. Une variante de HTML appelée XHTML a été créée afin de faire de tout document XHTML un document XML valide. Certains des navigateurs récents sont capables d'afficher des fichiers XML. Ainsi si nous appelons personne.xml le document XML présenté en exemple ci-dessus et que nous le visualisons avec IE6, nous obtenons l'affichage suivant :

Image

IE6 reconnaît les balises et les colore. Il reconnaît également la structure du document grâce aux balises. Ainsi si nous appelons personne2.xml le document suivant :

<personne><prenom>Pierre</prenom><nom>Lucas</nom><age>28</age></personne>

et le visualisons avec IE6, nous obtenons le même affichage :

Image

IE6 a reconnu correctement la structure et le contenu du document. Tout l'intérêt du document XML repose dans cette propriété : il est facile de retrouver la structure et le contenu d'un document XML. Cela se fait avec un programme appelé un parseur XML. Les documents XML ont tendance à devenir la norme dans les échanges de documents sur le web. Prenons une machine A devant envoyer un document DOC à une machine B. Le document DOC est construit à partir des informations contenues dans une base de données DB-A. La machine B elle doit stocker le document DOC dans une base de données DB-B. L'échange pourra se faire de la façon suivante :

  • la machine A récupère les données dans la base DB-A et encapsule celles-ci dans un document texte XML
  • le document XML est envoyé à la machine B à travers le réseau
  • la machine B analyse le document reçu avec un parseur XML et en récupère et la structure et les données (comme l'a fait IE6 dans notre exemple). Elle peut alors stocker les données reçues dans la base DB-B

Nous n'en dirons pas plus sur le langage XML qui mérite un livre à lui tout seul.

Ici donc, Tomcat est configuré par le fichier XML server.xml. Si nous visualisons celui-ci avec IE6, nous obtenons un document complexe. Nous nous attarderons simplement sur les lignes suivantes :

Image

C'est la balise <Context ...> qui nous intéresse ici. Elle sert à définir des applications web. Deux de ses attributs sont à noter :

  • path : c'est le nom de l'application web
  • docBase : c'est le dossier dans lequel elle se trouve. Ici c'est un nom relatif : examples. Relatif à quel dossier ? La réponse se trouve également dans le fichier server.xml dans la ligne qui suit :

Image

La ligne ci-dessus définit le serveur web :

  • name : nom du serveur web
  • appBase : racine de l'arborescence des documents qu'il distribue. De nouveau, on a un nom relatif : webapps. Il est relatif au répertoire d'installation du serveur Tomcat <tomcat>. Ainsi donc, il s'agit du dossier <tomcat>\webapps.

L'application web examples a ses documents dans le dossier examples (cf docBase plus haut). Ce nom est relatif à la racine de l'arborescence web du serveur, c.a.d. <tomcat>\webapps. Il s'agit donc du dossier <tomcat>\webapps\examples. Allons-voir de plus près ce dossier :

Image

Nous y retrouvons le dossier WEB-INF\classes dans lequel nous avons rangé nos servlets pour les tester. Le dossier WEB-INF contient un fichier appelé web.xml :

Image

Ce fichier sert à configurer l'application web examples. Nous n'entrerons pas dans les détails de ce fichier trop complexe pour le moment. Nous nous attarderons simplement sur les quelques lignes suivantes :

    <servlet>
      <servlet-name>
          servletToJsp
      </servlet-name>
      <servlet-class>
          servletToJsp
      </servlet-class>
    </servlet>

La balise <servlet> sert à définir une servlet au sein d'une application web. Rappelons ici que l'application web en question est examples. La balise servlet contient ici deux autres balises :

  • <servlet-name>servletToJsp</servlet-name> : définit le nom de la servlet
  • <servlet-name>servletToJsp</servlet-name> : définit le nom de la classe à exécuter lorsque la servlet est demandée. Dans cet exemple, la servlet et sa classe portent le même nom. Ce n'est pas obligatoire.

Comment la servlet servletToJsp est-elle demandée par un navigateur au serveur Tomcat ?

  • le navigateur demande l'URL http://localhost:8080/examples/servlet/servletToJsp
  • Tomcat analyse le chemin de la servlet /examples/servlet/servletToJsp. Il interprète la première partie du chemin /examples comme le nom d'une application web et cherche dans son fichier de configuration server.xml où les documents de cette application ont été rangés. Nous l'avons vu précédemment, c'est dans le dossier <tomcat>\webapps\examples.
  • Tomcat utilise le reste du chemin de la servlet pour localiser celle-ci dans l'application web examples. Ce chemin /servlet/servletToJsp indique qu'il doit exécuter la servlet portant le nom servletToJsp. Tomcat va alors lire le fichier web.xml de configuration de l'application examples qu'il va trouver dans <tomcat>\webapps\examples\WEB-INF. Il va trouver dans ce fichier que la servlet servletToJsp est liée à la classe Java servletToJsp (cf fichier web.xml ci-dessus). Il va alors chercher cette classe dans le dossier WEB-INF\classes de l'application web examples, c.a.d. dans <tomcat>\webapps\examples\WEB-INF\classes et l'exécutera.

Image

3.3.2. Exemple : déploiement de l'application web liste

Nous reprenons une servlet déjà étudiée et qui présentait à l'utilisateur une liste de nombres parmi lesquel il en choisissait un. La servlet lui confirmait ensuite le nombre qu'il avait choisi :

Image

Comme le montre le champ Address du navigateur ci-dessus, le fichier classe de la servlet s'appelait gener3. D'après les explications qui ont été données précédemment :

  • l'URL /examples/servlet/gener3 montre qu'il s'agit d'une servlet appelée gener3 de l'application web examples
  • dans le fichier web.xml de l'aplication examples, on ne trouvera rien qui parle d'une servlet gener3. Comment Tomcat l'a-t-il alors trouvée ? Ayant parcouru la totalité du fichier web.xml, je ne peux répondre avec certitude... La question reste posée...

Nous choisissons de déployer la servlet gener3.class sous le nom lstValeurs dans une application web appelée liste située dans le dossier E:\data\serge\Servlets\lstValeurs :

Image

Nous plaçons le fichier gener3.class dans le dossier WEB-INF\classes ci-dessus :

Image

Nous configurons l'application web liste en ajoutant dans le fichier server.xml les lignes suivantes au-dessus de celles qui définissent l'application web manager :

                <!-- Perso : lstValeurs -->
                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

                <!-- Tomcat Manager Context -->
                <Context path="/manager" docBase="manager" debug="0" privileged="true" />
                <!-- Tomcat Examples Context -->
                <Context path="/examples" docBase="examples" debug="0" reloadable="true" crossContext="true">
........

La ligne qui définit l'application liste indique qu'elle se trouve dans le dossier e:/data/serge/servlets/lstValeurs. Nous devons maintenant définir le fichier web.xml de cette application. Ce fichier définira l'unique servlet de l'application :

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
    <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
</web-app>

Le fichier ci-dessus indique que la servlet nommée lstValeurs est associée au fichier de classe gener3.class. Ce fichier web.xml doit être créé et sauvegardé dans le dossier WEB-INF de l'application liste :

Image

La copie d'écran ci-dessus montre un dossier src dans lequel on a placé le fichier source gener3.java. Ce dossier pourrait ne pas exister. Il ne sert à rien dans la démonstration présente. Nous sommes prêts à faire les tests :

  • arrêtez et lancez Tomcat afin qu'il relise son fichier de configuration server.xml. Ici nous sommes sous Windows. Sous Unix, on peut forcer Tomcat à relire son fichier de configuration sans pour autant l'arrêter.
  • avec un navigateur, demandez l'URL http://localhost:8080/liste/servlet/lstValeurs

Image

On voit que l'URL précédente contient le mot clé servlet comme toutes les URL de servlets utilisées jusqu'à maintenant. On peut s'en passer en associant dans le fichier web.xml de l'application liste la servlet lstValeurs à un modèle d'URL (url-pattern) :

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
    <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
</web-app>

Dans la balise <servlet-mapping>, nous associons le chemin /valeurs à la servlet lstValeurs définie dans les lignes qui précèdent. Nous sauvegardons le nouveau fichier web.xml et demandons l'URL http://localhost:8080/liste/valeurs :

Image

3.3.3. Déploiement des pages publiques d'une application web

Nous venons de voir le déploiement d'une application web formée d'une unique servlet. Une application web peut avoir de nombreuses composantes : des servlets, des pages JSP, des fichiers HTML, des applets Java, ... Où place-t-on ces éléments de l'application ? Si <application> est le dossier de l'application web défini par l'attribut docBase de l'application dans le fichier server.xml de configuration de Tomcat, nous avons vu que les servlets étaient placées dans <application>\WEB-INF\classes. Les autres éléments de l'application peuvent être placés n'importe où dans l'arborescence du dossier <application> sauf dans le dossier WEB-INF. Considérons l'application JSP listvaleurs.jsp déjà étudiée :

Image

Cette page JSP avait été stockée dans le dossier <tomcat>\webapps\ examples\jsp\perso\listvaleurs. Cette page pourrait être une composante de l'application liste déployée précédemment. Plaçons le fichier listvaleurs.jsp directement dans le dossier de cette application :

Image

Rappelons la configuration de l'application liste dans le fichier server.xml :

                <Context path="/liste" docBase="e:/data/serge/servlets/lstValeurs" />

Toute URL commençant par le chemin /liste est considérée comme faisant partie de l'application liste et sera cherchée dans le dossier indiqué. Demandons l'URL http://localhost:8080/liste/listvaleurs.jsp avec un navigateur :

Image

Nous avons bien obtenu la page JSP attendue.

3.3.4. Paramètres d'initialisation d'une servlet

Nous avons vu qu'une servlet était configurée par le fichier <application>\WEB-INF\web.xml<application> est le dossier de l'application web à laquelle elle appartient. Il est possible d'inclure dans ce fichier des paramètres d'initialisation de la servlet. Revenons à notre servlet lstValeurs de l'application web liste et dont le fichier de configuration était le suivant :

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
    <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
</web-app>

La classe associée à la servlet est la classe gener3. On trouve dans le code source de celle-ci la définition de quelques constantes :

public class gener3 extends HttpServlet{
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";

Rappelons la signification des quatre constantes définies ci-dessus :


title

titre du document HTML généré par la servlet


DSNValeurs

nom DSN de la base ODBC où la servlet va chercher des données


admDbValeurs

nom d'un utilisateur ayant un droit de lecture sur la base précédente


mdpDbvaleurs

son mot de passe

Si l'administrateur de la base DSNValeurs change le mot de passe de l'utilisateur admDbValeurs, le source de la servlet doit être modifié et recompilé. Ce n'est pas très pratique. Le fichier web.xml de configuration de la servlet nous offre une alternative en permettant la définition de paramètres d'initialisation de la servlet avec la balise <init-param> :

    <init-param>
        <param-name>...</param-name>
        <param-value>...</param-value>
    </init-param>

<param-name>

permet de définir le nom du paramètre


<param-value>

définit la valeur associée au paramètre précédent

La servlet a accès à ses paramètres d'initialisation grâce aux méthodes suivantes :


[Servlet].getServletConfig()

méthode de la classe Servlet dont dérive la classe HttpServlet utilisée pour la programmation web. Rend un objet ServletConfig donnant accès aux paramètres de configuration de la servlet.


[ServletConfig].getInitParameter("paramètre")

méthode de la classe ServletConfig qui donne la valeur du paramètre d'initialisation "paramètre"

Nous configurons l'application liste avec le nouveau fichier web.xml suivant :

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <servlet>
    <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
    <servlet>
    <servlet-name>lstValeurs2</servlet-name>
    <servlet-class>gener5</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
    <init-param>
        <param-name>DSNValeurs</param-name>
      <param-value>odbc-valeurs</param-value>
    </init-param>
    <init-param>
        <param-name>admDbValeurs</param-name>
      <param-value>admDbValeurs</param-value>
    </init-param>
    <init-param>
        <param-name>mdpDbValeurs</param-name>
      <param-value>mdpDbValeurs</param-value>
    </init-param>   
  </servlet>
  <servlet-mapping>
    <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>lstValeurs2</servlet-name>
    <url-pattern>/valeurs2</url-pattern>
  </servlet-mapping>
</web-app>

Dans l'application liste, nous définissons une seconde servlet appelée lstValeurs2 liée au fichier de classe gener5. Ce dernier a été placé dans <application>\WEB-INF\classes :

Image

La servlet lstValeurs2 a quatre paramètres d'initialisation : title, DSNValeurs, admDbValeurs, mdpDbValeurs. Par ailleurs, l'alias /valeurs2 a été défini pour la servlet à l'aide de la balise <servlet-mapping>. Aussi, la servlet lstValeurs2 de l'application liste sera-t-elle accessible via l'URL http://localhost:8080/liste/valeurs2.

Le code source de la servlet a été modifié de la façon suivante pour récupérer les paramètres d'initialisation de la servlet :

public class gener5 extends HttpServlet{
    // le titre de la page
    private String title=null;
    // la base de données des valeurs de liste
    private String DSNValeurs=null;
    private String admDbValeurs=null;
    private String mdpDbValeurs=null;
...............

        // initialisation de la servlet
        public void init(){
            // on récupère les paramètres d'initialisation de la servlet
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            DSNValeurs=config.getInitParameter("DSNValeurs");
            admDbValeurs=config.getInitParameter("admDbValeurs");
            mdpDbValeurs=config.getInitParameter("mdpDbValeurs");

            //a-t-on récupéré tous les paramètres ?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

            // on remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
...............

Pour tester la servlet, il faut relancer Tomcat afin qu'il prenne en compte le nouveau fichier de configuration web.xml de l'application liste. Avec un navigateur, on demande l'URL de la servlet http://localhost:8080/liste/valeurs2 :

Image

Si l'un des paramètres d'initialisation nécessaires à la servlet est absent du fichier web.xml, on obtient la page suivante :

Image

3.3.5. Paramètres d'initialisation d'une application web

Dans l'exemple précédent, seule la servlet lstValeurs2 a accès aux paramètres title, DSNValeurs, admDbValeurs, mdpDbValeurs. On pourrait imaginer qu'une autre servlet de la même application liste ait besoin des données dans la même base de données qu'utilise la servlet lstValeurs2. Il faudrait alors redéfinir les paramètres DSNValeurs, admDbValeurs, mdpDbValeurs dans la partie configuration du fichier web.xml de la nouvelle servlet. Une autre solution est de définir les paramètres communs à plusieurs servlets au niveau de l'application et non plus au niveau des servlets. Le nouveau fichier web.xml de l'application devient le suivant :

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

  <context-param>
    <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
    <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
    <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   

    <servlet>
    <servlet-name>lstValeurs</servlet-name>
    <servlet-class>gener3</servlet-class>
  </servlet>
    <servlet>
    <servlet-name>lstValeurs3</servlet-name>
    <servlet-class>gener6</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>lstValeurs</servlet-name>
    <url-pattern>/valeurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>lstValeurs3</servlet-name>
    <url-pattern>/valeurs3</url-pattern>
  </servlet-mapping>
</web-app>

La nouvelle servlet s'appelle lstValeurs3 et est liée au fichier de classe gener6 et a été associée à l'alias /valeurs3 (servlet-mapping). Le paramètre title est le seul paramètre qui a été conservé dans la définition de la servlet. Les autres ont été placés dans la configuration de l'application dans des balises <context-param>. Cette balise sert à définir des informations propres à l'application et non à une servlet ou page JSP particulière. Comment la servlet Java a-t-elle accès à ces paramètres souvent appelés paramètres de contexte ? Les méthodes disponibles pour obtenir les informations de contexte sont très analogues à celles rencontrées pour obtenir les paramètres d'initialisation particuliers à une servlet :


[Servlet].getServletContext()

méthode de la classe Servlet dont dérive la classe HttpServlet utilisée pour la programmation web. Rend un objet ServletContext donnant accès aux paramètres de configuration de l'application


[ServletContext].getInitParameter("paramètre")

méthode de la classe ServletContext qui donne la valeur du paramètre d'initialisation "paramètre"

La classe gener6.java amène les seules modification suivantes au code Java de gener5.java utilisé précédemment :

            // on récupère les paramètres d'initialisation de la servlet
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");

            ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

            //a-t-on récupéré tous les paramètres ?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

            // on remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
...............

Le paramètre title propre à la servlet est obtenu via un objet ServletConfig. Les trois autres paramètres définis au niveau application sont eux obtenus via un objet ServletContext. Nous compilons cette classe et la mettons comme les autres dans <application>\WEB-INF\classes :

Image

Nous relançons Tomcat pour qu'il prenne en compte le nouveau fichier web.xml de l'application et demandons l'URL http://localhost:8080/liste/valeurs3 :

Image

3.3.6. Paramètres d'initialisation d'une page JSP

Nous avons vu comment définir des paramètres d'initialisation pour une servlet ou une application web. Peut-on faire de même pour une page JSP ? Revenons sur le début du code de la page listvaleurs.jsp déjà étudiée :

<%@ page import="java.sql.*, java.util.*" %>

<%!
        // variables globales de l'application
        // le titre de la page
        private final String title="Génération d'un formulaire";
        // la base de données des valeurs de liste
        private final String DSNValeurs="odbc-valeurs";
        private final String admDbValeurs="admDbValeurs";
        private final String mdpDbValeurs="mdpDbValeurs";
.........

On retrouve les quatre constantes title, DSNValeurs, admDbValeurs et mdpDbValeurs définies dans le fichier web.xml de l'application. Les constantes DSNValeurs, admDbValeurs et mdpDbValeurs ont été maintenant définies au niveau application et on peut donc supposer qu'une page JSP faisant partie de cette application y aura accès. C'est le cas. Nous savons que la page JSP sera traduite en une servlet. Celle-ci aura accès au contexte via la méthode getServletContext(). Plus délicat est le cas de la constante title. En effet nous l'avons définie au niveau servlet et non au niveau application de la façon suivante :

    <servlet>
    <servlet-name>lstValeurs3</servlet-name>
    <servlet-class>gener6</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>

Pour la page JSP la syntaxe précédente ne convient plus car on n'a plus la notion de fichier de classe. La syntaxe de configuration d'une page JSP est cependant très proche de celle d'une servlet. C'est la suivante :

    <servlet>
    <servlet-name>JSPlstValeurs</servlet-name>
    <jsp-file>/listvaleurs2.jsp</jsp-file>
...
  </servlet>

En fait, une page JSP est assimilée à une servlet à laquelle on donne un nom (servlet-name). Au lieu d'associer un fichier de classe à cette servlet, on associe le fichier source de la page JSP à exécuter (jsp-file). Ainsi les quelques lignes précédentes définissent une servlet appelée JSPlstvaleurs associée à la page JSP /listvaleurs2.jsp. Le chemin /listvaleurs2.jsp est mesuré par rapport à la racine de l'application. Ainsi dans le cas de notre application liste, le fichier listvaleurs2.jsp se trouverait dans le dossier docBase (cf server.xml) de l'application liste :

Image

La configuration de la page JSP sera la suivante dans le fichier web.xml de l'application :

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
    <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
    <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
    <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   
.......     
    <servlet>
    <servlet-name>JSPlstValeurs</servlet-name>
    <jsp-file>/listvaleurs2.jsp</jsp-file>
    <init-param>
        <param-name>JSPtitle</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
  </servlet>
..........
  <servlet-mapping>
    <servlet-name>JSPlstValeurs</servlet-name>
    <url-pattern>/jspvaleurs</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
........
</web-app>

La page JSP listvaleurs2.jsp est placée dans la racine de l'application liste et associée au nom de servlet JSPlstValeurs (servlet-name) qui est lui même associé à l'alias /jspvaleurs (servlet-mapping). Ainsi notre page JSP sera-t-elle accessible via l'URL http://localhost:8080/liste/jspvaleurs.

La page JSP initiale listvaleurs.jsp est modifiée en listvaleurs2.jsp et récupère ses quatre paramètres d'initialisation dans la méthode jspInit() :

<%!
        // variables globales de l'application
        // le titre de la page
        private String title=null;
        // la base de données des valeurs de liste
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
        // valeurs de liste
        private String[] valeurs=null;
        // msg d'erreur
        private String msgErreur=null;

        // initialisation de la page JSP - n'est exécutée qu'une seule fois
        public void jspInit(){

            // on récupère les paramètres d'initialisation de la servlet
      ServletConfig config=getServletConfig();
            title=config.getInitParameter("JSPtitle");
      ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");

            //a-t-on récupéré tous les paramètres ?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null){
                msgErreur="Configuration incorrecte";
                return;
            }

            // remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
..............

La page JSP récupère ses paramètres d'initialisation de la même façon que les servlets. Le fichier précédent est sauvegardé dans la racine de l'application web liste :

Image

Le serveur Tomcat est relancé pour le forcer à relire le nouveau fichier de configuration web.xml de l'application. On peut alors demander l'URL http://localhost:8080/liste/jspvaleurs :

Image

3.3.7. Collaboration servlets/pages JSP au sein d'une application web

Lorsqu'un client fait une requête à un serveur Web, la réponse peut être élaborée par plusieurs servlets et pages JSP. Jusqu'à maintenant, la réponse avait été élaborée par une unique servlet ou page JSP. Nous avons vu que la page JSP amenait une meilleure lisibilité de la structure du document HTML généré. Cependant elle contient aussi en général beaucoup de code Java. On peut améliorer les choses en mettant

  • dans une ou plusieurs servlets, le code Java qui ne génère pas le code HTML de la réponse
  • dans des pages JSP, le code de génération des différents documents HTML envoyés en réponse au client

On peut ainsi espérer améliorer la séparation code Java/code HTML. Nous allons appliquer cette nouvelle structuration à notre application liste : une servlet Java lstValeurs4 sera chargée de lire au démarrage les valeurs dans la base de données et ensuite d'analyser les requêtes des clients. Selon le résultat de celle-ci, la requête du client sera dirigée vers une page d'erreur erreur.jsp ou vers la page d'affichage de la liste de nombres liste.jsp. L'application liste sera donc formée d'une servlet et de deux pages JSP.

Comment une servlet peut-elle passer la requête qu'elle a reçue d'un client à une autre servlet ou à une page JSP ? Nous utiliserons les méthodes suivantes :


[ServletContext].getRequestDispatcher(
String url)

méthode de la classe ServletContext qui rend un objet RequestDispatcher. Le paramètre url est le nom de l'URL à qui on veut transmettre la requête du client. Ce passage de requête ne peut se faire qu'au sein d'une même application. Aussi le paramètre url est un chemin relatif à l'arborescence web de cette application.


[RequestDispatcher].forward
(ServletRequest request,
 ServletResponse response)

méthode de l'interface RequestDispatcher qui transmet à l'URL précédente la requête request du client et l'objet response qui doit être utilisé pour élaborer la réponse.


[ServletRequest].setAttribute(String nom, Object obj)

lorsqu'une servlet ou page JSP passe une requête à une autre servlet ou page JSP, elle a en général besoin de passer à celle-ci d'autres informations que la seule requête du client, informations issues de son propre travail sur la requête. La méthode setAttribute de la classe ServletRequest permet d'ajouter des attributs à l'objet request du client sous une forme qui ressemble à un dictionnaire de couples (attribut, valeur)attribut est le nom de l'attribut et valeur un objet quelconque représentant la valeur de celui-ci.


[ServletRequest].getAttribute(
String attribut)

permet de récupérer les valeurs des attributs d'une requête. Cette méthode sera utilisée par la servlet ou la page JSP à qui on a relayé une requête pour obtenir les informations qui y ont été ajoutées.

La servlet chargée de traiter le formulaire sera configurée de la façon suivante dans le fichier web.xml :

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
  <context-param>
    <param-name>DSNValeurs</param-name>
    <param-value>odbc-valeurs</param-value>
  </context-param>
  <context-param>
    <param-name>admDbValeurs</param-name>
    <param-value>admDbValeurs</param-value>
  </context-param>
  <context-param>
    <param-name>mdpDbValeurs</param-name>
    <param-value>mdpDbValeurs</param-value>
  </context-param>   

............
    <servlet>
    <servlet-name>lstValeurs4</servlet-name>
    <servlet-class>gener7</servlet-class>
    <init-param>
        <param-name>title</param-name>
      <param-value>Génération d'un formulaire</param-value>
    </init-param>
    <init-param>    
        <param-name>JSPerreur</param-name>
      <param-value>/erreur.jsp</param-value>
    </init-param>      
    <init-param>      
        <param-name>JSPliste</param-name>
      <param-value>/liste.jsp</param-value>
    </init-param>      
    <init-param>      
        <param-name>URLservlet</param-name>
      <param-value>/liste/valeurs4</param-value>
    </init-param>      
  </servlet>
...........
  <servlet-mapping>
    <servlet-name>lstValeurs4</servlet-name>
    <url-pattern>/valeurs4</url-pattern>
  </servlet-mapping>
.......
</web-app>

La servlet lstValeurs4 aura quatre paramètres d'initialisation qui lui seront propres :


title

le titre du document HTML à généréer


JSPerreur

l'URL de la page JSP d'erreur


JSPliste

l'URL de la page JSP présentant la liste de nombres


URLservlet

l'URL associée à l'attribut action du formulaire présenté par la page JSPliste. Cette URL sera celle de la servlet lstValeurs4

La servlet aura l'alias /valeurs4 (servlet-mapping) et sera donc accessible via l'URL http://localhost:8080/liste/valeurs4. Elle est liée au fichier classe gener7.java dont le code source complet est le suivant :

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import java.util.*;

public class gener7 extends HttpServlet{
        // le titre de la page
        private String title=null;
        // la base de données des valeurs de liste
        private String DSNValeurs=null;
        private String admDbValeurs=null;
        private String mdpDbValeurs=null;
        // les pages JSP d'affichage
        private String JSPerreur=null;
        private String JSPliste=null;
        // l'URL de la servlet
        private String URLservlet=null;
        // valeurs de liste
        private String[] valeurs=null;
        // msg d'erreur
        private String msgErreur=null;

        // -----------------------------------------------------------------
        // GET
        public void doGet(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on met msgErreur,title dans les attributs de la requête
            request.setAttribute("msgErreur",msgErreur);
            request.setAttribute("title",title);
            request.setAttribute("URLservlet",URLservlet);

            // y-a-t-il eu erreur au chargement de la servlet ?
            if(msgErreur!=null){
                // on passe la main à une page JSP d'erreur
                getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
                // fin
                return;
            }

            // il n'y a pas eu d'erreur
            // on met la liste des valeurs dans les attributs de la requête
            request.setAttribute("valeurs",valeurs);

            // on récupère l'éventuel choix de l'utilisateur
            String choix=request.getParameter("cmbValeurs");
            if(choix==null) choix="";
            request.setAttribute("choix",choix);

            // on passe la main à la page JSP de présentation de la liste
            getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
            // fin
            return;
        }//GET

        // -----------------------------------------------------------------
        // POST
        public void doPost(HttpServletRequest request,HttpServletResponse response)
                    throws IOException, ServletException{

            // on renvoie sur GET
            doGet(request,response);
        }//POST

        // -----------------------------------------------------------------
        // initialisation de la servlet
        public void init(){

            // on récupère les paramètres d'initialisation de la servlet
            ServletConfig config=getServletConfig();
            title=config.getInitParameter("title");
            JSPerreur=config.getInitParameter("JSPerreur");
            JSPliste=config.getInitParameter("JSPliste");
            URLservlet=config.getInitParameter("URLservlet");

            ServletContext context=getServletContext();
            DSNValeurs=context.getInitParameter("DSNValeurs");
            admDbValeurs=context.getInitParameter("admDbValeurs");
            mdpDbValeurs=context.getInitParameter("mdpDbValeurs");


            //a-t-on récupéré tous les paramètres ?
            if(title==null || DSNValeurs==null || admDbValeurs==null
                 || mdpDbValeurs==null || JSPerreur==null || JSPliste==null || URLservlet==null){
                msgErreur="Configuration incorrecte";
                return;
            }

            // remplit le tableau des valeurs à partir d'une base de données ODBC
            // de nom DSN : DSNvaleurs
            Connection connexion=null;
            Statement st=null;
            ResultSet rs=null;
            try{
                // connexion à la base ODBC
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
                connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
                // objet Statement
                st=connexion.createStatement();
                // exécution requête select pour récupérer les valeurs
                rs=st.executeQuery("select valeur from Tvaleurs");
                // les valeurs sont récupérées et mises dans un tableau dynamique
                ArrayList lstValeurs=new ArrayList();
                while(rs.next()){
                    // on enregistre la valeur dans la liste
                    lstValeurs.add(rs.getString("valeur"));
                }//while
                // transformation liste --> tableau
                valeurs=new String[lstValeurs.size()];
                for (int i=0;i<lstValeurs.size();i++){
                    valeurs[i]=(String)lstValeurs.get(i);
                }
            }catch(Exception ex){
                // problème
                msgErreur=ex.getMessage();
            }finally{
                try{rs.close();}catch(Exception ex){}
                try{st.close();}catch(Exception ex){}
                try{connexion.close();}catch(Exception ex){}
            }//try
        }//init
    }//classe

La nouveauté dans cette classe est la transmission de la requête du client à la page JSPerreur en cas d'erreur et à la page JSPliste sinon. La classe n'élabore pas elle-même la réponse. Ce sont les pages JSP JSPerreur et JSPliste qui s'en chargent. Auparavant la servlet a ajouté des attributs (setAttribute) à la requête du client :

  • un message d'erreur msgErreur en cas d'erreur pour la page JSPerreur
  • les valeurs (valeurs) à afficher, la valeur choisie (choix) par l'utilisateur, le titre (title) du formulaire, l'URL (URLservlet) de l'attribut action du formulaire pour la page JSPliste

Cette classe est compilée et mise dans les classes de l'application :

Image

La page JSP affichant un message d'erreur est configurée de la façon suivante :

    <servlet>
    <servlet-name>JSPerreur</servlet-name>
    <jsp-file>/erreur.jsp</jsp-file>
    <init-param>
        <param-name>mainServlet</param-name>
      <param-value>/valeurs4</param-value>
    </init-param>
  </servlet>
.........
<servlet-mapping>
    <servlet-name>JSPerreur</servlet-name>
    <url-pattern>/JSPerreur</url-pattern>
  </servlet-mapping>

Le fichier JSP associé à la page d'erreur s'appelle erreur.jsp et se trouve dans la racine de l'application :

Image

Elle a pour alias /JSPerreur ce qui la rend accessible via l'URL http://localhost:8080/liste/JSPerreur. Elle a un paramètre d'initialisation appelé mainServlet et dont la valeur est l'alias de la servlet principale décrite précédemment. On notera que cet alias est relatif à la racine de l'application liste, sinon on aurait /liste/valeurs4. Le code de la page erreur.jsp est le suivant :

<%
    // code de _jspService
  // on récupère le paramètre d'initialisation mainServlet
  String servletListValeurs=config.getInitParameter("mainServlet");
  // on récupère l'attribut msgErreur
  String msgErreur=(String)request.getAttribute("msgErreur");
  // attribut valide ?
  if(msgErreur!=null){
%>
    <!-- code HTML -->
    <html>
        <head>
        <title>Erreur</title>
      </head>
      <body>
        <h3>Application indisponible (<%= msgErreur %>)</h3>
      </body>
    </html>
<%
    } else { // attribut msgErreur invalide - retour à la servlet principale
%>
    <jsp:forward page="<%= servletListValeurs %>" />  
<%    
  }
%>  

Cette page doit normalement être appelée par la servlet précédente qui doit lui passer l'attribut msgErreur. Cependant rien n'empêche de l'appeler directement si on connaît son URL. Aussi si on découvre que l'attribut msgErreur est absent, on passe la requête à la servlet principale. Ici, on utilise une balise propre aux pages JSP et dont la syntaxe est :

<jsp:forward page="URL" />

où URL est l'URL de la servlet à qui on transfère la requête du client. Si l'attribut msgErreur est présent, la page d'erreur est affichée.

La page JSP affichant la liste des nombres est configurée de la façon suivante :

    <servlet>
    <servlet-name>JSPliste</servlet-name>
    <jsp-file>/liste.jsp</jsp-file>
    <init-param>
        <param-name>mainServlet</param-name>
      <param-value>/valeurs4</param-value>
    </init-param>
.........
  <servlet-mapping>
    <servlet-name>JSPliste</servlet-name>
    <url-pattern>/JSPliste</url-pattern>
  </servlet-mapping>

Le fichier JSP associé à la page d'erreur s'appelle liste.jsp et se trouve dans la racine de l'application :

Image

La servlet a pour alias /JSPliste ce qui la rend accessible via l'URL http://localhost:8080/liste/JSPliste. Elle a un paramètre d'initialisation appelé mainServlet et dont la valeur est l'alias de la servlet principale. Le code de la page liste.jsp est le suivant :

  <%-- page d'affichage de la liste des valeurs --%>
  <%
    // code de jspService
    // on récupère le paramètre d'initialisation
    String servletListValeurs=config.getInitParameter("mainServlet");

    // on récupère les attributs de la requête venant de la servlet principale
    String title=(String) request.getAttribute("title");
    String[] valeurs=(String[]) request.getAttribute("valeurs");
    String choix=(String) request.getAttribute("choix");
    String URLservlet=(String) request.getAttribute("URLservlet");

    // attributs valides ?
    if(title==null || valeurs==null || choix==null){
        // il y a un attribut invalide - on passe la main à la servlet
   %>
   <jsp:forward page="<%= servletListValeurs %>" />
    <%
    }//if
  %>

  <%-- code HTML --%>  
    <html>
        <head>
        <title><%= title %></title>
      </head>
      <body>
        <h3>Choisissez une valeur</h3>
        <form method="POST" action="<%= URLservlet %>">
            <select name="cmbValeurs">
            <%
                // affichage dynamique des valeurs
                        String selected="";
                        for (int i=0;i<valeurs.length;i++){
                            if(valeurs[i].equals(choix)) selected="selected"; else selected="";
                            out.println("<option "+selected+">"+valeurs[i]+"</option>");
                        }//for
            %>
          </select>
          <input type="submit" value="Envoyer">
        </form>
                <%
            // y-avait-il une valeur choisie ?
                    if(! choix.equals("")){
                // on affiche le choix de l'utilisateur
                %>        
                <hr>Vous avez choisi le nombre<h2><%= choix %></h2>
                <%        
                    }//if
                %>        
      </body>
    </html>

Cette page procède comme la page erreur.jsp. Elle doit normalement être appelée par la servlet /liste/valeurs4 et recevoir les attributs title, valeurs et choix. Si l'un de ces paramètres est manquant, on passe la main à la servlet URLservlet (/liste/valeurs4). Si les paramètres sont tous présents, la liste des nombres est affichée ainsi que le nombre choisi par l'utilisateur s'il en avait choisi un.

Si on demande l'URL de la servlet principale, on obtient le résultat suivant :

Image

avec le code source suivant (View/Source) :

<html>
    <head>
    <title>Génération d'un formulaire</title>
  </head>
  <body>
    <h3>Choisissez une valeur</h3>
    <form method="POST" action="/liste/valeurs4">
        <select name="cmbValeurs">
        <option >0</option>
        <option >1</option>
        <option >2</option>
        <option >3</option>
        <option >4</option>
        <option >6</option>
        <option >5</option>
        <option >7</option>
        <option >8</option>
        <option >9</option>
      </select>
      <input type="submit" value="Envoyer">
    </form>

  </body>
</html>

Ce document HTMl a été généré par la page JSP liste.jsp. On voit que les attributs title, valeurs, URLservlet ont bien été récupérés.

Pour conclure sur la collaboration servlets/pages JSP, nous observons que les pages JSP sont ici très courtes et dépourvues du code Java ne concourant pas à créer directement la réponse HTML. La structure des documents générés est ainsi plus visible.

3.4. Cycle de vie des servlets et pages JSP

3.4.1. Le cycle de vie

Nous nous intéressons ici au cycle de vie des servlets. Celui des pages JSP en découle. Considérons une servlet appelée pour la première fois. Une instance de classe est alors créée par le serveur Web et chargée en mémoire. Elle va alors servir la requête. Ceci fait, la servlet n'est pas déchargée de la mémoire. Il y reste afin de servir d'autres requêtes afin d'optimiser les temps de réponse du serveur. Elle sera déchargé lorsqu'un temps suffisamment long sera passé sans qu'elle ait servi de nouvelles requêtes. Ce temps est en général configurable au sein du serveur web.

Lorsqu'elle est en mémoire, la servlet peut servir plusieurs requêtes simultanément. Le serveur Web crée un thread par requête qui tous utilisent la même instance de servlet :

Image

Tous les threads ci-dessus partagent les variables de l'instance de servlet. Il peut y avoir besoin de synchroniser les threads pour éviter la corruption des données de la servlet. Nous allons y revenir.

Au chargement d'une servlet, une méthode particulière de la servlet est exécutée :

public void init() throws ServletException{
}

Pour une page JSP, c'est la méthode


  public void jspInit(){
  }

qui est exécutée. Voici un exemple de page JSP utilisant la méthode jspInit :

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
  // variables et méthodes globales de la page JSP

  // variable d'instance
  int compteur;

  // méthode pour incrémenter le compteur  
  public int getCompteur(){
    // on incrémente le compteur
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
    // on le rend
    return compteur;
  }

  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

La page JSP précédente initialise à 100 un compteur dans jspInit. Toute requête ultérieure à la servlet incrémente puis affiche la valeur de ce compteur :

La première fois :

Image

La seconde fois :

Image

On voit bien ci-dessus, qu'entre les deux requêtes, la servlet n'a pas été déchargée sinon on aurait eu le compteur à 101 lors de la seconde requête. Lorsque la servlet est déchargée, la méthode

public void destroy(){
}

est exécutée si elle existe. Pour les pages JSP c'est la méthode


  public void jspDestroy(){
  }

Dans ces méthodes, on pourra par exemple fermer des connexions à des bases de données, connexions qui auront été ouvertes dans les méthodes init correspondantes.

3.4.2. Synchronisation des requêtes à une servlet

Revenons à la page JSP précédente qui incrémente un compteur et renvoie celui-ci au client web. Supposons qu'il y ait 2 requêtes simultanées. Deux threads sont alors créés pour les exécuter, threads qui vont utiliser la même instance de servlet donc ici le même compteur. Rappelons le code qui incrémente le compteur :


  public int getCompteur(){
    // on incrémente le compteur
    int myCompteur=compteur;
    myCompteur++;
    compteur=myCompteur;
    // on le rend
    return compteur;
  }

L'incrémentation du compteur a été écrite volontairement de façon maladroite. Supposons que l'exécution des deux threads se passe de la façon suivante :

Image  
  1. au temps T1, le thread TH1 est exécuté. Il lit le compteur (=145) dans myCompteur puis il est interrompu et perd le processeur. Il n'a dons pas eu le temps d'incrémenter myCompteur et de recopier la nouvelle valeur dans compteur.
  2. au temps T2, le thread TH2 est exécuté. Il lit le compteur (=145) dans myCompteur puis il est interrompu et perd le processeur. On notera que les deux threads ont des variables myCompteur différentes. Ils ne partagent que les variables d'instance, celles qui sont globales aux méthodes.
  3. au temps T3, le thread TH1 reprend la main et termine. Il renvoie donc 146 à son client.
  4. au temps T4, le thread TH2 reprend la main et termine. Il renvoie lui aussi 146 à son client alors qu'il aurait du renvoyer 147.

On a là, un problème de synchronisation de threads. Lorsque TH1 veut incrémenter le compteur, il faudrait alors empêcher tout autre thread de le faire également. Pour mettre en évidence ce problème, nous réécrivons la page JSP de la façon suivante :

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
  // variables et méthodes globales de la page JSP

  // variable d'instance
  int compteur;

  // méthode pour incrémenter le compteur  
  public int getCompteur(){
    // on lit le compteur
    int myCompteur=compteur;
    // on s'arrête 10 secondes
    try{
      Thread.sleep(10000);
    }catch (Exception ignored){}
    // on incrémente le compteur
    compteur=myCompteur+1;
    // on le rend
    return compteur;
  }

  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

Ici, nous avons forcé le thread à s'arrêter 10 secondes après avoir lu le compteur. Il devrait donc perdre le processeur et un autre thread pouvoir lire à son tour un compteur qui n'a pas été incrémenté. Lorsque nous faisons des requêtes avec un navigateur, nous ne voyons pas de différence si ce n'est l'attente de 10 secondes avant d'avoir le résultat.

Image

Maintenant si nous ouvrons deux fenêtres de navigateur et faisons deux requêtes suffisamment rapprochées dans le temps :

Image

Image

Nous obtenons la même valeur de compteur. Nous pouvons mieux mettre en évidence le problème avec un client programmé plutôt que manuel comme l'est le navigateur. Suit un client perl qui s'appelle de la façon suivante :

    programme URL N

URL est l'URL de la servlet de comptage

N le nombre de requêtes à faire à cette servlet

Voici les résultats obtenus pour 5 requêtes qui montrent bien le problème de mauvaise synchronisation des threads : elles obtiennent toutes la même valeur du compteur.


DOS>java clientCompteurJSP http://localhost:8080/examples/jsp/perso/compteur/compteur2.jsp 5
Compteur=121
Compteur=121
Compteur=121
Compteur=121
Compteur=121

Le code du client Java est le suivant.

import java.net.*;
import java.util.regex.*;
import java.io.*;

public class clientCompteurJSP {

    public static void main(String[] params){

        // données
        String syntaxe="Syntaxe : pg URL nbAppels";

        // vérification des paramètres
        if(params.length!=2){
            System.err.println(syntaxe);
            System.exit(1);
        }//if
        // URL
        URL urlCompteur=null;
        try{
            urlCompteur=new URL(params[0]);
            String query=urlCompteur.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
            System.err.println(syntaxe);
            System.err.println("URL ["+params[0]+" incorrecte");
            System.exit(2);
        }//try-catch
        // nombre d'appels
        int nbAppels=0;
        try{
            nbAppels=Integer.parseInt(params[1]);
            if(nbAppels<=0) throw new Exception();
        }catch(Exception ex){
            System.err.println(syntaxe);
            System.err.println("Nombre d'appels ["+params[1]+" incorrect");
            System.exit(3);
        }//try-catch

        // les paramètres sont corrects - on peut faire les connexions à l'URL
        try{
            getCompteurs(urlCompteur,nbAppels);
        }catch(Exception ex){
            System.err.println(syntaxe);
            System.err.println("L'erreur suivante s'est produite : "+ex.getMessage());
            System.exit(4);
        }//try-catch
    }//main

    private static void getCompteurs (URL urlCompteur, int nbAppels)
            throws Exception {
        // fait nbAppels à l'URL urlCompteur
        // affiche à chaque fois la valeur du compteur renvoyée par le serveur web


        // on retire d'urlCompteur les infos nécessaire à la connexion au serveur d'impôts
        String path=urlCompteur.getPath();
        if(path.equals("")) path="/";
        String host=urlCompteur.getHost();
        int port=urlCompteur.getPort();
        if(port==-1) port=urlCompteur.getDefaultPort();

        // on fait les appels à l'URL
        Socket[] clients=new Socket[nbAppels];
        for(int i=0;i<nbAppels;i++){
            // on se connecte au serveur
            clients[i]=new Socket(host,port);
            // on cré un flux d'écriture vers le serveur
            PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
            // on demande l'URL - envoi des entêtes HTTP
            OUT.println("GET " + path + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            OUT.println("Connection: close");
            OUT.println("");
        }//for

        // données locales
        String réponse=null;                        // réponse du serveur
        // le modèle recherché dans la réponse HTML du serveur
        Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
        // le modèle d'une réponse correcte
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
        // le résultat de la comparaison au modèle
        Matcher résultat=null;

        for(int i=0;i<nbAppels;i++){
            // chaque client lit la réponse que lui envoie le serveur

            // on crée les flux d'entrée-sortie du client TCP
            BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));

            // on lit la 1ère ligne de la réponse
            réponse=IN.readLine();
            // on compare la ligne HTTP au modèle de la réponse correcte
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // on a un problème d'URL
                throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
            }//if

            // on lit la réponse jusqu'à la fin des entêtes
            while((réponse=IN.readLine())!=null && ! réponse.equals("")){
            }//while

            // c'est fini pour les entêtes HTTP - on passe au code HTML
            // pour récupérer la valeur du compteur
            boolean compteurTrouvé=false;
            while((réponse=IN.readLine())!=null){
                // on compare la ligne au modèle du compteur
                if(! compteurTrouvé){
                    résultat=modèleCompteur.matcher(réponse);
                    if(résultat.find()){
                        // compteur trouvé
                        System.out.println("Compteur="+résultat.group(1));
                        compteurTrouvé=true;
                    }//if
                }//if
            }//while

            // c'est fini
            clients[i].close();
        }//for
    }//getCompteurs

}//classe

Explicitons le code précédent :

  • le programme admet deux paramètres :
    • l'URL de la page JSP du compteur
    • le nombre de clients à créer pour cette URL
  • le programme commence donc par vérifier la validité des paramètres : qu'il y en a bien deux, que le premier ressemble syntaxiquement à une URL et que le second à un nombre entier >0. Pour vérifier que l'URL est syntaxiquement correcte, on utilise la classe URL et son constructeur URL (String) qui construit un objet URL à partir d'une chaîne de caractères telle que http://istia.univ-angers.fr. Une exception est lancée si la chaîne de caractères n'est pas une URL syntaxiquement valide. Cela nous permet de vérifier la validité du premier paramètre.
  • une fois les paramètres vérifiés, la main est passée à la procédure getCompteurs. Celle-ci va créer nbAppels clients qui tous vont se connecter en même temps (ou quasiment) à l'URL urlCompteur.
  • le port et la machine sur laquelle les clients doivent se connecter sont tirés de l'URL urlCompteur : [URL].getHost() permet d'avoir le nom de la machine et [URL].getPort() permet d'obtenir le port.
  • une première boucle permet à chaque client :
    • de se connecter au serveur web
    • de lui demander l'URL urlCompteur

Dans cette boucle, le client n'attend pas la réponse du serveur. En effet, on veut amener le serveur à recevoir des demandes quasi simultanées.

  • une seconde boucle permet à chaque client de recevoir et traiter la réponse que lui envoie le serveur. Le traitement consiste à trouver dans la réponse la ligne qui contient la valeur du compteur et à afficher celle-ci.

Pour résoudre le problème mis en évidence précédemment (même compteur envoyé aux cinq clients) il nous faut synchroniser les threads du service de comptage sur un même objet avant d'entrer dans la section critique de lecture et mise à jour du compteur. La nouvelle page JSP est la suivante :

<html>
  <head>
    <title>Compteur synchronisé</title>
  </head>
  <body>
    Compteur= <%= getCompteur() %>
  </body>
</html>

<%!
  // variables et méthodes globales de la page JSP

  // variables d'instance
  int compteur;
  Object verrou=new Object();

  // méthode pour incrémenter le compteur  
  public int getCompteur(){

    // on syncronise la section critique
    synchronized(verrou){
      // on lit le compteur
      int myCompteur=compteur;
      // on s'arrête 10 secondes
      try{
        Thread.sleep(10000);
      }catch (Exception ignored){}
      // on incrémente le compteur
      compteur=myCompteur+1;
    }//synchronized
    // on le rend
    return compteur;
  }//getCompteur

  // la méthode exécutée au chargement initial de la page
  public void jspInit(){
    // init compteur
    compteur=100;
  }
%>

A l'exécution, on obtient alors les résultats suivants :

dos>c:\perl\bin\perl.exe client2.pl http://localhost:8080/examples/jsp/perso/compteur/compteur3.jsp 5
    Compteur= 104
    Compteur= 106
    Compteur= 105
    Compteur= 107
    Compteur= 108

La documentation indique que le serveur Web peut parfois créer plusieurs instances d'une même servlet. Dans ce cas, la synchronisation précédente ne fonctionne plus car la variable verrou est locale à une instance et n'est donc pas connue des autres instances. Il en est de même pour la variable compteur. Pour les rendre globales à toutes les instances, on écrira :


// variable de classe
  static int compteur;
  static Object verrou=new Object();

Le reste du code ne change pas.