Introduction aux servlets
(1)
Dans le monde Java, les servlets constituent
la pierre de touche des technologies composants web. En règle
générale, les servlets sont utilisées pour traiter la
logique applicative d'une application Web, alors que la logique
de présentation est quant à elle plutôt dévolue aux
JSP.
1
Principe général
1.1
Architecture
Les servlets sont des programmes
s'exécutant sur le serveur Web, et retournant des
pages Web dynamiques (à la volée), au même titre que les CGI et
les langages de script côté serveur tels que PHP ou ASP.
Les servlets se chargent et s'exécutent
dans un moteur de servlet (appelé également conteneur de
servlet), à l'intérieur d'une machine virtuelle Java
(JVM). Concrètement, c'est ce conteneur qui
implémente toutes les interfaces et classes que l'on
appelle traditionnellement l'API Servlet ou littéralement
Servlet Application Programmer Interfaces. Ce mécanisme rend
possible deux choses :
- les servlets sont portables,
c'est-à-dire dans ce cas-ci indépendantes des plates-formes et des
serveurs, conformément à la philosophie java. Avec les servlets,
vous pouvez réellement "écrire une fois, servir partout".
- l'API Servlet représente tout ce que le
programmeur Java a besoin de connaître pour développer des
servlets, sans se soucier des détails de l'implémentation.
1.2
Packages
Les servlets Java, telles que définies par la
division Java Software de Sun MicroSystems, constituent un
paquetage optionnel à Java. Cela signifie qu'elles sont
officiellement reconnues par Sun et qu'elles font bien
partie du langage Java, mais qu'elles ne font pourtant pas partie
de l'API noyau de Java : à la place, elles font partie
intégrante de la plate-forme J2EE.
L'API Servlet est divisée en deux packages
principaux : javax.servlet et
javax.servlet.http.[1]
1.2.1
Package javax.servlet
Ce package contient les interfaces et
classes relatives aux servlets indépendantes de tout
protocole.
1.2.1.A
Interface javax.servlet.Servlet
Cette interface représente l'interface
centrale de l'API Servlet. Toute servlet digne de
ce nom est tenue de l'implémenter, directement ou
indirectement. Elle possède cinq méthodes :
init() :
- Cette méthode est appelée par le
conteneur de servlet afin d'indiquer à la servlet
qu'elle doit s'initialiser et être prête pour le
service. Le conteneur passe un objet de type ServletConfig
comme paramètre.
service() :
- Cette méthode est appelée par
le conteneur de servlet à chaque requête du client
afin de permettre à la servlet de répondre à la requête.
La méthode
service()traite
les requêtes et crée les réponses. Le conteneur de servlets
invoque cette méthode lorsqu'il reçoit une requête de la servlet
concernée. La signature complète de la méthode est la suivante
:
public void service (ServletRequest,
ServletResponse)
throws ServletException, java.io.IOException;
destroy() :
Cette méthode est appelée par le
conteneur de servlet après que la servlet a été
retirée et que toutes les requêtes destinées à la
servlet ont été traitées.
getServletConfig() :
Retourne un type d'information
disponible concernant la servlet, comme celle d'un paramètre
passé à la méthode init().
getServletInfo() :
Retourne l'information
disponible concernant la servlet, comme l'auteur, la
version, et l'information copyright.
1.2.1.B
Classe javax.servlet.GenericServlet
La classe GenericServlet
implémente l'interface Servlet.
Il s'agit d'une classe abstraite puisqu'elle fournit une
implémentation pour toutes les méthodes excepté pour
la méthode service().
Elle ajoute également quelques méthodes pour le logging. On peut
étendre cette classe et implémenter la méthode service() pour
écrire n'importe quel genre de servlet.
1.2.1.C
Interface javax.servlet.ServletRequest
L'interface javax.servlet.ServletRequest
fournit une vue générique de la requête envoyée par le client. Elle
définit des méthodes qui extraient l'information de la requête.
1.2.1.D
Interface javax.servlet.ServletResponse
L'interface javax.servlet.ServletResponse
fournit une moyen générique d'envoyer une réponse. Elle définit des
méthodes qui permettent d'envoyer une réponse convenable au
client.
1.2.2
Package
javax.servlet.http
Ce package fournit les fonctionnalités
de bases requises par les servlets HTTP. Les
différentes interfaces et classes de ce package
héritent des interfaces et classes du package
javax.servlet
correspondantes, afin de constituer un support spécifique pour le
protocole HTTP.
1.2.2.A
Classe javax.servlet.http.HttpServlet
javax.servlet.http.HttpServlet est une classe
abstraite qui hérite de GenericServlet. Parmi les
méthodes les plus importantes qu'elle ajoute, signalons-en
trois principales :
service() :
avec la signature suivante [2] :
protected void
service (HttpServletRequest, HttpServletResponse)
throws ServletException, java.io.Exception;
doGet() :
avec la signature suivante :
protected void
doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
L'implémentation par défaut de
service()
dans HttpServletdistribue
les requêtes HTTP GET à cette méthode, et les servlets
l'implémentent donc pour traiter les requêtes GET.
L'implémentation par défaut retourne une erreur HTTP
SC_BAD_REQUEST.
doPost() :
avec la signature suivante :
protected void
doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException
L'implémentation par défaut de service()
dans HttpServlet distribue les requêtes HTTP POST à cette méthode,
et les servlets l'implémentent donc pour traiter les requêtes
POST. L'implémentation par défaut retourne une erreur HTTP
SC_BAD_REQUEST.
2
Exemples de servlet
2.1
HelloWorld
Voici le code du désormais classique
HelloWorld. Listing
HelloWorld.java
import
java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class
HelloWorldServlet extends HttpServlet {
public void service(HttpServeltRequest requete,
HttpServletResponse reponse) throws ServletException, IOException
{
PrintWriter pw =
reponse.getWriter();
pw.println("<html>");
pw.println("<head>");
pw.println("</head>");
pw.println("<body>");
pw.println("<h3>Hello World</h3>");
pw.println("<body>");
pw.println("</html>");
}
}
Le code est composé des étapes suivantes :
1°) On hérite de la classe abstraite
HttpServlet en redéfinissant la méthode protégée
service().
2°) La méthode getWriter() de
ServletResponse retourne un objet PrintWriter qui peut être
utilisée pour envoyer des données au client. Cet objet est très
utilisé par les servlets pour générer des pages Web
dynamiquement.
3°) On remarque que contrairement à un
programme classique, mais comme une applet, une servlet ne dispose
pas de méthode main(). A la place, chaque fois que le
serveur achemine une requête à une servlet qui lui est
destinée, il invoque sa méthode service().
2.2
Un compteur simple
Regardons à présent comment écrire une servlet
qui compte et affiche le nombre de fois que l'on y a accédé.
Listing CompteurServlet.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class CompteurServlet extends
HttpServlet
{
int nb_visites = 0;
public void doGet(HttpServeltRequest
requete, HttpServletResponse reponse) throws ServletException,
IOException
{
PrintWriter pw = reponse.getWriter();
nb_visites++;
pw.println("<html>");
pw.println("<head>");
pw.println("</head>");
pw.println("<body>");
pw.println("<h3>Vous avez accédé à cette serlvet " +
nb_visites + " fois.</h3>");
pw.println("<body>");
pw.println("</html>");
}
}
Le code est très simple : il permet simplement
d'incrémenter la variable d'instance nb_visites
à chaque fois qu'une requête est faite sur la servlet. Cela
est rendu possible parce que lorsque le conteneur de
servlet charge cette servlet, il en crée une seule instance pour
traiter chaque requête faite sur la servlet. Ainsi, les
mêmes variables d'instance existent entre les invocations
successives. On appelle cela la persistance
d'instance.
Ici, on a redéfini la méthode
doGet() et non la méthode protégée service(),
ce qui constitue une meilleure approche. En effet, comme il
a été dit plus haut, l'implémentation par défaut de service()
appelle la méthode doGet() ou doPost() suivant le type de la
requête HTTP. En redéfinissant service() comme nous l'avons
fait dans l'exemple HelloWorld, on perd ce comportement par
défaut bien utile [3].
Introduction aux servlets (2) -
ServletRequest et ServletResponse
Après avoir expliqué les bases des servlets
nous allons nous focaliser ici plus particulièrement sur les
objets ServletRequest
et ServletResponse.
3
Introduction
Comme nous
l'avons déjà dit, l'API Servlet s'applique à n'importe quel
protocole, mais, en pratique, force est de constater que
la majorité des servlets sont écrites pour le protocole
HTTP.
3.1
Les méthodes HTTP
Comme vous le
savez sûrement, le protocole HTTP consiste en
échanges de requêtes/réponses, les requêtes étant
effectuées du client vers le serveur, et les réponses étant
envoyées en retour par le serveur au client. Un navigateur Web
envoie une requête HTTP à un serveur Web lorqu'une des
situations suivantes se produit :
- Un
utilisateur clique sur un hyperlien au sein d'une
page HTML.
- Un
utilisateur remplit un formulaire dans une page HTML et le
valide.
- Un
utilisateur entre une URL dans la barre d'adresse du
navigateur et presse le bouton Entrer. Bien sûr, on peut
appeler des pages Web par beaucoup d'autres moyens, notamment
via des scripts, mais ces situations reviennent aux trois
principales énumérées ci-dessus.
Par
défaut, le navigateur
utilise la méthode HTTP GET dans l'ensemble de ces cas.
Cependant, on peut configurer le comportement du navigateur pour
qu'il utilise d'autres méthodes HTTP, par exemple via
l'attribut method
dans le cas d'un formulaire
:
<FORM
name='loginForm' method='POST'
action='/loginServlet'>
<INPUT type='text'
name='utilisateur'>
<INPUT type='password'
name='mot_de_passe'>
<INPUT type='submit'
name='loginBouton' value='Login'>
</FORM>
Le tableau suivant résume les principales
différences entre les méthodes GET et POST.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Les données ne
peuvent être mémorisées dans l'historique du navigateur.
|
De ces
caractéristiques, il découle que l'on utilisera GET plutôt
:
- pour
retrouver un fichier HTML, XML, un fichier image, etc. parce
que seul le nom du fichier nécessite d'être
envoyé.
Et que l'on
utilisera plutôt POST dans les cas suivants :
- pour
envoyer une grosse quantité de données.
- pour
uploader un fichier, parce que la taille du fichier
pourrait excéder 255 caractères ou bien qu'il pourrait s'agir d'un
fichier binaire.
- pour saisir
un nom d'utilisateur/mot de passe, pour des raisons
évidentes de sécurité.
En résumé, on
pourrait dire que la méthode GET est utilisée pour lire
des informations [1] (document, graphique, ou le résultat d'une
requête sur une base de données) tandis que la méthode POST
est quant à elle conçue pour poster des
informations.
Il existe
d'autres types de méthodes dans le protocole HTTP, comme par
exemple la méthode HEAD, utilisée principalement pour
récupérer des méta-informations sur la ressource.
Typiquement, on fait appel à cette méthode pour contrôler la
date de la dernière modification d'une ressource sur le
serveur, et s'épargner ainsi la tâche d'un téléchargement
inutile.
En fait pour
chaque méthode du protocole HTTP, il existe une méthode
correspondante dans la classe HttpServlet
de type :
public void
doXXX(HttpServletRequest, HttpServletResponse) throws
ServletException, IOException;
où
doXXX()
dépend de la méthode
HTTP, comme le montre le
tableau suivant [2] :
|
|
GET
|
doGet()
|
HEAD
|
doHead()
|
POST
|
doPost()
|
PUT
|
doPut()
|
DELETE
|
doDelete()
|
OPTIONS
|
doOptions()
|
TRACE
|
doTrace()
|
La
classe HttpServlet
fournit des implémentations vides
pour chacune de ces méthodes. On doit donc les redéfinir
pour les adapter à notre propre logique applicative [3].
3.2
La séquence d'évènements dans HttpServlet
Vous vous
demandez peut-être qui appelle ces différentes méthodes
doXXX(). En voilà ci-dessous le processus détaillé :
1
- Le conteneur de servlet
commence par appeler la méthode
service(ServletRequest,
ServletResponse) de HttpServlet.
2
- La méthode service(ServletRequest,
ServletResponse) de HttpServletappelle
la méthode service(HttpServletRequest,
HttpServletResponse) de la
même classe. Comme il a été déjà remarqué, on observe en
passant que la méthode service est surchargée (overloaded)
dans la classe HttpServlet[4]
(modificateur).
3
- La méthode service(HttpServletRequest,
HttpServletResponse) de HttpServlet
analyse la requête et
détermine quelle méthode HTTP est utilisée; elle
appelle alors la méthode doXXX()
correspondante de la servlet. Par
exemple, si la requête utilise la méthode POST, la méthode service
protégée appelle la méthode doPost()
de la servlet [5].
4
Analyser la requête
La
classe ServletRequest
et sa sous-classe
HttpServletRequest
[6] permettent toutes les deux
d'analyser une requête. La première fournit des
méthodes relatives à n'importe quel protocole, alors que la
seconde qui en hérite ajoute des méthodes spécifiques à
HTTP. On utilise presque tout le temps
HttpServletRequest,
mais il est tout de même utile de connaître quelles
méthodes sont spécifiquement implémentées par
HttpServletRequest,
et lesquelles sont héritées de HttpServlet.
4.1
Comprendre ServletRequest
Le premier
objectif de ServletRequest
est de récupérer les
paramètres envoyés par un client. Le
tableau ci-dessous décrit les méthodes utilisées à cette fin
:
|
|
String
getParameter(String nomParam)
|
|
String[]
getParameterValues(String nomParam)
|
|
Enumeration getParameterNames()
|
|
4.2
Comprendre HttpServletRequest
4.2.1
Récupérer des paramètres nommés (posté par un formulaire par
exemple)
La classe qui
implémente l'interface HttpServletRequest
implémente toutes les méthodes
de ServletRequest,
appliquées au protocole HTTP : cette classe permet donc
d'analyser et d'interpréter des messages http en particulier de
récupérer les paramètres nommés (posté par un formulaire, par
exemple) et de fournir les informations appropriées à la
servlet.
Voici un
exemple illustrant la façon dont on peut utiliser ces différentes
classes et méthodes. Un formulaire HTML permet à un
utilisateur d'envoyer deux paramètres au serveur, son nom
ainsi que les langages informatiques qu'il connaît :

