Les servlets et le moteur JServ

Une servlet est une partie de code Java, exécutant un traitement à la demande d'un serveur Web, et fournissant à ce dernier une page html.

La suite de ce document est centrée sur le lien avec le serveur Web, et n'aborde pas du tout l'utilisation, assez fréquente, des servlets, pour accéder à une base de données, car cela relève d'une programmation Java standard. Dans le même souci de simplifier la présentation, on n'envisage que l'utilisation des requêtes GET et POST du protocole Http.

Le lien avec le serveur Web est mis en place par un moteur de servlet; nous présentons l'exemple de JServ, moteur lié à Apache.

 

I. Présentation des servlets

Les servlets sont un des outils liés à une évolution de la technologie des serveurs Web, depuis les pages statiques vers les pages dynamiques. Voici un liste d'outils, présentée dans l'ordre chronologique, à partir des plus anciens;

Les classes utilisées font partie de la bibliothèque jsdk.jar.  

II. Premier exemple: création d'une page

Cette servlet montre comment est constituée la page envoyée au client.

Tout d'abord on utilise les paquetages javax.servlet et javax.servlet.http pour créer une classe Coucou qui dérive de la classe HttpServlet, dans laquelle on définit en particulier la méthode doGet().
Cette méthode est activée quand la servlet est directement référencée dans l'Url d'un lien hypertexte, ou dans l'attribut action d'un formulaire. Par contre, si l'attribut method a la valeur POST, alors la méthode doPost() est appelée; ces deux méthodes ont les mêmes paramètres.

Ces méthodes reçoivent en paramètres:

