Installation
href="#Architecture de base d'un servlet">Architecture de base d'un servlet
Exemple: traitement de formulaires
Maintien de sessions
Cookies
href="#Problemes de threading">Problèmes de threading
href="#Java DataBase Connectivity">JDBC: Java DataBase Connectivity
href="xalan.html">Servlets et transformations XSLT avec Xalan-J
href="#Cocoon">Un servlet évolué: Cocoon
href="#Upload de fichiers">Upload de fichiers
Les sections suivants supposent que le JDK de SUN, ainsi que Apache 1.2 et spérieur ont été préalablement installé.
1.1 Installation du Kit de Développement de Servlets : le JSDK
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.
1.2 Installation du module côté serveur : ApacheJserv
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.)
Lors d'une seconde intallation sous Mandrake 7.0, la commande est devenue :
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
olly%>make install
1.3 Configuration pour l'éxécution de servlets
Exemple de ligne à inclure dans httpd.conf (après avoir copié le fichier jserv.conf à l'endroit indiqué):
Include /etc/httpd/jserv.conf
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.
Par exemple, si l'on souhaite deux zones nommées zone_a et zone_b, le fichier jserv.properties devra contenir les lignes suivantes :
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.1 Exemple : Hello World!
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.
2.2 Contenu du package javax.servlet
String useragent = request.getHeader("user-agent");
if (useragent.indexOf("Netscape")) {
/** instructions éxécutées si le navigateur client est Netscape **/
}Exemple de surcharge de la méthode init() :
3.1 Méthode GET
| <form ACTION="/servlets/cherche" METHOD="GET"> <B>Chercher</B> <input TYPE="text" NAME="query" VALUE="" SIZE=20> <B>avec</B> <select NAME="type"> <option VALUE="Google" SELECTED>Google <option VALUE="Altavista">Altavista <option VALUE="Metacrawler">Metacrawler <option VALUE="Dictionnaire">Dictionnaire </select> <input TYPE="submit" VALUE="Now!"> </form> |
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:
//affiche le formulaire
} else {
//traite le formulaire
}
3.2 Méthode PUT
HttpSession session = request.getSession(true);
Rien de plus pour l'instant.
Bien que les classes et méthodes utilsées pour le maintien de session utilisent les cookies sans que l'on ai besoin d'y toucher directement, l'API Servlet comprends la classe javax.servlet.http.Cookie qui permet de manipuler des objets Cookies
L'instruction suivante permet de récupèrer l'ensemble des cookies:
Cookie[] cookies = req.getCookies();
La classe Cookie possède alors les méthodes suivantes:
public String getName() pour récupèrer le nom du cookie
public String getDomain() pour récupèrer le domaine
public String getValue() pour récupérer la valeur stockée dans le cookie
Un exemple d'un tel servlet est le suivant :
...
}
Le serveur s'assure alors que la méthode service n'est pas appelée plus d'une seule fois simultanément.
Un moteur de base de données comporte bien souvent des systèmes de verrouillage d'accès et n'est donc pas un bon exemple. Prenons plutot l'exemple d'un fichier texte contenant un nombre plusieurs clients pourraient vouloir modifier simultanément
La deuxième possibilité est de synchroniser l'accès à la ressource.
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 :
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
import java.sql.*;
public class jdbctest {
public static void main(String args[])
{
String url = "jdbc:postgresql:test";
Connection con;
Statement stmt;try
{
Class.forName("postgresql.Driver");
}
catch(java.lang.ClassNotFoundException e)
{
System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage());
}try
{
con = DriverManager.getConnection(url, "olly", "");
stmt = con.createStatement();ResultSet rs = stmt.executeQuery("select * from stories");
while (rs.next())
{
String s = rs.getString("story_title");
System.out.println(s);
}
stmt.close();
con.close();}
catch (SQLException ex)
{
System.err.println("SQLException: " + ex.getMessage());
}
}
}
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.
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:
servlet.org.apache.cocoon.Cocoon.initArgs=properties=[path-to-cocoon]/conf/cocoon.properties
Il faut également dire à Apache d'associer tous fichier xml avec Cocoon. Pour cela, il faut ajouter:
Cas 1: Le fichier XML existe deja
Le fichier XML est le suivant, et nous voulons afficher ses infos dans un document HTML, sous forme de table.
<story>
<story_title>Linuxcare Responds To Tim O'Reilly's Article</story_title>
<story_url>http://slashdot.org/article.pl?sid=00/05/15/0254252</story_url>
<story_date>2000-05-15</story_date>
</story>
<story>
<story_title>New Internet VCR Service</story_title>
<story_url>http://slashdot.org/article.pl?sid=00/05/14/2048217</story_url>
<story_date>2000-05-15</story_date>
</story>
</stories>
La feuille de style que l'on doit appliquer pour obtenir le résultat souhaité est très simple:
<xsl:template match="stories">
<table>
<xsl:apply-templates select="story"/>
</table>
</xsl:template>
<xsl:template match="story">
<tr><td><a href="{story_url}"><xsl:value-of select="story_title"/></a></td><td><xsl:value-of select="story_date"/></td></tr>
</xsl:template>
</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.
<stories>
.
.
<!-- 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.
<page>
<connectiondefs>
<connection name="test_connection">
<driver>postgresql.Driver</driver>
<dburl>jdbc:postgresql:test</dburl>
<username>olly</username>
<password></password>
</connection>
</connectiondefs>
<query connection="test_connection" doc-element="stories" row-element="story">
select story_title, story_url, story_date from stories
</query>
</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.
<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>
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();
}
}
}
Publié par unpetitannuaire à 20:39:40 dans un petit annuaire | Commentaires (0) | Permaliens
XALAN_HOME=/path/to/xalan.jar
XERCES_HOME=/path/to/xerces.jar
export CLASSPATH=$CLASSPATH:$XALAN_HOME:$XERCES_HOME
/xslt
/xslt/WEB-INF
/xslt/WEB-INF/classes
To do so, type the following commands:
> cd $TOMCAT_HOME/webapps
> mkdir xslt
> cd xslt
> mkdir WEB-INF
> cd WEB-INF
> mkdir classes
> cd classes
The /xslt/WEB-INF/classes directory is the place where you will store all the servlets classes described below.
Once the directories are created, you should install a servlet context, by editing the file $TOMCAT_HOME/conf/server.xml, and adding the following lines:
<Context path="/xslt" docBase="webapps/xslt" debug="0" reloadable="true" >
</Context>
path="/xslt" tells Tomcat that all requests starting with /onjava belong to the onjava web application.
docBase="webapps/xslt" tells the servlet container that the web application is located on webapps/xslt"
Once you have done these operations, you need to restart Tomcat.
If you want the output to be displayed on the screen, simply omit the -out flag and argument.
import org.xml.sax.SAXException;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
public class XalanXslProcessorBean {
TransformerFactory tFactory;
//the constructor simply gets a new TransformerFactory instance
public XalanXslProcessorBean() {
tFactory = TransformerFactory.newInstance();
}
//this method takes as input a XML source, a XSL source, and returns the output of the transformation to the servlet output stream
public void process(StreamSource xmlSource,
StreamSource xslSource,
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException, SAXException {
try {
Templates templates = tFactory.newTemplates(xslSource);
Transformer transformer = templates.newTransformer();
transformer.transform(xmlSource, new StreamResult(response.getOutputStream()));
}
catch (Exception e) {
color=#33ff33>//should log some message here
}
}
}
<story>
<title>NASA Proposes Launch Solar Sail Vehicle For 2010</title>
<url>http://slashdot.org/article.pl?sid=00/05/15/058238</url>
<time>2000-05-15 07:54:15</time>
<author>timothy</author>
<department>ralph-nader-will-have-to-hire-a-chase-car</department>
<topic>space</topic>
<comments>99</comments>
<section>articles</section>
<image>topicspace.gif</image>
</story>
<story>
<title>Linuxcare Responds To Tim O'Reilly's Article</title>
<url>http://slashdot.org/article.pl?sid=00/05/15/0254252</url>
<time>2000-05-15 02:57:07</time>
<author>timothy</author>
<department>consider-source-horses-mouth-grain-of-salt</department>
<topic>linuxbiz</topic>
<comments>142</comments>
<section>articles</section>
<image>topiclinuxbiz.gif</image>
</story>
<story>
<title>New Internet VCR Service</title>
<url>http://slashdot.org/article.pl?sid=00/05/14/2048217</url>
<time>2000-05-14 20:51:57</time>
<author>timothy</author>
<department>this-is-cool-but-can-they-do-that?</department>
<topic>news</topic>
<comments>189</comments>
<section>articles</section>
<image>topicnews.gif</image>
</story>
<story>
<title>Google Releases WAP Search Tool</title>
<url>http://slashdot.org/article.pl?sid=00/05/14/1240252</url>
<time>2000-05-14 17:49:11</time>
<author>emmett</author>
<department>wireless</department>
<topic>internet</topic>
<comments>141</comments>
<section>articles</section>
<image>topicinternet.jpg</image>
</story>
<story>
<title>No More Unreal Ports For Linux?</title>
<url>http://slashdot.org/article.pl?sid=00/05/14/1439224</url>
<time>2000-05-14 16:32:11</time>
<author>timothy</author>
<department>one-web-one-program-happy-mothers-day</department>
<topic>games</topic>
<comments>250</comments>
<section>articles</section>
<image>topicgames.jpg</image>
</story>
<story>
<title>Pioneer Introduces 1st DVD Recorder (In Japan)</title>
<url>http://slashdot.org/article.pl?sid=00/05/14/152210</url>
<time>2000-05-14 15:50:48</time>
<author>CmdrTaco</author>
<department>steam-rising-from-the-riaas-forehead</department>
<topic>tv</topic>
<comments>98</comments>
<section>articles</section>
<image>topictv.jpg</image>
</story>
<story>
<title>QuakeForge And QuakeWorld Forever Merge</title>
<url>http://slashdot.org/article.pl?sid=00/05/14/1447248</url>
<time>2000-05-14 15:07:27</time>
<author>CmdrTaco</author>
<department>and-then-there-was-one</department>
<topic>quake</topic>
<comments>57</comments>
<section>articles</section>
<image>topicquake.gif</image>
</story>
<story>
<title>What Happens When Open Source And Work Collide?</title>
<url>http://slashdot.org/article.pl?sid=00/05/09/016208</url>
<time>2000-05-14 14:04:07</time>
<author>Cliff</author>
<department>sticky-situations</department>
<topic>programming</topic>
<comments>170</comments>
<section>askslashdot</section>
<image>topicprogramming.gif</image>
</story>
<story>
<title>Black Holes Don't Exist???</title>
<url>http://slashdot.org/article.pl?sid=00/05/14/1339252</url>
<time>2000-05-14 13:39:24</time>
<author>Roblimo</author>
<department>pop-science-can-be-fun</department>
<topic>science</topic>
<comments>162</comments>
<section>articles</section>
<image>topicscience.gif</image>
</story>
<story>
<title>Los Alamos Lab: We're OK, You're OK</title>
<url>http://slashdot.org/article.pl?sid=00/05/14/0143228</url>
<time>2000-05-14 04:44:44</time>
<author>timothy</author>
<department>sir-please-step-*away*-from-the-plutonium-bin</department>
<topic>news</topic>
<comments>278</comments>
<section>articles</section>
<image>topicnews.gif</image>
</story>
</backslash>
Here is the HTML stylesheet (named slashdot.xsl) we will use:
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="backslash/story"/>
</body>
</html>
</xsl:template>
<xsl:template match="backslash/story">
<li><a href="{url}"><xsl:value-of select="title"/></a></li>
</xsl:template>
</xsl:stylesheet>
import javax.xml.transform.stream.*;
public class XslProcessorServlet extends HttpServlet {
XalanXslProcessorBean processor;
public void init(ServletConfig config) {
processor = new XalanXslProcessorBean();
}
public void doGet (HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
//sets the Content-Type portion of the HTTP header to text/html
response.setContentType("text/html");
try {
processor.process(new StreamSource("slashdot.xml"), new StreamSource("slashdot.xsl"), request, response);
}
catch (Exception e) {
}
}
}
Since slashdot.xml is often updated, you may prefer to fetch it directly from the slashdot.org site: you then need to change the processor.process(...) line to: processor.process(new StreamSource(new InputStreamReader((new URL("http://slashdot.org/slashdot.xml")).openStream())), new StreamSource("slashdot.xsl"), request, response);
Then restart your servlet container if needed, and reload the page.
Suppose that you want to make your content available to both HTML and WML navigators. Basically, you just need a XSLT stylesheet that can transform XML to HTML, and another one that can transform XML to WML (Wireless Meta Language). You then need to implement a mechanism that can use the appropriate stylesheet, depending on the navigator information contained in the HTTP request header.
Here is the WML stylesheet. Note the xsl:output tag, which is the only way to produce the <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml"> string in the WML output.
<xsl:template match="/">
<wml>
<template>
<do type="prev" name="Previous" label="Back">
<prev/>
</do>
</template>
<card id="card1" title="Slashdot news">
<p>
<xsl:apply-templates select="backslash/story"/>
</p>
</card>
</wml>
</xsl:template>
<xsl:template match="backslash/story">
<a href="{url}"><xsl:value-of select="title"/></a><br/>
</xsl:template>
</xsl:stylesheet>
Note that the following servlet code now contains some code to fetch the user agent from the HTTP header, and uses the WML stylesheet when the user agent string contains the word (Nokia). Obviously, this only works with a Nokia phone or with some Nokia emulator.
import javax.xml.transform.stream.*;
public class XslProcessorServlet extends HttpServlet {
XalanXslProcessorBean processor;
public void init(ServletConfig config) {
processor = new XalanXslProcessorBean();
}
public void doGet (HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
//fetch the user agent part of the HTTP header
String useragent = request.getHeader("user-agent");
//if the user agent contains the string "Nokia", then use the WML stylesheet, otherwise use the HTML one
StreamSource xslsource;
if (useragent.indexOf("Nokia") >= 0) {
color=#33cc00> //send the correct Content-Type
response.setContentType("text/vnd.wap.wml");
xslsource = new StreamSource("slashdot_wml.xsl");
} else {
response.setContentType("text/html");
xslsource = new StreamSource("slashdot_html.xsl");
}
try {
processor.process(new StreamSource("slashdot.xml"), xslsource, request, response);
}
catch (Exception e) {
}
}
}
Publié par unpetitannuaire à 20:38:56 dans un petit annuaire | Commentaires (0) | Permaliens