Développons en Java 2.30 | |
Copyright (C) 1999-2022 Jean-Michel DOUDOUX | (date de publication : 15/06/2022) |
|
Niveau : | Supérieur |
A la base, les serveurs web sont seulement capables de renvoyer des fichiers présents sur le serveur en réponse à une requête d'un client. Cependant, pour permettre l'envoi d'une page HTML contenant par exemple une liste d'articles répondant à différents critères, il faut créer dynamiquement cette page HTML. Plusieurs solutions existent pour ces traitements. Les servlets Java sont une de ces solutions.
Mais les servlets peuvent aussi servir à d'autres usages.
Des informations sur les servlets sont disponibles sur la page :
https://www.oracle.com/java/technologies/servlet-technology.html
Ce chapitre contient plusieurs sections :
Une servlet est un programme qui s'exécute côté serveur en tant qu'extension du serveur. Elle reçoit une requête du client, elle effectue des traitements et renvoie le résultat. La liaison entre la servlet et le client peut être directe ou passer par un intermédiaire comme par exemple un serveur http.
Même si pour le moment la principale utilisation des servlets est la génération de pages html dynamiques utilisant le protocole http et donc un serveur web, n'importe quel protocole reposant sur le principe de requête/réponse peut faire usage d'une servlet.
Ecrite en Java, une servlet en retire ses avantages : la portabilité, l'accès à toutes les API de Java dont JDBC pour l'accès aux bases de données, ...
Une servlet peut être invoquée plusieurs fois en même temps pour répondre à plusieurs requêtes simultanées.
Dans une architecture Client/Serveur trois tiers, la servlet se positionne dans le tiers du milieu entre le client léger chargé de l'affichage et la source de données.
Il existe plusieurs versions des spécifications de l'API Servlets :
Version | |
2.0 | 1997 |
2.1 | Novembre 1998, partage d'informations grâce au Servletcontext La classe GenericServlet implémente l'interface ServletConfig une méthode log() standard pour envoyer des informations dans le journal du conteneur objet RequestDispatcher pour le transfert du traitement de la requête vers une autre ressource ou inclure le résultat d'une autre ressource |
2.2 | Aout 1999, format war pour un déploiement standard des applications web mise en buffer de la réponse inclus dans J2EE 1.2 |
2.3 | Septembre 2001, JSR 053 : nécessite Java 1.2 minimum ajout d'un mécanisme de filtre ajout de méthodes pour la gestion d'événements liés à la création et la destruction du context et de la session inclus dans J2EE 1.3 |
2.4 | Novembre 2003, JSR 154 inclus dans J2EE 1.4 |
2.5 | Septembre 2005, JSR 154 inclus dans Java EE 5, nécessite Java SE 5 minimum |
3.0 | Décembre 2009, JSR 315 inclus dans Java EE 6, nécessite Java SE 6 minimum |
3.1 | Mai 2013, JSR 340 inclus dans Java EE 7 |
4.0 | Septembre 2017, JSR 369 |
Un serveur d'applications permet de charger et d'exécuter les servlets dans une JVM. C'est une extension du serveur web. Ce serveur d'applications contient entre autres un moteur de servlets qui se charge de manager les servlets qu'il contient.
Pour exécuter une servlet, il suffit de saisir une URL qui désigne la servlet dans un navigateur.
Initialement, pour développer des servlets avec le JDK Standard Edition, il fallait utiliser le Java Server Development Kit (JSDK) qui est une extension du JDK. Pour réaliser les tests, le JSDK fournissait, dans sa version 2.0 un outil nommé servletrunner et depuis sa version 2.1, il fournit un serveur http allégé.
Actuellement, pour exécuter des applications web, il faut utiliser un conteneur web ou un serveur d'applications : il existe de nombreuses versions commerciales telles que IBM Webpshere ou BEA WebLogic mais aussi des versions libres telles que Tomcat du projet GNU Jakarta.
Ce serveur d'applications ou ce conteneur web doit utiliser ou inclure un serveur http dont le plus utilisé est Apache.
Le choix d'un serveur d'applications ou d'un conteneur web doit tenir compte de la version du JSDK qu'il supporte pour être compatible avec celle utilisée pour le développement des servlets. Le choix entre un serveur commercial et un libre doit tenir compte principalement du support technique, des produits annexes fournis et des outils d'installation et de configuration.
Pour simplement développer des servlets, le choix d'un serveur libre se justifie pleinement de part sa gratuité et sa « légèreté ».
Un conteneur web est un moteur de servlets qui prend en charge et gère les servlets : chargement de la servlet, gestion de son cycle de vie, passage des requêtes et des réponses ... Un conteneur web peut être intégré dans un serveur d'applications qui va contenir d'autres conteneurs et éventuellement proposer d'autres services..
Le chargement et l'instanciation d'une servlet se font selon le paramétrage soit au lancement du serveur soit à la première invocation de la servlet. Dès l'instanciation, la servlet est initialisée une seule et unique fois avant de pouvoir répondre aux requêtes. Cette initialisation peut permettre de mettre en place l'accès à des ressources telles qu'une base de données.
Les programmes ou script CGI (Common Gateway Interface) sont aussi utilisés pour générer des pages HTML dynamiques. Ils représentent la plus ancienne solution pour réaliser cette tâche.
Un CGI peut être écrit dans de nombreux langages.
Il existe plusieurs avantages à utiliser des servlets plutôt que des CGI :
Les servlets sont conçues pour agir selon un modèle de requête/réponse. Tous les protocoles utilisant ce modèle peuvent être utilisés : http, ftp, etc ...
L'API servlets est une extension du jdk de base, et en tant que telle elle est regroupée dans des packages préfixés par javax.
L'API servlet regroupe un ensemble de classes dans deux packages :
Le package javax.servlet définit plusieurs interfaces, méthodes et exceptions :
javax.servlet | Nom | Rôle |
Les interfaces | RequestDispatcher | Définition d'un objet qui permet le renvoi d'une requête vers une autre ressource du serveur (une autre servlet, une JSP ...) |
Servlet | Définition de base d'une servlet | |
ServletConfig | Définition d'un objet pour configurer la servlet | |
ServletContext | Définition d'un objet pour obtenir des informations sur le contexte d'exécution de la servlet | |
ServletRequest | Définition d'un objet contenant la requête du client | |
ServletResponse | Définition d'un objet qui contient la réponse renvoyée par la servlet | |
SingleThreadModel | Permet de définir une servlet qui ne répondra qu'à une seule requête à la fois | |
Les classes | GenericServlet | Classe définissant une servlet indépendante de tout protocole |
ServletInputStream | Flux permettant la lecture des données de la requête cliente | |
ServletOutPutStream | Flux permettant l'envoi de la réponse de la servlet | |
Les exceptions | SevletException | Exception générale en cas de problème durant l'exécution de la servlet |
UnavailableException | Exception levée si la servlet n'est pas disponible |
Le package javax.servlet.http définit plusieurs interfaces et méthodes :
Javax.servlet | Nom | Rôle |
Les interfaces | HttpServletRequest | Hérite de ServletRequest : définit un objet contenant une requête selon le protocole http |
HttpServletResponse | Hérite de ServletResponse : définit un objet contenant la réponse de la servlet selon le protocole http | |
HttpSession | Définit un objet qui représente une session | |
Les classes | Cookie | Classe représentant un cookie (ensemble de données sauvegardées par le brower sur le poste client) |
HttpServlet | Hérite de GenericServlet : classe définissant une servlet utilisant le protocole http | |
HttpUtils | Classe proposant des méthodes statiques utiles pour le développement de servlets http |
Une servlet est une classe Java qui implémente l'interface javax.servlet.Servlet. Cette interface définit 5 méthodes qui permettent au conteneur web de dialoguer avec la servlet : elle encapsule ainsi les méthodes nécessaires à la communication entre le conteneur et la servlet.
Méthode | Rôle |
void service (ServletRequest req, ServletResponse res) | Cette méthode est exécutée par le conteneur
lorsque la servlet est sollicitée : chaque requête du
client déclenche une seule exécution de cette méthode. Cette méthode pouvant être exécutée par plusieurs threads, il faut prévoir un processus d'exclusion pour l'utilisation de certaines ressources. |
void init(ServletConfig conf) | Initialisation de la servlet. Cette méthode est appelée
une seule fois après l'instanciation de la servlet. Aucun traitement ne peut être effectué par la servlet tant que l'exécution de cette méthode n'est pas terminée. |
ServletConfig getServletConfig() | Renvoie l'objet ServletConfig passé à la méthode init |
void destroy() | Cette méthode est appelée lors de la destruction de la servlet. Elle permet de libérer proprement certaines ressources (fichiers, bases de données ...). C'est le serveur qui appelle cette méthode. |
String getServletInfo() | Renvoie des informations sur la servlet. |
Les méthodes init(), service() et destroy() assurent le cycle de vie de la servlet en étant respectivement appelées lors de la création de la servlet, lors de son appel pour le traitement d'une requête et lors de sa destruction.
La méthode init() est appelée par le serveur juste après l'instanciation de la servlet.
La méthode service() ne peut pas être invoquée tant que la méthode init() n'est pas terminée.
La méthode destroy() est appelée juste avant que le serveur ne détruise la servlet : cela permet de libérer des ressources allouées dans la méthode init() telles qu'un fichier ou une connexion à une base de données.
L'interface ServletRequest définit plusieurs méthodes qui permettent d'obtenir des données sur la requête du client :
Méthode | Rôle |
ServletInputStream getInputStream() | Permet d'obtenir un flux pour les données de la requête |
BufferedReader getReader() | Idem |
L'interface ServletResponse définit plusieurs méthodes qui permettent de fournir la réponse faite par la servlet suite à ses traitements :
Méthode | Rôle |
SetContentType | Permet de préciser le type MIME de la réponse |
ServletOutputStream getOutputStream() | Permet d'obtenir un flux pour envoyer la réponse |
PrintWriter getWriter() | Permet d'obtenir un flux pour envoyer la réponse |
Une servlet qui implémente simplement l'interface Servlet doit évidemment redéfinir toutes les méthodes de l'interface.
Il est très utile lorsque que l'on crée une servlet qui implémente directement l'interface Servlet de sauvegarder l'objet ServletConfig fourni par le conteneur en paramètre de la méthode init() car c'est le seul moment où l'on a accès à cet objet.
Exemple ( code Java 1.1 ) : |
import java.io.*;
import javax.servlet.*;
public class TestServlet implements Servlet {
private ServletConfig cfg;
public void init(ServletConfig config) throws ServletException {
cfg = config;
}
public ServletConfig getServletConfig() {
return cfg;
}
public String getServletInfo() {
return "Une servlet de test";
}
public void destroy() {
}
public void service (ServletRequest req, ServletResponse res )
throws ServletException, IOException {
res.setContentType( "text/html" );
PrintWriter out = res.getWriter();
out.println( "<HTML>" );
out.println( "<HEAD>");
out.println( "<TITLE>Page generee par une servlet</TITLE>" );
out.println( "</HEAD>" );
out.println( "<BODY>" );
out.println( "<H1>Bonjour</H1>" );
out.println( "</BODY>" );
out.println( "</HTML>" );
out.close();
}
} |
Le protocole HTTP est un protocole qui fonctionne sur le modèle client/serveur. Un client qui est une application (souvent un navigateur web) envoie une requête à un serveur (un serveur web). Ce serveur attend en permanence les requêtes sur un port particulier (par défaut le port 80). A la réception de la requête, le serveur lance un thread qui va la traiter pour générer la réponse. Le serveur renvoie la réponse au client une fois les traitements terminés.
Une particularité du protocole HTTP est de maintenir la connexion entre le client et le serveur uniquement durant l'échange de la requête et de la réponse.
Il existe deux versions principales du protocole HTTP : 1.0 et 1.1.
La requête est composée de trois parties :
La première ligne de la requête contient la commande à exécuter par le serveur. La commande est suivie éventuellement d'un argument qui précise la commande (par exemple l'url de la ressource demandée). Enfin la ligne doit contenir la version du protocole HTTP utilisé, précédée de HTTP/.
Exemple :
GET / index.html HTTP/1.0
Avec HTTP 1.1, les commandes suivantes sont définies : GET, POST, HEAD, OPTIONS, PUT, DELETE, TRACE et CONNECT. Les trois premières sont les plus utilisées.
Il est possible de fournir sur les lignes suivantes de la partie en-tête des paramètres supplémentaires. Cette partie en-tête est optionnelle. Les informations fournies peuvent permettre au serveur d'obtenir des informations sur le client. Chaque information doit être mise sur une ligne unique. Le format est nom_du_champ:valeur. Les champs sont prédéfinis et sont sensibles à la casse.
Une ligne vide doit précéder le corps de la requête. Le contenu du corps de la requête dépend du type de la commande.
La requête doit obligatoirement être terminée par une ligne vide.
La réponse est elle aussi composée des trois mêmes parties :
La première ligne de l'en-tête contient un état qui est composé : de la version du protocole HTTP utilisé, du code de statut et d'une description succincte de ce code.
Le code de statut est composé de trois chiffres qui donnent des informations sur le résultat du traitement qui a généré cette réponse. Ce code peut être associé à une catégorie en fonction de sa valeur :
Plage de valeurs du code | Signification |
100 à 199 | Information |
200 à 299 | Traitement avec succès |
300 à 399 | La requête a été redirigée |
400 à 499 | La requête est incomplète ou erronée |
500 à 599 | Une erreur est intervenue sur le serveur |
Plusieurs codes sont définis par le protocole HTTP dont les plus importants sont :
L'en-tête contient des informations qui précisent le contenu de la réponse.
Le corps de la réponse est précédé par une ligne vide.
|
La suite de cette section sera développée dans une version future de ce document
|
L'usage principal des servlets est la création de pages HTML dynamiques. L'API fournit une classe qui encapsule une servlet utilisant le protocole http. Cette classe est la classe HttpServlet.
Cette classe hérite de GenericServlet, donc elle implémente l'interface Servlet, et redéfinit toutes les méthodes nécessaires pour fournir un niveau d'abstraction permettant de développer facilement des servlets avec le protocole http.
Ce type de servlet n'est pas utile seulement pour générer des pages HTML bien que cela soit son principal usage, elle peut aussi réaliser un ensemble de traitements tels que mettre à jour une base de données. En réponse, elle peut générer une page html qui indique le succès ou non de la mise à jour. Une servlets peut aussi par exemple renvoyer une image qu'elle aura dynamiquement générée en fonction de certains paramètres.
Elle définit un ensemble de fonctionnalités très utiles : par exemple, elle contient une méthode service() qui appelle certaines méthodes à redéfinir en fonction du type de requête http (doGet(), doPost(), etc ...).
La requête du client est encapsulée dans un objet qui implémente l'interface HttpServletRequest : cet objet contient les données de la requête et des informations sur le client.
La réponse de la servlet est encapsulée dans un objet qui implémente l'interface HttpServletResponse.
Typiquement pour définir une servlet, il faut définir une classe qui hérite de la classe HttpServlet et redéfinir la méthode doGet() et/ou doPost() selon les besoins.
La méthode service() héritée de HttpServlet appelle l'une ou l'autre de ces méthodes en fonction du type de la requête http :
Une servlet peut traiter un ou plusieurs types de requêtes grâce à plusieurs autres méthodes :
La classe HttpServlet hérite aussi de plusieurs méthodes définies dans l'interface Servlet : init(), destroy() et getServletInfo().
Si cette méthode doit être redéfinie, il est important d'invoquer la méthode héritée avec un appel à super.init(config), config étant l'objet fourni en paramètre de la méthode. Cette méthode définie dans la classe HttpServlet sauvegarde l'objet de type ServletConfig.
De plus, la classe GenericServlet implémente l'interface ServletConfig. Les méthodes redéfinies pour cette interface utilisent l'objet sauvegardé. Ainsi, la servlet peut utiliser sa propre méthode getInitParameter() ou utiliser la méthode getInitParameter() de l'objet de type ServletConfig. La première solution permet un usage plus facile dans toute la servlet.
Sans l'appel à la méthode héritée lors d'une redéfinition, la méthode getInitParameter() de la servlet lèvera une exception de type NullPointerException.
La méthode service() est la méthode qui est appelée lors de l'invocation de la servlet.
Par défaut dans la classe HttpServlet, cette méthode contient du code qui réalise une analyse de la requête client contenue dans l'objet HttpServletRequest. Selon le type de requête GET ou POST, elle appelle la méthode doGet() ou doPost(). C'est bien le type de requête qui indique la méthode à utiliser dans la servlet.
Ainsi, la méthode service() n'est pas à redéfinir pour ces requêtes et il suffit de redéfinir les méthodes doGet() et/ou doPost() selon les besoins.
Une requête de type GET est utile avec des liens. Par exemple :
<A HREF="http://localhost:8080/examples/servlet/tomcat1.MyHelloServlet">test de la servlet</A>
Dans une servlet de type HttpServlet, une telle requête est associée à la méthode doGet().
La signature de la méthode doGet() : |
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
} |
Le traitement typique de la méthode doGet() est d'analyser les paramètres de la requête, alimenter les données de l'en-tête de la réponse et d'écrire la réponse.
Une requête POST n'est utilisable qu'avec un formulaire HTML.
Exemple : de code HTML |
<FORM ACTION="http://localhost:8080/examples/servlet/tomcat1.TestPostServlet"
METHOD="POST">
<INPUT NAME="NOM">
<INPUT NAME="PRENOM">
<INPUT TYPE="ENVOYER">
</FORM> |
Dans l'exemple ci-dessus, le formulaire comporte deux zones de saisies correspondant à deux paramètres : NOM et PRENOM.
Dans une servlet de type HttpServlet, une telle requête est associée à la méthode doPost().
La signature de la méthode doPost() : |
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException {
} |
La méthode doPost() doit généralement recueillir les paramètres pour les traiter et générer la réponse. Pour obtenir la valeur associée à chaque paramètre il faut utiliser la méthode getParameter() de l'objet HttpServletRequest. Cette méthode attend en paramètre le nom du paramètre dont on veut la valeur. Ce paramètre est sensible à la casse.
Exemple : |
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String nom = request.getParameter("NOM");
String prenom = request.getParameter("PRENOM");
} |
La servlet envoie sa réponse au client en utilisant un objet de type HttpServetResponse. HttpServletResponse est une interface : il n'est pas possible d'instancier un tel objet mais le moteur de servlets instancie un objet qui implémente cette interface et le passe en paramètre de la méthode service.
Cette interface possède plusieurs méthodes pour mettre à jour l'en-tête http et la page HTML de retour.
Méthode | Rôle |
void sendError (int) | Envoie une erreur avec un code retour et un message par défaut |
void sendError (int, String) | Envoie une erreur avec un code retour et un message |
void setContentType(String) | Héritée de ServletResponse, cette méthode permet de préciser le type MIME de la réponse |
void setContentLength(int) | Héritée de ServletResponse, cette méthode permet de préciser la longueur de la réponse |
ServletOutputStream getOutputStream() | Héritée de ServletResponse, elle retourne un flux pour l'envoi de la réponse |
PrintWriter getWriter() | Héritée de ServletResponse, elle retourne un flux pour l'envoi de la réponse |
Avant de générer la réponse sous forme de page HTML, il faut indiquer dans l'en-tête du message http, le type MIME du contenu du message. Ce type sera souvent « text/html » qui correspond à une page HTML mais il peut aussi prendre d'autres valeurs en fonction de ce que retourne la servlet (une image par exemple). La méthode à utiliser est setContentType().
Il est aussi possible de préciser la longueur de la réponse avec la méthode setContentLength(). Cette précision est optionnelle mais si elle est utilisée, la longueur doit être exacte pour éviter des problèmes.
Il est préférable de créer une ou plusieurs méthodes recevant en paramètre l'objet HttpServletResponse qui seront dédiées à la génération du code HTML afin de ne pas alourdir les méthodes doXXX().
Il existe plusieurs façons de générer une page HTML : elles utiliseront toutes soit la méthode getOutputStream() ou getWriter() pour obtenir un flux dans lequel la réponse sera envoyée.
Exemple ( code Java 1.1 ) : |
protected void GenererReponse1(HttpServletResponse reponse) throws IOException {
//creation de la reponse
StringBuffer sb = new StringBuffer();
sb.append("<HTML>\n");
sb.append("<HEAD>\n");
sb.append("<TITLE>Bonjour</TITLE>\n");
sb.append("</HEAD>\n");
sb.append("<BODY>\n");
sb.append("<H1>Bonjour</H1>\n");
sb.append("</BODY>\n");
sb.append("</HTML>");
// envoi des infos de l'en-tete
reponse.setContentType("text/html");
reponse.setContentLength(sb.length());
// envoi de la réponse
reponse.getOutputStream().print(sb.toString());
} |
L'avantage de cette méthode est qu'elle permet facilement de déterminer la longueur de la réponse.
Dans l'exemple, l'ajout des retours chariot '\n' à la fin de chaque ligne n'est pas obligatoire mais facilite la compréhension du code HTML surtout s'il devient plus complexe.
Exemple : |
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class TestServlet4 extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html");
ServletOutputStream out = res.getOutputStream();
out.println("<HTML>\n");
out.println("<HEAD>\n");
out.println("<TITLE>Bonjour</TITLE>\n");
out.println("</HEAD>\n");
out.println("<BODY>\n");
out.println("<H1>Bonjour</H1>\n");
out.println("</BODY>\n");
out.println("</HTML>");
}
} |
Exemple ( code Java 1.1 ) : |
protected void GenererReponse2(HttpServletResponse reponse) throws IOException {
reponse.setContentType("text/html");
PrintWriter out = reponse.getWriter();
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>Bonjour</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");
out.println("<H1>Bonjour</H1>");
out.println("</BODY>");
out.println("</HTML>");
} |
Avec cette méthode, il faut préciser le type MIME avant d'écrire la réponse. L'emploi de la méthode println() permet d'ajouter un retour chariot en fin de chaque ligne.
Si un problème survient lors de la génération de la réponse, la méthode sendError() permet de renvoyer une erreur au client : un code retour est positionné dans l'en-tête http et le message est indiqué dans une simple page HTML.
Toute servlet doit au moins importer trois packages : java.io pour la gestion des flux et deux packages de l'API servlet ; javax.servlet.* et javax.servlet.http.
Il faut déclarer une nouvelle classe qui hérite de HttpServlet.
Il faut redéfinir la méthode doGet() pour y insérer le code qui va envoyer dans un flux le code HTML de la page générée.
Exemple ( code Java 1.1 ) : |
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MyHelloServlet 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>Bonjour tout le monde</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Bonjour tout le monde</h1>");
out.println("</body>");
out.println("</html>");
}
} |
La méthode getWriter() de l'objet HttpServletResponse renvoie un flux de type PrintWriter dans lequel on peut écrire la réponse.
Si aucun traitement particulier n'est associé à une requête de type POST, il est pratique de demander dans la méthode doPost() d'exécuter la méthode doGet(). Dans ce cas, la servlet est capable de renvoyer une réponse pour les deux types de requête.
Exemple ( code Java 1.1 ) : |
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws ServletException,IOException {
this.doGet(request, response);
} |
Une servlet est exécutée dans un contexte particulier mis en place par le moteur de servlets.
La servlet peut obtenir des informations sur ce contexte.
La servlet peut aussi obtenir des informations à partir de la requête du client.
Dès que de la servlet est instanciée, le moteur de servlets appelle sa méthode init() en lui donnant en paramètre un objet de type ServletConfig.
ServletConfig est une interface qui possède deux méthodes permettant de connaître les paramètres d'initialisation :
Exemple : |
String param;
public void init(ServletConfig config) {
param = config.getInitParameter("param");
} |
Exemple ( code Java 1.1 ) : |
public void init(ServletConfig config) throws ServletException {
cfg = config;
System.out.println("Liste des parametres d'initialisation");
for (Enumeration e=config.getInitParameterNames(); e.hasMoreElements();) {
System.out.println(e.nextElement());
}
} |
La déclaration des paramètres d'initialisation dépend du serveur qui est utilisé.
La servlet peut obtenir des informations à partir d'un objet ServletContext retourné par la méthode getServletContext() d'un objet ServletConfig.
Il est important de s'assurer que cet objet ServletConfig, obtenu par la méthode init() est soit explicitement sauvegardé soit sauvegardé par l'appel à la méthode init() héritée qui effectue cette sauvegarde.
L'interface ServletContext contient plusieurs méthodes dont les principales sont :
méthode | Rôle | Deprecated |
String getMimeType(String) | Retourne le type MIME du fichier en paramètre | |
String getServletInfo() | Retourne le nom et le numéro de version du moteur de servlet | |
Servlet getServlet(String) | Retourne une servlet à partir de son nom grâce au context | Ne plus utiliser depuis la version 2.1 du jsdk |
Enumeration getServletNames() | Retourne une énumération qui contient la liste des servlets relatives au contexte | Ne plus utiliser depuis la version 2.1 du jsdk |
void log(Exception, String) | Ecrit les informations fournies en paramètre dans le fichier log du serveur | Utiliser la nouvelle méthode surchargée log() |
void log(String) | Idem | |
void log (String, Throwable) | Idem |
Exemple : écriture dans le fichier log du serveur |
public void init(ServletConfig config) throws ServletException {
ServletContext sc = config.getServletContext();
sc.log( "Demarrage servlet TestServlet" );
} |
Le format du fichier log est dépendant du serveur utilisé :
Exemple : résultat avec tomcat |
Context log path="/examples" :Demarrage servlet TestServlet |
De nombreuses informations en provenance du client peuvent être extraites de l'objet ServletRequest passé en paramètre par le serveur (ou de HttpServletRequest qui hérite de ServletRequest).
Les informations les plus utiles sont les paramètres envoyés dans la requête.
L'interface ServletRequest dispose de nombreuses méthodes pour obtenir ces informations :
Méthode | Rôle |
int getContentLength() | Renvoie la taille de la requête, 0 si elle est inconnue |
String getContentType() | Renvoie le type MIME de la requête, null s'il est inconnu |
ServletInputStream getInputStream() | Renvoie un flux qui contient le corps de la requête |
Enumeration getParameterNames() | Renvoie une énumération contenant le nom de tous les paramètres |
String getProtocol() | Retourne le nom du protocole utilisé par la requête et sa version |
BufferedReader getReader() | Renvoie un flux qui contient le corps de la requête |
String getRemoteAddr() | Renvoie l'adresse IP du client |
String getRemoteHost() | Renvoie le nom de la machine cliente |
String getScheme | Renvoie le protocole utilisé par la requête (exemple : http, ftp ...) |
String getServerName() | Renvoie le nom du serveur qui a reçu la requête |
int getServerPort() | Renvoie le port du serveur qui a reçu la requête |
Exemple ( code Java 1.1 ) : |
package tomcat1;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
public class InfoServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException,ServletException {
GenererReponse(request, response);
}
protected void GenererReponse(HttpServletRequest request, HttpServletResponse reponse)
throws IOException {
reponse.setContentType("text/html");
PrintWriter out =reponse.getWriter();
out.println("<html>");
out.println("<body>");
out.println("<head>");
out.println("<title>Informations a disposition de la servlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<p>Type mime de la requête :"
+request.getContentType()+"</p>");
out.println("<p>Protocole de la requête :"
+request.getProtocol()+"</p>");
out.println("<p>Adresse IP du client :"
+request.getRemoteAddr()+"</p>");
out.println("<p>Nom du client : "
+request.getRemoteHost()+"</p>");
out.println("<p>Nom du serveur qui a reçu la requête :"
+request.getServerName()+"</p>");
out.println("<p>Port du serveur qui a reçu la requête :"
+request.getServerPort()+"</p>");
out.println("<p>scheme: "+request.getScheme()+"</p>");
out.println("<p>liste des paramètres </p>");
for (Enumeration e =request.getParameterNames() ; e.hasMoreElements() ; ) {
Object p = e.nextElement();
out.println("<p> nom : "+p+" valeur :"
+request.getParameter(""+p)+"</p>");
}
out.println("</body>");
out.println("</html>");
}
} |
Résultat : avec l'url http://localhost:8080/examples/servlet/tomcat1.InfoServlet?param1=valeur1¶m2=valeur2 : Une page html s'affiche contenant : |
Type mime de la requête : null
Protocole de la requête : HTTP/1.0
Adresse IP du client : 127.0.0.1
Nom du client : localhost
Nom du serveur qui a reçu la requête : localhost
Port du serveur qui a reçu la requête : 8080
scheme : http
liste des paramètres
nom : param2 valeur :valeur2
nom : param1 valeur :valeur1 |
Les cookies sont des fichiers contenant des données au format texte. Ils sont créés à l'initiative du serveur et envoyés par le serveur sur le poste client pour leur stockage. Les données contenues dans le cookie sont ensuite renvoyées au serveur à chaque requête.
Les cookies peuvent être utilisés explicitement ou implicitement par exemple lors de l'utilisation d'une session.
Les cookies ne sont pas dangereux car ce sont uniquement des fichiers textes qui ne sont pas exécutés. De plus, les navigateurs posent des limites sur le nombre (en principe 20 cookies pour un même serveur) et la taille des cookies (4ko maximum). Par contre les cookies peuvent contenir des données plus ou moins sensibles. Il est capital de ne stocker dans les cookies que des données qui ne sont pas facilement exploitables par une intervention humaine sur le poste client et en tout cas de ne jamais les utiliser pour stocker des informations sensibles telles qu'un numéro de carte bleue.
La classe javax.servlet.http.Cookie encapsule un cookie.
Un cookie est composé d'un nom, d'une valeur et d'attributs.
Pour créer un cookie, il suffit d'instancier un nouvel objet de type Cookie. La classe Cookie ne possède qu'un seul constructeur qui attend deux paramètres de type String : le nom et la valeur associée.
La classe Cookie possède plusieurs getters et setters pour obtenir ou définir des attributs qui sont tous optionnels.
Attribut | Rôle |
Comment | Commentaire associé au cookie |
Domain | Nom de domaine (partiel ou complet) associé au cookie. Seuls les serveurs contenant ce nom de domaine recevront le cookie |
MaxAge | Durée de vie en secondes du cookie. Une fois ce délai expiré, le cookie est détruit sur le poste client par le navigateur. Par défaut la valeur limite la durée de vie du cookie à la durée de vie de l'exécution du navigateur |
Name | Nom du cookie |
Path | Chemin du cookie. Ce chemin permet de renvoyer le cookie uniquement au serveur dont l'url contient également le chemin. Par défaut, cet attribut contient le chemin de l'url de la servlet. Par exemple, pour que le cookie soit renvoyé à toutes les requêtes du serveur, il suffit d'affecter la valeur "/" à cet attribut |
Secure | Booléen qui précise si le cookie ne doit être envoyé que par une connexion SSL |
Value | Valeur associée au cookie |
Version | Version du protocole utilisé pour gérer le cookie |
Pour envoyer un cookie au browser, il suffit d'utiliser la méthode addCookie() de la classe HttpServletResponse.
Exemple : |
vCookie monCookie = new Cookie("nom","valeur");
response.addCookie(monCookie); |
Pour lire un cookie envoyé par le browser, il faut utiliser la méthode getCookies() de la classe HttpServletRequest. Cette méthode renvoie un tableau d'objets Cookie. Les cookies sont renvoyés dans l'en-tête de la requête http. Pour rechercher un cookie particulier, il faut parcourir le tableau et rechercher le cookie à partir de son nom grâce à la méthode getName() de l'objet Cookie.
Exemple : |
Cookie[] cookies = request.getCookies();
String valeur = "";
for(int i=0;i<cookies.length;i++) {
if(cookies[i].getName().equals("nom")) {
valeur=cookies[i].getValue();
}
} |
|
Cette section sera développée dans une version future de ce document
|
Le format war (Web Application Archive) permet de regrouper en un seul fichier tous les éléments d'une application web que ce soit pour le côté serveur (servlets, JSP, classes java, ...) ou pour le côté client (ressources HTML, images, son ... ).
C'est une extension du format jar spécialement dédiée aux applications web qui a été introduite dans les spécifications de la version 2.2 des servlets. C'est un format indépendant de toute plate-forme et exploitable par tous les conteneurs web qui respectent a minima cette version des spécifications.
Le but principal est de simplifier le déploiement d'une application web et d'uniformiser cette action quel que soit le conteneur web utilisé.
Comme les fichiers jar, les fichiers war possèdent une structure particulière qui est incluse dans un fichier compressé de type "zip" possédant comme extension ".war".
Le nom du fichier .war est important car ce nom sera automatiquement associé dans l'url pour l'accès à l'application en concaténant le nom du domaine, un slash et le nom du fichier war. Par exemple, pour un serveur web sur le poste local avec un fichier test.war déployé sur le serveur d'applications, l'url pour accéder à l'application web sera http://localhost/test/.
Le répertoire WEB-INF et le fichier web.xml qu'il contient doivent obligatoirement être présents dans l'archive. Le fichier web.xml est le descripteur de déploiement de l'application web.
Le serveur web peut avoir accès par le serveur d'applications à toutes les ressources contenues dans le fichier .war hormis celles présentes dans le répertoire WEB-INF. Ces dernières ne sont accessibles qu'au serveur d'application.
Le répertoire WEB-INF/classes est automatiquement ajouté par le conteneur au CLASSPATH lors du déploiement de l'application web.
L'archive web peut être créée grâce à l'outil jar fourni avec le JDK ou avec un outil commercial. Avec l'outil jar, il suffit de créer l'arborescence de l'application, de se placer dans le répertoire racine de cette arborescence et d'exécuter la commande :
jar cvf nom_web_app.war .
Toute l'arborescence avec les fichiers qu'elle contient sera incluse dans le fichier nom_web_app.war.
Le fichier /WEB-INF/web.xml est un fichier au format XML qui est le descripteur de déploiement permettant de configurer : l'application, les servlets, les sessions, les bibliothèques de tags personnalisés, les paramètres de contexte, les types Mimes, les pages par défaut, les ressources externes, la sécurité de l'application et des ressources J2EE.
Le fichier web.xml commence par un prologue et une indication sur la version de la DTD à utiliser. Celle-ci dépend des spécifications de l'API servlet utilisée.
Exemple : servlet 2.2 |
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"> |
Exemple : servlet 2.3 |
<?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"> |
L'élément racine est le tag <web-app>. Cet élément peut avoir plusieurs tags fils dont l'ordre d'utilisation doit respecter celui défini dans la DTD utilisée.
Le tag <icon> permet de préciser une petite et une grande image qui pourront être utilisées par des outils graphiques.
Le tag <display-name> permet de donner un nom pour l'affichage dans les outils.
Le tag <description> permet de fournir un texte de description de l'application web.
Le tag <context-param> permet de fournir un paramètre d'initialisation de l'application. Ce tag peut avoir trois tags fils : <param-name>, <param-value> et <description>. Il doit y en avoir autant que de paramètres d'initialisation. Les valeurs fournies peuvent être retrouvées dans le code de la servlet grâce à la méthode getInitParameter() de l'objet ServletContext.
Le tag <servlet> permet de définir une servlet. Le tag fils <icon> permet de préciser une petite et une grande image pour les outils graphiques. Le tag <servlet-name> permet de donner un nom à la servlet qui sera utilisé pour le mapping avec l'URL par défaut de la servlet. Le tag <display-name> permet de donner un nom d'affichage. Le tag <description> permet de fournir une description de la servlet. Le tag <servlet-class> permet de préciser le nom complètement qualifié de la classe Java dont la servlet sera une instance. Le tag <init-param> permet de préciser un paramètre d'initialisation pour la servlet. Ce tag possède les tags fils <param-name>, <param-value>, <description>. Les valeurs fournies peuvent être retrouvées dans le code de la servlet grâce à la méthode getInitParameter() de la classe ServletConfig. Le tag <load-on-startup> permet de préciser si la servlet doit être instanciée lors de l'initialisation du conteneur. Il est possible de préciser dans le corps de ce tag un numéro de séquence qui permettra d'ordonner la création des servlets.
Exemple : servlet 2.2 |
<servlet>
<servlet-name>MaServlet</servlet-name>
<servlet-class>fr.jmdoudoux.dej.servlet.MaServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>param1</param-name>
<param-value>valeur1</param-value>
</init-param>
</servlet> |
Le tag <servlet-mapping> permet d'associer la servlet à une URL. Ce tag possède les tags fils <servlet-name> et <servlet-mapping>.
Exemple : servlet 2.2 |
<servlet-mapping>
<servlet-name>MaServlet</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping> |
Le tag <session-config> permet de configurer les sessions. Le tag fils <session-timeout> permet de préciser la durée maximum d'inactivité de la session avant sa destruction. La valeur fournie dans le corps de ce tag est exprimée en minutes.
Exemple : servlet 2.2 |
<session-config>
<session-timeout>15</session-timeout>
</session-config> |
Le tag <mime-mapping> permet d'associer des extensions à un type MIME particulier.
Le tag <welcome-file-list> permet de définir les pages par défaut. Chacun des fichiers est défini grâce au tag fils <welcome-file>
Exemple : servlet 2.2 |
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file-list> |
Le tag <error-page> permet d'associer une page web à un code d'erreur HTTP particulier ou a une exception java particulière. Le code erreur est précisé avec le tag fils <error-code>. L'exception Java est précisée avec le tag fils <exception-type>. La page web est précisée avec le tag fils <location>.
Le tag <tag-lib> permet de définir une bibliothèque de tags personnalisée. Le tag fils <taglib-uri> permet de préciser l'URI de la bibliothèque. Le tag fils <taglib-location> permet de préciser le chemin de la bibliothèque.
Exemple : déclaration de la biliothèque core de JSTL |
<taglib>
<taglib-uri>http://java.sun.com/jstl/core</taglib-uri>
<taglib-location>/WEB-INF/tld/c.tld</taglib-location>
</taglib> |
Le déploiement d'une archive web dans un serveur d'applications est très facile car il suffit simplement de copier le fichier .war dans le répertoire par défaut dédié aux applications web. Par exemple dans Tomcat, c'est le répertoire webapps. Attention cependant, si chaque conteneur qui respecte les spécifications 1.1 des JSP sait utiliser un fichier .war, son exploitation peut différer légèrement.
Par exemple avec Tomcat, il est possible de travailler directement dans le répertoire webapps avec le contenu de l'archive web décompressée. Cette fonctionnalité est particulièrement intéressante lors de la phase de développement de l'application car il n'est alors pas obligatoire de générer l'archive web pour tester chaque modification. Attention, si l'application est redéployée sous la forme d'une archive .war, il faut obligatoirement supprimer le répertoire qui contient l'ancienne version de l'application.
Log4J est un framework dont le but est de faciliter la mise en oeuvre de fonctionnalités de logging dans une application. Il est notamment possible de l'utiliser dans une application web. Pour plus de détails sur cette API, consultez la section qui lui est consacrée dans le chapitre «Le logging» de cet ouvrage.
Pour utiliser Log4J dans une application web, il est nécessaire d'initialiser Log4J avant utilisation. Le plus simple est d'écrire une servlet qui va réaliser cette initialisation et qui sera lancée automatiquement au chargement de l'application web.
Dans la méthode init() de la servlet, deux paramètres sont récupérés et utilisés pour :
Exemple : |
package fr.jmdoudoux.dej.log4j;
import org.apache.log4j.PropertyConfigurator;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
public class InitServlet extends HttpServlet {
public void init() {
String cheminWebApp = getServletContext().getRealPath("/");
String cheminLogConfig = cheminWebApp + getInitParameter("log4j-fichier-config");
String cheminLog = cheminWebApp + getInitParameter("log4j-chemin-log");
File logPathDir = new File( cheminLog );
System.setProperty( "log.chemin", cheminLog );
if (cheminLogConfig != null) {
PropertyConfigurator.configure(cheminLogConfig);
}
}
public void doGet(HttpServletRequest req, HttpServletResponse res) {
}
} |
Dans le fichier web.xml, il faut configurer les servlets utilisées et notamment la servlet définie pour initialiser Log4J. Celle-ci attend au moins deux paramètres :
Exemple : le fichier web.xml |
<?xml version="1.0"?>
<!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>initservlet</servlet-name>
<servlet-class>fr.jmdoudoux.dej.log4j.InitServlet</servlet-class>
<init-param>
<param-name>log4j-fichier-config</param-name>
<param-value>WEB-INF/classes/log4j.properties</param-value>
</init-param>
<init-param>
<param-name>log4j-chemin-log</param-name>
<param-value>WEB-INF/log</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>TestServlet</servlet-name>
<servlet-class>fr.jmdoudoux.dej.log4j.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TestServlet</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
</web-app> |
Il est important de demander le chargement automatique de la servlet en donnant la valeur 1 à son tag <load-on-startup>.
Il faut définir le fichier de configuration nommé par exemple log4j.properties et le placer dans le répertoire WEB-INF/classes de l'application.
Exemple : le fichier log4j.properties |
# initialisation de la racine du logger avec le niveau INFO
log4j.rootLogger=INFO, A1
# utilisation d'un fichier pour stocker les informations du journal
log4j.appender.A1=org.apache.log4j.FileAppender
log4j.appender.A1.file=${log.chemin}/application.log
# utilisation du layout de base
log4j.appender.A1.layout=org.apache.log4j.SimpleLayout |
L'utilisation de Log4J dans une servlet est alors équivalente à celle d'une application standalone.
Exemple : une servlet qui utilise Log4J |
package fr.jmdoudoux.dej.log4j;
import java.io.IOException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
public class TestServlet extends HttpServlet {
private static final Logger logger = Logger.getLogger(TestServlet.class);
public void init(ServletConfig config) throws ServletException {
super.init(config);
logger.info("initialisation de la servlet TestServlet");
}
public void doGet(HttpServletRequest req, HttpServletResponse res) {
StringBuffer sb = new StringBuffer();
logger.debug("appel doGet de la servlet TestServlet");
sb.append("<HTML>\n");
sb.append("<HEAD>\n");
sb.append("<TITLE>Bonjour</TITLE>\n");
sb.append("</HEAD>\n");
sb.append("<BODY>\n");
sb.append("<H1>Bonjour</H1>\n");
sb.append("</BODY>\n");
sb.append("</HTML>");
res.setContentType("text/html");
res.setContentLength(sb.length());
try {
res.getOutputStream().print(sb.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
} |
Lors de l'exécution de l'application web, le journal est stocké dans le fichier /WEB-INF/log/application.log.
|