IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Tutoriel sur les Servlets Java


précédentsommairesuivant

V. Les servlets HTTP

Vous devriez maintenant avoir acquis assez de connaissance en ce qui concerne le fonctionnement des servlets et de la couche logicielle en charge de leur fonctionnement : le moteur de servlets. Ces connaissances vont vous permettre de comprendre plus facilement certaines caractéristiques de ces dernières lorsque nous aborderons la programmation plus en détail.

Ce chapitre est le premier de ceux qui se consacrent à l'apprentissage de la programmation exclusivement. Les mécanismes sous-jacents seront abordés lorsque cela sera nécessaire, mais la priorité est donnée à l'apprentissage de l'utilisation de la bibliothèque de classes mise à votre disposition pour créer vos applications Web.

Comme nous l'avons vu précédemment, la majorité des servlets sont destinées à étendre un serveur Web. Vous savez également que les applications Web utilisent, pour communiquer, un protocole nommé HTTP dont le fonctionnement se décompose en deux grandes phases : la requête et la réponse à cette requête. Nous allons donc étudier les moyens mis à notre disposition pour obtenir le plus d'informations utiles à propos d'une requête effectuée par un client (traditionnellement un navigateur Web) et ceux mis à notre disposition pour organiser notre réponse.

V-A. La gestion des requêtes

Lorsqu'un client envoie une requête à une des servlets que vous venez de développer, la servlet peut avoir besoin de connaître trois types d'informations :

  1. Les informations sur le serveur ;
  2. Les informations sur le client qui a servi à effectuer la requête ;
  3. Les informations sur la requête elle-même.

V-A-1. Les informations à propos du serveur

Les informations sur le serveur peuvent servir à connaître les bons chemins vers les ressources disponibles sur celui-ci, à connaître la version du serveur utilisée ou encore son adresse IP. Cela peut permettre à vos servlets de s'adapter facilement dans le cas d'un déploiement sur des serveurs différents par exemple. Contrairement à des méthodes de développement comme CGI, ces informations sont accessibles via des méthodes appartenant à l'objet qui représente la requête (de type HttpServletRequest pour une servlet HTTP); Cela permet d'éviter les problèmes de typographie lors de l'obtention de ces informations, car le nom des méthodes est vérifié à la compilation et donnera une erreur si une des méthodes est mal orthographiée. Par exemple :

 
Sélectionnez
$port_serveur = $ENV{'SERVER_PRT'};
print "Le serveur écoute sur le port $port_serveur\n";

ne donnera pas la bonne information alors qu'apparemment tout est correct (même si vous avez remarqué l'erreur, ce dont je ne doute pas, faites semblant ou imaginez un code source de quelques milliers de lignes), et ceci sans aucun message d'erreur (à moins que l'on utilise des options comme le module strict, mais ce n'est pas le comportement par défaut). Par contre, dans une servlet :

 
Sélectionnez
int port_serveur = requete.getServerPrt();
out.println("Le serveur écoute sur le port " + port_serveur + "\n");

ce code générera une erreur de compilation, et vous corrigerez alors l'erreur sans aucun délai. Voici juste une illustration de ce que peut apporter le typage fort du langage Java par rapport à d'autres. À chaque variable d'environnement disponible avec CGI correspond une méthode associée à la requête pour obtenir des informations. Celles qui concernent le serveur sont :

  1. getServerName() : donne le nom du serveur, tel qu'il est donné dans la directive ServerName du fichier de configuration d'Apache (httpd.conf) ;
  2. getServletContext().getServerInfo() : retourne le nom du logiciel utilisé pour prendre en charge la requête , par exemple Tomcat/3.2.1 ;
  3. getServletContext().getAttribute(String nom_attribut) : permet de récupérer les différentes parties de l'information sur le logiciel serveur. Chaque moteur de servlets ou serveur Web possède ses propres noms d'attribut, les énumérer tous ici serait inutile. Je vous conseille donc de vous reporter à la documentation spécifique à votre moteur de servlets pour connaître les noms de ces attributs et leur signification ;
  4. getServerPort() : retourne le port sur lequel écoute le serveur Web qui a pris en charge la requête ;
  5. getRealPath(String nom_fichier) : retourne le chemin réel vers le fichier nom_fichier. En principe, ce chemin doit correspondre à getRealPath(« / ») + nom_fichier. C'est assez utile lorsque vous désirez effectuer des entrées/sorties vers des fichiers présents sur votre serveur ;
  6. getPathTranslated() : cette méthode permet de connaître le chemin réel vers le fichier sur le système de fichier du serveur lorsqu'un fichier est passé à la servlet. Par exemple, pour une URL comme celle-ci : http://www.monserveur.com/mon_application/servlet/MaServlet/index.html, getPathTranslated() renverra la même chose que getRealPath(« /index.html »).

Voici ci-dessous un programme classique affichant toutes les informations intéressantes d'un serveur et quelques autres informations utilisant les méthodes que nous venons de voir :

 
Sélectionnez
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class ServeurInfo extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse res) throws 
ServletException, IOExcepption {
    res.setContentType("text/plain"); // on produit du texte ASCII
    PrintWriter out = res.getWriter();
    out.println("Nom du serveur : " + req.getServerName() + " .");
    out.println("Logiciel utilisé : " + req.getServletContext().getServerInfo() + 
                " ."); 
    out.println("Port du serveur : " + req.getServerPort() + " .");
    out.println("Port du serveur : " + req.getServerPort() + " .");
    out.println("Chemin vers le fichier " + req.getPathInfo() + " : " + 
                req.getPathTranslated(req.getPathInfo()) + " .");
  }
}

Une autre servlet se servant de ces méthodes pourrait servir à retourner n'importe quel fichier passé en paramètre de la requête :

 
Sélectionnez
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
public class FileServer extends HttpServlet {
 // on peut être amené à envoyer des fichiers binaires, donc on utilise
 // un ServletOutputStream au lieu d'un PrintWriter
 ServletOutputStream out = res.getOutputStream();
 // récupération d'une référence sur le fichier demandé
 File fichier = new File(req.getPathTranslated();
 if (fichier == null) 
  // si le fichier est introuvable on envoie un code d'erreur 404
  res.sendError(HttpServletResponse.SC_NOT_FOUND);
 // sinon le type de contenu de la réponse correspond au type MIME
 // du fichier
 res.setContentType(getServletContext().getMimeType(fichier));
   try {
     // on utilise un tampon de 4 ko pour lire le fichier 
     // ce qui est plus rapide qu'une lecture ligne par ligne
     char[] tampon = new char[4 * 1024];
     FileInputStream in = new FileInputStream(fichier);
     while (in.read(tampon) >= 0) {
       out.write(tampon);
     }
   } catch (IOEXception e) {
     // si une erreur se produit au milieu de la réponse
     // on envoie le code d'erreur HTTP adéquat
     res.sendError(HttpServletResponse.SC_PARTIAL_CONTENT);
   } finally {
     if (fichier != null) 
       fichier.close();
   }
  }
}

