Programmation Réseau en Java

Support Numéro 3

Servlets

 

Présentation

Les servlets sont le pendant des applets du coté d’un serveur Web. En effet, alors que les applets permettent d’étendre les capacités d’un navigateur Web, les servlets sont des programmes Java destinés à enrichir un serveur Web compatible avec Java (c’est à dire qui possède une machine virtuelle Java).

Comme pour une applet, le bytecode d’une servlet peut être lu à partir du système de fichier local ou téléchargé depuis un autre serveur (Web ou FTP). Une servlet peut communiquer des informations à un client (navigateur Web, applet…) en générant une page HTML ou en incluant du code HTML dans une page HTML existante. Elle peut aussi traiter les informations fournies par un formulaire HTML (via une requête GET ou une requête POST). Enfin, elle peut aussi mettre en œuvre un protocole spécial connu des deux interlocuteurs.

Une servlet peut être chargée automatiquement lors du démarrage du serveur Web. Elle peut également être chargée lorsque le premier client demande les services de la servlet. Une fois chargées, les servlets restent actives dans l'attente d'autres requêtes du client.

Les servlets permettent l'extension des fonctions du serveur grâce à la création d'un environnement de prestation de services de requête/réponse via le Web. Un client envoie une requête au serveur. Ce dernier transmet au servlet les informations relatives à la requête. La servlet crée ensuite une réponse que le serveur renvoie au client.

Dans la mesure où il s'agit d'un programme Java, la servlet peut utiliser toutes les fonctions du langage Java lors de la création de la réponse. La servlet peut également communiquer avec des ressources externes telles que des fichiers ou des bases de données, ou avec d'autres applications (également écrites en langage Java ou dans d'autres langages), afin de créer la réponse et éventuellement de sauvegarder des informations relatives à l'interaction requête/réponse.

D’après leur spécification, les servlets peuvent être utilisées dans plusieurs types de serveurs néanmoins, actuellement elles ne sont utilisés vraiment que par des serveurs Webs. Les servlets permettent de remplacer les scripts CGI. L’écriture de générateurs de documents dynamiques est plus facile qu’en PERL tout en restant portable.

Ce support de cours est basé sur le tutoriel Servlets de SUN : http://java.sun.com/docs/books/tutorial/servlets/ et sur l’ouvrage : Java Servlets de Jason Hunter et William Crawford (Editions O’Reilly).

 

Architecture du paquetage Servlet

Le paquetage javax.servlet offre des interfaces et des classes permettant l’écriture de Servlets.

Voici l’architecture de ce paquetage :

 

L’interface Servlet

L’abstraction centrale du paquetage est l’interface Servlet. Toutes les servlets implémentent cette interface, soit directement soit, ce qui est plus souvent le cas, en héritant d’une classe qui l’implémente comme HttpServlet. Cette interface déclare des méthodes qui gèrent la servlet et ses communications avec les clients. Le programmeur doit fournir tout ou partie de cet ensemble de méthodes.

 

Interaction avec les clients

Quand une servlet accepte de répondre à la requête d’un client elle reçoit deux objets :

En fait, ServletRequest et ServletResponse sont des interfaces définies dans le paquetage javax.servlet.

 

L’interface ServletRequest

Cette interface offre à la servlet :

Les interfaces qui héritent de ServletRequest permettent la récupération d’informations spécifiques à un type de protocole.

 

L’interface ServletResponse

Cette interface offre à la servlet des méthodes pour répondre au client :

Les interfaces qui héritent de ServletResponse offrent des capacités spécifiques à un protocole donné.

 

Les servlets HTTP

Les servlets HTTP permettent de gérer une session Web complète. Le programmeur pourra par exemple, gérer un état décrivant la session d’un client spécifique qui persistera entre plusieurs connexions pendant une période de temps. D’autre part, les servlets HTTP peuvent gérer des " cookies " qui permettent de sauvegarder/récupérer de l’information au niveau du client.

Une servlet HTTP gère les requêtes envoyées par les clients grâce à la méthode service. Cette méthode gère les requêtes HTTP standard en distribuant chaque type de requête à une méthode ad hoc.

La méthode service délègue les requêtes HTTP aux méthodes suivantes :

Par défaut, ces méthodes renvoient une erreur " BAD_REQUEST (400) ". Une servlet doit surcharger la/les méthode(s) qu’elle doit gérer.

La méthode service appelle aussi la méthode doOptions quand la servlet reçoit une requête OPTIONS et la méthode doTrace quand elle reçoit une requête TRACE. La méthode doOptions détermine automatiquement les options HTTP gérées et renvoie cette information. La méthode doTrace renvoie une réponse contenant toutes les entêtes envoyées dans la requête TRACE. Ces méthodes ne sont généralement pas surchargées.

 

L’interface HttpServletRequest

Un objet de type HttpServletRequest, permet la récupération des entêtes spécifiques à HTTP. Il permet aussi d’accéder aux " cookies " et à la méthode utilisée par le client pour la requête (GET, POST…). Enfin, il permet l’accès aux paramètres de la requête.

La méthode getParameter renvoie la valeur d’un paramètre donné. Si le paramètre peut avoir plusieurs valeurs il faut utiliser à la place getParameterValues. Cette méthode renvoie un tableau de valeurs. Enfin, la méthode getParameterNames renvoie les noms des paramètres.

Pour traiter des requêtes POST, PUT ou DELETE,

Note : il vous faut choisir entre les méthodes getParameter/getParameterValues ou l’une des méthodes pour traiter les données brutes. Il n’est pas possible d’utiliser les unes et les autres sur la même requête.

 

L’interface HttpServletResponse

HttpServletResponse offre des méthodes qui permettent de générer des entêtes spécifiques à HTTP. Par exemple, la méthode setContentType permet de préciser le type de contenu (c’est généralement la seule entête qui est positionnée manuellement les autres sont positionnées automatiquement).

Note : il faut positionner les entêtes HTTP avant d’accéder aux objets permettant de renvoyer des données au client.

HttpServletResponse offre deux solutions pour renvoyer des données au client :

 

Problèmes liés au " multi-threading "

Les servlets HTTP sont généralement capables de gérer plusieurs clients de façon concurrente. Si les méthodes de votre servlet doivent accéder à des ressources partagées vous devez soit :

La seconde solution consiste simplement à indiquer que votre servlet implante l’interface SingleThreadModel. Il n’y a aucune méthode à écrire il suffit juste de déclarer que la servlet implante cette interface et le serveur HTTP s’assurera qu’un seul appel à une méthode service est actif à la fois.