Le code HTML
correspondant au formulaire de cette page est le suivant
:
<form
action="/introduction02/servlet/ServletForm"
method="POST">
<br>
<table>
<tr>
<td>Nom <input
type="text" name="nom" value=""></td>
</tr>
<tr>
<td>Langages </td>
<td><select name="langages" size="5"
multiple>
<option value="Java">Java</option>
<option value="C">C</option>
<option
value="C++">C++</option>
<option value="VB">VB</option>
<option value="XML">XML</option>
</select>
</td>
</tr>
</table>
<br><br>
<input type="submit"
value="Envoyer">
</form>
L'attribut
action de FORM indique
que la servlet ServletForm doit traiter la requête. D'autre
part, puisque l'attribut method vaut POST, les
paramètres renseignés vont être transmis au serveur
par une requête HTTP POST.
On remarque
également le chemin relatif à la racine pour indiquer
l'adresse de la servlet - commençant par / -, et qui est ajouté au
chemin http://localhost:8080 ou équivalent [8].
Le listing
ci-dessous montre une implémentation possible de la
méthode doPost()
de ServletForm,
récupérant les paramètres envoyés
:
public void
doPost(HttpServletRequest req,
HttpServletResponse rep)
{
PrintWriter pw = rep.getWriter();
String nom = req.getParameter("nom");
String[] langages =
req.getParameterValues("langages");
if (nom!=null && langages!=null){
pw.println("Nom du
candidat:" + nom);
pw.println("Langages
connus:");
pw.println("<ul>");
for (int i=0; i<langages.length;
i++)
{
pw.println("<li>" + langages[i] + "</li>");
}
pw.println("</ul>");
}
}
Dans
l'exemple ci-dessus,
on connaît les noms des paramètres, donc on peut utiliser
les méthodes getParameter()
et getParameterValues().
Dans le cas contraire, on aurait dû utiliser
getParameterNames()
pour retrouver ces noms, par exemple
comme ceci :
Enumeration
parametres = req.getParameterNames();
while
(parametres.hasMoreElements())
{
String parametre =
(String)parametres.nextElement();
String[] valeurs =
req.getParameterValues(parametre);
pw.println("<b>"
+ parametre + "</b>: ") ;
pw.println("<ul>");
for (int i=0;
i<valeurs.length; i++)
{
pw.println("<li>" + valeurs[i] + "</li>");
}
pw.println("</ul>");
}
On vérifie le
fonctionnement attendu de la servlet après avoir renseigné les
champs de saisie et cliqué sur le bouton Envoyer :

4.2.2
Méthodes pour récupérer les noms et valeurs des en-têtes HTTP
De même qu'il
existe des méthodes afin de récupérer les paramètres des requêtes,
il existe des méthodes pour récupérer les noms et valeurs des
en-têtes HTTP. A la différence cependant que les en-têtes sont
spécifiques au protocole HTTP, et donc que les méthodes qui les
traitent appartiennent à l'interface HttpServletRequest
et non à HttpServlet. Parmi ces
méthodes, on peut mentionner les suivantes :
|
|
String getHeader(String nomHeader)
|
|
Enumeration getHeaders(String nomHeader)
|
Cette méthode
retourne toutes les valeurs associées à l'en-tête nommé sous
la forme d'un Enumeration d'objets String ou un Enumeration vide si
l'en-tête n'a pas été spécifié.
|
Enumeration getHeaderNames()
|
Cette méthode,
retourne le nom de tous les en-têtes sous forme d'un
Enumeration d'objets String ou un Enumeration vide si l'en-tête n'a
pas été spécifié.
|
Voilà par
exemple comment afficher tous les en-têtes présents dans la
requête :
Enumeration
noms = req.getHeaderNames();
while
(noms.hasMoreElements())
{
String nom_tete =
(String) noms.nextElement();
String valeur =
req.getHeader(nom_tete);
pw.println("<b>" +nom_tete + "</b>: " + valeur+
"<br>");
}
En ajoutant cette partie de code à notre
servlet, on obtient le résultat suivant :

5
Envoyer la réponse
Sans surprise,
l'objet ServletResponse
fournit des méthodes valables
pour tout type de protocole, alors que
HttpServletResponse,
qui en hérite, propose des méthodes supplémentaires spécifiques
à HTTP.
5.1
Comprendre ServletResponse
ServletResponse
déclare plusieurs méthodes
génériques afin d'envoyer une réponse, parmi
lesquelles setContentType(),
getWriteret
getOutputStream().
5.1.1
méthode setContentType()
Cette méthode
définit le type du contenu de la réponse au type spécifié.
Dans les servlets HTTP, elle définit l'en-tête HTTP Content-Type.
Des exemples de valeur sont par exemple text/plain pour du
plein-texte, image/jpeg pour une image de type
JPEG ou plus fréquemment text/html ; il est également
possible d'y définir l'encodage utilisé. Cette méthode doit
être appelée avant d'invoquer un PrintWriter
5.1.2
méthode getWriter()
Cette méthode
retourne un objet de la classe java.io.PrintWriter
pour écrire des données
caractères. Cet objet code les caractères conformément au
charset donné précédemment par le type de contenu via
setContentType().
Nous avons vu précédemment comment utiliser cette méthode sur
l'interface HttpServletResponse.
5.1.3
méthode getOutputStream()
Cette méthode
retourne un objet javax.servlet.ServletOutputStream
nécessaire pour écrire des
données binaires (octet par octet). Aucun encodage n'est
effectué.
Attention,
on ne peut invoquer à la fois getWriter()
et getOutputStream()
sur une même instance
de ServletResponse
sous peine de déclencher une
IllegalStateException.
On peut appeler la même méthode plusieurs fois
cependant.
Par exemple, si
l'on souhaite envoyer un fichier binaire, de type JAR, au
client, nous devons invoquer la méthode getOutputStream()
:
public class
ZipServlet extends HttpServlet
{
public void
doGet(HttpServletRequest req,
HttpServletResponse rep) throws IOException
{
rep.setContentType("application/jar");
File fichier
= new File("test.jar");
byte[]
bytearray = new byte[(int)fichier.length()];
FileInputStream is = new FileInputStream(fichier);
is.read(bytearray);
OutputStream
os = rep.getOutputStream();
os.write(bytearray);
os.flush();
}
}
Si vous testez
cette servlet, assurez-vous auparavant d'avoir mis le fichier
test.jar dans le dossier bin de Tomcat.
5.2
Comprendre HttpServletResponse
HttpServletResponse declare des méthodes
spécifiques à HTTP, comme setHeader(), setStatus(),
sendRedirect() et sendError().
5.2.1
méthode setHeader()
public abstract
void setHeader(String Nom, String valeur)
La méthode
setHeader() fixe la valeur de l'en-tête indiqué sous forme
de String. Si l'en-tête a déjà été fixé, la nouvelle valeur écrase
la précédente. Cette méthode a été introduite dans l'API Servlet
2.2 et est à mettre en parallèle avec les deux méthodes
public
abstract void setIntHeader(String name, int valeur)
et public abstract void
setDateHeader(String name, long date) de la même interface. Cette dernière méthode
peut être utile pour indiquer une valeur sous forme de date au
header LastModified, indiquant la date de dernière modification
du document.
5.2.2
méthode sendRedirect()
public abstract void sendRedirect(String Location) throws
IOException, IllegalStateException
Après avoir
analysé une requête, une servlet peut rediriger la réponse vers
une adresse spécifiée, en définissant automatiquement l'en-tête
Location.
Par exemple, le
bout de code ci-dessous contrôle la valeur du paramètre et
redirige le browser vers une nouvelle adresse si la valeur n'est
pas celle attendue :
if
("Nietzsche".equals(request.getParameter("philosophe")))
{
//traitement particulier
}
else {
reponse.sendRedirect("http://www.philosophie.fr");
}
Deux choses
sont à noter ici :
- la
nouvelle URL doit être écrite sous forme absolue, en incluant le
protocole http://. Cette méthode doit être appelée
avant que la réponse ne soit validée, sinon elle lance
une java.lang.IllegalStateException.
Par exemple, le code suivant va générer un tel type d'erreur
:
public void
doPost(HttpServletRequest req, HttpServletResposne rep) throws
IOException, ServletException
{
PrintWriter pw =
rep.getWriter();
pw.println("<html><body>HelloWorld!</body></html>");
pw.flush();
pw.sendRedirect("http://www.philosophie.fr");
}
En effet,
l'instruction pw.flush() force l'envoi du header au
navigateur client : a ce point, la réponse est donc considérée
comme validée et il n'y a plus aucun intérêt à ajouter quoi que ce
soit au message.
5.2.3
méthode sendError()
Enfin, il peut
arriver que quelque chose tourne mal, par exemple lorsqu'on demande
à la servlet de retourner un fichier qui n'existe pas, ou
qu'on lui demande des choses qu'elles ne sait pas
faire.
L'interface
fournit les méthodes sendError(int
status_code) et
sendError(int
status_code, String message) pour gérer de telles situations et envoyer le
code d'état approprié au client.
Par exemple,
si la servlet ne trouve pas un fichier, on peut
renseigner le client par l'instruction suivante :
reponse.sendError(HttpServletResponse.SC_NOT_FOUND);
Dans le cas
d'une chose impossible à faire :
reponse.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED);
NOTES:
[1] les seules
informations que la méthode GET est susceptible de fournir sont une
séquence de caractères ajoutés à l'URL de la requête dans ce que
l'on appelle une chaîne d'interrogation
[2] Les
requêtes HTTP 1.0 ne peuvent être que d'un des trois types suivants
: GET, POST ou HEAD. Les autres méthodes sont disponibles
uniquement via HTTP 1.1.
[3] Plus
exactement, il est nécessaire d'en redéfinir au moins
une
[4] C'est du
reste le fait qu'elle soit surchargée overloaded et non redéfinie
overrided qui permet également à cette seconde méthode de même nom
service d'avoir un modificateur d'accès différent : protected au
lieu de public
[5] Si vous
redéfinissez (override) la méthode service dans votre servlet, vous
perdez le bénéfice de cette redirection automatique et il vous
reviendra alors la tâche d'abord de déterminer la nature de la
méthode HTTP, ensuite d'effectuer l'appel à la méthode doXXX()
correcte.
[6] Il est
nécessaire de clarifier les choses ici. Strictement
parlant, javax.servlet.ServletRequest
et javax.servlet.http.HttpServletRequest
sont définies en tant
qu'interfaces dans l'API Servlet. Ici, on parlera
indistinctement d'interface ou de classe, sachant :
- premièrement
qu'en toute rigueur, on devrait parler « d'un objet d'une
classe qui implémente l'interface javax.servlet.ServletRequest ou
l'interface
javax.servlet.http.HttpServletRequest ».
- deuxièmement
que c'est le conteneur de servlet qui fournit de telles
classes, et que le nom de ces classes d'implémentation n'a
aucune importance pour le développeur qui doit s'en tenir
aux noms d'interfaces fournis par l'API Servlet.
[7] Si on
appelle getParameter()
sur un paramètre ayant plusieurs
valeurs, la valeur retournée est la première valeur retournée
par getParameterValues()
[8] Une autre
façon de faire est d'appeler l'URL relative à la page HTML courante
via <form action="../servlet/ServletForm2"
method="POST">
Introduction aux servlets
(3)
Cycle de
vie – ServletConfig – ServletContext
Après la
lecture des deux premières parties, il devrait être assez clair
qu'une servlet reçoit une requête, la traite, et renvoie une
réponse à l'aide d'une des méthodes doXXX().
Cependant,
avant qu'une servlet puisse correctement répondre à la requête d'un
client, le conteneur de servlet doit suivre certaines
étapes afin que cette servlet se trouve dans un état propre à
pouvoir répondre aux requêtes.
6
Cycle de vie d'une servlet
Regardons d'un
peu plus l'ensemble de ces étapes, appelées cycle de vie de la
servlet, et qui se décomposent ainsi :
- créer et
initialiser la servlet.
- traiter
les services demandés par les clients.
- détruire
la servlet et la passer au ramasse-miettes.
6.1
Charger et instancier une servlet
Nous avons dit précédemment qu'une servlet
persistait entre les requêtes sous forme d'instance d'objet.
Autrement dit, lorsque le code de la servlet est chargé, le
conteneur de servlet ne crée qu'une seule instance de classe.
Ainsi, les coûts liés à la création d'un nouvel objet sont
considérablement diminués, et la servlet dispose en permanence
d'un certain nombre d'informations dont elle risque fort
d'avoir besoin lors des prochaines requêtes [1]. Un exemple
courant est une connexion à une base de données qui est ouverte une
seule fois, et est ensuite utilisée à chaque nouvelle requête sur
la base de données. Le processus d'instanciation est le suivant
:
Lorsque l'on démarre le conteneur de
servlet, il cherche un ensemble de fichiers de
configuration appelés descripteurs de déploiement, décrivant
toutes les applications Web.
Chaque application Web possède son propre
fichier web.xml, incluant
une entrée pour chaque servlet qu'il utilise. Une entrée
spécifie le nom de la servlet et le nom de la classe de servlet
[2]. Le conteneur de servlet crée une
instance de la classe de la servlet concernée en
utilisant la méthode Class.forName(nomClasse).newInstance().
Pour utiliser une telle méthode, la classe de servlet doit
posséder un constructeur sans argument : typiquement, on ne
définit pas de tel constructeur dans la servlet, et on laisse le
compilateur ajouter le constructeur par défaut. A ce moment-là,
la servlet est chargée.
6.2
Initialiser une servlet : méthode init()
Il est
parfaitement envisageable d'initialiser une servlet avec un
certain nombre de données lorsqu'elle est chargée. Comment
rendre cela possible alors que l'on ne définit aucun constructeur
?
Réponse :
lorsque le conteneur de servlet crée l'instance de servlet,
il appelle la méthode init(ServletConfig)
sur cette instance nouvellement
créée. L'objet ServletConfig
contient tous les paramètres
d'initialisation qui ont été définis dans le descripteur de
déploiement pour l'application web à laquelle appartient la
servlet.
La servlet est
initialisée une fois que la méthode init() a retourné son résultat.
De plus, comme il ne sert strictement à rien d'initialiser un objet
de façon répétitive, on est assuré que le conteneur de servlet
n'appellera cette méthode init() qu'une seule fois sur l'instance
de servlet.
Si vous
regardez attentivement l'API Servlet et cherchez la classe
GenericServlet,
vous allez vous apercevoir que celle-ci possède une version
surchargée de init(ServletConfig),
la méthode init() définie sans arguments. Cette dernière
peut être redéfinie (overriden) sans aucun problème dans le
code de votre servlet. Autrement, si vous choisissez de
redéfinir la méthode init() prenant en argument un
servletConfig, vous devrez immanquablement y inclure comme
première instruction un appel à super.init(config).
Comme exemple
simple d'initialisation de servlet, avec utilisation d'un
paramètre d'initialisation défini dans web.xml, on peut
reprendre notre exemple de servlet compteur de visites, et
« tricher » en faisant démarrer le compteur à 30
visites :
import java.io.*;
import javax.servlet.*;
import
javax.servlet.http.*;
public class
CompteurInitieServlet extends HttpServlet
{
int nb_visites = 0;
public void init() throws ServletException {
String initial =
getInitParameter("initial");
try {
nb_visites = Integer.parseInt(initial);
}
catch (NumberFormatException
e){
nb_visites =
0;
}
}
public void doGet(HttpServletRequest requete, HttpServletResponse
reponse) throws ServletException, IOException
{
PrintWriter pw =
reponse.getWriter();
nb_visites++;
pw.println("<html>");
pw.println("<head>");
pw.println("</head>");
pw.println("<body>");
pw.println("<h3>Vous avez accédé à cette servlet " +
nb_visites + " fois.</h3>");
pw.println("<body>");
pw.println("</html>");
}
}
On verra plus
en détail la méthode getInitParameter(String)
de l'interface ServletConfigdans
la suite de cet article. Disons simplement ici que cette méthode
:
- permet de
récupérer un paramètre par son nom lorsqu'il est défini
dans le fichier web.xml de l'application web (voir
ci-dessous).
- la
signature de cette méthode impose un retour de
valeur sous la forme d'un String, d'où l'utilisation
d'une méthode de conversion en int.
La définition
du paramètre d'initialisation dans le fichier web.xml est très
simple, et se fait de la façon suivante :
<?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>CompteurInitieServlet</servlet-name>
<servlet-class>CompteurInitieServlet</servlet-class>
<init-param>
<param-name>
initial
</param-name>
<param-value>
30
</param-value>
</init-param>
<description>
Valeur initiale du compteur
</description>
</servlet>
</web-app>
On définit le
nom du paramètre au sein de la balise <param-name> tandis que
sa valeur est spécifiée dans <param-value>, ces deux balises
sœurs étant englobées par la balise parent
<init-param>.
Si vous avez
correctement paramétré votre application, vous devriez alors
obtenir l'écran suivant, en pointant pour la première fois sur la
servlet :
6.3
Mise hors service d'une servlet : méthode destroy()
La méthode
d'instance public void
destroy() est appelée
par le moteur de servlets afin d'indiquer à une
servlet qu'elle a été mise hors service. Après l'appel de cette
méthode, la méthode de service de la servlet ne sera plus appelée
par le conteneur de servlets.
Une servlet
peut surcharger cette méthode afin de sauvegarder son état, libérer
ses ressources (connexions à une bases de données,
etc.).
Par exemple, la
méthode destroy()
pourrait être utilisée de la façon
suivante pour fermer une connexion à une base de données,
l'objet de type Connection dbConnexion ayant été précédemment
défini :
public void
destroy(){
try{
dbConnexion.close();
}
catch(Exception e)
{
System.out.println("Erreur lors de la fermeture de la connexion" +
e.getMessage());
}
}
7
L'interface ServletConfig
On a vu dans la
section précédente que le conteneur de servlets passait un objet
ServletConfig dans la méthode init(ServletConfig). Dans cette
section, nous allons regarder un peu plus en détail cet
objet.
7.1
Les méthodes de ServletConfig
L'interface ServletConfig
est définie dans le package
javax.servlet, et est plutôt simple à utiliser. Elle fournit
quatre méthodes, ainsi que le montre le tableau ci-dessous
:
Méthode
|
Description
|
String getInitParameter(String name)
|
|
Enumeration getInitParameterNames()
|
Cette méthode
retourne le nom de tous les paramètres d'initialisation sous
forme d'un Enumeration
d'objets String ou un
Enumeration
vide s'il n'existe pas de
paramètres.
|
ServletContext getServletContext()
|
Cette méthode
retourne l'objet ServletContext()
de la servlet, permettant une
interaction avec le conteneur de servlets.
|
String getServletName()
|
|
On peut
remarquer que ServletConfig
fournit des méthodes seulement pour
récupérer des paramètres. On ne peut pas ajouter ou même simplement
définir des paramètres de ServletConfig
via les méthodes de cet
objet.
On a déjà
utilisé plus haut une des méthodes de ServletConfig,
i.e. getInitParameter.
On peut se demander du reste pourquoi on avait pu l'appeler
directement dans le code. En fait, la méthode init()telle
que définie dans la section 1 de cet article est exactement
équivalente à la définition suivante :
public void
init() throws ServletException {
ServletConfig
config = getServletConfig();//1
String initial =
config.getInitParameter("initial");//2
try {
nb_visites = Integer.parseInt(initial);
}
catch (NumberFormatException
e){
nb_visites = 0;
}
A la ligne 1,
nous utilisons la méthode getServletConfig() mentionnée dans
Introduction aux servlets (1) afin de récupérer l'objet
ServletConfig sauvegardé par la méthode init(). A la ligne 2, nous
appliquons alors la méthode getInitParameter() à cet objet afin de
récupérer le paramètre d'initialisation.
Question :
Pourquoi la version n°1 est-elle légitime ? Comment pouvons-nous
nous passer de l'appel à getServletConfig() de l'interface Servlet
?
Réponse : Tout
simplement parce que la classe GenericServlet implémente
l'interface javax.servlet.ServletConfig en plus d'implémenter
l'interface javax.servlet.Servlet. Par conséquent, un appel direct
à getInitParameter() est de ce fait rendu possible.
Cela est vrai
de n'importe quelle méthode de ServletConfig et l'on peut donc
ajouter sans rique d'erreur, avant d'avoir récupéré l'objet
ServletConfig lui-même, la première ligne suivante, qui se charge
d'écrire un message dans la console avec le nom de la servlet
concernée :
public void
init() throws ServletException {
System.out.println(getServletName() + " :
Initialisation...");
ServletConfig
config = getServletConfig();
String initial =
config.getInitParameter("initial");
try {
nb_visites = Integer.parseInt(initial);
}
catch (NumberFormatException
e){
nb_visites = 0;
}
Deux remarques
pour terminer cette section :
- il n'est
pas nécessaire d'inclure le code de récupération des paramètres
d'initialisation dans la méthode init() même si c'est
fréquemment le cas.
- Les
paramètres d'initialisation sont disponibles aux servlets qui ne
sont pas des servlets HTTP, puisque cette fonctionnalité est
celle de l'objet javax.servlet.GenericServlet.
Une servlet générique peut donc être utilisée dans un serveur Web
même s'il lui manque les fonctionnalités spécifiques à
HTTP.
L'exemple de
code ci-dessous montre comment une sous-classe de GenericServlet
peut récupérer ses paramètres d'initialisation :
import java.io.*;
import java.util.*;
import javax.servlet.*;
public class InitServlet extends GenericServlet
{
public void service(ServletRequest
requete, ServletResponse reponse) throws ServletException,
IOException
{
reponse.setContentType("text/plain");
PrintWriter out =
reponse.getWriter();
out.println("Paramètres d'initialisation de la servlet " +
getServletName());
Enumeration enum =
getInitParameterNames();
while(enum.hasMoreElements()){
String name = (String)enum.nextElement();
out.println(name + " : " + getInitParameter(name));
}
}
}
Cette servlet
récupère le nom de la servlet ainsi que le nom et la valeur de
chacun des paramètres d'initialisation, alors qu'elle n'hérite
que de la classe GenericServlet. Si vous avez spécifié correctement
de tels paramètres dans le fichier web.xml de l'application web,
vous devriez obtenir un résultat de la forme suivante :

8
L'interface ServletContext
L'interface ServletContext
définit un ensemble de méthodes
que l'on peut utiliser pour communiquer avec le conteneur de
servlets. Pour la
servlet, c'est une sorte de fenêtre lui permettant d'avoir
une vue sur son environnement [3], tel que les paramètres de
l'application web dont elle fait partie, ou encore la
version de son conteneur de servlet. Cela inclut aussi
trouver l'information de chemin, accéder aux autres
servlets s'exécutant sur le serveur et écrire dans le journal de
serveur.
Chaque
application web dispose d'un contexte de servlet
différent.
L'interface ServletContext
dispose d'un grand nombre de
méthodes. Aussi allons-nous nous limiter dans cette section
d'introduction seulement à deux d'entre elles,
getResource()
et getResourceAsStream(),
toutes les deux utilisées pour retrouver une ressource associée
à un chemin.
8.1
Méthode getResource(String
path)
Signature : java.net.URL getResource(String path).
Cette méthode
retourne une URL pour la ressource associée au chemin indiqué en
paramètre. Le chemin doit commencer par / et il est
interprété relativement à la racine du contexte. La méthode
peut renvoyer null si aucune ressource n'est associée au chemin.
Elle permet à un conteneur de servlets de rendre disponible aux
servlets n'importe quelle ressource.
Attention, le
contenu de la requête est renvoyé tel quel. La requête sur une page
.jsp renvoie le code source de la page ; nous verrons dans un
article ultérieur une autre technique pour inclure les résultats de
l'exécution d'une page JSP [4].
Pour illustrer
l'utilisation de cette méthode, on peut reprendre l'exemple de
notre servlet ZipServlet (envoi d’un fichier binaire de type jar au
client) de Introduction aux servlets(2), et réécrire le code pour
rendre la ressource portable d'un conteneur de servlets à un autre
:
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class
ZipServlet extends HttpServlet
{
public void
doGet(HttpServletRequest req,
HttpServletResponse rep) throws IOException
{
rep.setContentType("application/zip");
ServletContext context = getServletContext();
URL url = context.getResource("/zip/introduction03.zip");
//ouvre une connexion sur cet URL
//et renvoie un
InputStream
InputStream is =
url.openStream();
OutputStream os = rep.getOutputStream();
int byteslus =
0;
while( (byteslus = is.read(bytearray)) !=-1)
{
os.write(byteslus);
}
os.flush();
is.close();
}
}
8.2
Méthode
getResourceAsStream(String path)
Signature : java.io.InputStream getResource(String
path).
Cette méthode
retourne un InputStream
pour lire le contenu de la
ressource associée au chemin indiqué. La méthode peut renvoyer null si aucune
ressource ne peut être associée au chemin.
Elle représente
une méthode raccourci de getResource(path).openStream().
Par exemple,
dans le code ci-dessus, la parie concernant la
récupération d'un InputStream à partir de l'objet URL
pourrait aussi s'écrire, directement à partir de l'objet
ServletContext :
ServletContext
context = getServletContext();
InputStream is =
context.getResourceAsStream ("/zip/introduction03.zip");
Attention,
quoique plus pratique que celui de la première méthode, l'emploi de
getResourceAsStream() perd un certain nombre de méta-informations
comme la taille du contenu et son type, disponibles avec
getResource()
NOTES:
[1] On a
illustré cela dans Introduction aux servlets(1) avec l'exemple du
compteur de visites et l'utilisation de variables
d'instance.
[2]
Introduction aux servlets (1) pour un exemple.
[3] Un peu
comme la monade chez Leibniz constitue une fenêtre sur le
monde...
[4] Il s'agit
de l'interface javax.servlet.RequestDispatcher.
Servlets et
accès aux SGBDR
9
Exemple de servlet faisant appelle à JDBC
La servlet qui
suit lance un appel sur une base de données et renvoie les données
au format HTML.
import java.io.*;
import java.sql.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class
DBServlet extends HttpServlet {
private
Connection con;
private
PrintWriter out;
public void
init(ServletConfig conf) throws ServletException
{
super.init(conf);
try
{
Class.forName("omnidex.jdbc.OdxJDBCDriver");
con
=DriverManager.getConnection ("jdbc:omnidex:c:/datas/test.dsn",
"login", "mdpasse");
}
catch(Exception e) {
System.err.println(e);
}
}
public void
doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html");
try
{
out =
res.getWriter();
out.println("<html><head><title>");
out.println("JDBC Servlet");
out.println("</title></head><body>");
Statement stmt
= con.createStatement();
ResultSet rs =
stmt.executeQuery("SELECT nom FROM clients WHERE id >
20");
out.println("<UL>");
while(rs.next()) {
out.println("<LI>" + rs.getString("nom"));
}
out.println("</UL>");
rs.close();
stmt.close();
}
catch(SQLException e) {
out.println("Exception SQL");
}
catch(IOException e) {
system.err.println("Exception I/O");
}
out.println("</body></html>");
out.close();
}
public void
destroy() {
try
{
con.close();
} catch(SQLException e) {
;
}
}
} // classe
La servlet
classe (abstraite) HttpServlet, qui nous permet de gérer tout ce
qui concerne le protocole HTTP requis par une application
Web.
Trois méthodes
sont ensuite définies: init(), doGet() et destroy(). Elles sont
décrites dans ce qui suit, hors les gestionnaires
d'exceptions.
C'est ici que
se déroule l'ensemble des connexions à la base de données, ainsi
que tous les processus ne nécessitant qu'un seul
lancement.
Ici, deux
processus sont lancés: l'enregistrement du pilote JDBC, et la
connexion à la base de données.
C'est la
méthode Class.forName qui permet de choisir le pilote JDBC que l'on
utilise, ici le pilote Omnidex.
La méthode
getConnection() ouvre une connexion vers l'adresse (l'URL) JDBC de
la base de données. Les URLs JDBC offrent une manière unique
d'identifier les bases de données, sous la forme
« jdbc:nomdupilote:nomdelasource ».
Dans notre
servlet, nous utilisons le pilote
Omnidex, et la base de données se trouve sous la forme d'un fichier
datasource nommé test.dsn, situé dans le répertoire C:\datas (DOS
noté les ‘/’ dans l’URL).
Après
l'initialisation, le servlet est en mesure de gérer de nombreuses
requêtes par le biais d'une seule occurrence. Chaque requête en
provenance du client déclenche l’appel de cette méthode.
La connexion
(lancée par init()) réussie, le servlet est prêt à envoyer et
recevoir des requêtes.
Pour construire
la page HTML, il faut en premier lieu lui donner un type
(text/html) à l'aide de setContentType(). Le flux de sortie est
créé avec out=res.getWriter(). Une fois créé, il ne reste plus qu'à
construire le HTML.
On créé un
objet Statement, utilisé pour lancer la requête SQL, qui renverra
un objet ResultSet. Cet objet sera utilisé pour afficher les
données renvoyées par la requête SQL.
Une fois la
requête terminée, les objets contenant le résultat de la requête et
le Statement sont fermés.
Cet exemple
permet d'observer une connexion persistante à une base de
données, de telle sorte qu'une nouvelle requête du client ne
nécessite pas de reconnections.
Une fois le
servlet utilisé par le serveur, la méthode destroy() permet de
fermer complètement la connexion.
11
Nota Bene
Le driver
permettant l’accès au SGBDR devra être accessible à votre servlet.
Pour cela placez le fichier .jar du driver dans un répertoire lib
sous le répertoire d’accueil de votre application (au besoin créez
ce répertoire)