import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; public class Coucou extends HttpServlet { //Initialiser les variables globales public void init(ServletConfig config) throws ServletException { super.init(config); } //Traiter la requête HTTP Get public void doGet(HttpServletRequest requete, HttpServletResponse reponse) throws ServletException, IOException { reponse.setContentType("text/html"); PrintWriter page = new PrintWriter (reponse.getOutputStream()); page.println("<html>"); page.println("<head><title>coucou</title></head>"); page.println("<body><h1>Coucou !!!</h1></body>"); page.println("</html>"); page.close(); } //Obtenir les informations de servlet public String getServletInfo() { return "Information Coucou"; } } La méthode getServletInfo() est utilisée par le moteur de servlet quand il veut publier des informations sur la servlet.  

III. Cycle de vie d'une servlet

Une servlet est chargée à la première demande d'un client par une Url apparaissant dans un lien ou par la méthode action d'un formulaire.
Le moteur charge la servlet en lui fournissant un certain nombres d'arguments (cf. fichier zone.properties), puis : La servlet reste chargée tant que le moteur de servlet fonctionne.  

IV. Liaison avec un formulaire

Cet exemple montre comment récupérer des informations, transmises depuis un formulaire. Ce formulaire est un mini-calculateur qui permet la saisie de 2 valeurs numériques, et effectue une opération (choix addition/soustraction) entre ces valeurs.
La servlet est appellée par l'attribut action de la balise FORM; les informations lui sont envoyées par la méthode POST.  

Le formulaire HTML

.
Texte Présentation
<form method="POST" action="http://machine/servlet/ServCalc"> <input type="text" name="a" size="5">, <input type="text" name="b" size="5"> <p></p> <input type="submit" name="op" value="+"> <input type="submit" name="op" value="-"> </form>
,

 

La servlet


Voici la définition de la classe ServCalc qui effectue le traitement lié à ce questionnaire.

Dans la programmation la génération de la page envoyée au client est placée dans un sous-programme (méthode remplirPage).
Reparquez que les informations sont d'abord récupérées sous forme chaîne (type String), avant d'être converties en valeur numérique pour efffectuer un calcul. De façon analogue le résultat, est converti de sa représentation numérique à la forme chaîne avant d'être envoyé au client. /** ServCalc.java : micro calculateur */ import javax.servlet.*; // dans jsdk.jar import javax.servlet.http.*; // dans jsdk.jar import java.io.*; public class ServCalc extends HttpServlet { //Traiter la requête HTTP Post public void doPost( HttpServletRequest req, HttpServletResponse reponse) throws ServletException, IOException { // Récupérer, sous forme int, les opérandes String ch_a=req.getParameter("a"); int a=Integer.parseInt(ch_a); String valsPar[] = req.getParameterValues("b"); int b=Integer.parseInt(valsPar[0]); // Récupérer l'opération String ch_op = req.getParameter("op"); // Calculer le résultat, le mettre sous forme chaîne String ch_res; if( ch_op.equals("+") ) ch_res= Integer.toString(a+b); else if (ch_op.equals("-")) ch_res=Integer.toString(a-b); else ch_res="erreur"; // Fournir le résultat, via le serveur Http reponse.setContentType("text/html"); PrintWriter p = new PrintWriter( reponse.getOutputStream()); remplirPage(p, ch_res); p.close(); } void remplirPage(PrintWriter p, String res) { p.println("<html>"); p.println("<head><title>Résultat</title></head>"); p.println("<body><h3>"); p.println("Résultat: " + res); p.println("</h3></body>"); p.println("</html>"); } //Initialiser les variables de l'objet ServCalc public void init(ServletConfig config) throws ServletException { super.init(config); } //Obtenir les informations de servlet public String getServletInfo() { return "Information ServCalc"; } } // public class ServCalc extends HttpServlet Les valeurs des champs du formulaire sont récupérées à partir du nom du champ définissant l'objet du formulaire (attribut name des balises) par une des méthodes:  

V. Architecture

 

1. Les modules exécutables

Le tableau ci-dessous présente les divers modules logiciels du serveur Web et du moteur de servlets. Les modules liés au navigateur, qui communiquent avec le serveur, et des modules éventuels utilisés par les servlets (on peut penser à un serveur de BD) ne sont pas représentés .

L'exemple de moteur de servlets est Apache JServ; un certain nombre d'informations reste valable pour tout moteur. On réalise que le serveur http et le moteur de servlets sont bien séparés; le module mod_jserv (librairie dynamique, sous-forme dll pour Windows) gère l'échange d'informations pour le moteur Apache.

   Serveur Http     Moteur de servlets  
<--
-->
Apache mod_jserv <--
-->
Java bib. jserv servlets <--
-->
   
auxilliaire du serveur Web, gérant le lien avec le moteur de servlets
  machine virtuelle A.P.I servlets (bib. de classes) zone(s) des classes  
  httpd.conf jserv.conf   jserv.properties   zone.properties  
Dans l'architecture Apache, le serveur Web et le moteur de servlets communiquent par sockets; on pourra donc avoir l'un et l'autre sur des machines différentes. De plus Apache peut communiquer avec plusieurs moteurs distincts; il est donc possible que les servlets soient exécutées par des machines fonctionnant avec des systèmes d'exploitation différents (solaris, Windows, linux ...).  

2. Démarrage

Les fichiers de configurations, qui sont des fichiers 'texte', ne sont lus qu'une fois, au démarrage des exécutables. Quand on modifie un fichier, pour que la modification soit prise en compte, il faudra arrêter et relancer l'exécutable concerné afin de voir la modification prise en compte.  

3. Emplacement des fichiers

Dans la configuration standard, les fichiers sont rangés dans une structure arborescente; voici l'emplacement des fichiers de configurations et des fichiers 'journaux', d'extension *.log:  

4. Lien URL / servlet

Les termes placés dans une Url ne désignent pas l'emplacement d'un fichier sur la machine serveur. En fait le serveur et le moteur réalisent des conversions; ce mécanisme permet de réorganiser les emplacements des fichiers, si nécessaire, tout en gardant la même Url pour les atteindre.

Soit une servlet de nom 'Ser'; un lien hypertexte tel que: va provoquer l'appel de la méthode doGet() de la servlet Ser; mais pour trouver l'emplacement du fichier Ser.class des conversions sont réalisés.

Depuis un formulaire, suivant la méthode indiquée, doGet() ou doPost() est appelée; par exemple, si la balise est écrite: alors, quand ce formulaire est soumis,  

VI. Partage d'informations entre internautes

L'exemple suivant montre que l'information mémorisée dans une servlet est conservée d'un appel à l'autre, ces appels pouvant provenir de plusieurs internautes.

Modifions le calculateur pour qu'il puisse cumuler des valeurs. Avec cette version appelée Cadeau, l'internaute fait appel à la servlet Cadeau, et obtient une page; il tape alors un nombre et la servlet met à jour un totalisateur avec la nouvelle valeur fournie.
Cette servlet peut être utilisée par un groupe d'internautes qui veut faire un cadeau commun; chacun appelle la servlet en indiquant la somme qu'il verse pour le cadeau; à chaque versement, la somme récoltée est affichée.

Dans cet exemple la méthode doGet() est utilisée pour le premier affichage de la page; dans cette page le formulaire sollicite la même servlet, mais avec la méthode doPost(); Cette méthode va renvoyer la même page, après exécution des calculs, est affichée. /** Cadeau.java servlet 'partagée' Les méthodes doGet() et doPost() affichent une page. */ import javax.servlet.*; // dans jsdk.jar import javax.servlet.http.*; // dans jsdk.jar import java.io.*; public class Cadeau extends HttpServlet { int total, nval; // informations partagées final String cf_val="val"; // nom du champ valeur à ajouter //Initialiser les variables de l'objet Cadeau public void init(ServletConfig config) throws ServletException { super.init(config); total=0; nval=0; } //Traiter la requête HTTP Post public void doPost( HttpServletRequest req, HttpServletResponse reponse) throws ServletException, IOException { // Récupérer, sous forme int, la valeur à ajouter String ch_val = req.getParameter(cf_val); int val=Integer.parseInt(ch_val); // Mise à jour total += val; nval+=1; String ch_total=Integer.toString(total); String ch_nval=Integer.toString(nval); // Fournir la page reponse.setContentType("text/html"); PrintWriter p = new PrintWriter( reponse.getOutputStream()); remplirPage(p, ch_nval, ch_total, ch_val); p.close(); } public void doGet( HttpServletRequest req, HttpServletResponse reponse) throws ServletException, IOException { // Fournir le résultat, via le serveur Http reponse.setContentType("text/html"); PrintWriter p = new PrintWriter( reponse.getOutputStream()); remplirPage(p, Integer.toString(nval),Integer.toString(total),""); p.close(); } // Génère un formulaire, méthode POST; fournit la valeur du champs val void remplirPage(PrintWriter p, String nval, String tot, String der) { p.println("<html><head><title>Le cadeau</title></head>"); p.println("<body> "); p.println("<form method='POST'> " + "<input type='text' name='nval' size='4' value='"+nval+"'> " + "valeurs pour un total de " + "<input type='text' name='tot' size='8' value='"+ tot +"'><br>" + "(dernier don:" + "<input type='text' name='der' size='5' value='"+ der + "'>) " + "<hr> " + "Autre valeur: " + "<input type='text' name='"+cf_val+"' size='5'> " + "<p></p><input type='submit' name='op' value='Cumuler'> " + "</form>" ); p.println("</body></html> "); } //Obtenir les informations de servlet public String getServletInfo() { return "Information Cadeau"; } } // public class Cadeau extends HttpServlet  

VII. Informations associées à une session

Nous allons convertir la servlet précédente afin que chaque internaute dispose d'un totalisateur. Pour cela, associons une session à un internaute; elle va contenir les informations que l'on veut conserver pour identifier l'internaute.  

1. Un exemple

La classe Compte représente les informations à conserver sur un compte, ici le nombre de valeurs ajoutées et la somme totale.

La méthode doGet() affiche simplement la page, alors que la méthode doPost() récupére les informations (nouvelle valeur à ajouter), les traite, et re-affiche la page.

A chaque appel, la servlet identifie la session ou crée un session et si celle-ci est nouvelle, un nouveau compte est mémorisé: Puis, dans tous les cas, le compte de la session est récupéré par et les deux champs sont mis à jour: Voici le source complet. /** Cumul.java servlet utilisant une session */ import javax.servlet.*; // dans jsdk.jar import javax.servlet.http.*; // dans jsdk.jar import java.io.*; public class Cumul extends HttpServlet { final String cf_val="val"; // nom du cahmp valeur à ajouter //Initialiser les variables de l'objet Cumul public void init(ServletConfig config) throws ServletException { super.init(config); } //Traiter la requête HTTP Post public void doPost( HttpServletRequest req, HttpServletResponse reponse) throws ServletException, IOException { // AVANT d'utiliser la sortie reponse // Créer une session si nécessaire HttpSession session = req.getSession(true); if( session.isNew() ) session.setAttribute("compte", new Compte()); // Récupérer, sous forme int, la valeur à ajouter String ch_val = req.getParameter(cf_val); int val=Integer.parseInt(ch_val); // Récupérer le compte de cette session et le mettre à jour Compte cpt= (Compte) session.getAttribute("compte"); cpt.total += val; cpt.nval+=1; // Fournir la page reponse.setContentType("text/html"); PrintWriter p = new PrintWriter( reponse.getOutputStream()); remplirPage(p, ch_val, cpt); p.close(); } public void doGet( HttpServletRequest req, HttpServletResponse reponse) throws ServletException, IOException { // Fournir le résultat, via le serveur Http reponse.setContentType("text/html"); PrintWriter p = new PrintWriter( reponse.getOutputStream()); remplirPage(p, "",null); p.close(); } // Génère un formulaire, méthode POST, qui affiche l'état du compte et // la dernière valeur ajoutée; puis une zone pour saisir la nouvelle valeur // est créée. void remplirPage(PrintWriter p, String der, Compte cpt) { String tot=new String(), nval=new String(); if( cpt != null ) { tot=Integer.toString(cpt.total); nval=Integer.toString(cpt.nval); } p.println("<html><head><title>calcul de cumul</title></head>"); p.println("<body>"); p.println("<form method='POST'> " + "<input type='text' name='nval' size='3' value='"+nval+"'> " + "valeurs pour un total de : &nbsp; " + "<input type='text' name='tot' size='8' value='"+ tot +"'><br>\n" + "(dernière valeur: " + "<input type='text' name='der' size='5' value='"+ der + "'>) \n" + "<hr> \n" + "Autre valeur: " + "<input type='text' name='"+cf_val+"' size='5'> \n" + "<p></p><input type='submit' name='op' value='Cumul'> " + "</form>" ); p.println("</body></html>"); } //Obtenir les informations de servlet public String getServletInfo() { return "Information Cumul"; } class Compte { int total,nval; Compte() { total=0; nval=0;; } } } // public class Cumul extends HttpServlet  

2. Une session

On peut définir une session comme un ensemble d'interactions entre un client unique et un serveur Web. C'est une notion qui n'est pas naturelle avec Http, car c'estun protocole 'déconnecté'; c'est à dire qu'après chaque réponse le serveur coupe la communication avec le client.

Pour établir une session, il faut que le serveur identifie le (même) client; pour cela:

Plusieurs techniques sont utilisées:
  1. réécriture d'Url
  2. champ de formulaire caché
  3. par des cookies
  4. Jsdk et l'objet HttpSession

 

 

VIII. Deux servlets communiquent

Deux servlets sont exécutées:
  1. la servlet SaisieNumero affiche un formulaire qui déclenche l'appel de la servlet CumulCompte (méthode doPost())
  2. la servlet CumulCompte affiche aussi un formulaire qui peut déclencher:
 

La servlet SaisieNumero

Elle permet, par la méthode doGet(), de saisir un numéro de compte, et appelle indirectement CumulCompte. Par sa méthode doPost(), elle enregistre la valeur du compte, par un appel à la méthode gereCompte(), qui est ici réduite à sa plus simple expression. /** SaisieNumero.java servlet */ import javax.servlet.*; // dans jsdk.jar import javax.servlet.http.*; // dans jsdk.jar import java.io.*; public class SaisieNumero extends HttpServlet { final String cf_numcpt="numcpt"; // nom du champ numéro de compte public void init(ServletConfig config) throws ServletException { super.init(config); } // Affiche le formulaire de saisie de numéro de compte. public void doGet( HttpServletRequest req, HttpServletResponse reponse) throws ServletException, IOException { String numcpt=null; int [] cpt=null; HttpSession session = req.getSession(false); if( session != null ) session.invalidate(); // Fournir la page reponse.setContentType("text/html"); // reponse.setHeader("pragma","no-cache"); PrintWriter p = new PrintWriter( reponse.getOutputStream()); remplirPage(p, ""); p.close(); } //Traiter la requête Post utilisée par CumulCompte pour actualiser public void doPost( HttpServletRequest req, HttpServletResponse reponse) throws ServletException, IOException { String numcpt=null, msg=""; int [] cpt=null; HttpSession session = req.getSession(false); if( session != null ) { numcpt = (String)session.getAttribute(cf_numcpt); cpt = (int [])session.getAttribute("compte"); if( numcpt != null ) msg += gereCompte(numcpt, cpt); session.invalidate(); } // Fournir la page reponse.setContentType("text/html"); PrintWriter p = new PrintWriter( reponse.getOutputStream()); remplirPage(p, msg); p.close(); } void remplirPage(PrintWriter p, String msg) { String tot=new String(), nval=new String(); p.println("<html><head><title>intro de compte</title></head>"); p.println("<body>"); p.println("<h3>"+msg+"</h3>\n<hr>"); p.println("<h2>Gestion de compte</h2>"); p.println("<form method='POST' action='CumulCompte'> \n" +"Numéro du compte : &nbsp; " +"<input type='text' name='"+cf_numcpt+"' size='9'> \n" +"<p></p><input type='submit' value='Mettre à jour'> \n" +"</form> \n" ); p.println("</body></html>"); } String gereCompte( String numcpt, int cpt[]) { return "Le compte "+numcpt+" (total "+cpt[0]+") est mis à jour"; } //Obtenir les informations de servlet public String getServletInfo() { return "Information SaisieNumero"; } } // public class SaisieNumero extends HttpServlet  

La servlet CumulCompte

Elle est sollicitée par un formulaire(POST) qui fournit un numéro de compte. Elle affiche alors une page permettant de saisir une somme à accumuler sur ce compte; cette page présente:

Voici le texte de la servlet CumulCompte.

/** CumulCompte.java servlet 'partagée'*/ import javax.servlet.*; // dans jsdk.jar import javax.servlet.http.*; // dans jsdk.jar import java.io.*; public class CumulCompte extends HttpServlet { final int I_TOTAL=0, I_NVAL=1; final String cf_val="val"; // nom du champ valeur à ajouter final String cf_numcpt="numcpt"; // nom du champ numéro de compte String numcpt; //Initialiser les variables de l'objet CumulCompte public void init(ServletConfig config) throws ServletException { super.init(config); } //Traiter la requête HTTP Post public void doPost( HttpServletRequest req, HttpServletResponse reponse) throws ServletException, IOException { // Créer une session si nécessaire String ch_val = ""; int cpt[]=null; String msg=""; HttpSession session = req.getSession(true); // Si on dispose d'un champ numero de compte, cela correspond // au premier 'appel': on crée un session et on récupère l'état // du compte (ici on part de zéro) String ch = req.getParameter(cf_numcpt); if( ch != null) { numcpt=ch; cpt = new int[]{0,0}; session.setAttribute(cf_numcpt, ch); session.setAttribute("compte", cpt); msg=" ch != null " + ch; } else { // Récupérer, sous forme int, la valeur à ajouter int val; ch_val = req.getParameter(cf_val); try { val=Integer.parseInt(ch_val);} catch (NumberFormatException e) { val=0; } // Mise à jour cpt = (int [])session.getAttribute("compte"); if(cpt!=null) { cpt[I_TOTAL] += val; cpt[I_NVAL] += 1; msg=" ch == null cpt[0]=" + cpt[0]; } else msg="cpt==null"; } // Fournir la page reponse.setContentType("text/html"); // reponse.setHeader("pragma","no-cache"); PrintWriter p = new PrintWriter( reponse.getOutputStream()); remplirPage(p, numcpt, ch_val, cpt); p.close(); } // Génère un formulaire, méthode POST qui fournit la valeur du champs val void remplirPage(PrintWriter p, String numcpt, String der, int cpt[]) { String tot=new String(), nval=new String(); if( cpt == null ) {tot=""; nval=""; } else { tot=Integer.toString(cpt[I_TOTAL]); nval=Integer.toString(cpt[I_NVAL]); } p.println("<html><head><title>calcul de cumul</title></head>"); p.println("<body>"); p.println("<h2>Compte numéro "+numcpt+"</h2><hr>"); p.println("<form name='tutu' method='POST'> \n" +"<input type='text' name='nval' size='3' value='"+ nval + "'> " +"valeurs pour un total de : &nbsp; " +"<input type='text' name='tot' size='8' value='"+ tot +"'><br>\n" +"(dernière valeur: " +"<input type='text' name='der' size='5' value='"+ der + "'>) \n" +"<hr> \n" +"Autre valeur: " +"<input type='text' name='"+cf_val+"' size='5'> \n" +"<p></p><input type='submit' name='op' value='Cumul'> \n" +"</form> \n" ); p.println("<form name=toto method='POST' action='SaisieNumero'> \n" +"<p></p><input type='submit' name='op' value='Autre compte'> \n" +"</form>" ); p.println("</body></html>"); } //Obtenir les informations de servlet public String getServletInfo() { return "Information CumulCompte"; } } // public class CumulCompte extends HttpServlet
 

Références

Un livre
Andrew Patner, Programmation Java, coté Serveur, Eyrolles

Quelques adresses sur le Web: