Outils pour utilisateurs

Outils du site


developpement:java:servlets

Servlets

Pour développer des applications web, la technologie java fournit deux types de composants : les servlets et les pages JSP. Le composant fondamental est le servlet parce que une page JSP est prise par le conteneur web et sera compilée pour donner un servlet.

Une servlet est un programme Java s'exécutant au sein d'un serveur et répondant à des requêtes de clients. La servlet est déployée dans un conteneur. Le conteneur fournit un environnement d'exécution à la servlet et gère son cycle de vie. Dans pratiquement tous les cas, on est dans le contexte du réseau Internet ou d'un réseau intranet et les requêtes des clients sont des requêtes HTTP. Le conteneur est alors un conteneur web et la servlet fait partie d'une application web. « Conteneur web » est la nouvelle appellation de « moteur de servlet ».

Le conteneur web doit respecter les spécifications des servlets1). On ne déploie pas de servlet, on déploie une application web et la plus simple des application web est composé d'une servlet. La servlet fait forcément partie d'une application web et l'application web fait partie d'un conteneur. Un conteneur web est une application réseau qui respecte les spécifications de la norme JEE et ce processus est standardisé, c'est-à-dire que si votre servlet et application web fonctionne sur un conteneur web, ce dernier fonctionnera sur tous les autres conteneurs web.

Une application web est un ensemble de composants de deux types. Il peut y avoir plusieurs servlet et plusieurs pages JSP dans une application, ainsi qu'un fichier de configuration et ces composants sont déployés dans un conteneur web. Le conteneur fourni un environnement d'exécution, c'est-à-dire essentiellement une machine virtuelle et il gère aussi le cycle de vie de l'application.

Afin que le conteneur puisse gérer correctement le fonctionnement et le cycle de vie d'une servlet, il faut que celle-ci respecte un certain nombre de règles. Le contrat entre le conteneur web et tes servlets se présente sous forme de l'interface Servlet du package javax.servlet : toute servlet doit être instance d'une classe qui implémente cette interface.

L'interface Servlet contient les cinq méthodes décrites dans te tableau 1. On y trouve, en particulier, une méthode d'initialisation de la servlet, une méthode pour fournir un service aux clients et une méthode de libération de ressources lors de la destruction de la servlet

Conteneur web

Le conteneur web, ou conteneur de servlet, ou moteur de servlet. Le plus populaire est Tomcat d'Apache (installation sur Mac OS X), mais beaucoup d'autres existent comme Jetty, JBoss, BEA WebLogic Server, etc.

Implémentation d'une servlet

Afin que le conteneur puisse gérer correctement le fonctionnement et le cycle de vie d'une servlet, il faut qu'il applique un certain nombre de règles, donc qu'il y ait un contrat entre le conteneur et la servlet. Ce contrat se présente comme une interface du package javax.Servlet, qui est Servlet. Donc, tous les servlet doivent implémenter cet interface.

Il y a cinq méthodes dans une servlet. Une première méthode qui est exécuté par le conteneur web pour initier la servlet et qui se nomme init(). La surdéfinition de la méthode init() est facultative, sauf s'il y a besoin d'allouer des ressources supplémentaires.

Avant de mettre fin à la servlet, la méthode destroy() est invoquée. Encore une fois, on a pas besoin de surdéfinir cette méthode, sauf s'il y a des ressources allouées dans init() qu'il faut libérer.

Il y a aussi les méthodes getServletConfig() et getServletInfo(). La méthode principale est service(ServletRequest request, ServletResponse response).

Méthode service()

Cette méthode est faite à la base pour traiter une requête. C'est celle-ci qui reçoit une requête et qui répond. Elle reçoit deux objets, soit ServletRequest et ServletResponse qui représentent respectivement la requête et la réponse.

L'objet requête détient toutes les informations concernant la requête envoyée par le client. Cela comprend les informations GET et POST qu'une requête HTTP peut envoyer.

Normalement, cette méthode prend la requête et la réoriente vers d'autres méthodes qui sont plus appropriées comme doGet(ServletRequest request, ServletResponse response) et doPost(HttpServletRequest request, HttpServletResponse response).

Interfaces

J2EE, ou simplement JEE, fournit plusieurs interfaces et classes.

  • javax.Servlet
  • javax.GenericServlet
  • javax.HttpServlet

En installant Tomcat, on peut trouver les classes nécessaires à la compilation. En conséquence, on peut exporter le CLASSPATH pour que les classes Java puissent être compilées de partout :

export CLASSPATH="/usr/local/apache-tomcat-5.5.27/common/lib/servlet-api.jar"

Développement d'une servlet

En réalité, la classe HttpServlet fournit d'autres méthodes plus adaptées au protocole HTTP. En particulier, elle fournit une série de méthodes do…() correspondant à chaque type de requête HTTP : doPost(), doGet(), doPut(), doHead(), etc. Le tableau résume les méthodes spécifiques à la classe HttpServlet :

Méthodes spécifiques à la classe HttpServlet
void service (ServletRequest r, ServletResponse s) Redirige la requête du client vers la méthode service() protégée suivante.
void service (HttpServLetRequest r, HttpServLetResponse s) Méthode protégée invoquée par la méthode précédente et qui oriente la requête du client vers la bonne méthode do…() en fonction de la requête du client.
void doGet(HttpSeMetRequest r, HttpServetResponse s) invoquée pour traiter une requête HTTP de type GET.
void doPost(HttpServLetRequest r, HttpServLetResponse s) invoquée pour traiter une requête HTTP de type POST.
void doPut(HttpServLetRequest r, HttpServLetResponse s) invoquée pour traiter une requête HTTP de type PUT.
void doHead(HttpServLetRequest r, HttpServletResponse s) invoquée pour traiter une requête HTTP de type HEAD.
void doDeLete(HttpServLetRequest r, HttpServLetResponse s) invoquée pour traiter une requête HTTP de type DELETE.
void doOptions(HttpServletRequest r, HttpServletResponse s) invoquée pour traiter une requête HTTP de type OPTIONS.
void doTrace(HttpServletRequest r, HttpServLetResponse s) invoquée pour traiter une requête HTTP de type TRACE.
long getLastModified(HttpServLetRequest r) Retourne la date et l'heure de la dernière modification de l'objet requête r.

La méthode publique service() est surdéfinie ici de telle sorte qu'elle transmette la requête à la méthode protégée de même nom. Cette dernière analyse la requête pour déterminer son type et la transmet à la bonne méthode do…().

Notre classe TestServlet doit surdéfinir au moins une des méthodes do…() suivantes, en fonction de la façon dont elle sera invoquée :

  • doGet(), pour répondre à une requête HTTP de type GET.
  • doPost(), pour répondre à une requête HTTP de type POST.
  • doPut(), pour répondre à une requête HTTP de type PUT.
  • doDelete(), pour répondre à une requête HTTP de type DELETE.
  • doHead(), pour répondre à une requête HTTP de type HEAD.

Il n'y a pas de raison de surdéfinir la méthode service() dont le rôle est d'orienter la requête du client vers la bonne méthode, selon son type. De même, il n'y a pas de raison de surdéfinir les méthodes doOptions() et doTrace(). Par contre, si la servlet doit effectuer des initialisations au moment de son démarrage, comme l'allocation de ressources, dans ce cas, sa classe doit surdéfinir la méthode init() pour allouer ces ressources et la méthode destroy() pour les libérer. De même, la classe peut surdéfinir la méthode getServletInfo() pour retourner une chaîne de caractères fournissant des informations générales sur la servlet (auteur, version,…).

Ainsi, la classe de la servlet TestServlet peut surdéfinir la méthode doGet() de manière à envoyer un message de bienvenue à l'utilisateur.

TestServlet.java
import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;
 
public class TestServlet extends HttpServlet {
 
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        PrintWriter out = response.getWriter();
        out.println("Bonjour et bienvenue à votre première servlet");
    }
}

Remarquons que la méthode doGet() reçoit un paramètre de type HttpServletRequest qui représente la requête HTTP qui a invoquée la servlet et un paramètre de type HttpServletResponse qui représente la réponse de la servlet. Dans l'exemple de la servlet TestServlet, la méthode doGet() utilise la méthode getWriter() de ce deuxième paramètre pour obtenir un flux de sortie de type PrintWriter. Ce flux est dirigé vers le client et permet de lui envoyer des réponses textuelles (au format HTML ou XML).

Figure 2. La servlet TestServlet envoie un message de bienvenue à l'utilisateur.
On peut même personnaliser le message en y incluant le nom du client. Dans ce cas, on doit permettre à ce dernier de transmettre son nom à la servlet. Il peut le faire à travers, par exemple, un formulaire web (Figure 3) :

bienvenue.html
<html>
  <head>
    <titie>Bienvenue</title>
  </head>
  <body>
    <form action="Test_servlet" method="get">
      Nom : <input type="text" name="name"> <br />
      <input type="submit" value="OK">
    </form>
  </body>
</html>

figure 3. Formulaire HTML où le client peut saisir son nom.

En cliquant sur le bouton OK, les données du formulaire sont envoyées à la servlet TestServlet. Cette dernière va pouvoir récupérer le nom contenu dans le champ name. Elle va pour cela utiliser la méthode getParameter() de l'argument request de la méthode doGet(). Ce paramètre représente la requête du client et encapsule toutes les informations sur cette requête :

String nom = request.getParameter("name");

Une fois récupéré, le nom est inséré dans le message de bienvenue (Figure 4) :

out.println("Bonjour "+nom+" et bienvenue à votre première servlet")

IMAGE Figure 4. Le message envoyé par la servlet TestServlet est personnalisé en y incluant le nom de l'utilisateur.

La servlet TestServlet répond à des requêtes GET des clients. Rappelons que les requêtes GET peuvent être envoyées de plusieurs façons :

  • En soumettant un formulaire avec la méthode GET, comme dans l'exemple ci-dessus.
  • En invoquant la servlet en saisissant son URL dans la barre d'adresse du navigateur ou dans un lien hypertexte
<a href="/Test_servlet?name=username">invoquer la servlet TestServlet</a>

Si on invoque la servlet TestServlet avec la méthode POST, en modifiant la méthode de soumission du formulaire

<form action="/Test_servlet" method="post">

la servlet ne répond plus correctement (Figure 5). En effet, cette fois-ci c'est la méthode doPost() de la servlet qui est invoquée. Or cette méthode n'a pas été surdéfinie dans la classe TestServlet.

IMAGE Figure 5. La servlet ne répond pas à une requête POST car sa classe n'a pas surdéfini la méthode doPost().

Si on veut que la servlet réponde de la même manière, qu'elle soit invoquée à l'aide d'une requête GET ou d'une requête POST, il faut surdéfinir les deux méthodes doGet() et doPost() de manière à ce qu'elles fassent le même traitement. Habituellement, il est plus commode de définir le traitement de la servlet dans une 3ème méthode qui sera appelée par doGet() et par doPost() :

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    traiter(request, response);
}
 
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    traiter(request, response);
}
 
public void traiter(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    PrintWriter out = response.getWriter();
    String nom request.getParameter("name");
    out.println("Bonjour "+nom+" et bienvenue à votre premiere servlet");
}

Déploiement d'une servlet

Développer une servlet, ce n'est pas tout. Une fois le code de la servlet écrit et compilé, la servlet doit être déployée au sein d'un conteneur web. Nous allons utiliser le conteneur Tomcat. Tomcat est l'implémentation de référence en terme de conteneurs web. C'est un outil gratuit produit dans le cadre du projet Jakarta de la fondation Apache. Tomcat est intégré à la plupart des serveurs d'applications professionnels et il inclut aussi les fonctionnalités d'un serveur web. Cela veut dire qu'avec Tomcat nous n'avons pas besoin d'un serveur web tel que IIS ou Apache HTTP Server.

Le processus de déploiement de servlets et, de façon générale, d'applications web, est standardisé. Cela veut dire que si te déploiement réussi sur un conteneur, il réussira sur tous les autres conteneurs (respectant les spécifications J2EE, bien entendu).

L'installation et la configuration de Tomcat sont relativement aisées. Dans toute la suite, nous supposerons que Tomcat est installe et configuré correctement. De plus, nous supposerons que Tomcat est installé sur l'ordinateur à partir duquel nous invoquerons les applications web. Cet ordinateur a comme nom localhost. Enfin, nous supposerons que Tomcat écoute sur le port 8080, tel qu'il est configuré par défaut.

Par conséquent, toutes les URL qui invoquent des composants d'applications hébergées par Tomcat commenceront ainsi : http://localhost:8080.

Structure d'une application web

Dans les spécifications J2EE, une application web doit être dans un et un seul dossier. Deux applications web sont forcément dans deux dossiers séparés. De plus, l'application peut être fournie dans un seul fichier compressé de type WAR (Web ARchive) contenant le dossier de l'application. Les conteneurs web sont tenus d'accepter ce format.

Dans Tomcat, le dossier de l'application ou son fichier WAR est placé dans le sous-dossier webapps du dossier d'installation de Tomcat. Une application web J2EE est composée de quatre parties (Figure 6):

  1. un dossier public : c'est le dossier racine de l'application excepté son sous-dossier WEB-INF. Le conteneur web peut servir tous les fichiers du dossier public.
  2. Le fichier WEB-INF/web.xml : c'est le descripteur de déploiement de l'application.
  3. Le dossier de classes WEB-INF/classes : il contient les classes compilées des servlets, des classes utilitaires, etc.
  4. Le dossier de bibliothèques WEB-INF/lib : il contient d'éventuelles bibliothèques de classes fournies par des parties tierces, souvent sous forme de fichiers d'archives JAR.

Le dossier WEB-INF est privé et le conteneur web ne donne pas accès à son contenu aux utilisateurs. Le contenu de ce dossier est réservé au conteneur web.

IMAGE Figure 6. Structure des répertoires d'une application web sous J2EE.

Descripteur de déploiement

Les descripteurs de déploiement font partie intégrante des applications web J2EE. Ils permettent de gérer la configuration des applications web après leur déploiement. Pour les conteneurs web, un descripteur de déploiement est un fichier XML de nom web.xml stocké dans le dossier WEB-INF, sous-dossier du dossier principal de l'application.

La DTD (Document Type Definition) pour le descripteur de déploiement est fournie par la spécification des servlets2).

Le descripteur de déploiement joue plusieurs rôles :

  • Initialisation des paramètres pour servlets et applications : ça permet de minimiser les initialisations dans le code (hardcodées). Par exemple, si une servlet nécessite des accès à une base de données, le meilleur endroit pour placer les détails sur cette base de données (URL de la base de donnée et chaîne de pilote) est le descripteur de déploiement. On pourra ainsi changer la configuration de la base de données sans avoir à recompiler la servlet.
  • Définition des servlets/JSP : chaque page JSP ou servlet doit être définie dans le descripteur de déploiement. Pour une servlet, cette définition inclut le nom, la classe et une éventuelle description.
  • Mappings pour JSP/servlets : le conteneur web utilise ces informations pour « mapper » les requêtes vers les pages JSP et servlets.
  • Types MIME : une application web peut contenir différents types de contenu. Pour chacun d'eux, on spécifie le type MIME correspondant dans le descripteur de déploiement.
  • Sécurité : gère le contrôle d'accès à l'application. Il permet, en particulier, d'indiquer quelle page de login utiliser si l'application nécessite que l'utilisateur se connecte.
  • On peut aussi personnaliser des éléments tels que : les pages d'accueil, les pages d'erreurs, la configuration des sessions, etc.

Pour déployer la servlet TestServlet, ajouter dans le descripteur de déploiement la balise <servlet> :

<servlet>
  <servlet-name>TestServlet</servlet-name>
  <servlet-class>TestServlet</servlet-class>
</servlet>

et éventuellement un mapping pour cette servlet:

<servlet-mapping>
  <servlet-name>TestServlet</servlet-name>
  <uri-pattern>/Test_servlet</uri-pattern>
</servlet-mapping>

Ce mapping permet de se référer à la servlet par l'URL, en supposant que le dossier racine de l'application s'appelle cours : http://localhost:8080/test/Test_servlet.

Voici le contenu minimal du descripteur de déploiement (fichier web.xml) de l'application web qui contient la servlet TestServlet :

minimal 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>
  <servlet>
    <servlet-name>TestServlet</servlet-name>
    <servlet-class>TestServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>TestServlet</servlet-name>
    <url-pattern>/Test_servlet</url-pattern>
  </servlet-mapping>
</web-app>

La configuration des servlets

En plus de son nom, il est possible d'attribuer à une servlet des paramètres initiaux. Le meilleur endroit pour définir ces paramètres initiaux est le descripteur de déploiement. L'exemple suivant définit deux paramètres initiaux pour la servlet TestServlet : un paramètre de nom webmestre contenant l'adresse de courriel de l'administrateur de l'application et un paramètre de nom bdURL contenant l'URL d'une base de données MySQL :

<servlet>
  <servlet-name>TestServlet</servlet-name>
  <servlet-class>TestServlet</servlet-class>
  <init-param>
    <param-name>webmestre</param-name>
    <param-value>courriel@domaine.com</param-value>
  </init-param>
  <init-param>
    <param-name>bdURL</param-name>
    <param-value>jdbc:mysql://localhost/bdname?user=root</param-value>
  </init-param>
</servlet>

Le principal avantage de cette façon de faire est qu'on peut ensuite modifier les valeurs des paramètres sans avoir à recompiler la servlet.

La configuration d'une servlet est représentée par un objet de type javax.servlet.ServletConfig. Cette configuration consiste en plusieurs informations comme le nom de la servlet, les paramètres d'initialisation et un objet de type javax.servlet.ServletContext qui représente l'application web de laquelle fait partie la servlet.

Lors du lancement de la servlet, le conteneur construit un objet de type ServletConfig contenant les informations de configuration. Cet objet est transmis à la méthode init() de la servlet. La méthode init(), telle que définie dans la classe GenericServlet, stocke cet objet dans un attribut privé. Cela veut que lorsqu'on surdéfinit cette méthode, on doit obligatoirement appeler la version de la classe mère comme ceci :

public void init(ServletConfig x) {
    super.init(x);
    ...
}

Dans le code de la servlet, on peut récupérer une référence à cet objet en utilisant la méthode getServletConfig() de l'interface javax.servlet.Servlet :

ServletConfig sc = getServletConfig();

Ensuite, on peut accéder aux informations de configuration en utilisant cet objet.

String adrWebmestre = sc.getInitParameter("webmestre");
String uriBD = sc.getInitParameter("bdURL");
String nomServlet = sc.getServletName();

On peut aussi récupérer la liste de tous les noms des paramètres initiaux :

Enumeration listeParam = sc.getInitParameterNames();

Enfin, on peut obtenir une référence à l'objet ServletContext associé à la servlet :

ServletContext contexte = sc.getServletContext();

Cet objet représente l'application web de laquelle fait partie la servlet.

Le contexte des servlets

Chaque servlet s'exécute dans le cadre d'une application web. Cette application web constitue donc le contexte au sein duquel fonctionne cette servlet. Ce contexte est représenté par un objet de type ServletContext qu'on peut obtenir à partir de l'objet ServletConfig associé à la servlet, comme ceci :

ServletConfig sc = getServletConfig();
ServletContext contexte = sc.getServletContext();

Il n'y a qu'un seul objet ServletContext par application web. Par conséquent, ce même objet est partagé par toutes les servlets de l'application.

L'objet ServletContext fournit plusieurs méthodes :

  1. setAttribute()
  2. getAttribute()
  3. removeAttribute()
  4. getAttributeNames()

Ils permettent de gérer des données que les servlets se partagent. Les utilisateurs aussi se partagent ces données.

contexte.setAttribute("nomAttribut", x);   // enregistrer
Object x = contexte.getAttribute("nomAttribut");  // récupérer
Enumeration liste = contexte.getAttributeNames();  // obtenir la liste des attributs
contexte.removeAttribute("nomAttribut');  // supprimer

Les autres méthodes sont :

  • getInitParameter() et getInitParameterNames() → tout comme on peut définir des paramètres initiaux pour une servlet, on peut en définir pour une application. Pour cela, on se sert de la balise <context-param> dans le descripteur de déploiement. On peut ensuite chercher la valeur de ces paramètres à l'aide de la méthode getInitParameter(). On peut aussi obtenir la liste de ces paramètres avec la méthode getInitParameterNames().
  • getServletContextName() → retourne le nom de l'application tel qu'il est défini dans la balise <display-name> du descripteur de déploiement.
  • getRequestDispatcher() et getNamedDispatcher() → ces méthodes sont utilisées pour récupérer une référence vers une autre ressource de l'application (voir la section sur la collaboration des servlets).

La gestion des sessions

On peut faire persister les données d'un utilisateur d'une page à une autre pour la durée d'une session. Pour cela, on utilise un objet session instance de la classe javax.servlet.HttpSession.

javax.servlet.http.HttpSession session = request.getSession(true);
session.putValue("nom", request.getParameter("nom"));

Si on transmet la valeur false à la méthode getSession(), une nouvelle session ne sera pas créée, si elle n'existe pas. La méthode retourne dans ce cas null. De manière générale, on peut enregistrer dans l'objet session n'importe quel objet.

session.putValue("nom", x);

On peut récupérer une valeur stockée dans l'objet session à l'aide de ta méthode getValue().

x = session.getValue("nom");
 
java.awt.Color cp = new java.awt.Color(0.75,0.25,0.75);
session.putValue("couleurPreferee ", cp);
 
//Pour récupérer cette valeur :
 
java.awt.Color coul;
coul = (java.awt.Color) session.getValue("couleurPreferee");

Collaboration entre servlets

Il existe des situations où une servlet ne peut pas répondre seule à une requête. Deux situations peuvent se présenter :

  • Redirection (forward) → La servlet effectue une partie du traitement et la transmet à une autre ressource. Elle peut par exemple traiter une partie de la requête puis la transférer à une autre servlet pour la compléter. Elle peut aussi s'occuper de la logique d'affaire puis transmettre la requête à une page JSP qui va s'occuper de la présentation de la réponse au client.
  • Inclusion (include) → la servlet peut inclure le traitement effectué par une autre ressource dans son propre traitement.

Ces deux types de collaboration sont réalisés grâce à l'interface RequestDispatcher. Cette-ci fournit les deux méthodes :

public void forward(ServletRequest r, ServietResponse s) throws ServletException, IOException
public void include(ServletRequest r, ServietResponse s) throws ServletException, IOException

Remarquons d'abord que ces deux méthodes reçoivent deux paramètres de même type que les méthodes de service des servlets (service(), doGet(), doPost(), etc).

La méthode forward() permet de déléguer la requête à une autre ressource (servlet, JSP ou autre). La méthode include() permet d'inclure le traitement d'une autre ressource (servlet, JSP ou autre) dans celui de la servlet courante.

Dans tous les cas, il faut d'abord que la servlet obtienne une référence à la ressource. Pour cela, on utilise une des trois méthodes suivantes :

  1. La méthode getRequestDispatcher() de la classe ServletContext : public RequestDispatcher getRequestDispatcher (String chemin);. Cette méthode reçoit le chemin absolu vers la ressource demandée. Ce chemin est exprimée à partir du dossier racine de l'application.
  2. La méthode setRequestDispatcher() de la classe ServletRequest : public RequestDispatcher getRequestDispatcher (String chemin);. Cette méthode accepte à la fois le chemin relatif et le chemin absolu vers la ressource demandée.
  3. La méthode getNamedDispatcher() de la classe ServletContext : public RequestDispatcher getNamedDispatcher (String nom);. Cette méthode reçoit le nom de la servlet tel qu'il est défini à l'aide de la balise <servlet-name> dans le descripteur de déploiement.

Ressources

1)
Les spécifications des servlets sont publiées sur le site de Sun Microsystems consacré à Java : http://java.sun.com/products/servlet/
2)
Par exemple, pour la version 2.3 des spécifications des servlets, la DTD se trouve à: http://java.sun.com/dtd/web-app_2_3.dtd
developpement/java/servlets.txt · Dernière modification : 2023/10/03 23:14 de sgariepy