|
Ressources: |
Programmation de servlets JavaCe tutorial, loin d'être exhaustif, a pour objectif de présenter de la manière la plus claire possible les fonctions de bases mises à disposition par l'API Servlet. Plan:Installation 1 Installation
La première étape consiste à télécharger le JSDK2.0 sur le site de SUN (ici), le décompresser puis à copier (cp -R) le répertoire JSDK2.0 dans le répertoire de votre choix (on prendra /usr/lib/ pour la suite de ce tutorial). Il faut alors mettre à jour le path en incluant /usr/lib/JSDK2.0/bin (export PATH=/usr/lib/JSDK2.0/bin:$PATH), ainsi que le classpath en y ajoutant le chemin de l'archive jsdk.jar contenant les classes (export CLASSPATH = /usr/lib/JSDK2.0/lib/jsdk.jar :$CLASSPATH sous bash). Pour vérifier que tout s'est bien déroulé, on lance l'utilitaire servletrunner qui devrait afficher les lignes suivantes : olly%>servletrunner servletrunner starting with settings: port = 8080 backlog = 50 max handlers = 100 timeout = 5000 servlet dir = ./examples document dir = ./examples servlet propfile = ./examples/servlet.properties servletrunner permet de tester les servlets, c'est à dire
qu'il va faire office de mini-serveur, écoutant sur un port spécifique
(8080 par défaut). Il est possible de modifier les paramètres
par défaut de servletrunner, notamment les flags -d suivi du répertoire
contenant les servlets, et -s suivi du fichier de propriétés
de la zone à considérer.
La première étape consiste à télécharger le moteur de servlets d'Apache, ApacheJServ, puis à décompresser l'archive quelque part sur le disque, sous /tmp par exemple. Il y a ensuite deux façons de procéder pour rajouter le module mod_jserv. La première consiste à recompiler complètement Apache après lui avoir ajouté le source de mod_jserv. La seconde nécessite une version d'Apache 1.3.*, avec support des Dynamic Shared Objects (DSO) et permet d'ajouter le module mod_jserv de manière dynamique, c'est à dire sans avoir à recompiler Apache. Pour des raisons de temps, seule la deuxième procédure, plus "moderne", est décrite ici. Comme pour la plupart des packages fournis sous forme de code source, il est nécessaire dans un premier temps d'éxécuter le script de configuration avec configure. configure s'efforce de deviner le maximum de choses, et si l'ensemble des fichiers sont à leur place habituelle, tout se passeras parfaitement. Pour ma part, voici la ligne de commande que j'ai utilisé (avec succès sous SuSE) : olly%>./configure -with-jsdk=/usr/lib/JSDK2.0/lib/jsdk.jar -disable-debugging -with-apache-install=/usr (La directive -with-apache-install=/usr est nécéssaire
sous SuSE (mon cas) car configure croit que apxs est sous apache_dir/sbin/,
alos qu'il est en réalité sous /usr/sbin.)
olly%>./configure --with-apxs=/usr/sbin/apxs --prefix=/usr/lib/jserv --with-jdk-home=/usr/lib/jdk1.2.2 --with-JSDK=/usr/lib/JSDK2.0/lib/jsdk.jar --disable-debugging --with-java-platform=2 olly%>make
L'environnement d'Apache JServ est divisé en zones, totalement indépendantes les unes des autres. Cela permet de séparer les servlets selon le possesseur, le mesures de sécurité désirées, ou encore les ressources allouées. Toutefois, une unique zone suffit dans la plupart des utilisations. Apache JServ possède un fichier de configuration nommé jserv.conf qui sert à définir son comportement au regard d'Apache. Le fichier de configuration d'Apache /etc/httpd/httpd.conf doit donc contenir une directive Include permettant d'inclure le fichier jserv.conf en son sein. Exemple de ligne à inclure dans httpd.conf (après avoir
copié le fichier jserv.conf à l'endroit indiqué):
Le fichier jserv.conf contient à son tour une directive ApJServProperties
/chemin/vers/jserv.properties pointant sur le fichier jserv.properties
qui contient les propriétés des diférents servlets
utilisés. jserv.properties permet entre autres choses de spécifier
le nombre de zones souhaitées, le nom donné à chacune,
ainsi que leur emplacement sur le disque.
# List of servlet zones Apache JServ manages zone=zone_a, zone_b Configuration : plusieurs zones. Chaque zone possède un fichier nom_zone.properties propre, dans lequel est entre autres indiqué le répertoire où trouver les servlets (repositories=/chemin/vers/classes). Les zones disponibles doivent être indiquées dans le fichier jserv.properties. Les lignes zones=zone1, zone2, ... et le chemin des fichiers de propriété leur correspondant : zone1.properties=/chemin/vers/zone1.properties, zone2.properties=/chemin/vers/zone2.properties, etc.. Une fois JServ configuré, il est nécessaire de relancer
Apache, avec une des commandes suivantes: rcapache restart, apachectl
restart ou bien kill -NOHUP `cat var/httpd.pid`.
2 Architecture de base d'un servletAvant de rentre dans les détails, examinons le code d'un servlet avec fonctionnalités minimale.Ce premier exemple de servlet effectue une tâche toute simple : il crée une page HTML intitulée "mon premier servlet", laquelle affiche simplement "Salut tout le monde".
import javax.servlet.*; //importe le package servlet import javax.servlet.http.*; //importe servlet.http import java.io.*; //importe les classes d'entrée-sortie public class ExempleServlet extends HttpServlet { // ExempleServlet hérite de HttpServlet /* surcharge la méthode doGet */ public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { PrintWriter out; response.setContentType("text/html"); // then write the data of the response out = response.getWriter(); out.println("<HTML> <HEAD><TITLE> mon premier servlet </TITLE></HEAD> <BODY><P>Salut tout le monde!</BODY></HTML>"); out.close(); } } Ce servlet hérite donc de la classe HttpServlet, et surcharge la méthode doGet, cette dernière étant appelée lorsqu'un client envoie une requète GET au serveur. Pour éxécuter ce servlet, il suffit de placer le code
compilé (ExempleServlet.class) dans l'un des répertoires
correspondant aux zones configurées, par exemple ~/example/ puis
d'ouvrir avec un browser l'URL http://localhost/example/ExempleServlet.
Le coeur du package est la classe Servlet de laquelle tout servlet doit obligatoirement hériter. Un servlet n'utilise pas nécéssairement le protocole HTTP , c'est pourquoi il existe une interface HttpServlet héritant de la classe Servlet. Tous les servlets utilisés avec ApacheJserv devront bien sur hériter de l'interface HttpServlet et surcharger certaines de ses méthodes, par exemple les méthodes doGet ou doPost Le package Servlet contient également deux classes qui permettent l'interaction avec les clients du serveur web, les classes ServletRequest et ServletResponse. La classe ServletRequest fournit des informations concernant la requète parvenue au serveur, notamment les noms et valeurs des paramêtres passés, l'adresse IP du client, ls cookies et donne également accès via un stream aux données encapsulées dans le corps de requètes HTTP, notamment lors de l'envoi de données au serveur via une méthode POST. La classe ServletResponse fournit des méthodes permettant de répondre aux clients, par exemple une méthode permettant de fixer le type MIME de la réponse, ainsi que des streams via lesquels il est possible d'envoyer des données au client. Les classes HttpServletRequest et HttpServletResponse sont des interfaces qui étendent un peu plus les classes ServletRequest et ServletResponse en permettant l'accès aux caractèristiques spécifiques des headers HTTP.
String useragent = request.getHeader("user-agent"); if (useragent.indexOf("Netscape")) { /** instructions éxécutées si le navigateur client est Netscape **/ } La méthode getParameter retourne une chaîne de caractères contenant la valeur du paramètre passé en argument. Par exemple:
String bookId = request.getParameter("bookid"); Cette instruction stocke dans la variable bookId la valeur du paramètre nommé bookid (getParameter renvoie NULL si le paramètre n'existe pas). Il est également possible d'obtenir une chaine de caractères unique contenant l'ensemble des paramètres et valeurs grâce à la méthode getQueryString, et de la parser soi-même par la suite. Lors d'une requète POST, on utilisera pour lire les données contenues dans le corps de la requète la méthode getReader, qui renvoie un BufferedReader (cas de données texte) ou la méthode getInputStream qui renvoie un ServletInputStream.(données binaires).
2.2 Cycle de vie d'un servletExemple de surcharge de la méthode init() :
public class MonServlet extends HttpServlet { public void init(ServletConfig config) throws ServletException { super.init(config); try { Class.forName("xxx"); } catch(java.lang.ClassNotFoundException e) { ... } } public void doGet(...) { ... } }
3 Exemple : traitement de formulairesLes formulaires au sein d'une page HTML permettent l'interaction entre client et serveur web. Un formulaire contient généralement plusieurs éléments ou contrôles, comme par exemple des zones de saisie de texte, des boutons, des listes de choix, qu'il appartient à l'utilisateur de remplir, cliquer ou sélectionner selon ses désirs. Chacun des choix de l'utilisateur est stocké dans une paire NAME/VALUE.Il existe deux méthodes pour faire parvenir les données de l'utilisateur au serveur. La première, dite méthode GET, transmet les données utilisateurs dans le header de la requète, tandis que la seconde, la méthode PUT, les transmet dans le corps de la requète, permettant de transférer des volumes de données beaucoup plus important.
Pour traiter une requète GET, il est nécessaire de surcharger la méthode doGet. A l'intérieur de cette méthode, la méthode getParameter permet de récupérer la valeur d'un paramètre passé au servlet avec la requète GET. Par exemple: String bookId = request.getParameter("bookId"); //permet de récuperer
la valeur du paramètre "bookid". La méthode renvoie null
si le paramètre n'existe pas. Il est possible d'utiliser cela pour
compacter les pages dynamique en un seul servlet:
String bookId = request.getParameter("bookId"); if (bookid == null) { //affiche le formulaire } else { //traite le formulaire }
La méthode PUT est généralement utilisée pour uploader des fichiers du disque de l'utilisateur vers le serveur. Elle peut par exemple être utilisée pour soumettre des images, ou encore des fichiers contenant des données à analyser par le serveur. 4 Maintien de sessions
Rien de plus pour l'instant. 5 Cookies
5.1 Envoyer un cookiePour créer un cookie:Cookie c = new Cookie("login", str_login); c.setVersion(1); c.setDomain(".mon_domaine.com"); res.addCookie(authCookie); 5.2 Récupérer un cookie
Cookie[] cookies = req.getCookies(); La classe Cookie possède alors les méthodes suivantes:
public String getDomain() pour récupèrer le domaine public String getValue() pour récupérer la valeur
stockée dans le cookie
6 Problèmes de threadingUn serveur web pouvant servir plusieurs clients de manière concurrente, lorsqu'un servlet est conçu de manière à accèder à une ressource partagée, par exemple une base de données, il est parfois nécéssaire de prévenir l'accès simultané par plusieurs clients à cette même ressource. Afin d'obliger un servlet à ne traiter qu'une seule requète à tout moment, il existe deux possibilités : la première consiste à concevoir le servlet de façon à ce qu'il implémente l'interface SingleThreadModel.Un exemple d'un tel servlet est le suivant :
public class ReceiptServlet extends HttpServlet implements SingleThreadModel { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... } ...
Le serveur s'assure alors que la méthode service n'est pas appelée
plus d'une seule fois simultanément.
La deuxième possibilité est de synchroniser l'accès à la ressource. 7 Java DataBase ConnectivityIl serait difficile de parler de l'API Servlet sans également mentionner l'API JDBC. En effet, ce dernier permet à tout servlet d'accèder de manière directe à pratiquement n'importe quelle base de donnée externe, pourvu que celle ci possède un driver JDBC. Dans cet article, nous utilisons la base de données PostgreSQL, dont le driver JDBC est disponible à cette adresse. Il faut avant de commencer s'assurer que le chemin du driver JDBC est présent dans le $CLASSPATH. (au besoin faire un olly>export CLASSPATH=$CLASSPATH:/usr/lib/pgsql/jdbc6.5-1.2.jarou bien insérer la ligne wrapper.classpath=/usr/lib/pgsql/jdbc6.5-1.2.jardans le fichier jserv.properties)Pour faire fonctionner l'example fourni, il est nécéssaire d'avoir une base de données (ici nommée test), et d'y insérer une table avec quelques données. Pour cela, il suffit de créer un fichier texte tout simple nommé par exemple jdbc.sql contenant les quelques commandes SQL suivantes :
create table stories (story_title varchar(100), story_url varchar(100), story_date date); insert into stories values ('NASA Proposes Launch Solar Sail Vehicle For 2010', 'http://slashdot.org/article.pl?sid=00/05/15/058238', '2000-05-15'); insert into stories values ('Linuxcare Responds To Tim O\'Reilly's Article', 'http://slashdot.org/article.pl?sid=00/05/15/0254252', '2000-05-15'); insert into stories values ('New Internet VCR Service', 'http://slashdot.org/article.pl?sid=00/05/14/2048217', '2000-05-15'); Ensuite la commande olly>psql test< jdbc.sql permet d'éxécuter ces commandes (et donc de créer la table et d'y insérer les trois enregistrements). 7.1 Connection à une base de donnée Dans le monde JDBC, une base de données est représentée
par une URL, par exemple jdbc:postgresql://host:port/base. En ce qui concerne le driver JDBC pour PostgreSQL, host est
par défaut égal à localhost et port à
5432
Stockage d'objet Java dans une base PostgreSQL Postgresql est une base de données d'un genre un peu particulier : elle possède par rapport à une base relationnelle traditionnelle des extensions objets, qui lui permettent par exemple de stocker une table dans un champs d'une autre table. Il est donc tout à fait possible en Java de stocker un objet
dans un stream, si la classe de l'objet implémente l'interface java.io.Serializable.
Cette caractèristique rend alors possible le stockage d' objets
Java dans une base Postgresql, au moyen des LargeObject. Cependant, Postgresql
est un moteur de base de données d'un genre un peu particulier :
il possède par rapport à une base relationnelle traditionnelle
des extensions objets, qui lui permettent par exemple de stocker une table
dans un champs d'une autre table. La classe postgresql.util.Serialize du
driver JDBC se sert de cette caractèristique pour fournir un moyen
de stocker un objet Java en tant que table.
8 Un servlet évolué: CocoonCocoon permet de transformer des données XML grâce a des feuilles de style XSL de façon à obtenir non seulement du HTML pour les navigateurs classiques, mais également du WML pour les browser WAP contenus dans certains téléphones mobiles, ou encore du PDF . Il permet donc d'ouvrir les sites web inter ou intranet à un nombre considérable de clients, sans que soient nécessaires les refontes de chaque document pour le rendre compatible avec les systèmes de navigation spécifiques à chaque client.Installation de CocoonLa première chose a faire consiste à télécharger Cocoon, à partir du site http://xml.apache.org, puis à le compiler (comme à l'habitude, ./configure, make, make install)Il est ensuite nécessaire de rendre les classes Cocoon visibles par le moteur de servlets : il faut pour cela ajouter des wrapper.classpath vers tous les .jar requis par Cocoon dans le fichier jserv.properties cocoon.jar : xerces.jar : parseur XML xalan.jar : moteur de transformation XSLT fop.jar : formating par exemple: wrapper.classpath=/usr/local/java/lib/cocoon.jar Il faut ensuite choisir la zone servlet dans laquelle Cocoon doit résider,
zone dans cette exemple. Il faut donc passer le fichier cocoon.properties
en parametre au fichier zone.properties, en y ajoutant la ligne suivante:
Il faut également dire à Apache d'associer tous fichier
xml avec Cocoon. Pour cela, il faut ajouter:
Action cocoon /servlet/org.apache.cocoon.Cocoon AddHandler cocoon xml dans le fichier jserv.conf. (/servlet/ est le point de montage de la zone servlet a laquelle appartient Cocoon) Nous sommes alors prêts pour créer notre premier document XML, ainsi que sa feuille de style. Plusieurs cas de figures : le fichier XML existe, il faut simplement lui adjoindre une feuille XSL. Deuxième cas, l'information est stockée dans une base de données, par exemple postgreSQL, et il va être nécessaire d'extraire dans un premier temps cette information, pour la mettre au format XML, puis lui appliquer une transformation XSL. Cas 1: Le fichier XML existe deja
<?xml version="1.0" encoding="UTF-8"?> <stories> <story> <story_title>NASA Proposes Launch Solar Sail Vehicle For 2010</story_title> <story_url> http://slashdot.org/article.pl?sid=00/05/15/058238</story_url> <story_date>2000-05-15</story_date> </story> <story>
<story>
La feuille de style que l'on doit appliquer pour obtenir le résultat souhaité est très simple:
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <html> <body> <xsl:apply-templates select="stories"/> </body> </html> </xsl:template> <xsl:template match="stories">
<xsl:template match="story">
</xsl:stylesheet>
Cette feuille de style est stockée dans le fichier stories.xsl. Pour signifier à Cocoon qu'il doit appliquer cette feuille au document XML ci-dessus, il faut rajouter une ligne à ce dernier, dans son entête, juste après le tag de version.
<?xml version="1.0"?> <?xml-stylesheet href="stories.xsl" type="text/xsl"?> <?cocoon-process type="xslt"?> <stories>
En pointant votre browser sur l'url de stories.xml, on obtient le code HTML suivant:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><body><table><tr><td><a href="http://slashdot.org/article.pl?sid=00/05/15/058238">NASA Proposes Launch Solar Sail Vehicle For 2010</a></td><td>2000-05-15</td></tr><tr><td><a href="http://slashdot.org/article.pl?sid=00/05/15/0254252">Linuxcare Responds To Tim O'Reilly's Article</a></td><td>2000-05-15</td></tr><tr><td><a href="http://slashdot.org/article.pl?sid=00/05/14/2048217">New Internet VCR Service</a></td><td>2000-05-15</td></tr></table></body></html> <!-- This page was served in 748 milliseconds by Cocoon 1.7 -->
Cas 2 : l'information est stockée dans une base de données
Il faut passer par ce que l'on appelle un SQLProcessor. XSQL, fourni par Oracle, est un exemple de produit transformant un résultat d'une requète SQL en XML. Pour pouvoir utiliser SQLProcessor, il faut s'assurer qu'une ligne du type processor.type.sql = org.apache.cocoon.processor.sql.SQLProcessor est présente dans le fichier cocoon.properties. Cet exemple éxécute une requète SQL sur une base PostGreSQL grâce à JDBC (il faut donc lui fournir les coordonnées du driver, ainsi que des infos utilisateurs, tq login et password) puis récupère les résultats et les formatte en XML. Il s'agit simplement de remplacer le bloc <query> ... </query> par le résultat, puis de lui appliquer la feuille de style normale.
<?xml version="1.0"?> <?xml-stylesheet href="stories.xsl" type="text/xsl"?> <?cocoon-process type="sql"?> <?cocoon-process type="xslt"?> <page> <connectiondefs>
<query connection="test_connection" doc-element="stories" row-element="story">
</page>
Les attributs doc-element et row-element permettent de spécifier
respectivement le nom que va prendre l'ensemble des résultats (ResultSet),
et les lignes (rows). S'ils ne sont pas spécifié, ces noms
seront ROWSET et ROW.
9 Upload de fichiersPour "uploader" un fichier grace à un servlet Java, il faut utiliser le package com.oreilly.servlet disponible à cette adresse. Imaginons que l'on ai le formulaire suivant:<form enctype="multipart/form-data" action="action" method="post"><br> <textarea name="indata" cols="70" rows="10"></textarea><br> <input type="file" name="infile"><br> <input type="submit" name="submit" value="Continue"><br> </form><br> Ce formulaire contient deux champs, un champ de type texte (textarea), et un champ de type "file". Supposons maintenant que l'on veuille sauver les données du formulaire (fichier ou texte) dans le fichier /tmp/dataXX-YY. La méthode doPost() permettant de réaliser cela est la suivante : import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import java.util.Date; import com.oreilly.servlet.multipart.*; . . public void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { /** récupère la date courante **/ Date d = new Date(); /** forge une chaine de caractère contenant la date **/ String timestamp = new String(d.getMinutes() + "-" + d.getSeconds()); /** crée un nouveau multipart parser, la taille des objets parsés étant de 10 MB au maximum **/ MultipartParser mp = new MultipartParser(request, 10*1024*1024); // 10MB Part part; /** itére tous les parties **/ while ((part = mp.readNextPart()) != null) { /** s'il s'agit du fichier **/ if (part.getName().equals("infile")) { /** recupere les données sous la forme d'un FilePart **/ FilePart filePart = (FilePart) part; String fileName = filePart.getFileName(); /** recopie dans un nouveau fichier **/ if (fileName != null) { long size = filePart.writeTo(new File("/tmp/data" + timestamp)); break; } /** s'il s'agit de la zone de texte **/ } else if (part.getName().equals("indata")) { /** recupere les données sous la forme d'un objet ParamPart **/ ParamPart paramPart = (ParamPart) part; /** récupere la valeur du champ "indata" **/ String indata = paramPart.getStringValue(); /** pour écrire dans un fichier **/ PrintWriter fos = new PrintWriter(new FileWriter("/tmp/data" + timestamp)); fos.print(indata); fos.close(); } } } |
|||||||