Voici un exemple :

         public class ReceiptServlet extends HttpServlet
                                     implements SingleThreadModel {

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

 

Un exemple de servlet

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

	 public class ServletSimple extends HttpServlet
         { 
             /**
              * Gère la méthode GET d’HTTP en construisant une page HTML simple.
              */
             public void doGet (HttpServletRequest requete,
                                HttpServletResponse reponse)
             throws ServletException, IOException
             {
                 PrintWriter         fluxSortie;
                 String              titre = "Réponse d’une Servlet simple";

                 // positionne une entête de la réponse
                 reponse.setContentType("text/html");

                 // then write the data of the response
 		 fluxSortie = reponse.getWriter();

 		 fluxSortie.println("<HTML><HEAD><TITLE>");
 		 fluxSortie.println(titre);
 		 fluxSortie.println("</TITLE></HEAD><BODY>");
 		 fluxSortie.println("<H1>" + titre + "</H1>");
 		 fluxSortie.println("<P>Ceci est généré par ServletSimple.");
 		 fluxSortie.println("</BODY></HTML>");
 		 fluxSortie.close();
             }
         }

Les interfaces et classes mentionnées précédemment sont présentées en gras :

 

Le cycle de vie d’une servlet

Toutes les servlets ont le même cycle de vie :

 

Initialisation d’une servlet

Quand un serveur charge une servlet, il exécute la méthode init de la servlet. L’initialisation est faite avant le traitement de la première requête et avant que l’applet soit détruite.

Il n’y a pas de problème de " multi-threading " lors de l’appel de la méthode init.

La méthode n’est appelée qu’une seule fois.

 

Destruction d’une servlet

Les servlets fonctionnent jusqu’à ce que leur serveur les détruisent. Avant de détruire une servlet, un serveur appelle sa méthode destroy.

La méthode n’est appelée qu’une seule fois.

Quand le serveur invoque la méthode destroy un autre thread peut être en train de faire fonctionner la méthode service. Il vous faudra peut-être gérer ce problème.

 

Gestion de l’état d’une session

HTTP étant un protocole sans état, un serveur ne peut pas reconnaître qu’une séquence de requêtes proviennent toutes du même client. Pour gérer une application web avec état (par exemple le caddie d’un site commercial), il y a cinq solutions :

 

Autorisation de l’utilisateur

Presque tous les serveurs web intègrent des fonctionnalités pour restreindre l’accès de certaines pages à un ensemble d’utilisateurs enregistrés. Le détail de ce type de fonctionnalité dépends du serveur mais le fonctionnement est globalement le suivant : la première fois que le navigateur tente d’accéder à une page protégée, le serveur web répond qu’il a besoin d’une authentification de l’utilisateur. Lorsque le navigateur reçoit cette réponse, il ouvre généralement une fenêtre demandant à l’utilisateur de taper un nom et un mot de passe. Une fois cette information précisée, le navigateur tente à nouveau d’atteindre la page demandée en ajoutant à la requête le nom et le mot de passe de l’utilisateur. Si le nom et le mot de passe sont corrects l’accès est possible sinon un message d’erreur est renvoyé.

Lorsqu’un accès a été ainsi restreint par le serveur, une servlet peut obtenir le nom de l’utilisateur en utilisant la méthode getRemoteUser() de HttpServletRequest. On peut alors utiliser le nom de l’utilisateur pour suivre la session d’un client (par exemple en stockant le caddie de l’utilisateur dans une table de hachage - java.util.Hashtable - indexée par le nom de l’utilisateur).

Avantages : facilité de mise en œuvre et possibilité de suivre des sessions avec un utilisateur qui se connecte depuis plusieurs machines.

Inconvénients : impose que chaque utilisateur s’enregistre pour utiliser la servlet, un utilisateur ne peut avoir plusieurs sessions en même temps, pas supporté par tous les serveurs.

  • Champs de formulaire cachés
  • Pour gérer un suivi de session anonyme on peut utiliser les champs de formulaire cachés. Comme leur nom l’indique, ces champs ajoutés à un formulaire HTML ne sont pas affichés dans le navigateur du client mais ils sont envoyés au serveur et ensuite à la servlet (ou au script CGI) quand le formulaire qui les contient est renvoyé. On peut inclure les champs de la façon suivante :

    <FORM ACTION= "/servlet/MaServlet" METHOD="POST">
    …
    <INPUT TYPE=hidden NAME="article" VALUE="livre1">
    <INPUT TYPE=hidden NAME="article" VALUE="CD3">
    …
    </FORM>
    

    Ainsi, pour un caddie on rajoutera, à chaque fois qu’un nouvel article est choisi, un nouveau champ caché.

    Avantages : supporté par tous les navigateurs, support de l’anonymat, ne nécessitent rien coté serveur, plusieurs sessions peuvent être gérées pour un même utilisateur.

    Inconvénients : ne fonctionne que pour une séquence de formulaires générés dynamiquement, l’arrêt du navigateur entraîne la perte de toute l’information.

     

    Réécriture d’URL

    La réécriture d’URL est une autre solution pour gérer un suivi anonyme de session. Dans ce cas, l’URL de départ (tapée par l’utilisateur ou récupérée lors d’un clic sur un lien) est modifiée dynamiquement, ou réécrite, afin d’inclure des informations supplémentaires. Ces informations peuvent prendre trois formes :

    - ajout d’un chemin supplémentaire (ex. http://serveur/servlet/Reecrit devient http://serveur/servlet/Reecrit/123)

    - ajout de paramètres (l’URL précédente devient par exemple http://serveur/servlet/Reecrit?session=123)

    - changement personnalisé (l’URL devient par exemple http://serveur/servlet/Reecrit;$session$123)

    Pour utiliser ces techniques il suffit de placer dans la page renvoyée par la servlet des liens ou des actions de formulaires avec la/les nouvelles URL.

    Chaque technique à ces avantages et ces inconvénients :

    - l’ajout de chemin fonctionne sur tous les serveurs et comme une cible de formulaires qui utilisent GET ou POST. Cependant, elle ne fonctionne pas bien si une servlet doit utiliser l’information de chemin supplémentaire comme un chemin réel.

    - l’ajout de paramètres fonctionne aussi sur tous les serveurs mais uniquement avec des formulaires utilisant POST.

    - le changement personnalisé fonctionne dans toutes les conditions mais il n’est pas disponible sur tous les serveurs.

    Les avantages et inconvénients de toutes ces techniques sont identiques à ceux des champs de formulaires cachés. La principale différence est que ces techniques fonctionnent pour tous les documents dynamiques et pas seulement pour ceux comportant des formulaires.

  • Cookies persistants
  • Une quatrième technique pour effectuer le suivi d’une session implique l’utilisation des cookies persistants. Un " cookie " est une information envoyée à un navigateur par un serveur web qui est sauvée sur le disque par le navigateur. Ensuite, le cookie sera relu est renvoyé au serveur chaque fois qu’une de ses pages sera demandée, conformément à certaines règles. La valeur d’un cookie pouvant identifier de façon unique un client, ils sont souvent utilisés pour le suivi de session.

    Les cookies ont été créés par Netscape et sont devenus un standard de facto. Ils sont gérés aujourd’hui par Netscape Communicator et Microsoft Internet Explorer et sont en cours de standardisation (cf. la RFC 2109 http://www.pasteur.fr/cgi-bin/mfs/01/21xx/2109).

    La classe javax.servlet.http.Cookie permet de créer et de gérer des cookies. Pour créer un cookie on utilise le constructeur suivant qui crée un cookie avec un nom initial et une valeur (les règles de validité pour les noms et les valeurs sont dans la RFC 2109) :

    	public Cookie(String nom, String valeur);

    Une servlet peut envoyer un cookie à un client en passant un objet Cookie à la méthode addCookie() de HttpServletResponse :

    	public void addCookie(Cookie cookie);

    Les cookies sont envoyés dans les entêtes HTTP de la réponse. La seule contrainte des navigateurs et qu’ils acceptent au maximum 20 cookies par site, 300 par utilisateur et ils peuvent limiter la taille de chaque cookie à 4 096 octets.

    Pour retrouver les cookies une servlet doit utiliser la méthode getCookies() de HttpServletRequest :

    	public Cookie[] getCookies();

    Pour accéder au nom et à la valeur des cookies récupérés on utilise les méthodes getName() et getValue().

    Exemple :

    	Cookie[] cookies = req.getCookies();
    	if (cookies != null) {
    		for (int i=0; i<cookies.length; i++) {
    			String nom = cookies[i].getName();
    			String val = cookies[i].getValue();
    		}
    	}
    

    Pour modifier la valeur du cookie on utilisera la méthode setValue(String val). On peut enfin utiliser la méthode setMaxAge(int age) qui permet de spécifier l’âge maximum du cookie en secondes. Une valeur de -1 – la valeur par défaut – indique que le cookie doit être détruit lorsque le navigateur se termine, 0 indique que le navigateur doit détruire le cookie immédiatement.

    Avantages des cookies : facilité de mise en œuvre. Les cookies sont partagés par toutes les servlets d’un même serveur.

    Inconvénients des cookies : ils ne sont pas supportés par tous les navigateurs et s’il le sont, l’utilisateur peut les avoir désactivés. Dans ce cas, il, faut se rabattre sur les solutions précédentes ou afficher un message d’erreur.

  • Suivi de session
  • Le suivi de session est un mécanisme que les servlets peuvent utiliser pour maintenir un état commun à une série de requêtes provenant d’un même utilisateur (c’est à dire des requêtes provenant d’un même navigateur web) pendant une certaine période de temps.

    Ce mécanisme est en fait le plus souvent réalisé avec des cookies persistants mais certains serveurs, comme Java Web Servers, peuvent utiliser la réécriture d’URL si les cookies sont refusés par le navigateur.

    Le mécanisme est plus puissant puisqu’on peut sauver dans l’objet représentant la session n’importe quel objet Java.

    Pour utiliser ce mécanisme il convient tout d’abord de récupérer l’objet HttpSession courant (associé à l’utilisateur effectuant la requête) en utilisant la méthode getSession de HttpServletRequest (si la session n’existe pas et que le paramètre creer est à vrai une nouvelle session sera créée sinon si creer est à faux null sera retourné) :

    	public HttpSession getSession(boolean creer);

    Note : pour garantir que la session est correctement maintenue, cette méthode doit être appelée au moins une fois avant de commencer à écrire la réponse (et ce dans toutes les servlets qui peuvent composer votre application).

    Pour ajouter des données à un objet HttpSession on utilise la méthode putValue qui ajoute l’objet spécifié avec le nom spécifié (tout objet lié à un nom identique est remplacé) :

    	public void putValue(String nom, Object val);

    Pour retrouver un objet nommé dans une session on utilise la méthode getValue (qui renvoie null si l’objet n’existe pas) :

    	public Object getValue(String nom);

    On peut obtenir le nom de tous les objets liés à la session avec la méthode getValueNames() :

    	public String[] getValueNames();

    Pour supprimer un objet d’une session on utilise removeValue :

    	public void removeValue(String nom);

    Chacune de ces méthodes peut générer une java.lang.IllegalStateException si la session est invalide

    Une session expire soit automatiquement, après un certain temps d’inactivité (30 minutes par défaut pour Java Web Server), soit manuellement, lorsqu’elle est explicitement invalidée par une servlet. Lorsque la session expire (ou est invalidée), l’objet HttpSession et les données qu’il contient sont supprimés du système.

    On utilise la méthode invalidate() pour invalider manuellement une session.