Vous avez pris connaissance avec les principales méthodes permettant de connaître des informations relatives au serveur sur lequel tourne une servlet. Pour en savoir plus, je vous invite à consulter les méthodes de l'API de J2EE contenues dans l'interface ServletResponse pour en connaître d'autres.

V-A-2. Les informations à propos du client

Nous avons maintenant besoin d'informations sur le client qui a effectué la requête. En effet, vous pouvez penser qu'il est utile de connaître l'adresse IP de la machine qui a effectué la requête pour des besoins d'authentification ou bien que la version du navigateur utilisée influe sur les données à transmettre. Ces informations sont accessibles via l'interface ServletRequest comme pour les informations du serveur. Voici une liste des principales méthodes concernant la requête de cette interface :

  1. getRemoteAddr() : récupère l'adresse IP de l'hôte qui a initié la requête ;
  2. getRemoteHost() : renvoie le nom d'hôte de la machine qui a initié la requête. Si le nom d'hôte ne peut pas être récupéré, la représentation de l'adresse IP du client est renvoyé sous forme de chaîne de caractères.

Ces requêtes permettent d'identifier une machine sur le réseau Internet et de disposer ainsi d'une méthode d'authentification au niveau de la machine. Cependant, ces méthodes restent basiques et ne permettent pas de disposer d'un système d'authentification robuste. Pour cela, il est nécessaire de mettre en place des systèmes plus sûrs et fiables basés par exemple sur le protocole HTTPS par exemple, et qui peuvent se servir des méthodes sus-citées.

Voici une servlet basique qui, en s'inspirant de la servlet qui sert les fichiers, met en place un mécanisme basique de contrôle des accès :

 
Sélectionnez
import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    public class FileServer extends HttpServlet {
      private final static String fichierIP = "ip.txt";
      private final static String fichierhotes = "hotes.txt";
     
      public void doGet(Httpservletrequest req, Httpservletresponse res) 
          throws ServletException, IOException {
     // on peut être amené à envoyer des fichiers binaires, donc on utilise
     // un ServletOutputStream au lieu d'un PrintWriter
     ServletOutputStream out = res.getOutputStream();
     if (verifieIp(req.getRemoteAddr()) || verifieHote(req.getRemoteHost()) {
       // la machine cliente est autorisée à accéder à la servlet
       // récupération d'une référence sur le fichier demandé
       File fichier = new File(req.getPathTranslated();
       if (file == null) 
       // si le fichier est introuvable on envoie un code d'erreur 404
       res.sendError(HttpServletResponse.SC_NOT_FOUND);
       // sinon le type de contenu de la réponse correspond au type MIME
       // du fichier
       res.setContentType(getServletContext().getMimeType(file));
         try {
           // on utilise un tampon de 4 ko pour lire le fichier 
           // ce qui est plus rapide qu'une lecture ligne par ligne
           char[] tampon = new char[4 * 1024];
           FileInputStream in = new FileInputStream(file);
           while (in.read(tampon) >= 0) {
             out.write(tampon);
           }
         } catch (IOEXception e) {
           // si une erreur se produit au milieu de la réponse
           // on envoie le code d'erreur HTTP adéquat
           res.sendError(HttpServletResponse.SC_PARTIAL_CONTENT);
         } finally {
           if (file != null) 
             file.close();
         }
     
        } else {
        out.println("Vous n'êtes pas autorisé à accéder à cette servlet.");
      }
      
    }
      
    public int verifieIP(String ipRequete) {
      File f = new File(fichierIP);
      FileInputStream in = new FileInputStream(f); 
      String s;
      while ((s = in.readLine())  != null) {
        // l'ip du client est-elle autorisée ?
        if (s.equals(ipRequete))
          return 1;
        return 0;
      }
    }
      
    public int verifieHote(String hoteRequete) {
      File f = new File(fichierHotes);
      FileInputStream in = new fileInputStream(f); 
      String s;
      while ((s = in.readLine())  != null) {
        // l'hote est-il autorisé ?
        if (s.equals(hoteRequete))
           return 1; 
        return 0;
       } 
    }

Gardez bien en tête que cette Servlet ne constitue en aucun cas un mécanisme de sécurité valable. Elle illustre modestement l'emploi des méthodes relatives aux informations sur la machine cliente. En effet, toute vulnérabilité applicable au protocole IP est exploitable ici, comme le « spoofing ».

V-A-3. Les informations sur la requête

Nous allons maintenant aborder l'analyse de la principale source d'information : la requête elle-même.

Plusieurs catégories d'informations sont importantes au niveau de la requête. Ces catégories correspondent aux différentes caractéristiques d'une requête HTTP. Nous pouvons avoir besoin d'informations sur :

  1. L'URL sur lequel porte la requête. Par extension on peut obtenir l'URI, qui n'est qu'un concept plus abstrait que celui d'URL4.1 ;
  2. Le schéma utilisé par la requête. Je considère que la méthode HTTP (GET, POST, etc.), le protocole utilisé et sa version (HTTP/1.0, HTTP/1.1) et enfin l'entête (http://, https://, etc.) font partie de ce que j'appellerai le schéma ;
  3. Les paramètres passés à la requête. Ce sont les données envoyées par le client avec la requête qui peuvent servir à préciser l'objet de la requête ou à envoyer des données au serveur (dans le cas de l'envoi de fichier par exemple) ;
  4. L'entête de la requête : le client peut apporter diverses précisions avec la requête, comme le format de données qu'il accepte, le logiciel du client (le nom du navigateur web par exemple) ou encore l'URL d'où provient la requête (si l'utilisateur a cliqué sur un lien pour effectuer la requête).

Pour obtenir l'URL de la requête, il est nécessaire d'utiliser la méthode getRequestURL() de la classe HttpUtils du package javax.servlet.http. Cette méthode reconstruit l'URL complète de la requête, sous une forme telle que http://www.monserveur.com/MaServlet par exemple. Cette URL est renvoyée sous forme d'objet de type StringBuffer et est donc facilement modifiable. L'URL qui est retournée peut être différente de celle entrée dans le navigateur web du client, car tout y est :le nom du serveur, le port, l'entête (http://), alors qu'un utilisateur ne précise pas le port sur lequel tourne le serveur si celui-ci écoute sur le port 80 (ou sur d'autres ports selon la configuration des navigateurs). Les caractères spéciaux présents dans les paramètres de la requête (après le « ? » par exemple pour une requête utilisant la méthode GET) peuvent aussi être différents par rapport à ceux présents dans le champ de saisie de l'URL du navigateur : les « %20 » seront par exemple remplacés par des espacements. Parfois cependant, il n'est pas nécessaire d'avoir des informations sur l'URL entière, mais seulement sur la partie concernant la ressource demandée. Pour cela vous pouvez utiliser la méthode getRequestURI() de l'interface HttpServletRequest. Cette méthode retourne donc l'Uniform Ressource Identifier de la requête. Pour des requêtes portant sur des ressources statiques (pages HTML par exemple), l'URI correspond exactement à l'URL sans l'entête (http:// par exemple), le nom du serveur et le numéro de port. Par exemple, pour l'URL suivant : http://www.monserveur.com/index.html, l'URI correspondant est /index.html. Pour des URL référençant un contenu dynamique (des servlets par exemple), le procédé de traduction entre URL et URI est parfois plus subtil. Voici une liste d'exemples qui vous permettra de mieux cerner ces subtilités :

  1. L'URL http://www.monserveur.com/MaServlet correspond à l'URI /MaServlet ;
  2. L'URL http://www.monserveur.com/MaServlet?param1=valeur1 correspond à l'URI /MaServlet ;
  3. L'URL http://www.monserveur.com/MaServlet/info_chemin?param1=valeur1 correspond à l'URI /MaServlet/info_chemin ;
  4. L'URL http://www.monserveur.com/index.html correspond à l'URI /index.html.

Pour être encore plus précis, il est possible de connaître le chemin de la servlet correspondant à une URL donnée. Il suffit d'utiliser la méthode getServletPath() de l'interface HttpServletRequest. Cette méthode retourne sous la forme d'un objet de type String la partie de l'URL qui appelle la Servlet, ou la valeur null si l'URL ne fait pas référence à une servlet. Lorsqu'une servlet fait partie d'une chaîne de servlets, la valeur renvoyée par getServletPath() correspond à celui qui aurait été renvoyé par la première servlet de la chaîne (étant donné que ce n'est pas l'URL qui décide quelles servlets doivent faire partie de la chaîne, et que cette URL ne connaît donc que le premier maillon de cette chaîne). Voici une liste d'exemples qui illustre ce que je viens de dire :

  1. Si l'URL est http://www.monserveur.com/MaServlet, la méthode getServletPath() renvoie « /servlet/MaServlet » ;
  2. Si l'URL est http://www.monserveur.com/MaServlet?param=valeur, la méthode getServletPath() renvoie « /servlet/MaServlet » ;
  3. Si l'URL est http://www.monserveur.com/MaServlet/info_chemin?param=valeur, la méthode getServletPath() renvoie « /servlet/MaServlet » ;
  4. Si l'URL est http://www.monserveur.com/index.html, la méthode getServletPath() renvoie null ;
  5. Si l'URL est http://www.monserveur.com/alias_servlet.html, la méthode getServletPath() /alias_servlet.html. Ici alias_servlet.html est un alias sur une servlet. C'est-à-dire que l'on précise au serveur web que toute requête demandant le document alias_servlet.html de la racine de son arborescence doit être traduite en une requête vers la servlet associée.

Pour une Servlet invoquée via SSI (voir section ssi), getServletPath() renvoie null si la servlet a été incluse dans un document statique, ou le chemin vers la première servlet d'une chaîne si elle fait partie de cette chaîne de servlets.

En ce qui concerne le schéma utilisé par la requête, il existe de nombreuses méthodes permettant d'obtenir des informations. La méthode getSheme() permet de récupérer l'entête de la requête : par exemple « https » si le client a demandé une connexion sécurisée. La méthode getProtocol() permet de récupérer le protocole utilisé par la requête (par exemple « HTTP/1.0 »). La méthode getMethod() permet de connaître la méthode HTTP utilisée. La valeur renvoyée peut être soit « GET », « POST » ou toute autre méthode HTTP (voir protocole_http).

Les paramètres d'une requête sont compris dans celle-ci. Chaque paramètre possède un nom et une valeur. Les paramètres sont passés différemment au serveur selon que la requête utilise la méthode HTTP GET ou POST. Dans le cas de l'utilisation de la méthode POST, les paramètres sont directement envoyés au serveur comme des données classiques, et le serveur les récupère sur son entrée standard. Dans le cas où la requête utilise la méthode HTTP GET, les paramètres ainsi que leur valeur sont passés au serveur en ajoutant ces informations à l'URL. Par exemple, pour passer un paramètre « nom » qui aura pour valeur « gilli » à une servlet nommée MaServlet, l'URL correspondante sera : http://www.monserveur.com/servlet/MaServlet?nom=gilli . Chaque paire nom=valeur est séparée par un « & » de la paire suivante. Pour passer deux paramètres nom et prénom, l'URL prendra la forme suivante (toujours si la requête utilise la méthode HTTP GET) :

 
Sélectionnez
http://www.monserveur.com/servlet/MaServlet?nom=gilli&prenom=julien

Afin de pouvoir coder les caractères comme l'espace ou le signe « % », ils sont remplacés par leur code hexadécimal correspondant, ou « + » pour l'espace. Il faut alors bénéficier d'une méthode permettant d'assurer le décodage de ces caractères spéciaux pour recueillir des informations correctes.

Ces paramètres peuvent être récupérés de plusieurs façons. Tout dépend de la connaissance que l'on peut avoir des paramètres passés. Dans une requête utilisant la méthode HTTP POST, l'utilisateur ne peut entrer « à la main » des paramètres dont on ne pourrait connaître à l'avance le nom et la valeur. Dans ce cas il n'est pas utile de pouvoir récupérer le nom des paramètres passés à la requête. Au contraire, pour une requête utilisant la méthode HTTP GET, les paramètres sont modifiables par le client puisqu’ajoutés à l'URL qui est visible dans le champ de saisie de l'adresse au niveau du navigateur web. Il peut être alors utile de récupérer le nom de ces paramètres. Ceci est effectué par la méthode getParameterNames() de l'interface ServletRequest. Cette méthode renvoie un objet de type Enumeration que vous devriez savoir manipuler si vous satisfaites les connaissances requises (voir connaissances). Pour obtenir les valeurs de tous les paramètres, vous devez utiliser la méthode getParameterValues() qui renvoie un objet de type String[] contenant les valeurs de chaque paramètre. Enfin, la méthode getParameter(String nomParametre) permet de connaître la valeur du paramètre nomParametre représentée par un objet de type String. Ci-dessous se trouve une servlet permettant d'afficher toutes les informations sur tous les paramètres de la requête :

 
Sélectionnez
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class ServeurInfo extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse res) 
  throws ServletException, IOExcepption {
    res.setContentType("text/plain"); // on produit du texte ASCII
    PrintWriter out = res.getWriter();
    Enumeration parametres = req.getParameterNames();
    out.println("Affichage des informations sur les paramètres de la 
                requête");
    while (parametres.hasMoreElements()) {
    String nomParametre = (String) parametres.nextElement(); 
    out.println("Le paramètre " + nomParametre + " a la valeur : " + 
                getParameter(nomParametre) + " .");
   }
  }
}

Il faut noter qu'il existe une méthode permettant de récupérer tous les paramètres passés à la requête à la fois, c'est-à-dire toutes les paires paramètre=valeur sans décodage des caractères spéciaux préalable. Ceci n'est pas d'une utilité renversante à mon goût, et je ne pense pas que vous l'utiliserez beaucoup. Sachez cependant que c'est la méthode getQueryString() de l'interface HttpServletRequest qui peut vous rendre ce service. Toutes les autres méthodes vues au-dessus décode les paramètres pour vous avant de les renvoyer.

Les entêtes de la requête précisant diverses choses comme :

  1. Le logiciel utilisé par le client ;
  2. Les types MIME des formats de données acceptés ;
  3. Le type d'authentification utilisé dans le cas d'une connexion sécurisée ;
  4. La date de dernière requête du client pour la ressource demandée ;
  5. Le jeu de caractères utilisé par le navigateur ;
  6. La valeur du cookie associé au domaine auquel appartient la servlet demandée ;
  7. Le langage utilisé par le client.

Comme pour la récupération des paramètres associés à la requête, il existe plusieurs façons de récupérer la valeur et le nom des entêtes de requête. À chaque entête correspond une méthode distincte qui permet de récupérer la valeur de cet entête en particulier. Ces méthodes sont mentionnées dans la liste ci-dessus. Ensuite il est possible de récupérer une liste des noms des entêtes passés par le client lors de la requête avec la méthode getHeaderNames() de l'interface HttpServletRequest. Cette méthode renvoie un objet de type Enumeration. Une fois le nom de tous les entêtes récupérés, il suffit de récupérer la valeur de chaque entête avec la méthode getHeader(String nomEntete) de la même interface pour obtenir tous les entêtes correspondant à une requête. La servlet suivante affiche tous les entêtes passés au serveur avec la requête :

 
Sélectionnez
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class ServeurInfo extends HttpServlet {
  public void doGet(HttpServletRequest req, HttpServletResponse res) throws 
ServletException, IOExcepption {
    res.setContentType("text/plain"); // on produit du texte ASCII
    PrintWriter out = res.getWriter();
    out.println("Affichage des informations sur les entêtes de la requête");
    Enumeration entetes = req.getHeaderNames();
    while (entetes.hasMoreElements()) {
      String nomEntete = (String) entetes.nextElement();
      out.println("a l'entête " + nomEntete + " correspond la valeur " + 
                  getHeader(nomEntete) + ".");
    }
  }
}

Le résultat est différent pour la même requête avec des navigateurs différents, car de nombreux entêtes dépendent du navigateur, comme « User-Agent » ou « Accept ». Cette dernière définit le type de fichiers acceptés par un navigateur.

Certaines des valeurs d'entêtes peuvent être mieux traitées en étant récupérées comme un entier ou un objet de type Date. Les méthodes getIntHeader() et getDateHeader() servent respectivement à cela. Si le format de l'entête ne correspond pas au type retourné par l'une de ces méthodes, une IllegalNumberException sera générée pour getIntHeader et une IllegalArgumentException sera générée pour la méthode getDateHeader()

Le langage utilisé par le client (correspondant à l'entête « Accept-Language ») est disponible via la méthode getLocale() de l'interface ServletRequest. La méthode getLocales() de la même interface permet de récupérer dans l'ordre décroissant de préférence les langages acceptés. Ces informations peuvent être utiles si vous gérez des servlets internationalisées. Pour plus d'informations à ce sujet, consultez la section « internationalisation » du tutoriel Java à l'URL suivante : http://java.sun.com/docs/books/tutorial/i18n/index.html.

En ce qui concerne les connexions dites « sécurisées », il est possible d'en connaître plus grâce à la méthode isSecure() de l'interface ServletRequest qui renvoie un booléen précisant si la requête a été faîte en utilisant un canal sécurisé ou non (comme le protocole HTTPS qui utilise la méthode de cryptage SSL, qui signifie Secure Socket Layer). La méthode utilisée pour établir une connexion sécurisée est obtenue par la méthode getAuthType() de la même interface. Cette méthode retourne un objet de type String qui peut prendre des valeurs comme « BASIC » lorsque les fonctionnalités d'identification du serveur web sont utilisées ou « SSL » par exemple. Pour plus de renseignements à propos de la sécurisation des accès HTTP au niveau utilisateur, vous pouvez consulter le document disponible à l'URL http://www.apacheweek.com/features/userauth. En ce qui concerne la mise en place du protocole SSL, je vous conseille de consulter le document accessible à l'URL http://www.apacheweek.com/features/ssl.

Il existe également des attributs spéciaux qui peuvent être mis en place par le moteur de servlets lui même en plus des attributs dont vous pouvez récupérer le nom avec la méthode getHeaderNames() vue juste au-dessus. Chaque moteur de servlets peut décider de mettre en place des attributs spéciaux, mais ce n'est pas obligatoire. Ils sont accessibles via la méthode getAttributeName(String nomParametre) de l'interface ServletRequest, qui ressemble à la méthode du même nom de l'interface ServletContext. Le serveur Java Web Server définit par exemple trois attributs supplémentaires qui ont pour nom javax.net.ssl.cipher_suite, javax.net.ssl.peer-certificates et javax.net.ssl.session. Ils permettent de récupérer des informations sur une requête sécurisée utilisant le protocole HTTPS (et donc SSL). Cette méthode permet également de partager des données au sein du même contexte (voir contextes). Une seule valeur peut être associée à un seul nom d'attribut. Il est recommandé que les noms d'attributs suivent une convention similaire aux packages du JDK (voir la spécification du langage Java à l'URL http://java.sun.com/docs/books/jls pour plus de renseignements).

Enfin, nous terminerons cette section concernant la requête en décrivant comment traiter celles qui permettent d'envoyer des données brutes au serveur. Je vous ai dit précédemment que les requêtes de type HTTP POST étaient passées au serveur web par son flux d'entrée standard (plus communément appelé STDIN, pour STanDard INput). Ce flux d'entrée peut être utile pour d'autres choses : chaîner plusieurs servlets en associant le flux de sortie standard d'une servlet au flux d'entrée standard d'une autre, passer à une servlet non HTTP des données brutes. Par « données brutes », il faut comprendre données binaires comme des fichiers graphiques au format jpeg par exemple. Pour traiter ce flux il est nécessaire de connaître le type de contenu transmis et la longueur du flux. pour connaître le type MIME du flux disponible sur l'entrée standard, il faut utiliser la méthode getContentType() de l'interface ServletRequest. Cette méthode renvoie un objet de type String qui correspond à un type MIME, par exemple ``« t ext/html » ou « image/png ». Pour connaître la longueur des données passées à l'entrée standard, il suffit d'employer la méthode getContentLength() de la même interface, qui retourne un entier correspondant à la longueur des données en entrée.

Vous devriez maintenant être en mesure de traiter n'importe quel type de requête. Il faut maintenant réagir à cette requête en envoyant une réponse. Nous étudierons tout d'abord les moyens mis à notre disposition pour émettre une réponse conforme aux caractéristiques d'une réponse HTTP, et nous verrons ensuite les différentes façons de produire du contenu HTML et multimédia (images, animations).

V-B. La réponse

Comme nous l'avons vu en protocole_http, la réponse est organisée en plusieurs sections. Les méthodes de l'interface ServletResponse (et donc de HttpServletResponse puisqu'elle dérive de la précédente) permettent de contrôler chacune de ces sections.

En ce qui concerne le code d'état, l'interface HttpservleResponse possède un nombre assez impressionnant d'attributs de classes (statiques) de type entier représentant chacun un code d'état. Afin de transmettre le bon entête, il suffit donc d'utiliser la méthode setStatus(int etat) en passant comme paramètre un des attributs statiques de l'interface. Par exemple, pour notre servlet qui permet de servir des fichiers, nous aurions pu utiliser l'instruction setStatus(HttpServletRequest.SC_NOT_FOUND) dans le cas d'un fichier introuvable. Le code d'état HttpServletRequest.SC_NOT_FOUND correspond au code d'état HTTP 404. Vous pourrez trouver tout l'inventaire des attributs de l'interface HttpServletRequest avec leur code d'état HTTP correspondant dans la documentation de l'API des servlets disponible à http://java.sun.com/j2ee/j2sdkee/techdocs/api/index.html.

V-B-1. Les entêtes HTTP

Une fois le code d'état envoyé par la réponse, un ensemble de paramètres d'entête doivent être donnés. Ces entêtes permettent au client d'adapter son comportement afin de recevoir correctement les données. Ces entêtes ne sont pas classables en diverses catégories, et remplissent chacune une fonction bien particulière. Je préfère donc vous donner une énumération de celles-ci en vous signalant leur utilité et comment les manipuler avec les servlets. Notez également qu'il n'est pas nécessaire de préciser la valeur de tous ces entêtes, mais seulement de celles qui possèdent une valeur particulière pour votre servlet.

Tout d'abord, l'entête « Content-Type » définit le type MIME de contenu que la servlet s'apprête à fournir. La méthode setContentType(String typeMime) de l'interface ServletResponse permet de définir ce type. Par exemple, l'instruction

 
Sélectionnez
res.setContentType("text/html");

permet de signaler au client web que le contenu produit est du texte au format HTML.

Un autre exemple d'entête est « Location », qui permet de rediriger l'utilisateur. Il est possible de préciser cet entête grâce à la méthode setHeader(String name, String valeurEntete) de l'interface HttpServletResponse. Cette méthode correspond à l'accesseur en lecture des entêtes de requête venant du client : la méthode getHeader(String name). Il est possible de spécifier chaque entête HTTP via cette méthode, il suffit de connaître le nom de l'entête. Pour cela, je vous invite à consulter DOCUMENT REFERENCE HTTP. Comme pour la méthode getHeader(String name), il est possible de spécifier la valeur d'un entête avec un autre type de données que la classe String. Pour cela il suffit d'utiliser les paramètres de type long ou entier des autres méthodes setHeader(). Pour connaître toutes les versions de la méthode setHeader possibles, reportez-vous à la documentation de référence de l'API des servlets, disponible à http://java.sun.com/j2ee/j2sdkee/techdocs/api/index.html.

Afin de ne pas à devoir se rappeler de la signification des codes d'états et de leurs entêtes correspondants, quelques méthodes sont disponibles et permettent l'envoi de ces deux informations à la fois. Par exemple la méthode sendRedirect(String nouvelleUrl) permet d'effectuer le même traitement que les deux instructions :

 
Sélectionnez
res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
res.setHeader("Location", "http://www.monserveur.com/autreUrl.html");

Je vous invite à consulter la documentation de référence sur l'API des servlets disponible ici http://java.sun.com/j2ee/j2sdkee/techdocs/api/index.html pour connaître les autres méthodes de ce type (comme sendError).

Quelques conventions sont à respecter pour utiliser les entêtes correctement. Tout d'abord, vous ne pouvez pas avoir deux valeurs différentes pour le même entête. C'est pour cela qu'un autre appel à la méthode setHeader concernant le même nom d'entête écrasera l'ancienne valeur. Ensuite vous devez absolument envoyer les entêtes avant le corps de la réponse, c'est-à-dire que l'instruction

 
Sélectionnez
out.println("envoi de données");

suivie de l'instruction

 
Sélectionnez
res.setHeader("Location", "http://www.monserveur.com/bidule.html");

n'est pas correct. Il n'est pas certain que cette incorrection génère une erreur. Cela dépend du moteur de servlets qui fait tourner votre servlet. Par exemple le Java Web Server possède un tampon qui stocke une petite quantité de données (quatre kilo-octets) avant de les envoyer. Cela vous laisse un peu de temps pour envoyer les entêtes. Enfin, il vaut mieux définir le type de contenu à envoyer avent d'obtenir le flux de sortie (dont je parle un peu plus tard), ceci afin d'obtenir un flux de sortie correctement configuré en fonction du type de contenu à envoyer, sans avoir à effectuer aucune manipulation.

Vous pouvez maintenant produire un entête de réponse conforme au protocole HTTP de façon à ce que tous les navigateurs comprennent les données que vous fournissez. Voyons maintenant le corps de la réponse.

V-B-2. Envoyer les données

Le corps de la réponse est les données elles-mêmes. Si une requête porte sur le fichier index.html d'un répertoire donné, le corps de la réponse sera le contenu du fichier index.html. Pour pouvoir écrire des données sur le flux de sortie, il est nécessaire d'obtenir un flux (au sens « Java » du terme). Vous pouvez choisir entre deux types de flux : le PrintWriter obtenu par la méthode getWriter() de l'interface HttpServletResponse ou le ServletOutputStream obtenu grâce à la méthode getOutputStream() de l'interface ServletResponse. Le premier type de flux permet d'écrire efficacement un flux de caractères, destiné à être lu et supportant l'internationalisation. Le deuxième type est conçu pour écrire des données au format binaire. Si vous appelez, tentez d'obtenir un flux de type ServletOutputStream après avoir obtenu un flux de type PrintWriter ou inversement, une exception de type IllegalStateException sera générée.

Pour envoyer du contenu au client, il suffit alors d'utiliser les méthodes disponibles selon le flux choisi, par exemple la méthode println(String contenu) pour un flux de type PrintWriter ou ServletOutputStream.

Il faut savoir que la sortie peut être gérée de deux manières : tamponnée ou directe. Une sortie tamponnée permet d'accroître les performances d'écriture sur le flux de sortie. Par contre, les besoins en mémoire vive peuvent s'accroître, car une plus grosse quantité de données est gardée en mémoire plus longtemps, et les servlets ne fournissant qu'une ressource (par exemple un seul fichier HTML sans image) très grande (la connexion dure longtemps) ne bénéficieraient pas de cette méthode.

La méthode directe quant à elle envoie les données au fur et à mesure qu'elles sont générées (à chaque retour à la ligne).

Aucun moteur de servlets n'est tenu de fournir un mécanisme de tampon, vous devrez donc consulter la documentation du moteur de servlets que vous utilisez pour savoir s'il est possible d'utiliser un tel mécanisme. Pour obtenir des informations sur la sortie tamponnée, plusieurs méthodes existent :

  1. getBufferSize() : fournit la taille du tampon utilisé actuellement pour l'écriture ;
  2. setBufferSize(int tailleTampon) : ajuste la taille du tampon à tailleTampon. Le moteur de servlets définit alors un tampon au moins égal à la taille donnée. Le fait de disposer d'un peu de liberté pour définir la taille réelle du tampon permet au moteur de servlets de réutiliser un éventuel tampon dont la taille serait supérieure ou égale à la taille demandée. Outre l'accélération des performances en écriture, une taille de tampon supérieure à zéro permet la mise en place des entêtes un peu après l'envoi des premières données. Cette méthode doit être appelée avant l'envoi des premières données (les entêtes et le code d'état font partie des données ici) ;
  3. isComitted() : permet de savoir si des données ont déjà été envoyées au client. Vous pouvez encore spécifier le code d'état et les entêtes de la réponse si cette méthode renvoie faux ;
  4. reset() : efface toutes les données présentes dans le tampon si aucun élément de la réponse n'a été encore envoyé. Les entêtes seront aussi effacés, vous devrez donc en spécifier de nouveaux (mais qui peuvent être identiques). Si vous appelez cette méthode alors que des éléments de la réponse ont été envoyés, une exception du type IllegalStateException est générée ;
  5. flushBuffer() : permet de forcer l'envoi des données lorsque le tampon n'est pas encore plein. Après l'appel de cette méthode, le code d'état et l'entête HTTP ont été communiqués au client et un appel à la méthode isCommitted() renverra la valeur « faux ».

Comme tout tampon utilisé en programmation, lorsqu'il est plein, les données présentes dans ce dernier sont envoyées sur la sortie, et la méthode isComitted() renverra la valeur « vrai » à partir de ce moment.

Les données sont envoyées au travers d'une connexion établie entre le client (navigateur web) et le serveur. Or vous savez que tout navigateur est doté d'un bouton « stop » qui arrête le chargement de la page web demandée. Que se passe-t-il à ce moment-là ?

La connexion est alors coupée et le flux ouvert en sortie par la servlet vers le naviagetur n'est plus valide. Deux cas sont possibles :

  1. Vous utilisez un flux de type PrintWriter ;
  2. Vous utilisez un flux de type ServletOutputStream.

Dans le premier cas, l'écriture sur un flux non valide ne génère aucune exception. Pour savoir si le flux est toujours valide, vous devrez utiliser la méthode checkError() de la classe PrintWriter. Cette méthode retourne le booléen true si le flux de sortie n'est plus valable.

Dans le deuxième cas, l'écriture sur un flux non valide génère une exception de type IOException. Il suffit donc d'utiliser un bloc try { } catch (IOException e) { } pour gérer le problème.

Il est important d'utiliser ces mécanismes de contrôle lorsque des traitements coûteux en temps processeur peuvent être lancés par votre servlet. Un test avant le début d'un tel traitement permet d'éviter un gaspillage dans le cas où l'utilisateur aurait pressé le bouton « stop ».

V-B-3. Persistance de la connexion

Une autre particularité inhérente à HTTP et qu'il est intéressant de souligner est l'absence de persistance de connexion par défaut. C'est-à-dire qu'une ressource regroupant plusieurs types de données, par exemple une page web contenant du texte et trois images, nécessitera généralement plusieurs connexions : une pour chaque ressource contenue dans le document accédé (donc quatre connexions dans l'exemple utilisé).

La raison pour laquelle ceci se passe est qu'il est impossible pour le client web (le navigateur), en utilisant ce que nous avons vu du protocole HTTP, de connaître les différentes étapes de l'envoi des données sans utiliser une connexion pour chaque ressource. Pour utiliser la même connexion pour toutes les ressources d'un document, le serveur doit spécifier explicitement où débute l'envoi des données et où il se termine (en donnant la taille des données à envoyer). Toutes les ressources peuvent donc être envoyées sur la même connexion

Si la taille donnée par le serveur pour l'ensemble des ressources est inférieure à la taille de celles-ci, le client attendra les octets imaginaires manquants. Si la taille donnée est supérieure à la taille réelle de la ressource à envoyer, l'écriture dépassera la fin du flot et une exception sera probablement générée. Les détails concernant la gestion de ces erreurs dépendent du moteur de servlets utilisé, il se peut que vous ne rencontriez pas un comportement strictement identique.

Pour spécifier la taille des données à envoyer, il faut utiliser la méthode setContentLength(int taille) de l'interface ServletResponse. Le paramètre taille correspond à la taille en octet de la ressource qui va être envoyée. L'utilisation de cette méthode permet au navigateur, s'il le souhaite, de gérer la récupération du document avec une connexion persistante. La seule contrainte ici est d'appeler cette méthode avant d'envoyer quoi que ce soit sur le flux de sortie. Pour résoudre ce problème, il suffit décrire le contenu à envoyer dans un tampon duquel vous pourrez connaître la taille. Une fois cette taille connue, il suffit d'utiliser la méthode setContentLength(int taille) en passant la taille du tampon en paramètre de la méthode, puis d'écrire le tampon sur la sortie. Voici un exemple de servlet utilisant ce mécanisme :

 
Sélectionnez
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
 
public class ServletPersistante extends HttpServlet {
 
  public void doGet(HttpServletRequest req, HttpServletResponse res) throws 
ServletException, IOException {
    res.setContentType("text/html");
    
    // création du tampon contenant les données à envoyer
    // ce tampon s'étend au fur et à mesure que des données y
    // sont écrites
    ByteArrayOutputStream tampon = new ByteArrayOutputStream();
    // création du flux d'écrire de texte sans vidage automatique
    // du tampon utilisé
    PrintWriter out = new PrintWriter(tampon);
    out.println("Ceci est une servlet utilisant une connexion persistante.");
    // récupération de la taille du tampon
    // afin de déterminer la valeur de l'entête
    // Content-length
    res.setContentLength(tampon.size());
    // envoi des données 
    tampon.writeTo(res.getOutputStream()); 
  } 
}

Vous connaissez désormais les mécanismes fondamentaux qui gèrent la diffusion du contenu vers un client utilisant le protocole HTTP. Nous n'avons cependant pas considéré la forme du contenu. Étant donné que des pages web peuvent vite devenir conséquentes et que leur esthétique est importante, nous allons voir deux choses différentes. Tout d'abord vous étudierez les différentes façons de produire du code HTML, et ensuite les possibilités d'intégration de contenu multimédia (images, sons, animations).

V-B-4. La production de pages web écrites en HTML

Il existe plusieurs manières d'envoyer du code HTML vers un client utilisant le protocole HTTP. Nous en verrons trois d'entre elles, qui me semblent couvrir les bases reprises par d'éventuelles méthodes différentes. Comme vous le verrez, le choix de ces méthodes dépend essentiellement de la quantité de contenu à fournir, de la structure de votre document et du service fourni par celui-ci. En effet, si tous les documents d'un site possèdent la même forme, il sera intéressant de « factoriser » le code HTML en utilisant des modèles.Par contre si le site web contient un code HTML très simple il sera plus simple et rapide d'utiliser des fonctionnalités basiques.

Nous avons déjà vu, tout au long du document, la création d'un contenu HTML classique. Il suffit pour cela d'utiliser un flux de type PrintWriter et de spécifier l'entête « Content-type » adéquat comme ceci :

 
Sélectionnez
res.setContentType("text/html");

Les données au format HTML sont alors passées à la méthode println de classe PrintWriter comme des chaînes de caractères classiques de la manière suivante :

 
Sélectionnez
out.println("<b>voici une phrase en police grasse.</b>");

Il n'apparaît aucune limitation particulière sur cet exemple précis, mais il faut songer à l'utilisation de cette méthode pour produire un contenu complexe. Cela devient très rapidement pénible pour plusieurs raisons :

  1. Le code source de la servlet devient très vite énorme ;
  2. La majeure partie de ce contenu est statique (pas généré par un algorithme) ;
  3. Les erreurs sont fréquentes (oubli de fermeture d'une balise par exemple) ;
  4. On utilise pas la programmation orientée objet.

Pour remédier à cela, nous allons étudier deux alternatives : la génération HTML orientée objet et le système de templates (ou modèles). Ces deux techniques ne font pas partie de l'API des servlets et ne sont donc pas fournies par l'ensemble de classes que vous avez téléchargé lors de l'installation de Tomcat (voir installation_tomcat). Outre le fait que vous devrez les télécharger, il vous sera sûrement nécessaire de vous rehabituer à un mode de fonctionnement particulier à cet ensemble de classes et à sa documentation. Étant donné que je ne ferai qu'aborder le sujet, il vous sera probablement utile de fournir quelques efforts supplémentaires pour vous familiariser avec ces techniques, en lisant la documentation par exemple.

La première technique que nous allons étudier est la génération HTML orientée objet. Les classes que j'utilise dans les exemples qui suivent sont téléchargeables à l'URL http://www.weblogic.com/.Une fois ces classes téléchargées, il suffit d'ajouter leur chemin dans la variable environnement CLASSPATH comme ceci :

 
Sélectionnez
export CLASSPATH=$CLASSPATH:/chemin/vers/les/classes/leJar.jar

Cette instruction permet de pouvoir compiler et lancer les exemples. Appuyons-nous sur un exemple pour démarrer :

 
Sélectionnez
    import javax.servlet.*;
    import javax.servlet.http.*;
    import weblogic.html.*;
    import java.io.*;
     
    public class BonjourMondeObjet extends HttpServlet {
      public void doGet(HttpServletRequest req, HttpServletResponse res) throws 
                        ServletException, IOException {
        res.setContentType("text/html");
        ServletPage page = new ServletPage();
        page.getHead().addElement(new TitleElement("Bonjour, monde !"));
        page.getBody().addElement(new BigElement("Bonjour monde !"));
        
        page.output(res.getOuputStream());
      }
    }

Compilez et effectuez une requête sur cette servlet et un message célèbre apparaîtra. Vous pouvez déjà vous apercevoir que plus aucune balise HTML n'est utilisée. Cela évite déjà les nombreuses erreurs d'écritures qui peuvent être rencontrées lorsque l'on manipule directement ces balises. Les seules erreurs qu'il est possible de commettre au niveau de la structure HTML du document seront maintenant signalées à la compilation (nom de méthode inexistant par exemple), ce qui évite des séances fastidieuses de recherche d'erreur. Ensuite, on voit clairement que nous utilisons la programmation orientée objet. En effet, un objet de type ServletPage est créé et la production du code HTML se fait, en l'occurrence, par les deux instructions suivantes :

 
Sélectionnez
page.getHead().addElement(new TitleElement("Bonjour, monde !"));
page.getBody().addElement(new BigElement("Bonjour monde !"));

La méthode getHead() de la classe ServletPage retourne une référence sur un objet décrivant l'entête de la page HTML. Il est possible d'appeler d'autres méthodes sur cet objet, comme addElement. Cette méthode, comme vous pouviez vous en douter, ajoute un élément à l'entête. Ici c'est un élément décrivant le titre qui est ajouté, mais on aurait pu ajouter d'autres éléments appartenant à d'autres types. L'utilisation d'une génération orientée objet permet de changer les propriétés de tout le code HTML généré par toutes vos pages en changeant seulement des informations au niveau de l'objet utilisé. Ainsi, il suffit de modifier la classe TitleElement pour en modifier la mise en forme. C'est un peu le même principe que les feuilles de styles4.2 qui permettent de séparer le contenu de la forme. Les servlets produisant beaucoup de code HTML et utilisant cette méthode sont donc plus lisibles et faciles à maintenir. Il est également possible de rajouter ses propres types en créant des sous-classes ou de nouvelles classes. Rien ne vous empêche de créer vos propres classes de base utilisées par chacun de vos sites, puis pour chacun d'eux, créer des sous-classes spécialisées. Je vous laisse découvrir par vous-même cette méthode dans le chemin emprunté peut-être différent selon les besoins. Cela dit vous ne devriez éprouver aucune difficulté, en sachant que vous devez bien connaître la programmation orientée objet. Vous la connaissez n'est-ce pas ?

Malgré l'utilisation de cette méthode intéressante, comment peut-on éviter de voir proliférer le contenu statique au sein d'une servlet, et comment permettre aux graphistes de votre site web de travailler en toute indépendance des programmeurs ? Une solution envisageable est l'utilisation de templates. Le mot « template » signifie « modèle ». En effet, de nombreux sites web utilisent un contenu dynamique qui suit des règles précises. En utilisant un système de modèles, il suffit de préciser quelle partie du code HTML est dynamique, et de générer le contenu correspondant à ces petites parties par vos servlets. Tout le reste de la page est statique et extrait par le moteur de modèles à partir du fichier HTML. Un moteur de modèles est un ensemble de classe qui prend en charge la liaison entre les marqueurs définis dans les pages HTML pour distinguer le contenu dynamique du contenu statique et votre servlet. Puisque l'on parle de moteur de modèles, il faut savoir qu'il en existe plusieurs. Chaque moteur propose ses propres possibilités et sa manière de les implémenter. C'est à vous de choisir celui qui vous correspond le mieux. Les principaux moteurs de modèles sont :

  1. Enhydra : disponible à http://www.enhydra.org/ ;
  2. Webmacro : disponible à http://www.webmacro.org/ ;
  3. Freemarker : disponible à http://freemarker.sourceforge.net/.

tous ces moteurs sont des logiciels libres, vous pouvez donc vous les procurer gratuitement, bénéficier de nombreuses améliorations rapidement et participer au développement. Voici ci-dessous un exemple simple d'une servlet utilisant un modèle pour produire le contenu. Cet exemple est programmé avec le moteur de modèles Enhydra :

 
Sélectionnez
<html> 
    <body> 
    <span id="MonTitre">Categorie Vide</span> 
    <p> 
    <span i="MaLigne">&nbsp;&nbsp;&nbsp;<a id="MonUrl" href="#">Contenu Categorie</a></span> 
    </p> 
    </body> 
    </html>

La servlet permettant d'utiliser le modèle ci-dessus :

 
Sélectionnez
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.*;
    public class MaServlet extends HttpServlet {
    public void doGet(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException { 
      // instancier la page 
      MaPageHTML p = new MaPageHTML();
      // remplir le titre 
      p.setTextMonTitre(cat.isEmpty()?"":cat.getName());
      // retrouver les elements 
      Element ligne = p.getElementMaLigne(); 
      Element paragraph = ligne.getParentNode(); 
      Element url = p.getElementMonUrl();
      // retirer la ligne template et ajouter les lignes reelles 
      paragraph.removeChild(ligne); 
      for (int i=0; i < cat.size(); i++) { 
        // changer le lien 
        url.setHRef(encodeURL("/servlet/JTus?p=1&ca="+cat.refCateg)); 
        // changer le texte 
        url.removeChild(url.getFirstChild()); 
        url.appendChild(p.createTextNode(cat.Nom));
        // dupliquer et ajouter la ligne 
        Node clonedNode = ligne.cloneNode(true); 
        paragraph.appendChild(ligne); 
      } 
      // retourner la page au client 
      response.setContentType("text/html"); 
      java.io.PrintWriter writer = response.getWriter();  
      writer.print(p.toDocument()); 
      writer.flush(); 
    } 
    }

On peut voir également que pour une gestion des modèles de base, l'utilisation d'un moteur de modèles peut être trop lourd à utiliser.

Pensez simplement que vous pouvez très bien programmer vos classes représentant par exemple un pied de page standard. Cette classe contiendrait une méthode ecrireContenu(PrintWriter sortie) qui écrirait son contenu sur le flux de sortie de la servlet. Vous pourriez alors créer autant de pied de pages spécifiques en dérivant cette classe de base, tout en conservant la même forme pour tous les pieds de page du même type utilisés dans vos pages web. En utilisant des conventions de nommage (pour vos méthodes et attributs) simples et en les respectant scrupuleusement, vous pourriez arriver à disposer d'un système de modèles simple, utile et moins lourd que les ténors du genre.

Maintenant que nous savons générer du contenu HTML, voyons ce qui est mis à notre disposition pour générer un contenu multimédia.

V-B-5. La génération de contenu multimédia

Les deux types de contenu multimédia qu'il est possible de générer avec les servlets sont les images et les animations. Les images sont parfois très utiles, car elles permettent de représenter une information de manière synthétique en très peu de place. Dans certains cas, la même information écrite avec du texte prendrait beaucoup de place et serait moins compréhensible. Les graphiques sont une illustration du caractère irremplaçable des images au sein d'un système d'information. Quant aux animations, elles servent parfois l'information, mais ne sont pas facilement générées depuis un serveur. En effet, le protocole HTTP n'étant pas orienté connexion comme nous avons pu le voir (voir protocole_http), il est seulement possible de produire des animations lentes, grâce à des types d'entêtes particuliers. Il en est de même pour le son. Un fichier sonore ne peut pas être transmis en flux continu du fait du fonctionnement du protocole HTTP lui-même. Il est cependant tout à fait possible de gérer des animations graphiques ou un flux sonore, mais pas dans le cadre d'une servlet fournissant un service à un client HTTP comme un navigateur web. Il faut utiliser la connexion par sockets pour cela, et ce n'est pas l'objet de ce chapitre. Nous allons maintenant aborder la conception de graphique côté serveur en utilisant les servlets et le protocole HTTP.

V-B-6. La génération d'images

Une image est un fichier au même titre qu'un document HTML ou au format ASCII, mis à part qu'il utilise le format binaire (pas de notions utiles dans des documents textuels comme le retour à la ligne). Notre servlet d'exemple servant n'importe quel type de fichier (voir servlet_fichier) peut donc envoyer directement le fichier au client qui, grâce aux entêtes décrivant le format de l'image (« image/jpeg » par exemple), pourra la décoder et l'afficher sur l'écran de la machine cliente. Mais comment faire lorsque nous voulons générer dynamiquement une image et l'envoyer au visiteur de notre site web ?

Vous savez qu'il est possible de créer des images dynamiquement dans une application Java classique ou une applet. Un ensemble de classes est mis à disposition pour créer, manipuler et afficher des images. Elles font partie du package java.awt et disposent de toutes les fonctions utiles, dont certaines destinées à coder l'image brute créée en un format standard étudié pour l'échange de données graphiques sur Internet, comme le format Jpeg. Grâce à l'exemple suivant, vous allez vous apercevoir que la génération d'une image par une servlet ne diffère pas vraiment de la méthode utilisée par une application classique :

 
Sélectionnez
    import com.sun.image.codec.jpeg.*; 
    import javax.servlet.http.*; 
    import javax.servlet.*; 
    import java.awt.*; 
    import java.awt.image.*; 
    import java.io.*; 

    public class ImageServlet extends HttpServlet { 
      public void doGet(HttpServletRequest req, HttpServletResponse res) throws 
                        ServletException, IOException { 

       // le contenu produit est une image au format jpeg
       res.setContentType("image/jpeg"); 
       ServletOutputStream out = res.getOutputStream(); 

       // l'objet enc va encoder les données et les envoyer sur
       // le flux de sortie de type ServletOutputStream
       JPEGImageEncoder enc = JPEGCodec.createJPEGEncoder(out);

      // création d'une nouvelle image dune résolution de 1024 par  
      // 768
      BufferedImage image = new  
      BufferedImage(1024,768,BufferedImage.TYPE_BYTE_INDEXED); 

     // récupération du contexte graphique lié à l'image
      Graphics2D g = image.createGraphics(); 
      // la prochaine opération s'effectuera avec la couleur rouge
      g.setColor(Color.red); 

      // affichage de la célèbre phrase
      g.drawString("Bonjour monde !", 400, 500);

      // transformation des données au format jpeg et envoi
      // de celles-ci sur le flux standard de sortie (le navigateur)

      enc.encode(image); 
      } 
    }

Faites cependant attention à un problème relativement difficile à déceler. Une machine sous Unix, lorsqu'elle fournit des services au sein d'un réseau (comme un moteur de servlet par exemple) ne lance pas un environnement graphique. Or, lorsque nous décidons de créer une image, des classes du paquetage java.awt sont chargées et des objets de type Image décrivant les images à afficher sont créés. C'est pour cela que vous rencontrerez une erreur si la machine sur laquelle tourne le moteur de servlets ne dispose pas d'un environnement graphique et n'ajuste pas sa variable d'environnement DISPLAY. Vous devez également vous assurer que l'utilisateur sous lequel tourne le processus de la JVM est autorisé à utiliser le « display ». Lisez la documentation sur la commande xhost en entrant

 
Sélectionnez
man xhost

pour savoir comment autoriser des utilisateurs à utiliser le display.


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 Julien Gilli. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.