Acceptlanguage
|
Referer
|
l'URL qui a été demandée par le navigateur
|
Connection
|
le mode de connexion demandée par le navigateur. Keep-alive veut dire que le serveur ne doir pas couper la connexion après avoir servi la page demandée au navigateur. Si ce dernier découvre que la page reçue contient des liens sur des images par exemples, il pourra faire de nouvelles requêtes au serveur pour les demander sans avoir besoin de créer une nouvelle connexion. C'est le navigateur qui prendra alors l'initiative de fermer la connexion lorsqu'il aura reçu tous les éléments de la page.
|
2.1.4 Récupérer des informations d'environnement
La servlet qui suit montre comment accéder à des informations d'environnement d'exécution de la servlet. Certaines de celles-ci sont envoyées sous forme d'entêtes http par le navigateur et sont donc récupérables par la méthode précédente.
Le code de la servlet est le suivant :
import .*; import javax.servlet.*; import .*; public class RequestInfo extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println(""); out.println(""); out.println("");
out.println("Request Information Example"); out.println(""); out.println("");
out.println("
Request Information Example
");
|
out.println("Method: " + request.getMethod()); out.println("Request URI: " + request.getRequestURI()); out.println("Protocol: " + request.getProtocol()); out.println("PathInfo: " + request.getPathInfo()); out.println("Remote Address: " + request.getRemoteAddr());
|
out.println(""); out.println(""); }
{
doGet(request, response);
}
}
|
Les informations sont ici obtenues par diverses méthodes :
out.println("Method: " + request.getMethod()); out.println("Request URI: " + request.getRequestURI()); out.println("Protocol: " + request.getProtocol()); out.println("PathInfo: " + request.getPathInfo()); out.println("Remote Address: " + request.getRemoteAddr());
Une liste de certaines des méthodes disponibles et leur signification est la suivante :
méthode
|
signification
|
getServerName()
|
le nom du serveur Web
|
getServerPort()
|
le port de travail du serveur web
|
getMethod()
|
la méthode GET ou POST utilisée par le navigateur pour faire sa requête
|
getRemoteHost()
|
le nom de la machine client à partir de laquelle le navigateur a fait sa requête
|
getRemoteAddr()
|
l'adresse IP de cette même machine
|
getContentType()
|
le type de contenu envoyé par le navigateur (entête http Content-type)
|
getContentLength( le nombre de caractères envoyés par le navigateur (entête http Content-length)
)
getProtocol() la version du protocole http demandée par le navigateur
getRequestURI() l'URI demandée par le navigateur. Correspond à la partie de l'URL placée après l'identification hote:port dans http://hote:port/URI
2.1.5 Créer une servlet avec JBuilder, la déployer avec Tomcat
Nous décrivons maintenant comment créer et exécuter une servlet Java. On utilisera deux outils : Jbuilder pour compiler la servlet et Tomcat pour l'exécuter. Tomcat pourrait seul suffire. Néanmoins il offre des capacités de débogage limitées. Nous reprenons l'exemple développé précédemment qui affiche les paramètres reçus par le serveur. La servlet envoie tout d'abord le formulaire de saisie suivant :
La réponse envoyée par la servlet :
import .*; import .*; import javax.servlet.*; import .*; public class myRequestParamExample extends HttpServlet { String title="Récupération des paramètres d'un formulaire";
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.setContentType("text/html"); PrintWriter out = response.getWriter();
|
out.println(""); out.println(""); out.println("");
out.println("" + title + ""); out.println("");
out.println(""); out.println("
" + title + "
");
String firstName = request.getParameter("firstname"); String lastName = request.getParameter("lastname"); if (firstName != null || lastName != null) { out.println("firstname= " + firstName + " "); out.println("lastname= " + lastName);
} else {
out.println("pas de paramètres");
}
out.println("
");
out.print("
"); out.println("firstname= "); out.println(" ");
out.println("lastname= "); out.println(" ");
out.println("");
out.println(""); out.println(""); out.println("");
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
doGet(request, response); }
}
|
Ø créez un projet myRequestParamExample avec Jbuilder et y inclure le programme précédent.
Ø activer l'option Tools/Configure JDKs ou (Options/Configurer les JDK)
Dans la partie JDK Settings ci-dessus, on a normalement dans le champ Name un JDK 1.3.1. Si vous avez un JDK plus récent, utilisez le bouton Change pour désigner le répertoire d'installation de ce dernier. Ci-dessus, on a désigné le répertoire E:\Program Files\jdk14 où était installé un JDK 1.4. Désormais, JBuilder utilisera ce JDK pour ses compilations et exécutions. Dans la partie (Class, Source, Documentation) on a la liste de toutes les bibliothèques de classes qui seront explorées par JBuilder, ici les classes du JDK 1.4. Les classes de celui-ci ne suffisent pas pour faire du développement web en Java. Pour ajouter d'autres bibliothèques de classes on utilise le bouton Add et on désigne les fichiers .jar supplémentaires que l'on veut utiliser. Les fichiers .jar sont des bibliothèques de classes. Tomcat 4.x amène avec lui toutes les bibliothèques de classes nécessaires au développement web. Elles se trouvent dans \common\lib où est le répertoire d'installation de Tomcat :
Avec le bouton Add, on va ajouter ces bibliothèques, une à une, à la liste des bibliothèques explorées par JBuilder :
A partir de maintenant, on peut compiler des programmes java conformes à la norme J2EE, notamment les servlets Java. Jbuilder ne sert qu'à la compilation, l'exécution étant ultérieurement assurée par Tomcat.
Ø maintenant vous pouvez compiler le programme et produire la servlet myRequestParamExample.class. Où placer cette servlet ? Si la configuration initiale de Tomcat n'a pas été changée, les .class des servlets doivent être placés dans \webapps\examples\WEB-INF\classes (Tomcat 4.x).
Ø vérifiez que Tomcat est lancé et avec un navigateur demandez l'URL http://localhost:8080/examples/servlet/myRequestParamExample :
2.1.6 Exemples
Pour les exemples qui suivent, nous avons utilisé la méthode décrite précédemment :
Ø déploiement de la servlet XX.class dans \webapps\examples\WEB-INF\classes
Ø Tomcat lancé, demander avec un navigateur l'URL http://localhost:8080/examples/servlet/XX
2.1.6.1 Génération dynamique de formulaire - 1
Nous prenons comme exemple la génération d'un formulaire n'ayant qu'un contrôle : une liste. Le contenu de cette liste est construit dynamiquement avec des valeurs prises dans un tableau. Dans la réalité, elles sont souvent prises dans une base de données. Le formulaire est le suivant :
Si sur l'exemple ci-dessus, on fait Envoyer, on obtient la réponse suivante :
On remarquera que l'URL qui fait la réponse est la même que celle qui affiche le formulaire. Ici on a une servlet qui traite elle-même la réponse au formulaire qu'elle a envoyé. C'est un cas courant. Le code HTML du formulaire est le suivant :
Génération de formulaire
Choississez un nombre
|
|
zéro un deux |
troisquatre cinq six sept huit neuf |
|
|
On notera que les valeurs envoyées par le formulaire le sont par la méthode POST. Le code HTML de la réponse :
Voici ma réponse
Vous avez choisi le nombre
neuf
Le code de la servlet qui génère ce formulaire et cette réponse est le suivant :
import .*; import javax.servlet.*; import .*;
public class gener1 extends HttpServlet{
// variables d'instance
private String title="Génération d'un formulaire";
|
private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six", "sept","huit","neuf"};
|
private final String HTML1=
"" +
"" +
"Génération de formulaire"+ "" +
"" +
"
Choississez un nombre
"+
"
" +
"
"; private final String HTML2=""; private final String HTML3="
\n\n";
// GET
public void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
// on envoie le formulaire
PrintWriter out=response.getWriter();
// début out.println(HTML1);
|
// combo
out.println(" "+valeurs[i]+"
}//for
out.println("");
|
// fin formulaire
out.println(HTML2+HTML3);
}//GET
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response)
throws IOException, ServletException{
|
// on récupère le choix de l'utilisateur
String choix=request.getParameter("cmbValeurs");
if(choix==null) doGet(request,response);
|
// on prépare la réponse
String réponse="Voici ma réponse";
réponse+="Vous avez choisi le nombre
"+choix+"
";
// on indique au client le type de document envoyé response.setContentType("text/html");
// on envoie le formulaire
PrintWriter out=response.getWriter(); out.println(réponse);
|
}//POST
}//classe
La méthode doGet sert à générer le formulaire. Il y a une partie dynamique qui est le contenu de la liste, contenu provenant ici d'un tableau. La méthode doPost sert à générer la réponse. Ici la seule partie dynamique est la valeur du choix fait par l'utilisateur dans la liste du formulaire. Cette valeur est obtenue par request.getParameter("cmbValeurs") où cmbValeurs est le nom HTML de la liste :
On notera pour terminer les points suivants :
Ø le navigateur envoie les valeurs du formulaire à la servlet qui a généré le formulaire parce que la balise
n'a pas d'attribut . Dans ce cas, le navigateur envoie les données saisies dans le formulaire à l'URL qui l'a fourni.
Ø la balise
précise que les données du formulaire doivent être envoyées par la méthode POST. C'est pour cela que ces valeurs sont récupérées par la méthode doPost de la servlet.
2.1.6.2 Génération dynamique de formulaire - 2
Nous reprenons l'exemple précédent en le modifiant de la façon suivante. Le formulaire proposé est toujours le même :
Dans la réponse, on renvoie le formulaire, le nombre choisi par l'utilisateur étant indiqué dessous. Par ailleurs, ce nombre est celui qui apparaît comme sélectionné lorsque la liste est affichée. L'utilisateur peut alors choisir un autre nombre :
puis faire Envoyer. Il obtient la réponse suivante :
Le code la servlet appelée est la suivante :
import .*; import javax.servlet.*; import .*;
public class gener2 extends HttpServlet{
// variables d'instance
private String title="Génération d'un formulaire";
private final String[] valeurs={"zéro","un","deux","trois","quatre","cinq","six",
"sept","huit","neuf"}; private final String HTML1=
"" +
"" +
"Génération de formulaire"+
"" +
"" +
"
Choisissez un nombre
"+
"
" +
"
"; private final String HTML2="
\n"; private final String HTML3="\n";
// GET
public void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
|
// on récupère l'éventuel choix de l'utilisateur String choix=request.getParameter("cmbValeurs");
if(choix==null) choix="";
|
// on indique au client le type de document envoyé response.setContentType("text/html");
// on envoie le formulaire
PrintWriter out=response.getWriter();
// début
out.println(HTML1);
// combo
out.println("
|
String selected="";
for (int i=0;i<valeurs.length;i++){
|
if(valeurs[i].equals(choix)) selected="selected"; else selected=""; out.println("
"+valeurs[i]+"
");
|
}//for
out.println("");
// suite formulaire out.println(HTML2);
|
if(! choix.equals("")){
// on affiche le choix de l'utilisateur
out.println("
Vous avez choisi le nombre
"+choix+"
");
}//if
|
// fin du formulaire out.println(HTML3); }//GET
|
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
// on renvoie sur GET doGet(request,response);
}//POST
|
}//classe
Ø on vérifie si le paramètre cmbValeurs a une valeur.
Ø si c'est le cas, lorsqu'on élabore le contenu de la liste, on compare chaque élément de celle-ci au choix de l'utilisateur pour mettre l'attribut selected à l'élément choisi par l'utilisateur : élément. Par ailleurs, on affiche sous le formulaire la valeur du choix.
2.1.6.3 Génération dynamique de formulaire - 3
Nous reprenons le même problème que précédemment mais cette fois-ci les valeurs sont prises dans une base de données. Celle-ci est dans notre exemple une base MySQL :
Ø la base s'appelle dbValeurs
Ø son propriétaire est admDbValeurs ayant le mot de passe mdpDbValeurs
Ø la base a une unique table appelée tvaleurs
Ø cette table n'a qu'un champ entier appelé valeur
E:\Program Files\EasyPHP\mysql\bin>mysql --database=dbValeurs --user=admDbValeurs --password=mdpDbVa leurs
mysql> show tables;
+---------------------+
| Tables_in_dbValeurs |
+---------------------+
| tvaleurs |
+---------------------+ 1 row in set (0.00 sec)
mysql> describe tvaleurs;
+--------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------+------+-----+---------+-------+
| valeur | int(11) | | | 0 | | +--------+---------+------+-----+---------+-------+
mysql> select * from tvaleurs;
+--------+
| valeur |
+--------+
| 0 |
| 1 |
| 2 |
| 3 |
| 4 |
| 6 |
| 5 |
|
| 7 |
| 8 |
| 9 |
+--------+
10 rows in set (0.00 sec)
La base MySQL dbValeurs a été rendu accessible par un pilote ODBC pour MySQL. Son nom DSN (Data Source Name) est odbc-valeurs. Le code de la servlet est le suivant :
import .*; import javax.servlet.*; import .*; import .*; import .*;
public class gener3 extends HttpServlet{
private final String title="Génération d'un formulaire";
// la base de données des valeurs de liste
|
private final String DSNValeurs="odbc-valeurs"; private final String admDbValeurs="admDbValeurs"; private final String mdpDbValeurs="mdpDbValeurs";
|
// valeurs de liste
private String[] valeurs=null;
// msg d'erreur
private String msgErreur=null;
// code HTML
private final String HTML1=
"" +
"" +
"Génération de formulaire"+
"" +
"" +
"
Choisissez un nombre
"+
"
" +
"
"; private final String HTML2="
\n"; private final String HTML3="\n";
// GET
public void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
// on indique au client le type de document envoyé response.setContentType("text/html");
// flux de sortie
PrintWriter out=response.getWriter();
|
// l'initialisation de la servlet s'est-elle bien passée ? if (msgErreur!=null){
// il y a eu une erreur - on génère une page d'erreur out.println(""+title+""); out.println("
Application indisponible ("+msgErreur+
")");
return;
}//if
|
// on récupère l'éventuel choix de l'utilisateur String choix=request.getParameter("cmbValeurs"); if(choix==null) choix="";
// on envoie le formulaire
// début
out.println(HTML1);
// combo
out.println("
for (int i=0;i<valeurs.length;i++){ if(valeurs[i].equals(choix)) selected="selected"; else selected=""; out.println("
"+valeurs[i]+"
");
}//for
out.println("");
// suite formulaire out.println(HTML2); if(! choix.equals("")){
// on affiche le choix de l'utilisateur
out.println("
Vous avez choisi le nombre
"+choix+"
");
}//if
// fin du formulaire out.println(HTML3);
}//GET
|
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
// on renvoie sur GET doGet(request,response);
}//POST
|
// initialisation de la servlet public void init(){
// de nom DSN : DSNvaleurs
Connection connexion=null;
Statement st=null; ResultSet rs=null; try{
// connexion à la base ODBC
Class.forName(".JdbcOdbcDriver");
connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
// objet Statement
st=connexion.createStatement();
// exécution requête select pour récupérer les valeurs rs=st.executeQuery("select valeur from Tvaleurs");
// les valeurs sont récupérées et mises dans un tableau dynamique
ArrayList lstValeurs=new ArrayList(); while(()){
// on enregistre la valeur dans la liste (rs.getString("valeur"));
}//while
// transformation liste --> tableau valeurs=new String[()]; for (int i=0;i<();i++){ valeurs[i]=(String)(i);
}
}catch(Exception ex){
// problème
msgErreur=ex.getMessage();
}finally{ try{rs.close();}catch(Exception ex){} try{st.close();}catch(Exception ex){} try{connexion.close();}catch(Exception ex){}
}//try
}//init
|
}//classe
|
Les points importants à noter sont les suivants :
1. Une servlet peut être initialisée par une méthode dont la signature doit être public void init(). Cette méthode n'est exécutée qu'au chargement initial de la servlet
2. Une fois chargée, une servlet reste en mémoire tout le temps. Cela signifie que lorsqu'elle a servi un client, elle n'est pas déchargée. Elle répond ainsi plus vite aux requêtes des clients.
3. Dans notre servlet, une liste de valeurs doit être cherchée dans une base de données. Cette liste ne changeant pas au cours du temps, la méthode init est le moment idéal pour la récupérer. La base n'est ainsi accédée qu'une fois par la servlet, au moment du chargement initial de celle-ci, et non pas à chaque requête d'un client.
5. L'écriture de la méthode init utilise un accès classique à une base de données avec les pilotes Odbc-Jdbc. Si besoin est, le lecteur est invité à revoir les méthodes d'accès aux bases de données JDBC.
Lorsqu'on exécute la servlet et que le serveur MySQL n'a pas été lancé, on a la page d'erreur suivante :
Si maintenant, on lance le serveur MySQL, on obtient la page :
Si on choisit le nombre 6 et qu'on "envoie" :
2.1.6.4 Récupérer les valeurs d'un formulaire
Nous reprenons un exemple déjà rencontré, celui du formulaire web suivant :
Le code HTML du formulaire est le suivant :
Oui
Non
Cases à cocher 1 2 3 Champ de saisie Mot de passe Boîte de saisie ligne1 ligne2 ligne3 combo choix1 choix2choix3 liste à choix simple liste1liste2liste3liste4liste5 liste à choix multiple liste1liste2
balises
|
|
|
Etes-vous marié(e) |
|
liste3
liste4
liste5
bouton
rétablir
La balise
du formulaire a été définie comme suit :
Le navigateur "postera" les valeurs du formulaire à l'URL http://localhost:8080/examples/servlet/parameters qui est l'URL d'une servlet gérée par Tomcat et qui affiche les valeurs du formulaire précédent. Si on appelle la servlet parameters directement, on a les résultats suivants :
Si le formulaire saisi est celui-ci :
et qu'on appuie sur le bouton Envoyer (de type submit), la servlet parameters est cette fois appelée avec des paramètres. Elle renvoie alors la réponse suivante :
On retrouve bien dans cette réponse, les valeurs saisies dans le formulaire. Le code de la servlet est la suivante :
"); out.println("
"); out.println("
"); out.println("
");
out.println("
"); out.println("
"); for(int i=0;i<lignes.length;i++)
out.println("
"); out.println("
");
out.println("
"); if(lst2==null)
out.println("
"); else
for(int i=0;i<lst2.length;i++)
out.println("
"); out.println("
");
out.println(""); out.println(""); }
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException
{
// renvoie sur GET doGet(request,response);
}
}
import .*; import .*; import javax.servlet.*; import .*;
public class parameters extends HttpServlet{
// variables d'instance
String title="Récupération des paramètres d'un formulaire";
private String getParameter(HttpServletRequest request, String contrôle){ // rend la valeur request.getParameter(contrôle) ou "" si elle n'existe pas String valeur=request.getParameter(contrôle); if(valeur==null) return ""; else return valeur; }//getParameter
// GET
{
// on commence par récupérer les paramètres du formulaire
String R1=getParameter(request,"R1");
String C1=getParameter(request,"C1");
String C2=getParameter(request,"C2");
String C3=getParameter(request,"C3");
String txtSaisie=getParameter(request,"txtSaisie");
String txtMdp=getParameter(request,"txtMdp");
|
String areaSaisie=getParameter(request,"areaSaisie");
String[] lignes=areaSaisie.split("\\r\\n");
|
String cmbValeurs=getParameter(request,"cmbValeurs"); String lst1=getParameter(request,"lst1");
|
String[] lst2=request.getParameterValues("lst2");
|
String secret=getParameter(request,"secret");
// on indique le contenu du document response.setContentType("text/html");
// on envoie le document
PrintWriter out = response.getWriter(); out.println(""); out.println(""); out.println("");
out.println("" + title + ""); out.println("");
out.println(""); out.println("
" + title + "
"); out.println("
");
out.println("
");
out.println("
|
R1 |
"+R1+" |
C1 |
"+C1+" |
C2 |
"+C2+" |
C3 |
"+C3+" |
txtSaisie |
"+txtSaisie+" |
txtMdp |
"+txtMdp+" |
areaSaisie["+i+"] |
"+lignes[i]+" |
cmbValeurs |
"+cmbValeurs+" |
lst1 |
"+lst1+" |
lst2 |
|
lst2 |
"+lst2[i]+" |
secret |
"+secret+" |
On retrouve dans ce code les techniques présentées précédemment dans un autre exemple. On notera deux points :
2. le contrôle areaSaisie est un champ de saisie multilignes. request.getParameter("areaSaisie") donne le contenu du champ sous la forme d'une unique chaîne de caractères. Si dans celle-ci, on veut récupérer les différentes lignes qui la forment on pourra utiliser la fonction split de la classe String. Le code suivant
String areaSaisie=getParameter(request,"areaSaisie");
String[] lignes=areaSaisie.split("\\r\\n");
récupère les lignes du champ de saisie. Ces lignes sont terminées par les caractères \r\n (0D0A).
Pour faire les tests on a :
Ø construit et compilé la servlet parameters avec JBuilder comme il a été expliqué précédemment
Ø placé la classe générée dans \webapps\examples\WEB-INF\classes où est le répertoire d'installation de Tomcat.
Ø demandé l'URL dont le code a été présenté plus haut Ø rempli le formulaire et appuyé sur le bouton Envoyer.
2.1.6.5 Récupérer les entêtes HTTP d'un client web
Nous reprenons le même exemple que précédemment mais en réponse au client web qui a envoyé les valeurs du formulaire, nous lui envoyons les entêtes HTTP qu'il a envoyés en même temps. Nous introduisons un seul changement dans notre formulaire :
Les valeurs du formulaires seront envoyées par la méthode GET à une servlet java appelée headers placée dans \webapps\examples\WEB-INF\classes. La servlet headers a été construite et compilée avec JBuilder :
import .*; import .*; import javax.servlet.*; import .*; public class headers extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// on fixe la nature du document response.setContentType("text/html");
// on obtient un flux d'écriture
PrintWriter out = response.getWriter();
// affichage liste des entêtes HTTP
while (e.hasMoreElements()) {
String name = (String)e.nextElement(); String value = request.getHeader(name);
out.println(""+name + " = " + value + " ");
} }//GET
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{ //GET
doGet(request,response);
}//POST
}
|
On demande l'URL et on fait Envoyer sans modifier le formulaire. On obtient la réponse suivante
:
On remarquera l'URL paramétrée présente dans le champ Address du navigateur qui montre la façon (GET) utilisée pour transmettre les paramètres. Nous reprenons le même exemple mais en modifiant la façon d'envoyer les paramètres (POST) :
On obtient la nouvelle réponse suivante :
On remarquera les entêtes HTTP content-type et content-length caractéristiques d'un envoi par POST. Par ailleurs, on notera que dans le champ Address du navigateur, les valeurs du formulaire n'apparaissent plus.
Les pages JSP (Java Server Pages) sont une autre façon d'écrire des applications serveurs web. En fait ces pages JSP sont traduites en servlets avant d'être exécutées et on retrouve alors la technologie des servlets. Les pages JSP permettent de mieux mettre en relief la structure des pages HTML générées. Nous présentons ci-dessous des exemples dont certains sont accessibles en suivant le lien JSP de la page d'accueil de Tomcat :
2.2.1 Récupérer des informations d'environnement
Nous reprenons ici un exemple déjà traité avec une servlet : afficher les variables d'environnement d'une servlet. C'est l'exemple snoop des exemples JSP :
Le code source de la page JSP se trouve dans \jakarta-tomcat\examples\jsp\snp\ (Tomcat 3.x) ou \examples\jsp\snp\ (Tomcat 4.x)
On note les points suivants :
<%= request.getRemoteHost()%>
|
Ø cet exemple utilise les méthodes de l'objet Java request qui est l'objet request déjà rencontré dans l'étude des servlets. C'est donc un objet HttpServletRequest. Ainsi la balise sera remplacée dans le code HTML par le nom de la machine du client web qui a fait la requête.
Ø on peut arriver au même résultat avec une servlet mais ici la structure de la page web est plus évidente.
2.2.2 Récupérer les paramètres envoyés par le client web
Nous reprenons ici l'exemple déjà étudié avec une servlet. Il est présenté un formulaire au navigateur :
En réponse à la requête ci-dessus, le navigateur reçoit la page suivante :
Le code de la page JSP est le suivant :
<%
// variables locales à la procédure principale
Stringtitle="Récupération des paramètres d'un formulaire";
StringfirstName = request.getParameter("firstname");
StringlastName = request.getParameter("lastname");
%>
|
<%= title %>
<%= title %>
|
<%
if(firstName !=null|| lastName != null) { out.println("firstname= " + firstName + " ");
out.println("lastname= " + lastName);
}else{
out.println("pas de paramètres");
}
%>
|
firstname=
lastname=
|
Ø Si on retrouve la balise <%=expression%> déjà rencontrée dans l'exemple précédent, une nouvelle balise apparaît <%instructions Java;%>. La balise <% introduit du code Java. Ce code se termine à la rencontre de la balise de fermeture de code %>.
Ø Pour inclure des parties dynamiques dans le code HTML deux méthodes sont possibles : <%= expression %> ou out.println(expression). L'objet out est un flux de sortie analogue à celui de même nom rencontré dans les exemples de servlets mais pas du même type : c'est un objet JspWriter et non un PrintWriter. Il permet d'écrire dans le flux HTML avec le méthodes print et println.
Ø La page JSP reflète mieux la structure de la page HTML générée que la servlet équivalente.
2.2.3 Les balises JSP
Voici une liste de balises qu'on peut rencontrer dans une page JSP et leur signification.
balise
|
signification
|
|
commentaire HTML. Est envoyé au client.
|
<%-- commentaire --%>
|
commentaire JSP. N'est pas envoyé au client.
|
<%! déclarations, méthodes %>
|
déclare des variables globales et méthodes. Les variables seront connues dans toutes les méthodes
|
<%= expression %>
|
la valeur de expression sera intégrée dans la page HTML à la place de la balise
|
<% code Java %>
|
contient du code Java qui fera partie de la méthode principale de la page JSP
|
<%@ page attribut1=valeur1 attribut2=valeur2 … %>
|
|
fixe des attributs pour la page JSP. Par exemple : import=".*,.*" pour préciser les bibliothèques nécessaires à la page JSP extends="uneClasseParent" pour faire dériver la page JSP d'une autre classe
|
2.2.4 Les objets implicites JSP
Dans les exemples précédents, nous avons rencontré deux objets non déclarés : request et out. Ce sont deux des objets qui sont automatiquement définis dans la servlet dans laquelle est convertie la page JSP. On les appelle des objets implicites ou prédéfinis. Il en existe d'autres mais ce sont les plus utilisés avec l'obet response :
objet
|
signification
|
HttpServletRequest request
|
HttpServletResponse response
|
l'objet avec lequel on peut construire la réponse du serveur Web à son client. Permet de fixer les entêtes http à envoyer au client Web.
|
JspWriter out
|
le flux de sortie qui nous permet d'envoyer du code HTML au client (print,println)
|
2.2.5 La transformation d'une page JSP en servlet
Reprenons le code JSP de :
<%
// variables locales à la procédure principale
String title="Récupération des paramètres d'un formulaire";
String firstName = request.getParameter("firstname");
String lastName = request.getParameter("lastname"); %>
<%= title %>
<%= title %>
<%
if (firstName != null || lastName != null) { out.println("firstname= " + firstName + " "); out.println("lastname= " + lastName);
} else {
out.println("pas de paramètres");
}
%>
|
firstname=
lastname=
Lorsque le navigateur demande cette page JSP au serveur Tomcat, celui-ci va la transformer en servlet. Si l'URL demandée est , Tomcat 4.x va placer la servlet générée dans le répertoire \work\localhost\examples\jsp\perso\intro :
On retrouve dans ce nom l'URL de la page JSP. On voit cidessus qu'on a accès au code java de la servlet générée pour la page JSP. Dans notre exemple, c'est le suivant :
package ;
import javax.servlet.*; import .*; import .*; import org.apache.jasper.runtime.*;
|
public class myRequestParamExample$jsp extends HttpJspBase {
|
static { }
public myRequestParamExample$jsp( ) {
} private static boolean _jspx_inited = false;
public final void _jspx_init() throws org.apache.jasper.runtime.JspException { }
|
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws .IOException, ServletException {
|
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
|
Object page = this; String _value = null; try {
if (_jspx_inited == false) { synchronized (this) {
if (_jspx_inited == false) {
_jspx_init();
_jspx_inited = true;
}
}
}
_jspxFactory = JspFactory.getDefaultFactory(); response.setContentType("text/html;charset=ISO-8859-1");
pageContext = _jspxFactory.getPageContext(this, request, response, "", true, 8192, true);
|
application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession();
out = pageContext.getOut();
|
// variables locales à la procédure principale
|
String title="Récupération des paramètres d'un formulaire";
String firstName = request.getParameter("firstname");
String lastName = request.getParameter("lastname");
|
|
out.write("\r\n\r\n\r\n\r\n \r\n ");
out.print( title );
out.write("\r\n \r\n \r\n
");
out.print( title ); out.write("\r\n ");
out.println("lastname= " + lastName);
} else {
out.println("pas de paramètres");
}
|
|
out.write("\r\n
\r\n
\r\n firstname= \r\n \r\n lastname= \r\n \r\n \r\n
\r\n \r\n\r\n");
|
} catch (Throwable t) {
if (out != null && out.getBufferSize() != 0) out.clearBuffer();
if (pageContext != null) pageContext.handlePageException(t);
} finally {
if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
}
}
}
|
Le code généré est assez complexe. Nous ne retiendrons que les points suivants :
Ø La méthode principale de la servlet est la suivante :
public void _jspService(HttpServletRequest request, HttpServletResponse response) throws .IOException, ServletException {
C'est cette méthode qui est lancée au départ de la servlet. On voit qu'elle reçoit deux paramètres : la requête request du client et un objet response pour générer sa réponse au client web.
Ø Dans la méthode principale un objet JspWriter out est déclaré puis initialisé. C'est lui qui va permettre d'envoyer du code HTML au client par des instructions out.print("codeHTML").
JspWriter out = null; out = pageContext.getOut();
Ø Le code Java
<%
// variables locales à la procédure principale
String title="Récupération des paramètres d'un formulaire";
String firstName = request.getParameter("firstname");
String lastName = request.getParameter("lastname"); %>
Ø Le code HTML de la page JSP fait l'objet d'instructions out.print("codeHTML") ou out.write( ). Par exemple
out.write("\r\n \r\n \r\n
"); Ø Dans cet exemple, il n'y a pas d'autres méthodes que la méthode principale _jspService.
2.2.6 Les méthodes et variables globales d'une page JSP
Considérons la page JSP suivante :
// la balise précédente démarre la partie variables et méthodes globales
// cette partie sera reprise sans modification dans la servlet
// une variabe globale
String prenom="inconnu";
// une méthode private String sonChien(){ return "milou"; }//sonChien
// une autre méthode
private void afficheAmi(JspWriter out) throws Exception{ out.println("
Son ami s'appelle Haddock
"); }//afficheAmi
// fin de la partie globale de la servlet %>
<%
// la balise précédente indique que le code qui suit sera enregistré
// dans la méthode principale de la servlet
// variable locale à la méthode principale
String nom="tintin"; %>
<%-- code HTML --%>
Page JSP
Page JSP
Son nom est <%= nom %>
Son prénom est <%= prenom %>
Son chien s'appelle <%= sonChien() %>
<%
// le nom de son ami afficheAmi(out);
%>
|
Cette page JSP génère la page Web suivante :
Intéressons-nous à la façon dont sont générées les quatre lignes ci-dessus :
Son nom est <%= nom %>
Son prénom est <%= prenom %>
Son chien s'appelle <%= sonChien() %>
<%
// le nom de son ami afficheAmi(out);
%>
|
Les lignes ci-dessus sont dans une balise <%..%> et feront donc partie de la méthode principale _jspService de la servlet qui sera générée. Comment ont-elles accès aux variables nom, prenom et méthodes sonChien et afficheAmi ?
est une méthode publique de la page JSP et donc accessible de la méthode principale. On remarquera qu'on passe l'objet out en paramètre à la méthode. C'est ici obligatoire. En effet, l'objet out est déclaré et initialisé dans la méthode principale de la servlet et n'est pas une variable globale.
Voyons maintenant le code de la servlet java générée à partir de cette page JSP, une fois débarassé du code inutile :
package ;
import javax.servlet.*; import .*; import .*; import org.apache.jasper.runtime.*;
|
public class tintin$jsp extends HttpJspBase {
|
|
// la balise précédente démarre la partie variables et méthodes globales
// cette partie sera reprise sans modification dans la servlet
// une variabe globale
String prenom="inconnu";
// une méthode private String sonChien(){ return "milou";
}//sonChien
// une autre méthode
private void afficheAmi(JspWriter out) throws Exception{ out.println("
Son ami s'appelle Haddock
");
}//afficheAmi
// fin de la partie globale de la servlet
|
static { }
public tintin$jsp( ) {
} private static boolean _jspx_inited = false;
public final void _jspx_init() throws org.apache.jasper.runtime.JspException { }
|
public void _jspService(HttpServletRequest request, HttpServletResponse response)
throws .IOException, ServletException {
|
JspFactory _jspxFactory = null;
PageContext pageContext = null;
HttpSession session = null;
ServletContext application = null;
ServletConfig config = null;
JspWriter out = null;
if (_jspx_inited == false) { synchronized (this) {
if (_jspx_inited == false) {
_jspx_init();
_jspx_inited = true;
}
}
}
_jspxFactory = JspFactory.getDefaultFactory(); response.setContentType("text/html;charset=ISO-8859-1"); pageContext = _jspxFactory.getPageContext(this, request, response,
|
"", true, 8192, true);
application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); out.write(" \r\n\r\n");
|
// la balise précédente indique que le code qui suit sera enregistré
// dans la méthode principale de la servlet
// variable locale à la méthode principale
String nom="tintin";
|
|
out.write("\r\n\r\n\r\n");
out.write("\r\n\r\n \r\n Page JSP\r\n \r\n
\r\n
\r\n
Page JSP
\r\n
Son nom est ");
out.print( nom );
out.write("
\r\n
Son prénom est ");
out.print( prenom );
out.write("
\r\n
Son chien s'appelle ");
out.print( sonChien() ); out.write("
\r\n "); |
|
// le nom de son ami afficheAmi(out);
|
|
out.write("\r\n \r\n \r\n\r\n");
if (out != null && out.getBufferSize() != 0) out.clearBuffer();
if (pageContext != null) pageContext.handlePageException(t);
} finally {
if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext);
}
}
}
|
On voit ci-dessus que le code Java qui était situé entre les balise JSP <%! .. %> a été repris intégralement et ne fait pas partie de la méthode principale _jspService de la servlet. Les variables déclarées dans cette partie sont alors des variables d'instance donc globales aux méthodes et c'est également là qu'on peut définir des méthodes autres que _jspService.
// cette partie sera reprise sans modification dans la servlet
// une variabe globale
String prenom="inconnu";
// une méthode
private String sonChien(){ return "milou"; }//sonChien
// une autre méthode
private void afficheAmi(JspWriter out) throws Exception{ out.println("
Son ami s'appelle Haddock
"); }//afficheAmi
// fin de la partie globale de la servlet
|
2.2.7 Déploiement et débogage des pages JSP au sein du serveur Tomcat
Lorsqu'on veut construire une page JSP et l'utiliser avec le serveur Tomcat, se pose la question de l'endroit où placer la page dans l'arborescence du serveur. Il y a différentes façons de faire sur lesquelles nous reviendrons. Pour l'instant, la plus simple est de placer la page JSP dans un dossier de l'arborescence \webapps\examples\jsp (Tomcat 4.x) où est le répertoire d'installation de Tomcat. Ainsi l'URL de l'exemple précédent était . Cela signifie que la page était dans le dossier \webapps\examples\jsp\perso\tintin.
1. le code JSP de la page est erroné (erreurs dans les balises jsp utilisées par exemple) 2. le code Java inclus dans la page JSP est erroné
La première cause peut être éliminée en vérifiant le code JSP de la page. La seconde peut l'être en vérifiant le code Java. On pourra le faire en compilant directement le fichier .java généré pour la page JSP avec un outil tel que JBuilder qui offre des possibilités de débogage plus évoluées que celles de Tomcat.
2.2.8 Exemples
Nous reprenons l'exemple déjà traité avec une servlet où un utilisateur choisit un nombre dans une liste et le serveur lui dit quel nombre il a choisi tout en lui renvoyant la même liste avec comme élément sélectionné l'élément choisi par l'utilisateur :
Pour construire cette page, on a récupéré le code de la servlet qu'on a modifié de la façon suivante : Ø on a conservé tel quel le code Java qui ne produisait pas du code HTML
Ø le code Java qui produisait du code HTML a été transformé en un mix code HTML, code JSP
On obtient alors la page JSP suivante :
|
<%@ page import=".*, .*" %>
|
|
<%!
|
|
|
// variables globales de l'application
// le titre de la page
private final String title="Génération d'un formulaire";
// la base de données des valeurs de liste private final String DSNValeurs="odbc-valeurs"; private final String admDbValeurs="admDbValeurs"; private final String mdpDbValeurs="mdpDbValeurs";
// valeurs de liste
private String[] valeurs=null;
// msg d'erreur
private String msgErreur=null;
|
// initialisation de la page JSP - n'est exécutée qu'une seule fois
|
public void jspInit(){
// remplit le tableau des valeurs à partir d'une base de données ODBC
// de nom DSN : DSNvaleurs
Connection connexion=null;
Statement st=null; ResultSet rs=null;
try{
// connexion à la base ODBC
Class.forName(".JdbcOdbcDriver");
// objet Statement
st=connexion.createStatement();
// exécution requête select pour récupérer les valeurs rs=st.executeQuery("select valeur from Tvaleurs");
// les valeurs sont récupérées et mises dans un tableau dynamique
ArrayList lstValeurs=new ArrayList();
while(()){
// on enregistre la valeur dans la liste (rs.getString("valeur"));
|
}//while
// transformation liste --> tableau valeurs=new String[()]; for (int i=0;i<();i++){ valeurs[i]=(String)(i);
}
}catch(Exception ex){
// problème
msgErreur=ex.getMessage();
}finally{ try{rs.close();}catch(Exception ex){} try{st.close();}catch(Exception ex){} try{connexion.close();}catch(Exception ex){}
}//try
}//init
|
%>
|
|
|
<%
|
// code de _jspService exécuté à chaque requête cliente
// y-at-il eu une erreur lors de l'initialisation de la page JSP ? if(msgErreur!=null){
|
%>
|
Erreur
Application indisponible (<%= msgErreur %>
|
<%
// fin de jspService return;
}//if
// on récupère l'éventuel choix de l'utilisateur String choix=request.getParameter("cmbValeurs"); if(choix==null) choix="";
%>
<%-- pas d'erreur - code HTML de la page normale --%>
<%= title %>
Choisissez une valeur
<%
// affichage dynamique des valeurs
String selected="";
for (int i=0;i<valeurs.length;i++){
if(valeurs[i].equals(choix)) selected="selected"; else selected=""; out.println("
"+valeurs[i]+"
");
}//for
%>
<%
// y-avait-il une valeur choisie ? if(! choix.equals("")){
// on affiche le choix de l'utilisateur
%>
Vous avez choisi le nombre
<%= choix %>
<%
}//if
%>
|
|
|
|
Notons les points suivants :
Ø la balise <%! %> encadrent les variables globales et les méthodes java de l'aplication
Ø la méthode init de la servlet qui est exécutée une seule fois, au moment du chargement de la servlet, s'appelle pour une page JSP : jspInit. Ces deux méthodes ont le même rôle. On a donc repris ici intégralement le code de la méthode init de la servlet.
Ø les variables d'instance de la servlet, celles qui doivent être connues dans plusieurs méthodes ont été reprises à l'identique. Il s'agit essentiellement des variables title, valeurs et msgErreur qui sont ensuite utilisées dans le code JSP.
Ø les balises <% %> encadrent du code Java qui sera inclus dans la méthode _jspService exécutée au moment d'une requête d'un client.
Ø comme pour la servlet, la méthode _jspService commencera par vérifier la valeur de la variable msgErreur pour savoir si elle doit générer une page d'erreur. S'il y a erreur, elle génère la page d'erreur et s'arrête (return).
Ø s'il n'y a pas d'erreur, elle génère le formulaire avec la liste de valeurs
Ø ceci fait, elle vérifie si l'utilisateur avait choisi un nombre, auquel cas elle affiche ce nombre dans la page générée
Qu'a-t-on gagné vis à vis de la servlet ? Sans doute une meilleure vue du code HTML généré. Mais il reste encore beaucoup de code Java qui "pollue" cette vue. Nous verrons ultérieurement, une autre méthode appelée délégation où on pourra mettre l'essentiel du code Java dans une servlet, la page JSP ne conservant elle que le code HTML et JSP. On sépare alors nettement la partie traitement de la partie présentation.
2.3
|
Déploiement d'une application web au sein du serveur Tomcat
2.3.1 Les fichiers de configuration et
Jusqu'à maintenant, pour tester nos servlets et pages JSP, nous avons placé
§ les servlets dans le dossier \webapps\examples\WEB-INF\classes. Elles étaient alors accessibles via l'URL http://localhost:8080/examples/servlet/nomServlet
§ les pages JSP dans l'arborescence \webapps\examples\jsp. Elles étaient alors accessibles via l'URL http://localhost:8080/examples/jsp/nomPageJSP
Nous n'avons jamais expliqué pourquoi c'était ainsi. La configuration du serveur Tomcat est faite dans un fichier texte appelé qui se trouve dans le dossier \conf :
Ce fichier texte est en fait un fichier XML (eXtended Markup Language). Un document XML est un document texte contenant des balises comme l'est un document HTML. Cependant alors que les balises du langage HTML sont bien définies, celles du langage XML ne le sont pas. Ainsi le document suivant est un document XML :
Un document XML est simplement un document "balisé" et qui suit certaines règles de balisage :
§ un texte balisé à la forme texte
§ une balise peut être seule et avoir la forme
Les champs atti sont appelés attributs de la balise xx et les champs vali sont les valeurs associées à ces attributs. Certains documents HTML ne sont pas des documents XML valides. Par exemple la balise HTML n'est pas une balise XML valide. Elle devrait s'écrire pour en être une afin de respecter la règle qui veut que toute balise XML doit être fermée. Une variante de HTML appelée XHTML a été créée afin de faire de tout document XHTML un document XML valide. Certains des navigateurs récents sont capables d'afficher des fichiers XML. Ainsi si nous appelons le document XML présenté en exemple cidessus et que nous le visualisons avec IE6, nous obtenons l'affichage suivant :
PierreLucas28 et le visualisons avec IE6, nous obtenons le même affichage :
IE6 a reconnu correctement la structure et le contenu du document. Tout l'intérêt du document XML repose dans cette propriété : il est facile de retrouver la structure et le contenu d'un document XML. Cela se fait avec un programme appelé un parseur XML. Les documents XML ont tendance à devenir la norme dans les échanges de documents sur le web. Prenons une machine A devant envoyer un document DOC à une machine B. Le document DOC est construit à partir des informations contenues dans une base de données DB-A. La machine B elle doit stocker le document DOC dans une base de données DB-B. L'échange pourra se faire de la façon suivante :
§ la machine A récupère les données dans la base DB-A et encapsule celles-ci dans un document texte XML
§ le document XML est envoyé à la machine B à travers le réseau
§ la machine B analyse le document reçu avec un parseur XML et en récupère et la structure et les données (comme l'a fait IE6 dans notre exemple). Elle peut alors stocker les données reçues dans la base DB-B
Nous n'en dirons pas plus sur le langage XML qui mérite un livre à lui tout seul.
Ici donc, Tomcat est configuré par le fichier XML . Si nous visualisons celui-ci avec IE6, nous obtenons un document complexe. Nous nous attarderons simplement sur les lignes suivantes :
C'est la balise qui nous intéresse ici. Elle sert à définir des applications web. Deux de ses attributs sont à noter :
§ path : c'est le nom de l'application web
§ docBase : c'est le dossier dans lequel elle se trouve. Ici c'est un nom relatif : examples. Relatif à quel dossier ? La réponse se trouve également dans le fichier dans la ligne qui suit :
La ligne ci-dessus définit le serveur web :
§ name : nom du serveur web
L'application web examples a ses documents dans le dossier examples (cf docBase plus haut). Ce nom est relatif à la racine de l'arborescence web du serveur, c.a.d. \webapps. Il s'agit donc du dossier \webapps\examples. Allons-voir de plus près ce dossier :
Nous y retrouvons le dossier WEB-INF\classes dans lequel nous avons rangé nos servlets pour les tester. Le dossier WEB-INF contient un fichier appelé :
Ce fichier sert à configurer l'application web examples. Nous n'entrerons pas dans les détails de ce fichier trop complexe pour le moment. Nous nous attarderons simplement sur les quelques lignes suivantes :
servletToJsp servletToJsp
La balise sert à définir une servlet au sein d'une application web. Rappelons ici que l'application web en question est examples. La balise servlet contient ici deux autres balises :
§ servletToJsp : définit le nom de la servlet
§ servletToJsp : définit le nom de la classe à exécuter lorsque la servlet est demandée. Dans cet exemple, la servlet et sa classe portent le même nom. Ce n'est pas obligatoire.
Comment la servlet servletToJsp est-elle demandée par un navigateur au serveur Tomcat ?
§ le navigateur demande l'URL http://localhost:8080/examples/servlet/servletToJsp
§ Tomcat analyse le chemin de la servlet /examples/servlet/servletToJsp. Il interprète la première partie du chemin /examples comme le nom d'une application web et cherche dans son fichier de configuration où les documents de cette application ont été rangés. Nous l'avons vu précédemment, c'est dans le dossier \webapps\examples.
2.3.2 Exemple : déploiement de l'application web liste
Nous reprenons une servlet déjà étudiée et qui présentait à l'utilisateur une liste de nombres parmi lesquel il en choisissait un. La servlet lui confirmait ensuite le nombre qu'il avait choisi :
Comme le montre le champ Address du navigateur ci-dessus, le fichier classe de la servlet s'appelait gener3. D'après les explications qui ont été données précédemment :
§ l'URL /examples/servlet/gener3 montre qu'il s'agit d'une servlet appelée gener3 de l'application web examples
§ dans le fichier de l'aplication examples, on ne trouvera rien qui parle d'une servlet gener3. Comment Tomcat l'a-t-il alors trouvée ? Ayant parcouru la totalité du fichier , je ne peux répondre avec certitude La question reste posée
Nous choisissons de déployer la servlet gener3.class sous le nom lstValeurs dans une application web appelée liste située dans le dossier E:\data\serge\Servlets\lstValeurs :
Nous plaçons le fichier gener3.class dans le dossier WEB-INF\classes ci-dessus :
Nous configurons l'application web liste en ajoutant dans le fichier les lignes suivantes au-dessus de celles qui définissent l'application web manager :
La ligne qui définit l'application liste indique qu'elle se trouve dans le dossier e:/data/serge/servlets/lstValeurs. Nous devons maintenant définir le fichier de cette application. Ce fichier définira l'unique servlet de l'application :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
lstValeurs
gener3
|
Le fichier ci-dessus indique que la servlet nommée lstValeurs est associée au fichier de classe gener3.class. Ce fichier doit être créé et sauvegardé dans le dossier WEB-INF de l'application liste :
§ arrêtez et lancez Tomcat afin qu'il relise son fichier de configuration . Ici nous sommes sous Windows. Sous Unix, on peut forcer Tomcat à relire son fichier de configuration sans pour autant l'arrêter.
§ avec un navigateur, demandez l'URL http://localhost:8080/liste/servlet/lstValeurs
On voit que l'URL précédente contient le mot clé servlet comme toutes les URL de servlets utilisées jusqu'à maintenant. On peut s'en passer en associant dans le fichier de l'application liste la servlet lstValeurs à un modèle d'URL (url-pattern) :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
lstValeurs
gener3
|
lstValeurs
/valeurs
|
|
Dans la balise , nous associons le chemin /valeurs à la servlet lstValeurs définie dans les lignes qui précèdent. Nous sauvegardons le nouveau fichier et demandons l'URL http://localhost:8080/liste/valeurs :
2.3.3 Déploiement des pages publiques d'une application web
Nous venons de voir le déploiement d'une application web formée d'une unique servlet. Une application web peut avoir de nombreuses composantes : des servlets, des pages JSP, des fichiers HTML, des applets Java, Où place-t-on ces éléments de l'application ? Si est le dossier de l'application web défini par l'attribut docBase de l'application dans le fichier de configuration de Tomcat, nous avons vu que les servlets étaient placées dans \WEB-INF\classes. Les autres éléments de l'application peuvent être placés n'importe où dans l'arborescence du dossier sauf dans le dossier WEBINF. Considérons l'application JSP déjà étudiée :
Cette page JSP avait été stockée dans le dossier \webapps\ examples\jsp\perso\listvaleurs. Cette page pourrait être une composante de l'application liste déployée précédemment. Plaçons le fichier directement dans le dossier de cette application :
Rappelons la configuration de l'application liste dans le fichier :
Nous avons bien obtenu la page JSP attendue.
2.3.4 Paramètres d'initialisation d'une servlet
Nous avons vu qu'une servlet était configurée par le fichier \WEB-INF\ où est le dossier de l'application web à laquelle elle appartient. Il est possible d'inclure dans ce fichier des paramètres d'initialisation de la servlet. Revenons à notre servlet lstValeurs de l'application web liste et dont le fichier de configuration était le suivant :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
lstValeurs
gener3
lstValeurs
/valeurs
|
La classe associée à la servlet est la classe gener3. On trouve dans le code source de celle-ci la définition de quelques constantes :
public class gener3 extends HttpServlet{
// le titre de la page
private final String title="Génération d'un formulaire";
// la base de données des valeurs de liste private final String DSNValeurs="odbc-valeurs"; private final String admDbValeurs="admDbValeurs"; private final String mdpDbValeurs="mdpDbValeurs";
|
Rappelons la signification des quatre constantes définies ci-dessus :
title titre du document HTML généré par la servlet
DSNValeurs nom DSN de la base ODBC où la servlet va chercher des données admDbValeurs nom d'un utilisateur ayant un droit de lecture sur la base précédente mdpDbvaleurs son mot de passe
Si l'administrateur de la base DSNValeurs change le mot de passe de l'utilisateur admDbValeurs, le source de la servlet doit être modifié et recompilé. Ce n'est pas très pratique. Le fichier de configuration de la servlet nous offre une alternative en permettant la définition de paramètres d'initialisation de la servlet avec la balise :
permet de définir le nom du paramètre
définit la valeur associée au paramètre précédent
La servlet a accès à ses paramètres d'initialisation grâce aux méthodes suivantes :
programmation web. Rend un objet ServletConfig donnant accès aux paramètres de configuration de la servlet.
[ServletConfig].getInitParameter("paramètre") méthode de la classe ServletConfig qui donne la valeur du paramètre d'initialisation "paramètre"
Nous configurons l'application liste avec le nouveau fichier suivant :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
lstValeurs
gener3
|
lstValeurs2
|
gener5
title
Génération d'un formulaire
DSNValeurs
odbc-valeurs
admDbValeurs
admDbValeurs
mdpDbValeurs
mdpDbValeurs
|
lstValeurs
/valeurs
|
lstValeurs2
/valeurs2
|
|
Dans l'application liste, nous définissons une seconde servlet appelée lstValeurs2 liée au fichier de classe gener5. Ce dernier a été placé dans \WEB-INF\classes :
La servlet lstValeurs2 a quatre paramètres d'initialisation : title, DSNValeurs, admDbValeurs, mdpDbValeurs. Par ailleurs, l'alias /valeurs2 a été défini pour la servlet à l'aide de la balise . Aussi, la servlet lstValeurs2 de l'application liste sera-t-elle accessible via l'URL http://localhost:8080/liste/valeurs2.
Le code source de la servlet a été modifié de la façon suivante pour récupérer les paramètres d'initialisation de la servlet :
public class
|
gener5
|
extends HttpServlet{
|
// le titre de la page private String title=null;
// la base de données des valeurs de liste private String DSNValeurs=null; private String admDbValeurs=null; private String mdpDbValeurs=null;
|
// initialisation de la servlet
public void init(){
|
// on récupère les paramètres d'initialisation de la servlet
ServletConfig config=getServletConfig(); title=config.getInitParameter("title");
DSNValeurs=config.getInitParameter("DSNValeurs"); admDbValeurs=config.getInitParameter("admDbValeurs"); mdpDbValeurs=config.getInitParameter("mdpDbValeurs");
|| mdpDbValeurs==null){
msgErreur="Configuration incorrecte"; return;
}
|
// on remplit le tableau des valeurs à partir d'une base de données ODBC
// de nom DSN : DSNvaleurs
|
Pour tester la servlet, il faut relancer Tomcat afin qu'il prenne en compte le nouveau fichier de configuration de l'application liste. Avec un navigateur, on demande l'URL de la servlet http://localhost:8080/liste/valeurs2 :
Si l'un des paramètres d'initialisation nécessaires à la servlet est absent du fichier , on obtient la page suivante :
2.3.5 Paramètres d'initialisation d'une application web
Dans l'exemple précédent, seule la servlet lstValeurs2 a accès aux paramètres title, DSNValeurs, admDbValeurs, mdpDbValeurs. On pourrait imaginer qu'une autre servlet de la même application liste ait besoin des données dans la même base de données qu'utilise la servlet lstValeurs2. Il faudrait alors redéfinir les paramètres DSNValeurs, admDbValeurs, mdpDbValeurs dans la partie configuration du fichier de la nouvelle servlet. Une autre solution est de définir les paramètres communs à plusieurs servlets au niveau de l'application et non plus au niveau des servlets. Le nouveau fichier de l'application devient le suivant :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
|
DSNValeurs
odbc-valeurs
admDbValeurs
admDbValeurs
mdpDbValeurs
mdpDbValeurs
|
lstValeurs
gener3
|
lstValeurs3
gener6
title
|
Génération d'un formulaire
|
lstValeurs
/valeurs
|
lstValeurs3
/valeurs3
méthode de la classe Servlet dont dérive la classe HttpServlet utilisée pour la programmation web. Rend un objet ServletContext donnant accès aux paramètres de configuration de l'application méthode de la classe ServletContext qui donne la valeur du paramètre d'initialisation "paramètre"
La classe amène les seules modification suivantes au code Java de utilisé précédemment :
// on récupère les paramètres d'initialisation de la servlet
|
ServletConfig config=getServletConfig(); title=config.getInitParameter("title");
|
|
ServletContext context=getServletContext();
DSNValeurs=context.getInitParameter("DSNValeurs"); admDbValeurs=context.getInitParameter("admDbValeurs"); mdpDbValeurs=context.getInitParameter("mdpDbValeurs");
|
//a-t-on récupéré tous les paramètres ? if(title==null || DSNValeurs==null || admDbValeurs==null
|| mdpDbValeurs==null){
msgErreur="Configuration incorrecte";
return;
}
// on remplit le tableau des valeurs à partir d'une base de données ODBC
// de nom DSN : DSNvaleurs
|
Le paramètre title propre à la servlet est obtenu via un objet ServletConfig. Les trois autres paramètres définis au niveau application sont eux obtenus via un objet ServletContext. Nous compilons cette classe et la mettons comme les autres dans \WEBINF\classes :
Nous relançons Tomcat pour qu'il prenne en compte le nouveau fichier de l'application et demandons l'URL http://localhost:8080/liste/valeurs3 :
2.3.6 Paramètres d'initialisation d'une page JSP
Nous avons vu comment définir des paramètres d'initialisation pour une servlet ou une application web. Peut-on faire de même pour une page JSP ? Revenons sur le début du code de la page déjà étudiée :
<%@ page import=".*, .*" %>
<%!
// variables globales de l'application
// le titre de la page
private final String title="Génération d'un formulaire";
|
On retrouve les quatre constantes title, DSNValeurs, admDbValeurs et mdpDbValeurs définies dans le fichier de l'application. Les constantes DSNValeurs, admDbValeurs et mdpDbValeurs ont été maintenant définies au niveau application et on peut donc supposer qu'une page JSP faisant partie de cette application y aura accès. C'est le cas. Nous savons que la page JSP sera traduite en une servlet. Celle-ci aura accès au contexte via la méthode getServletContext(). Plus délicat est le cas de la constante title. En effet nous l'avons définie au niveau servlet et non au niveau application de la façon suivante :
lstValeurs3
gener6
|
title
Génération d'un formulaire
|
|
Pour la page JSP la syntaxe précédente ne convient plus car on n'a plus la notion de fichier de classe. La syntaxe de configuration d'une page JSP est cependant très proche de celle d'une servlet. C'est la suivante :
En fait, une page JSP est assimilée à une servlet à laquelle on donne un nom (servlet-name). Au lieu d'associer un fichier de classe à cette servlet, on associe le fichier source de la page JSP à exécuter (jsp-file). Ainsi les quelques lignes précédentes définissent une servlet appelée JSPlstvaleurs associée à la page JSP . Le chemin est mesuré par rapport à la racine de l'application. Ainsi dans le cas de notre application liste, le fichier se trouverait dans le dossier docBase (cf ) de l'application liste :
La configuration de la page JSP sera la suivante dans le fichier de l'application :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
DSNValeurs
odbc-valeurs
admDbValeurs
admDbValeurs
mdpDbValeurs
mdpDbValeurs
.
|
JSPlstValeurs
JSPtitle
Génération d'un formulaire
|
.
|
JSPlstValeurs
/jspvaleurs
|
..
La page JSP initiale est modifiée en et récupère ses quatre paramètres d'initialisation dans la méthode jspInit() :
<%!
// variables globales de l'application
|
// le titre de la page private String title=null;
// la base de données des valeurs de liste private String DSNValeurs=null; private String admDbValeurs=null; private String mdpDbValeurs=null;
|
// valeurs de liste
private String[] valeurs=null;
// msg d'erreur
private String msgErreur=null;
// initialisation de la page JSP - n'est exécutée qu'une seule fois
|
public void jspInit(){
|
|
// on récupère les paramètres d'initialisation de la servlet
|
ServletConfig config=getServletConfig(); title=config.getInitParameter("JSPtitle");
ServletContext context=getServletContext();
DSNValeurs=context.getInitParameter("DSNValeurs"); admDbValeurs=context.getInitParameter("admDbValeurs"); mdpDbValeurs=context.getInitParameter("mdpDbValeurs");
|
//a-t-on récupéré tous les paramètres ? if(title==null || DSNValeurs==null || admDbValeurs==null
|| mdpDbValeurs==null){
msgErreur="Configuration incorrecte";
return;
}
// remplit le tableau des valeurs à partir d'une base de données ODBC
// de nom DSN : DSNvaleurs
..
|
La page JSP récupère ses paramètres d'initialisation de la même façon que les servlets. Le fichier précédent est sauvegardé dans la racine de l'application web liste :
Le serveur Tomcat est relancé pour le forcer à relire le nouveau fichier de configuration de l'application. On peut alors demander l'URL http://localhost:8080/liste/jspvaleurs :
§ dans une ou plusieurs servlets, le code Java qui ne génère pas le code HTML de la réponse
§ dans des pages JSP, le code de génération des différents documents HTML envoyés en réponse au client
On peut ainsi espérer améliorer la séparation code Java/code HTML. Nous allons appliquer cette nouvelle structuration à notre application liste : une servlet Java lstValeurs4 sera chargée de lire au démarrage les valeurs dans la base de données et ensuite d'analyser les requêtes des clients. Selon le résultat de celle-ci, la requête du client sera dirigée vers une page d'erreur ou vers la page d'affichage de la liste de nombres . L'application liste sera donc formée d'une servlet et de deux pages JSP.
Comment une servlet peut-elle passer la requête qu'elle a reçue d'un client à une autre servlet ou à une page JSP ? Nous utiliserons les méthodes suivantes :
[ServletContext].getRequestDispatcheméthode de la classe ServletContext qui rend un objet RequestDispatcher. Le paramètre r(String url)
url est le nom de l'URL à qui on veut transmettre la requête du client. Ce passage de requête ne peut se faire qu'au sein d'une même application. Aussi le paramètre url est un chemin relatif à l'arborescence web de cette application.
[RequestDispatcher].forward(ServletRméthode de l'interface RequestDispatcher qui transmet à l'URL précédente la requête equest request, ServletResponse response)request du client et l'objet response qui doit être utilisé pour élaborer la réponse.
[ServletRequest].setAttribute(String lorsqu'une servlet ou page JSP passe une requête à une autre servlet ou page JSP, nom, Object obj)
[ServletRequest].getAttribute(String permet de récupérer les valeurs des attributs d'une requête. Cette méthode sera attribut) utilisée par la servlet ou la page JSP à qui on a relayé une requête pour obtenir les
informations qui y ont été ajoutées.
La servlet chargée de traiter le formulaire sera configurée de la façon suivante dans le fichier :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
DSNValeurs
odbc-valeurs
admDbValeurs
admDbValeurs
mdpDbValeurs
mdpDbValeurs
|
lstValeurs4
gener7
title
Génération d'un formulaire
JSPerreur
JSPliste
URLservlet
/liste/valeurs4
|
..
|
lstValeurs4
/valeurs4
|
.
|
La servlet lstValeurs4 aura quatre paramètres d'initialisation qui lui seront propres :
le titre du document HTML à généréer l'URL de la page JSP d'erreur l'URL de la page JSP présentant la liste de nombres l'URL associée à l'attribut action du formulaire présenté par la page JSPliste. Cette URL sera celle de la servlet lstValeurs4
La servlet aura l'alias /valeurs4 (servlet-mapping) et sera donc accessible via l'URL http://localhost:8080/liste/valeurs4. Elle est liée au fichier classe dont le code source complet est le suivant :
import .*; import javax.servlet.*; import .*; import .*; import .*;
|
public class
|
gener7
|
extends HttpServlet{
|
// le titre de la page private String title=null;
// la base de données des valeurs de liste private String DSNValeurs=null; private String admDbValeurs=null; private String mdpDbValeurs=null;
// les pages JSP d'affichage private String JSPerreur=null; private String JSPliste=null;
// l'URL de la servlet private String URLservlet=null;
|
// valeurs de liste
private String[] valeurs=null;
// msg d'erreur
private String msgErreur=null;
// -----------------------------------------------------------------
// GET
|
// on met msgErreur,title dans les attributs de la requête
request.setAttribute("msgErreur",msgErreur); request.setAttribute("title",title); request.setAttribute("URLservlet",URLservlet);
|
|
// y-a-t-il eu erreur au chargement de la servlet ?
if(msgErreur!=null){
// on passe la main à une page JSP d'erreur
getServletContext().getRequestDispatcher(JSPerreur).forward(request,response);
// fin return;
}
|
// il n'y a pas eu d'erreur
|
// on met la liste des valeurs dans les attributs de la requête
request.setAttribute("valeurs",valeurs);
|
// on récupère l'éventuel choix de l'utilisateur String choix=request.getParameter("cmbValeurs"); if(choix==null) choix="";
request.setAttribute("choix",choix);
|
// on passe la main à la page JSP de présentation de la liste
getServletContext().getRequestDispatcher(JSPliste).forward(request,response);
// fin return;
|
}//GET
// -----------------------------------------------------------------
// POST
public void doPost(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException{
// on renvoie sur GET doGet(request,response);
}//POST
|
// -----------------------------------------------------------------
// initialisation de la servlet
public void init(){
|
// on récupère les paramètres d'initialisation de la servlet
ServletConfig config=getServletConfig(); title=config.getInitParameter("title");
JSPerreur=config.getInitParameter("JSPerreur");
JSPliste=config.getInitParameter("JSPliste");
URLservlet=config.getInitParameter("URLservlet");
ServletContext context=getServletContext();
DSNValeurs=context.getInitParameter("DSNValeurs"); admDbValeurs=context.getInitParameter("admDbValeurs"); mdpDbValeurs=context.getInitParameter("mdpDbValeurs");
|
//a-t-on récupéré tous les paramètres ? if(title==null || DSNValeurs==null || admDbValeurs==null
return;
}
// remplit le tableau des valeurs à partir d'une base de données ODBC
// de nom DSN : DSNvaleurs
Connection connexion=null;
Statement st=null; ResultSet rs=null; try{
// connexion à la base ODBC
Class.forName(".JdbcOdbcDriver");
connexion=DriverManager.getConnection("jdbc:odbc:"+DSNValeurs,admDbValeurs,mdpDbValeurs);
// objet Statement
st=connexion.createStatement();
// exécution requête select pour récupérer les valeurs rs=st.executeQuery("select valeur from Tvaleurs");
// les valeurs sont récupérées et mises dans un tableau dynamique
ArrayList lstValeurs=new ArrayList(); while(()){
// on enregistre la valeur dans la liste (rs.getString("valeur"));
}//while
// transformation liste --> tableau valeurs=new String[()]; for (int i=0;i<();i++){ valeurs[i]=(String)(i); }
}catch(Exception ex){
// problème
msgErreur=ex.getMessage();
}finally{ try{rs.close();}catch(Exception ex){} try{st.close();}catch(Exception ex){} try{connexion.close();}catch(Exception ex){} }//try
}//init
}//classe
|
La nouveauté dans cette classe est la transmission de la requête du client à la page JSPerreur en cas d'erreur et à la page JSPliste sinon. La classe n'élabore pas elle-même la réponse. Ce sont les pages JSP JSPerreur et JSPliste qui s'en chargent. Auparavant la servlet a ajouté des attributs (setAttribute) à la requête du client :
§ un message d'erreur msgErreur en cas d'erreur pour la page JSPerreur
§ les valeurs (valeurs) à afficher, la valeur choisie (choix) par l'utilisateur, le titre (title) du formulaire, l'URL (URLservlet) de l'attribut action du formulaire pour la page JSPliste
Cette classe est compilée et mise dans les classes de l'application :
La page JSP affichant un message d'erreur est configurée de la façon suivante :
JSPerreur
mainServlet
/valeurs4
JSPerreur
/JSPerreur
Elle a pour alias /JSPerreur ce qui la rend accessible via l'URL http://localhost:8080/liste/JSPerreur. Elle a un paramètre d'initialisation appelé mainServlet et dont la valeur est l'alias de la servlet principale décrite précédemment. On notera que cet alias est relatif à la racine de l'application liste, sinon on aurait /liste/valeurs4. Le code de la page est le suivant :
<%
// code de _jspService
// on récupère le paramètre d'initialisation mainServlet
String servletListValeurs=config.getInitParameter("mainServlet");
// on récupère l'attribut msgErreur
String msgErreur=(String)request.getAttribute("msgErreur");
// attribut valide ? if(msgErreur!=null){
%>
Erreur
Application indisponible (<%= msgErreur %>)
<%
} else { // attribut msgErreur invalide - retour à la servlet principale
%>
<%
}
%>
|
Cette page doit normalement être appelée par la servlet précédente qui doit lui passer l'attribut msgErreur. Cependant rien n'empêche de l'appeler directement si on connaît son URL. Aussi si on découvre que l'attribut msgErreur est absent, on passe la requête à la servlet principale. Ici, on utilise une balise propre aux pages JSP et dont la syntaxe est :
où URL est l'URL de la servlet à qui on transfère la requête du client. Si l'attribut msgErreur est présent, la page d'erreur est affichée.
La page JSP affichant la liste des nombres est configurée de la façon suivante :
JSPliste
mainServlet
/valeurs4
|
JSPliste
/JSPliste
Le fichier JSP associé à la page d'erreur s'appelle et se trouve dans la racine de l'application :
La servlet a pour alias /JSPliste ce qui la rend accessible via l'URL http://localhost:8080/liste/JSPliste. Elle a un paramètre d'initialisation appelé mainServlet et dont la valeur est l'alias de la servlet principale. Le code de la page est le suivant :
<%
// code de jspService
// on récupère le paramètre d'initialisation
String servletListValeurs=config.getInitParameter("mainServlet");
// on récupère les attributs de la requête venant de la servlet principale
String title=(String) request.getAttribute("title");
String[] valeurs=(String[]) request.getAttribute("valeurs");
String choix=(String) request.getAttribute("choix");
String URLservlet=(String) request.getAttribute("URLservlet");
// attributs valides ? if(title==null || valeurs==null || choix==null){
// il y a un attribut invalide - on passe la main à la servlet %>
<%
}//if
%>
<%-- code HTML --%>
<%= title %>
Choisissez une valeur
<%
// affichage dynamique des valeurs
String selected="";
for (int i=0;i<valeurs.length;i++){
if(valeurs[i].equals(choix)) selected="selected"; else selected=""; out.println("
"+valeurs[i]+"
");
}//for
%>
<%
// y-avait-il une valeur choisie ? if(! choix.equals("")){
// on affiche le choix de l'utilisateur
%>
Vous avez choisi le nombre
<%= choix %>
<%
}//if
%>
|
Cette page procède comme la page . Elle doit normalement être appelée par la servlet /liste/valeurs4 et recevoir les attributs title, valeurs et choix. Si l'un de ces paramètres est manquant, on passe la main à la servlet URLservlet (/liste/valeurs4). Si les paramètres sont tous présents, la liste des nombres est affichée ainsi que le nombre choisi par l'utilisateur s'il en avait choisi un.
Si on demande l'URL de la servlet principale, on obtient le résultat suivant :
avec le code source suivant (View/Source) :
Choisissez une valeur
0 1 2 3 4 6 5 7 8 9 |
Ce document HTMl a été généré par la page JSP . On voit que les attributs title, valeurs, URLservlet ont bien été récupérés.
Pour conclure sur la collaboration servlets/pages JSP, nous observons que les pages JSP sont ici très courtes et dépourvues du code Java ne concourant pas à créer directement la réponse HTML. La structure des documents générés est ainsi plus visible.
2.4
|
Cycle de vie des servlets et pages JSP
|
2.4.1 Le cycle de vie
Nous nous intéressons ici au cycle de vie des servlets. Celui des pages JSP en découle. Considérons une servlet appelée pour la première fois. Une instance de classe est alors créée par le serveur Web et chargée en mémoire. Elle va alors servir la requête. Ceci fait, la servlet n'est pas déchargée de la mémoire. Il y reste afin de servir d'autres requêtes afin d'optimiser les temps de réponse du serveur. Elle sera déchargé lorsqu'un temps suffisamment long sera passé sans qu'elle ait servi de nouvelles requêtes. Ce temps est en général configurable au sein du serveur web.
Lorsqu'elle est en mémoire, la servlet peut servir plusieurs requêtes simultanément. Le serveur Web crée un thread par requête qui tous utilisent la même instance de servlet :
Tous les threads ci-dessus partagent les variables de l'instance de servlet. Il peut y avoir besoin de synchroniser les threads pour éviter la corruption des données de la servlet. Nous allons y revenir.
Au chargement d'une servlet, une méthode particulière de la servlet est exécutée :
public void init() throws ServletException{ }
Pour une page JSP, c'est la méthode
public void jspInit(){
}
qui est exécutée. Voici un exemple de page JSP utilisant la méthode jspInit :
Compteur synchronisé
Compteur= <%= getCompteur() %>
<%!
// variables et méthodes globales de la page JSP
// méthode pour incrémenter le compteur public int getCompteur(){ // on incrémente le compteur int myCompteur=compteur; myCompteur++; compteur=myCompteur; // on le rend return compteur; }
// la méthode exécutée au chargement initial de la page
|
publicvoidjspInit(){
// init compteur compteur=100;
}
|
%>
|
La page JSP précédente initialise à 100 un compteur dans jspInit. Toute requête ultérieure à la servlet incrémente puis affiche la valeur de ce compteur :
La première fois :
La seconde fois :
On voit bien ci-dessus, qu'entre les deux requêtes, la servlet n'a pas été déchargée sinon on aurait eu le compteur à 101 lors de la seconde requête. Lorsque la servlet est déchargée, la méthode
public void destroy(){
}
est exécutée si elle existe. Pour les pages JSP c'est la méthode
public void jspDestroy(){ }
Dans ces méthodes, on pourra par exemple fermer des connexions à des bases de données, connexions qui auront été ouvertes dans les méthodes init correspondantes.
2.4.2 Synchronisation des requêtes à une servlet
Revenons à la page JSP précédente qui incrémente un compteur et renvoie celui-ci au client web. Supposons qu'il y ait 2 requêtes simultanées. Deux threads sont alors créés pour les exécuter, threads qui vont utiliser la même instance de servlet donc ici le même compteur. Rappelons le code qui incrémente le compteur :
public int getCompteur(){ // on incrémente le compteur int myCompteur=compteur; myCompteur++; compteur=myCompteur; // on le rend return compteur; }
L'incrémentation du compteur a été écrite volontairement de façon maladroite. Supposons que l'exécution des deux threads se passe de la façon suivante :
2) au temps T2, le thread TH2 est exécuté. Il lit le compteur (=145) dans myCompteur puis il est interrompu et perd le processeur. On notera que les deux threads ont des variables myCompteur différentes. Ils ne partagent que les variables d'instance, celles qui sont globales aux méthodes.
3) au temps T3, le thread TH1 reprend la main et termine. Il renvoie donc 146 à son client.
4) au temps T4, le thread TH2 reprend la main et termine. Il renvoie lui aussi 146 à son client alors qu'il aurait du renvoyer 147.
On a là, un problème de synchronisation de threads. Lorsque TH1 veut incrémenter le compteur, il faudrait alors empêcher tout autre thread de le faire également. Pour mettre en évidence ce problème, nous réécrivons la page JSP de la façon suivante :
Compteur synchronisé
Compteur= <%= getCompteur() %>
<%!
// variables et méthodes globales de la page JSP
// variable d'instance int compteur;
// méthode pour incrémenter le compteur public int getCompteur(){ // on lit le compteur int myCompteur=compteur; // on s'arrête 10 secondes try{
Thread.sleep(10000);
}catch (Exception ignored){} // on incrémente le compteur compteur=myCompteur+1;
// on le rend return compteur; }
// la méthode exécutée au chargement initial de la page public void jspInit(){ // init compteur compteur=100;
}
%>
|
Ici, nous avons forcé le thread à s'arrêter 10 secondes après avoir lu le compteur. Il devrait donc perdre le processeur et un autre thread pouvoir lire à son tour un compteur qui n'a pas été incrémenté. Lorsque nous faisons des requêtes avec un navigateur, nous ne voyons pas de différence si ce n'est l'attente de 10 secondes avant d'avoir le résultat.
Maintenant si nous ouvrons deux fenêtres de navigateur et faisons deux requêtes suffisamment rapprochées dans le temps :
programme URL N
où
URL est l'URL de la servlet de comptage
N le nombre de requêtes à faire à cette servlet
Voici les résultats obtenus pour 5 requêtes qui montrent bien le problème de mauvaise synchronisation des threads : elles obtiennent toutes la même valeur du compteur.
DOS>
|
java clientCompteurJSP 5
|
|
Compteur=121
Compteur=121
|
Compteur=121
Compteur=121
Compteur=121
Le code du client Java est le suivant.
import .*; import .regex.*; import .*; public class clientCompteurJSP { public static void main(String[] params){
// données
String syntaxe="Syntaxe : pg URL nbAppels";
|
// vérification des paramètres
if(params.length!=2){
.println(syntaxe);
(1);
}//if
// URL
URL urlCompteur=null;
try{ urlCompteur=new URL(params[0]);
String query=urlCompteur.getQuery(); if(query!=null) throw new Exception();
}catch (Exception ex){
.println(syntaxe);
.println("URL ["+params[0]+" incorrecte");
(2);
}//try-catch
// nombre d'appels int nbAppels=0; try{ nbAppels=Integer.parseInt(params[1]); if(nbAppels<=0) throw new Exception();
}catch(Exception ex){
.println(syntaxe);
.println("Nombre d'appels ["+params[1]+" incorrect");
(3);
}//try-catch
|
|
// les paramètres sont corrects - on peut faire les connexions à l'URL
try{ getCompteurs(urlCompteur,nbAppels);
}catch(Exception ex){
.println(syntaxe);
.println("L'erreur suivante s'est produite : "+ex.getMessage());
(4);
}//try-catch
|
}//main
|
private static void getCompteurs (URL urlCompteur, int nbAppels) throws Exception {
|
// fait nbAppels à l'URL urlCompteur
// affiche à chaque fois la valeur du compteur renvoyée par le serveur web
|
// on retire d'urlCompteur les infos nécessaire à la connexion au serveur d'impôts
String path=urlCompteur.getPath(); if(path.equals("")) path="/";
String host=urlCompteur.getHost(); int port=urlCompteur.getPort();
|
|
// on fait les appels à l'URL
Socket[] clients=new Socket[nbAppels]; for(int i=0;i<nbAppels;i++){
// on se connecte au serveur clients[i]=new Socket(host,port);
// on cré un flux d'écriture vers le serveur
PrintWriter OUT=new PrintWriter(clients[i].getOutputStream(),true);
// on demande l'URL - envoi des entêtes HTTP
OUT.println("GET " + path + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
|
OUT.println("");
}//for
|
// données locales
String réponse=null; // réponse du serveur
// le modèle recherché dans la réponse HTML du serveur
Pattern modèleCompteur=Pattern.compile("^\\s*Compteur= (\\d+)");
// le modèle d'une réponse correcte
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
// le résultat de la comparaison au modèle Matcher résultat=null;
for(int i=0;i<nbAppels;i++){
// chaque client lit la réponse que lui envoie le serveur
// on crée les flux d'entrée-sortie du client TCP
BufferedReader IN=new BufferedReader(new InputStreamReader(clients[i].getInputStream()));
|
// on lit la 1ère ligne de la réponse réponse=IN.readLine();
// on compare la ligne HTTP au modèle de la réponse correcte résultat=réponseOK.matcher(réponse);
if(! ré()){
// on a un problème d'URL
throw new Exception("Client n° " + i + " - Le serveur a répondu : URL ["+ urlCompteur + "] inconnue");
}//if
|
|
// on lit la réponse jusqu'à la fin des entêtes
while((réponse=IN.readLine())!=null && ! réponse.equals("")){
}//while
|
|
// c'est fini pour les entêtes HTTP - on passe au code HTML
// pour récupérer la valeur du compteur
boolean compteurTrouvé=false;
while((réponse=IN.readLine())!=null){
// on compare la ligne au modèle du compteur if(! compteurTrouvé){ résultat=modèleCompteur.matcher(réponse); if(ré()){
// compteur trouvé
.println("Compteur="+résultat.group(1));
}//if
}//if
}//while
|
// c'est fini clients[i].close();
}//for
}//getCompteurs
}//classe
|
Explicitons le code précédent :
§ le programme admet deux paramètres : o l'URL de la page JSP du compteur o le nombre de clients à créer pour cette URL
§ le programme commence donc par vérifier la validité des paramètres : qu'il y en a bien deux, que le premier ressemble syntaxiquement à une URL et que le second à un nombre entier >0. Pour vérifier que l'URL est syntaxiquement correcte, on utilise la classe URL et son constructeur URL (String) qui construit un objet URL à partir d'une chaîne de caractères telle que . Une exception est lancée si la chaîne de caractères n'est pas une URL syntaxiquement valide. Cela nous permet de vérifier la validité du premier paramètre.
§ une fois les paramètres vérifiés, la main est passée à la procédure getCompteurs. Celle-ci va créer nbAppels clients qui tous vont se connecter en même temps (ou quasiment) à l'URL urlCompteur.
§ le port et la machine sur laquelle les clients doivent se connecter sont tirés de l'URL urlCompteur : [URL].getHost() permet d'avoir le nom de la machine et [URL].getPort() permet d'obtenir le port.
§ une première boucle permet à chaque client : o de se connecter au serveur web o de lui demander l'URL urlCompteur
Dans cette boucle, le client n'attend pas la réponse du serveur. En effet, on veut amener le serveur à recevoir des demandes quasi simultanées.
§ une seconde boucle permet à chaque client de recevoir et traiter la réponse que lui envoie le serveur. Le traitement consiste à trouver dans la réponse la ligne qui contient la valeur du compteur et à afficher celle-ci.
Compteur synchronisé
Compteur= <%= getCompteur() %>
<%!
// variables et méthodes globales de la page JSP
// variables d'instance int compteur;
Object verrou=new Object();
// méthode pour incrémenter le compteur public int getCompteur(){
// on syncronise la section critique synchronized(verrou){ // on lit le compteur int myCompteur=compteur; // on s'arrête 10 secondes try{
Thread.sleep(10000);
}catch (Exception ignored){} // on incrémente le compteur compteur=myCompteur+1;
}//synchronized // on le rend return compteur; }//getCompteur
// la méthode exécutée au chargement initial de la page public void jspInit(){ // init compteur compteur=100;
}
%>
|
A l'exécution, on obtient alors les résultats suivants :
dos>c:\perl\bin\ 5
Compteur= 104
Compteur= 106
Compteur= 105
Compteur= 107
Compteur= 108
La documentation indique que le serveur Web peut parfois créer plusieurs instances d'une même servlet. Dans ce cas, la synchronisation précédente ne fonctionne plus car la variable verrou est locale à une instance et n'est donc pas connue des autres instances. Il en est de même pour la variable compteur. Pour les rendre globales à toutes les instances, on écrira :
// variable de classe static int compteur;
static Object verrou=new Object();
|
Le reste du code ne change pas.
3. Suivi de session
Une application web peut consister en plusieurs échanges de formulaires entre le serveur et le client. On a alors le fonctionnement suivant :
§ étape 1
1. le client C1 ouvre une connexion avec le serveur et fait sa requête initiale.
2. le serveur envoie le formulaire F1 au client C1 et ferme la connexion ouverte en 1.
§ étape 2
4. celui-ci traite les données du formulaire 1, calcule des informations I1 à partir de celles-ci, envoie un formulaire F2 au client C1 et ferme la connexion ouverte en 3.
§ étape 3
5. le cycle des étapes 3 et 4 se répète dans des étapes 5 et 6. A l'issue de l'étape 6, le serveur aura reçu deux formulaires F1 et F2 et à partir de ceux-ci aura calculé des informations I1 et I2.
Le problème posé est : comment le serveur fait-il pour conserver les informations I1 et I2 liées au client C1 ? On appelle ce problème le suivi de la session du client C1. Pour comprendre sa racine, examinons le schéma d'une application serveur TCP-IP servant simultanément plusieurs clients :
Client 1
Client 2
Client 3
Dans une application client-serveur TCP-IP classique :
§ le client crée une connexion avec le serveur
§ échange à travers celle-ci des données avec le serveur
§ la connexion est fermée par l'un des deux partenaires
Les deux points importants de ce mécanisme sont :
1. une connexion unique est créée pour chacun des clients
2. cette connexion est utilisée pendant toute la durée du dialogue du serveur avec son client
Ce qui permet au serveur de savoir à un moment donné avec quel client il travaille, c'est la connexion ou dit autrement le "tuyau" qui le relie à son client. Ce tuyau étant dédié à un client donné, tout ce qui arrive de ce tuyau vient de ce client et tout ce qui est envoyé dans ce tuyau arrive au client.
Le mécanisme du client-serveur HTTP suit bien le schéma précédent avec cependant la particularité que le dialogue client-serveur est limité à un unique échange entre le client et le serveur :
§ le client ouvre une connexion vers le serveur et fait sa demande
§ le serveur fait sa réponse et ferme la connexion
Imaginons une administration qui fonctionnerait de la façon suivante :
§ Il y a une unique file d'attente
§ Il y a plusieurs guichets. Donc plusieurs clients peuvent être servis simultanément. Lorsqu'un guichet se libère, un client quitte la file d'attente pour être servi à ce guichet
§ Si c'est la première fois que le client se présente, la personne au guichet lui donne un jeton avec un numéro. Le client ne peut poser qu'une question. Lorsqu'il a sa réponse il doit quitter le guichet et passer à la fin de la file d'attente. Le guichetier note les informations de ce client dans un dossier portant son numéro.
§ Lorsqu'arrive à nouveau son tour, le client peut être servi par un autre guichetier que la fois précédente. Celui-ci lui demande son jeton et récupère le dossier ayant le numéro du jeton. De nouveau le client fait une demande, a une réponse et des informations sont rajoutées à son dossier.
§ et ainsi de suite Au fil du temps, le client aura la réponse à toutes ses requêtes. Le suivi entre les différentes requêtes est réalisé grâce au jeton et au dossier associé à celui-ci.
Le mécanisme de suivi de session dans une application client-serveur web est analogue au fonctionnement précédent :
§ lors de sa première demande, un client se voit donner un jeton par le serveur web
§ il va présenter ce jeton à chacune de ses demandes ultérieures pour s'identifier
Le jeton peut revêtir différentes formes :
§ celui d'un champ caché dans un formulaire o le client fait sa première demande (le serveur le reconnaît au fait que le client n'a pas de jeton)
o le serveur fait sa réponse (un formulaire) et met le jeton dans un champ caché de celui-ci. A ce moment, la connexion est fermée (le client quitte le guichet avec son jeton). Le serveur a pris soin éventuellement d'associer des informations à ce jeton.
o et ainsi de suite
L'inconvénient principal de cette technique est que le jeton doit être mis dans un formulaire. Si la réponse du serveur n'est pas un formulaire, la méthode du champ caché n'est plus utilisable.
§ celui du cookie o le client fait sa première demande (le serveur le reconnaît au fait que le client n'a pas de jeton)
o le serveur fait sa réponse en ajoutant un cookie dans les entêtes HTTP de celle-ci. Cela se fait à l'aide de la commande HTTP Set-Cookie :
Set-Cookie: param1=valeur1;param2=valeur2; .
où parami sont des noms de paramètres et valeursi leurs valeurs. Parmi les paramètres, il y aura le jeton. Bien souvent, il n'y a que le jeton dans le cookie, les autres informations étant consignées par le serveur dans le dossier lié au jeton. Le navigateur qui reçoit le cookie va le stocker dans un fichier sur le disque. Après la réponse du serveur, la connexion est fermée (le client quitte le guichet avec son jeton). o le client fait sa seconde demande au serveur. A chaque fois qu'une demande à un serveur est faite, le navigateur regarde parmi tous les cookies qu'il a, s'il en a un provenant du serveur demandé. Si oui, il l'envoie au serveur toujours sous la forme d'une commande HTTP, la commande Cookie qui a une syntaxe analogue à celle de la commande Set-Cookie utilisée par le serveur :
Cookie: param1=valeur1;param2=valeur2; .
Parmi les paramètres envoyés par le navigateur, le serveur retrouvera le jeton lui permettant de reconnaître le client et de retrouver les informations qui lui sont liées.
C'est la forme la plus utilisée de jeton. Elle présente un inconvénient : un utilisateur peut configurer son navigateur afin qu'il n'accepte pas les cookies. Ce type d'utilisateur n'a alors pas accès aux applications web avec cookie.
o lorsque l'utilisateur clique sur l'un des liens pour continuer l'application, le navigateur fait sa demande au serveur web en lui envoyant dans les entêtes HTTP l'URL URL;jeton=valeur demandée. Le serveur est alors capable de récupérer le jeton.
3.2
|
L'API Java pour le suivi de session
|
Nous présentons maintenant les principales méthodes utiles au suivi de session :
obtient l'objet Session auquel appartient la requête en cours. Si celle-ci ne faisait pas encore partie d'une session, cette dernière est créée.
idntifiant de la session en cours date de création de la session en cours (nombre de millisecondes écoulées depuis le 1er janvier 1970, 0h).
date du dernier accès à la session par le client durée maximale en secondes d'inactivité d'une session. Au bout de ce laps de temps, la session est invalidée.
fixe en secondes la durée maximale d'inactivité d'une session. Au bout de ce laps de temps, la session est invalidée.
vrai si la session vient d'être créée associe une valeur à un paramètre dans une session donnée. C'est ce mécanisme qui permet de mémoriser des informations qui resteront disponibles tout au long de la session. enlève parametre des données de la session.
valeur associée au paramètre paramètre de la session. Rend null si ce dernier n'existe pas.
liste sous forme d'énumération de tous les attributs de la session en cours clôt la session en cours. Toutes les informations associées à celle-ci sont détruites.
3.3
|
Exemple 1
L'application s'appelle sessions et est configurée de la façon suivante dans le fichier \conf\ :
Dans le dossier docBase ci-dessus, on trouve les éléments suivants :
Les fichiers , , sont tous trois associés à l'application sessions. Dans le dossier WEB-INF ci -dessus on trouve :
On voit ci-dessus le fichier de configuration de l'application sessions. Dans le dossier classes on trouve le fichier classe de la servlet :
Le fichier de l'application est le suivant :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
cycledevie
cycledevie
urlSessionValide
urlSessionInvalide
urlErreur
cycledevie
/cycledevie
|
La servlet principale s'appelle cycledevie (servlet-name) et est associée au fichier classe cycledevie.class (servlet-class). Elle a un alias /cycledevie (servlet-mapping) qui permet de l'appeler via l'URL http://localhost:8080/sessions/cycledevie. Elle a trois paramètres d'initialisation :
urlSessionValide url de la page qui présente les caractéristiques de la session en cours urlSessionInvalide url de la page présentée après une invalidation de la session en cours
urlErreur url de la page présentée en cas d'erreur d'initialisation de la servlet principale cycledevie
Les composantes de l'application sessions sont les suivantes :
servlet principale - analyse la requête du client :
§ si celle-ci fait partie d'une session, passe la main à la page qui va afficher les caractéristiques de cette session. A partir de cette page, l'utilisateur peut : o la recharger o l'invalider
§ si la requête demande à invalider la session en cours, la servlet passe la main à la page qui va proposer à l'utilisateur de recréer une nouvelle session
§ si lors de son initialisation, la servlet rencontre des erreurs, elle passe la main à la page qui affichera un message d'erreur.
o l'autre pour invalider la session en cours
affichée lorsque l'utilisateur a invalidé la session en cours. Propose alors d'en recréer une nouvelle. affichée lorsque la servlet principale rencontre des erreurs lors de son initialisation.
La servlet principale cycledevie est la suivante :
import .*; import javax.servlet.*; import .*; public class cycledevie extends HttpServlet{
// variables d'instance
String msgErreur=null;
String urlSessionInvalide=null;
String urlSessionValide=null;
String urlErreur=null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
// l'initialisation s'est-elle bien passée ? if(msgErreur!=null){
|
// on passe la main à la page d'erreur
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
|
}
// on récupère la session en cours
HttpSession session=request.getSession();
// on analyse l'action à faire
String action=request.getParameter("action");
// invalider la session courante
if(action!=null && action.equals("invalider")){
// on invalide la session courante session.invalidate();
|
// on passe la main à l'url urlSessionInvalide
getServletContext().getRequestDispatcher(urlSessionInvalide).forward(request,response);
|
}
// autres cas
|
// on passe la main à l'url urlSessionInvalide
getServletContext().getRequestDispatcher(urlSessionValide).forward(request,response);
|
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ doGet(request,response); }
|
//-------- INIT public void init(){
|
// on récupère les paramètres d'initialisation
ServletConfig config=getServletConfig();
urlSessionInvalide=config.getInitParameter("urlSessionInvalide"); urlSessionValide=config.getInitParameter("urlSessionValide");
urlErreur=config.getInitParameter("urlErreur");
}
}
|
On notera les points suivants :
§ dans sa méthode d'initialisation, la servlet récupère ses trois paramètres § dans le traitement (doGet) d'une requête, la servlet : o vérifie tout d'abord qu'il n'y a pas eu d'erreur lors de l'initialisation. S'il y en a eu, elle passe la main à la page .
o vérifie la valeur du paramètre action. Si ce dernier a la valeur "invalider", la servlet passe la main à la page sinon à la page .
La page JSP d'affichage des caractéristiques de la session courante :
<%@ page import=".*" %>
|
<%
// jspService
// ici on est dans le cas où on doit décrire la session en cours
String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";
%>
|
Cycle de vie d'une session
Cycle de vie d'une session
Etat session : <%= etat %>
ID session : <%= session.getId() %>
Heure de création : <%= new Date(session.getCreationTime()) %>
Heure du dernier accès : <%= new Date(session.getLastAccessedTime()) %>
Intervalle maximum d'inactivité : <%= session.getMaxInactiveInterval() %>
|
Invalider la session
Recharger la page
|
|
On notera que dans la ligne
String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";
on utilise un objet session venant de nulle part. En fait cet objet fait partie des objets implicites mis à la disposition des pages JSP comme le sont les objets request, response, out, config (ServletConfig), context (ServletContext) déjà rencontrés. Les deux liens de la page référencent la servlet cycledevie présentée précédemment :
Invalider la session
Recharger la page
a été utilisée. Elle indique au navigateur de ne pas utiliser de cache pour la page qu'il reçoit.
La page est la suivante :
Cycle de vie d'une session
Cycle de vie d'une session
Votre session a été invalidée
|
Créer une nouvelle session
|
|
Elle offre un lien pointant sur la servlet cycledevie sans le paramètre action. Ce lien amènera la servlet cycledevie à créer une nouvelle session.
La page est la suivante :
|
<%
// jspService
// ici on est dans le cas où on doit décrire la session en cours
String msgErreur= request.getAttribute("msgErreur"); if(msgErreur==null) msgErreur="Erreur non identifiée)";
%>
|
Cycle de vie d'une session
Cycle de vie d'une session
Application indisponible(<%= msgErreur %>)
|
Elle a pour rôle d'afficher le message d'erreur que lui a transmis la servlet cycledevie. Voyons maintenant des exemples d'exécution. La servlet est demandée une première fois :
La page ci-dessus indique qu'on est dans une nouvelle session. On utilise le lien "Recharger la page" :
Le résultat précédent indique qu'on est toujours dans la même session que dans la page précédente (même ID). On remarquera que l'heure du dernier accès à cette session a changé. Maintenant utilisons le lien "Invalider la session" :
On remarquera l'URL de cette nouvelle page avec le paramètre action=invalider. Utilisons le lien "Créer une nouvelle session" pour créer une nouvelle session :
On remarque qu'une nouvelle session a démarré. Dans les exemples précédents, la session s'appuie sur le mécanisme des cookies.
Nous utilisons maintenant le lien "Recharger la page" :
On peut voir deux choses :
§ l'ID de la session a changé
§ la servlet détecte la session comme une nouvelle session
Le serveur Tomcat apporte une solution au problème des utilisateurs qui inhibent l'utilisation des cookies sur leur navigateur. Il utilise deux mécanismes pour implémenter le jeton dont on a parlé au début de ce paragraphe : les cookies et la réécriture d'URL. Si le cookie de session n'est pas disponible, il essaiera d'obtenir le jeton à partir de l'URL demandée par le client. Pour cela, il faut que celle-ci contienne le jeton. De façon générale, il faut que tous les liens générés dans un document HTML vers l'application web contiennent le jeton de celle-ci. Cela peut se faire avec la méthode encodeURL :
String [HttpResponse].encodeURL(String URL)ajoute le jeton de la session encours à l'URL passée en paramètre sous la forme
URL;jsessionid=xxxx
Nous modifions notre application de la façon suivante :
§ dans la servlet les URL sont encodées :
// on passe la main à la page d'erreur
getServletContext().getRequestDispatcher(response.encodeURL(urlErreur)).forward(request,response); .
// on passe la main à l'url urlSessionInvalide
getServletContext().getRequestDispatcher(response.encodeURL(urlSessionInvalide)).forward(request,res ponse);
|
.
// on passe la main à l'url urlSessionInvalide
getServletContext().getRequestDispatcher(response.encodeURL(urlSessionValide)).forward(request,respo nse);
|
§ dans la page les URL sont encodées :
<%
// jspService
// ici on est dans le cas où on doit décrire la session en cours
String etat= session.isNew() ? "Nouvelle session" : "Ancienne session";
// encodage URL cycledevie
String URLcycledevie=response.encodeURL("/sessions/cycledevie");
%>
Invalider la session
Recharger la page
|
§ dans la page in les URL sont encodées :
<%
String URLcycledevie=response.encodeURL("/sessions/cycledevie");
%>
.
Créer une nouvelle session
|
Maintenant nous sommes prêts pour les tests. Nous utilisons Netscape 4.5 et les coookies ont été inhibés. Nous demandons une première fois la servlet cycledevie :
et nous rechargeons la page avec le lien "Recharger la page" :
Nous pouvons voir que :
§ la session n'a pas changé (même ID)
§ l'URL de la servlet cycledevie contient bien le jeton comme le montre le champ Adresse ci-dessus
§ le serveur Tomcat récupère donc le jeton de session dans l'URL demandée (si le développeur a pris soin d'encoder celleci).
Nous présentons maintenant un exemple montrant comment stocker des informations dans la session d'un client. Ici l'unique information sera un compteur qui sera incrémenté à chaque fois que l'utilisateur appellera l'URL de la servlet. Lorsque celle-ci est appelée la première fois, on a la page suivante :
Si on clique sur le lien "Recharger la page" ci-dessus, on obtient la nouvelle page suivante :
L'application a trois composantes :
§ une servlet qui traite la requête du client
§ une page jsp qui affiche la valeur du compteur
§ une page jsp qui affiche une éventuelle erreur
Ces trois composantes sont installées dans l'application web sessions déjà utilisée. Le fichier de celle-ci a été modifié pour configurer les nouvelles servlets :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
|
compteur
compteur
urlAffichageCompteur
urlErreur
|
|
compteur
/compteur
|
|
§ la servlet s'appelle compteur (servlet-name) et est liée au fichier classe compteur.class (servlet-class) § elle a deux paramètres d'initialisation : o urlAffichageCompteur : URL de la page JSP d'affichage du compteur o urlErreur : URL de la page JSP d'affichage d'une éventuelle erreur
La servlet est la suivante :
import .*; import javax.servlet.*; import .*; public class compteur extends HttpServlet{
// variables d'instance
String msgErreur=null;
String urlAffichageCompteur=null;
String urlErreur=null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
// l'initialisation s'est-elle bien passée ? if(msgErreur!=null){
// on passe la main à la page d'erreur
getServletContext().getRequestDispatcher(urlErreur).forward(request,response); }
|
// on récupère la session en cours
HttpSession session=request.getSession();
// et le compteur
String compteur=(String)session.getAttribute("compteur"); if(compteur==null) compteur="0";
// incrémentation du compteur try{ compteur=""+(Integer.parseInt(compteur)+1);
}catch(Exception ex){}
// mémorisation compteur dans la session session.setAttribute("compteur",compteur);
// et dans la requête
request.setAttribute("compteur",compteur);
|
// on passe la main à l'url d'affichage du compteur
getServletContext().getRequestDispatcher(urlAffichageCompteur).forward(request,response); }
|
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ doGet(request,response);
}
//-------- INIT public void init(){
// on récupère les paramètres d'initialisation ServletConfig config=getServletConfig();
urlAffichageCompteur=config.getInitParameter("urlAffichageCompteur"); urlErreur=config.getInitParameter("urlErreur");
// paramètres ok ? if(urlAffichageCompteur==null){ msgErreur="Configuration incorrecte"; }
}
}
|
Cette servlet a la structure des servlets déjà rencontrées. On notera simplement la gestion du compteur :
§ la session est récupérée via request.getSession()
§ le compteur est récupéré dans cette session via session.getAttribute("compteur")
§ le compteur est incrémenté, remis dans la session (session.setAttribute("compteur",compteur)) et placé dans la requête qui va être passée à la servlet d'affichage (request.setAttribute("compteur",compteur)).
La page d'affichage est la suivante :
|
<%
// jspService
// on récupère le compteur
String compteur= (String) request.getAttribute("compteur"); if(compteur==null) compteur="inconnu";
%>
|
Comptage au fil d'une session
Comptage au fil d'une session (nécessite l'activation des cookies)
|
compteur = (<%= compteur %>) Recharger la page
|
|
La page ci-dessus se contente de récupérer l'attribut compteur (request.getAttribute("compteur")) que lui a passé la servlet principale et l'affiche.
La page d'erreur est la suivante :
<%
// jspService
// une erreur s'est produite
String msgErreur= request.getAttribute("msgErreur"); if(msgErreur==null) msgErreur="Erreur non identifiée"; %>
Comptage au fil d'une session
Comptage au fil d'une session (nécessite l'activation des cookies)
Application indisponible(<%= msgErreur %>)
|
3.5
|
Exemple 3
|
Nous nous proposons d'écrire une application java qui serait cliente de l'application compteur précédente. Elle l'appellerait N fois de suite où N serait passé en paramètre. Notre but est de montrer un client web programmé et la façon de gérer les cookies. Notre point de départ sera un client web générique présenté dans le polycopié Java du même auteur. Il est appellé de la façon suivante :
clientweb URL GET/HEAD § URL : url demandée
§ GET/HEAD : GET pour demander le code HTML de la page, HEAD pour se limiter aux seuls entêtes HTTP
Voici un exemple avec l'URL http://localhost:8080/sessions/compteur :
dos>java clientweb http://localhost:8080/sessions/compteur GET
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=B8A9076E552945009215C34A97A0EC5D;Path=/sessions
Comptage au fil d'une session
Comptage au fil d'une session (nécessite l'activation des cookies)
compteur = (1)
Recharger la page
|
Le programme clientweb affiche tout ce qu'il reçoit du serveur. On voit ci-dessus la commande HTTP Set-cookie avec laquelle le serveur envoie un cookie à son client. Ici le cookie contient deux informations :
§ JSESSIONID qui est le jeton de la session
§ Path qui définit l'URL à laquelle appartient le cookie. Path=/sessions indique au navigateur qu'il devra renvoyer le cookie au serveur à chaque fois qu'il demandera une URL commençant par /sessions. Dans l'application sessions, nous avons utilisé différentes servlets dont les servlets /sessions/cycledevie et /sessions/compteur. Si on appelle la servlet /sessions/cycledevie le navigateur va recevoir un jeton J. Si avec ce même navigateur, on appelle ensuite la servlet /sessions/compteur, le navigateur va renvoyer au serveur le jeton J car celui-ci concerne toutes les URL commençant par /sessions. Dans notre exemple, les servlets cycledevie et compteur n'ont pas à partager le même jeton de session. Elles n'auraient donc pas du être mises dans la même application web. C'est un point à retenir : toutes les servlets d'une même application partagent le même jeton de session.
Le code du client web suit. Il sera ultérieurement le point de départ d'un autre client.
// paquetages importés import .*; import .*; public class clientweb{
// demande une URL
// affiche le contenu de celle-ci à l'écran
public static void main(String[] args){
// syntaxe
final String syntaxe="pg URI GET/HEAD";
|
// nombre d'arguments if(args.length != 2) erreur(syntaxe,1);
// on note l'URI demandée
String URLString=args[0];
String commande=args[1].toUpperCase();
// vérification validité de l'URI
URL url=null; try{ url=new URL(URLString);
}catch (Exception ex){ // URI incorrecte
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
// vérification de la commande
if(! commande.equals("GET") && ! commande.equals("HEAD")){
// commande incorrecte
erreur("Le second paramètre doit être GET ou HEAD",3);
}
// on extrait les infos utiles de l'URL String path=url.getPath(); if(path.equals("")) path="/"; String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost(); int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// on peut travailler
Socket client=null; // le client
BufferedReader IN=null; // le flux de lecture du client
PrintWriter OUT=null; // le flux d'écriture du client
String réponse=null; // réponse du serveur try{
// on se connecte au serveur client=new Socket(host,port);
// on crée les flux d'entrée-sortie du client TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// on demande l'URL - envoi des entêtes HTTP
OUT.println(commande + " " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
OUT.println("Connection: close");
OUT.println(); // on lit la réponse
// on traite la réponse
.println(réponse);
}//while // c'est fini client.close();
} catch(Exception e){ // on gère l'exception erreur(e.getMessage(),4);
}//catch
}//main
// affichage des erreurs
public static void erreur(String msg, int exitCode){
// affichage erreur
.println(msg);
// arrêt avec erreur
(exitCode);
}//erreur
}//classe
|
Nous créons maintenant le programme clientCompteur appelé de la façon suivante :
clientCompteur URL N [JSESSIONID]
§ URL : url de la servlet compteur
§ N : nombre d'appels à faire à cette servlet
§ JSESSIONID : paramètre facultatif - jeton d'une session
Le but du programme est d'appelet N fois la servlet compteur en gérant le cookie de session et en affichant à chaque fois la valeur du compteur renvoyée par le serveur. A la fin des N appels, la valeur de celui-ci doit être N. Voici un premier exemple d'exécution :
dos> clientCompteur http://localhost:8080/sessions/compteur 3
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A;Path=/sessionscookie trouvÚ : 92DB3808CE8FCB47D47D997C8B52294A compteur : 1
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector) compteur : 2
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:00 GMT
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector) compteur : 3
|
Le programme affiche :
§ les entêtes HTTP qu'il envoie au serveur sous la forme -->
§ les entêtes HTTP qu'il reçoit
§ la valeur du compteur après chaque appel
On voit que lors du premier appel :
§ le client n'envoie pas de cookie
§ le serveur en envoie un
Pour les appels suivants :
§ le client renvoie systématiquement le cookie qu'il a reçu du serveur lors du 1er appel. C'est ce qui va permettre au serveur de le reconnaître et d'incrémenter son compteur.
§ le serveur lui ne renvoie plus de cookie
Nous relançons le programme précédent en passant le jeton ci-dessus comme troisième paramètre :
dos> clientCompteur http://localhost:8080/sessions/compteur 3 92DB3808CE8FCB47D47D997C8B52294A
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector) compteur : 4
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector) compteur : 5
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Cookie: JSESSIONID=92DB3808CE8FCB47D47D997C8B52294A
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:25:25 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector) compteur : 6
Le code du client est le suivant :
// paquetages importés import .*; import .*; import .regex.*; public class clientCompteur{
// demande une URL
// affiche le contenu de celle-ci à l'écran
public static void main(String[] args){
// syntaxe
final String syntaxe="pg URL-COMPTEUR N [JSESSIONID]";
// nombre d'arguments
if(args.length !=2 && args.length != 3) erreur(syntaxe,1);
// on note l'URL demandée
String URLString=args[0];
// vérification validité de l'URL
URL url=null; try{ url=new URL(URLString);
}catch (Exception ex){ // URI incorrecte
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),2);
}//catch
// vérification du nombre d'appels N int N=0; try{
N=Integer.parseInt(args[1]); if(N<=0) throw new Exception();
}catch(Exception ex){
|
// argument N incorrect
erreur("Le nombre d'appels N doit être un entier >0",3);
}
// le jeton JSESSIONID a-t-il été passé en paramètre ? String JSESSIONID="";
if (args.length==3) JSESSIONID=args[2];
// on extrait les infos utiles de l'URL String path=url.getPath(); if(path.equals("")) path="/"; String query=url.getQuery();
if(query!=null) query="?"+query; else query="";
String host=url.getHost(); int port=url.getPort();
if(port==-1) port=url.getDefaultPort();
// on peut travailler
Socket client=null; // le client
BufferedReader IN=null; // le flux de lecture du client
PrintWriter OUT=null; // le flux d'écriture du client
String réponse=null; // réponse du serveur
|
// le modèle recherché dans les entêtes HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// le modèle recherché dans le code HTML
Pattern modèleCompteur=Pattern.compile("compteur = .*?(\\d+)");
|
// le résultat de la comparaison au modèle
Matcher résultat=null;
try{
// on fait les N appels au serveur
|
for(int i=0;i<N;i++){
|
// on se connecte au serveur client=new Socket(host,port);
// on crée les flux d'entrée-sortie du client TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
|
// on demande l'URL - envoi des entêtes HTTP envoie(OUT,"GET " + path + query + " HTTP/1.1"); envoie(OUT,"Host: " + host + ":" + port);
if(! JSESSIONID.equals("")){ envoie(OUT,"Cookie: JSESSIONID="+JSESSIONID);
}
envoie(OUT,"Connection: close");
envoie(OUT,"");
|
|
// on lit la réponse jusqu'à la fin des entêtes en cherchant l'éventuel cookie
while((réponse=IN.readLine())!=null){
// suivi réponse
.println(réponse);
// ligne vide ? if(réponse.equals("")) break;
// ligne HTTP non vide
// si on n'a pas le jeton de la session on le cherche if (JSESSIONID.equals("")){
// on compare la ligne HTTP au modèle du cookie résultat=modèleCookie.matcher(réponse);
if(ré()){
// on a trouvé le cookie
JSESSIONID=résultat.group(1);
}
}
}//while
|
|
// c'est fini pour les entêtes HTTP - on passe au code HTML compteurTrouvé=false;
while((réponse=IN.readLine())!=null){
// la ligne courante contient-elle le compteur ? if (! compteurTrouvé){ résultat=modèleCompteur.matcher(réponse);
if(ré()){
// on a trouvé le compteur - on l'affiche
.println("compteur : " + résultat.group(1)); compteurTrouvé=true;
}
}
|
}//while
|
// c'est fini client.close();
}//for
} catch(Exception e){ // on gère l'exception erreur(e.getMessage(),4);
}//catch
}//main
// affichage des erreurs
public static void erreur(String msg, int exitCode){
// affichage erreur
.println(msg);
// arrêt avec erreur
(exitCode);
}//erreur
// suivi échanges client-serveur
public static void envoie(PrintWriter OUT,String msg){
// envoie message au serveur
OUT.println(msg);
// suivi écran
}//erreur
}//classe
|
Décortiquons les points importants de ce programme :
§ on doit faire N échanges client-serveur. C'est pourquoi ceux-ci sont dans une boucle
§ à chaque échange, le client ouvre une connexion TCP-IP avec le serveur. Une fois celle-ci obtenue, il envoie au serveur les entêtes HTTP de sa requête :
// on demande l'URL - envoi des entêtes HTTP envoie(OUT,"GET " + path + query + " HTTP/1.1"); envoie(OUT,"Host: " + host + ":" + port); if(! JSESSIONID.equals("")){
envoie(OUT,"Cookie: JSESSIONID="+JSESSIONID);
}
envoie(OUT,"Connection: close"); envoie(OUT,"");
Si le jeton JSESSIONID est disponible, il est envoyé sous la forme d'un cookie, sinon il ne l'est pas.
§ Une fois sa requête envoyée, le client attend la réponse du serveur. Il commence par exploiter les entêtes HTTP de cette réponse à la recherche d'un éventuel cookie. Pour le trouver, il compare les lignes qu'il reçoit à l'expression régulière du cookie :
// le modèle recherché dans les entêtes HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// on lit la réponse jusqu'à la fin des entêtes en cherchant l'éventuel cookie while((réponse=IN.readLine())!=null){
// suivi réponse
.println(réponse);
// ligne vide ? if(réponse.equals("")) break;
// ligne HTTP non vide
// si on n'a pas le jeton de la session on le cherche
if (JSESSIONID.equals("")){
// on compare la ligne HTTP au modèle du cookie résultat=modèleCookie.matcher(réponse);
if(ré()){
// on a trouvé le cookie
JSESSIONID=résultat.group(1); }
}
}//while
|
§ lorsque le jeton aura été trouvé une première fois, il ne sera plus cherché lors des appels suivants au serveur. Lorsque les entêtes HTTP de la réponse ont été traités, on passe au code HTML de cette même réponse. Dans celle-ci, on cherche la ligne qui donne la valeur du compteur. Cette recherche est faite là aussi avec une expression régulière :
Pattern modèleCompteur=Pattern.compile("compteur = .*?(\\d+)");
.
// c'est fini pour les entêtes HTTP - on passe au code HTML compteurTrouvé=false;
while((réponse=IN.readLine())!=null){
// la ligne courante contient-elle le compteur ? if (! compteurTrouvé){ résultat=modèleCompteur.matcher(réponse); if(ré()){
// on a trouvé le compteur - on l'affiche
.println("compteur : " + résultat.group(1)); compteurTrouvé=true;
}
}
}//while
|
3.6
|
Exemple 4
|
Dans l'exemple précédent, le client web renvoie le jeton sous la forme d'un cookie. Nous avons vu qu'il pouvait aussi le renvoyer au sein même de l'URL demandée sous la forme URL;jsessionid=xxx. Vérifions-le. Le programme est transformé en et modifié de la façon suivante :
.
// on demande l'URL - envoi des entêtes HTTP if(JSESSIONID.equals("")) envoie(OUT,"GET " + path + query + " HTTP/1.1");
else envoie(OUT,"GET " + path + query + ";jsessionid=" + JSESSIONID + " HTTP/1.1"); envoie(OUT,"Host: " + host + ":" + port); envoie(OUT,"Connection: close"); envoie(OUT,""); .
|
Le client demande donc l'URL du compteur par GET URL;jsessionid=xx HTTP/1.1 et n'envoie plus de cookie. C'est la seule modification. Voici les résultats d'un premier appel :
dos> clientCompteur2 http://localhost:8080/sessions/compteur 2
--> GET /sessions/compteur HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:49:30 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=48A6DBA8357D808EC012AAF3A2AFDA63;Path=/sessionscookie trouvÚ : 48A6DBA8357D808EC012AAF3A2AFDA63 compteur : 1
--> GET /sessions/compteur;jsessionid=48A6DBA8357D808EC012AAF3A2AFDA63 HTTP/1.1
--> Host: localhost:8080
--> Connection: close
-->
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Thu, 08 Aug 2002 18:49:30 GMT
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector) compteur : 2
|
Lors du premier appel le client demande l'URL sans jeton de session. Le serveur lui répond en lui envoyant le jeton. Le client réinterroge alors la même URL en adjoignant le jeton reçu à celle-ci. On voit que le compteur est bien incrémenté preuve que le serveur a bien reconnu qu'il s'agissait de la même session.
Cet exemple montre une application composée de trois pages qu'on appellera page0, page1 et page2. L'utilisateur doit les obtenir dans cet ordre :
§ page0 est un formulaire demandant une information : un nom
§ page1 est un formulaire obtenu en réponse à l'envoi du formulaire de page0. Il demande une seconde information : un age
§ page2 est un document HTML qui affiche le nom obtenu par page0 et l'âge obtenu par page1.
Il y a là trois échanges client-serveur :
§ au premier échange le formulaire page0 est demandé par le client et envoyé par le serveur
§ au second échange le formulaire page1 est demandé par le client et envoyé par le serveur. Le client envoie le nom au serveur.
§ au troisième échange le document page3 est demandé par le client et envoyé par le serveur. Le client envoie l'âge au serveur. Le document page3 doit afficher le nom et l'âge. Le nom a été obtenu par le serveur au second échange et "oublié" depuis. On utilise une session pour enregistrer le nom à l'échange 2 afin qu'il soit disponible lors de l'échange 3.
La page page0 obtenue au premier échange est la suivante :
On remplit le champ du nom :
On utilise le bouton Suite et on obtient alors la page page1 suivante :
On remplit le champ de l'âge :
On utilise le bouton Suite et on obtient alors la page page2 suivante :
Lorsqu'on soumet la page page0 au serveur, celui-ci peut la renvoyer avec un code d'erreur si le nom est vide :
L'application est composée d'une servlet et de quatre pages JSP :
affiche page0 affiche page1 affiche page2 affiche une page d'erreur
L'application web s'appelle suitedepages et est configurée comme suit dans le fichier de Tomcat :
Le fichier de configuration de l'application suitedepages est le suivant :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
main
main
urlPage0
urlPage1
urlPage2
urlErreur
main
/main
|
La servlet principale s'appelle main et grâce à son alias (servlet-mapping) est accessible via l'URL http://localhost:8080/suitedepages/main. Elle a quatre paramètres d'initialisation qui sont les URL des quatre pages JSP utilisées pour les différents affichages. Le code de la servlet main est le suivant :
import .*; import javax.servlet.*; import .*; import .*; import .regex.*; public class main extends HttpServlet{
// variables d'instance
String msgErreur=null;
String urlPage0=null;
String urlPage1=null;
String urlPage2=null;
String urlErreur=null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
// l'initialisation s'est-elle bien passée ? if(msgErreur!=null){
// on passe la main à la page d'erreur
getServletContext().getRequestDispatcher(urlErreur).forward(request,response); }
|
// on récupère le paramètre étape
String étape=request.getParameter("etape");
// on récupère la session en cours
HttpSession session=request.getSession();
// on traite l'étape en cours
if(étape==null) étape0(request,response,session); if(étape.equals("1")) étape1(request,response,session); if(étape.equals("2")) étape2(request,response,session);
// autres cas sont invalides étape0(request,response,session);
|
}
//-------- POST
}
//-------- INIT public void init(){
|
// on récupère les paramètres d'initialisation
ServletConfig config=getServletConfig(); urlPage0=config.getInitParameter("urlPage0"); urlPage1=config.getInitParameter("urlPage1"); urlPage2=config.getInitParameter("urlPage2"); urlErreur=config.getInitParameter("urlErreur");
|
// paramètres ok ? if(urlPage0==null || urlPage1==null || urlPage2==null){ msgErreur="Configuration incorrecte"; }
}
//-------- étape0
public void étape0(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException, ServletException{
|
// on fixe quelques attributs request.setAttribute("nom","");
// on présente la page 0
request.getRequestDispatcher(urlPage0).forward(request,response);
|
}
//-------- étape1
public void étape1(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException, ServletException{
|
// on récupère le nom dans la requête
String nom=request.getParameter("nom");
// nom positionné ? if(nom==null) étape0(request,response,session);
// on enlève les éventuels espaces du nom
();
// on le met dans un attribut de la requête
request.setAttribute("nom",nom);
// nom vide ? if(nom.equals("")){
// c'est une erreur
|
ArrayList erreurs=new ArrayList();
("Nous n'avez pas indiqué de nom");
// on met les erreurs dans la requête request.setAttribute("erreurs",erreurs);
// retour à la page 0
étape0(request,response,session);
}
// nom valide - on le mémorise dans la session en cours
session.setAttribute("nom",nom);
// on fixe l'attribut age dans la requête
request.setAttribute("age","");
// on présente la page 1
request.getRequestDispatcher(urlPage1).forward(request,response);
|
}
//-------- étape2
public void étape2(HttpServletRequest request, HttpServletResponse response, HttpSession session) throws IOException, ServletException{
String nom=(String)session.getAttribute("nom");
// nom positionné ? if(nom==null) étape0(request,response,session);
// on le met dans un attribut de la requête
request.setAttribute("nom",nom);
// on récupère l'âge dans la requête
String age=request.getParameter("age");
// age positionné ?
if(age==null){
// retour à la page 1 request.setAttribute("age","");
request.getRequestDispatcher(urlPage1).forward(request,response);
}
// on mémorise l'âge dans la requête
();
request.setAttribute("age",age);
// age valide ? if(! Pattern.matches("^\\s*\\d+\\s*$",age)){
// c'est une erreur
ArrayList erreurs=new ArrayList(); ("Age invalide");
// on met les erreurs dans la requête request.setAttribute("erreurs",erreurs);
// retour à la page 1
request.getRequestDispatcher(urlPage1).forward(request,response);
}
// age valide - on présente la page 2
request.getRequestDispatcher(urlPage2).forward(request,response);
|
}
}
|
§ la méthode init récupère les quatre paramètres d'initialisation et positionne un message d'erreur si l'un d'eux est manquant
§ nous avons vu que la requête comprenait trois échanges. Pour savoir où on en est dans ceux-ci, les formulaires page0 et page1 ont une variable cachée etape qui a la valeur 1 (page0) ou 2 (page1). On pourrait ici voir ce numéro comme le numéro de page suivante à afficher. Dans la méthode doGet, ce paramètre est récupéré dans la requête et selon sa valeur le traitement est délégué à trois autres méthodes :
o étape0 traite la requête initiale et envoie page0 o étape1 traite le formulaire de page0 et envoie page1 ou de nouveau page0 s'il y a eu erreur o étape2 traite le formulaire de page1 et envoie page2 ou de nouveau page1 s'il y a eu erreur
§ étape0
o affiche page0 avec un nom vide
o vérifie que le nom est non vide. Si ce n'est pas le cas on affiche de nouveau page0 avec un message d'erreur.
o mémorise le nom dans la session courante et affiche page1 si le nom est valide.
§ étape2 o récupère le paramètre nom dans la session courante. o vérifie que le nom existe (pas null). Si ce n'est pas le cas on affiche de nouveau page0 comme si c'était le premier appel.
o récupère le paramètre age dans la requête courante envoyée par page1.
o vérifie que l'âge est valide. Si ce n'est pas le cas on affiche de nouveau page1 avec un message d'erreur.
o mémorise le nom et l'âge comme attributs de requête et affiche page2 si le nom et l'âge sont valides.
La page est la suivante :
<%@ page import=".*" %>
|
<% //
// on récupère les attributs de la requête
String nom=(String)request.getAttribute("nom");
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
// attributs valides ? if(nom==null){
// retour à la servlet principale
request.getRequestDispatcher("/main").forward(request,response);
}
%>
|
page 0
Page 0/2
|
|
|
Votre nom |
|
<% // erreurs ? if (erreurs!=null){
%>
Les erreurs suivantes se sont produites
<% for(int i=0;i<();i++){ %>
<% }//for %>
<% }//if %>
§ la page peut être appelée par la servlet principale dans deux cas : o lors de la requête initiale o après traitement du formulaire de page0 lorsqu'il y a une erreur
§ le paramètre nom à afficher lui est donné par la servlet principale ainsi que l'éventuelle liste d'erreurs. La servlet commence donc par récupérer ces deux informations.
§ le formulaire est "posté" à la servlet principale avec le champ caché (hidden) etape qui indique à quelle étape de l'application on se trouve.
Votre âge
<%@ page import=".*" %>
|
<% //
// on récupère les attributs de la requête
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
// attributs valides ?
if(nom==null || age==null){ // retour à la servlet principale
request.getRequestDispatcher("/main").forward(request,response);
}
%>
|
page 1
|
Page 1/2
|
|
|
Nom |
<%= nom %> |
<% // erreurs ? if (erreurs!=null){
%>
Les erreurs suivantes se sont produites
<% for(int i=0;i<();i++){ %>
<% }//for %>
<% }//if %>
La page a une structure analogue à celle de la page au détail près qu'elle reçoit maintenant deux attributs de la servlet principale : nom et age. Enfin la page est la suivante :
Votre âge <%= age %>
|
<%
//
// on récupère les attributs de la requête
String nom=(String)request.getAttribute("nom");
String age=(String)request.getAttribute("age");
// attributs valides ?
if(nom==null || age==null){ // retour à la servlet principale
request.getRequestDispatcher("/main").forward(request,response);
}
%>
|
page 2
Page 2/2
|
Nom |
<%= nom %> |
La page reçoit elle aussi les attributs nom et age de la servlet principale. Elle se contente de les afficher. Pour terminer la page chargée d'afficher une erreur en cas d'initialisation incorrecte de la servlet est la suivante :
<%
// jspService
// une erreur s'est produite
String msgErreur= request.getAttribute("msgErreur"); if(msgErreur==null) msgErreur="Erreur non identifiée"; %>
|
Suite de pages
Application indisponible(<%= msgErreur %>)
Elle affiche l'attribut msgErreur que lui a passé la servlet principale.
En conclusion, on pourra remarquer qu'au cours des trois étapes de l'application, c'est toujours la servlet principale qui est interrogée en premier par le navigateur. Mais ce n'est pas elle qui génère la réponse à afficher mais l'une des quatre pages JSP. L'utilisateur ne voit pas ce point, le navigateur continuant à afficher dans son champ "Adresse" l'URL initialement demandée donc celle de la servlet principale.
4. L'application IMPOTS
Nous reprenons ici l'application IMPOTS qui est utilisée à de nombreuses reprises dans le polycopié Java du même auteur. Rappelons-en la problématique. Il s'agit d'écrire une application permettant de calculer l'impôt d'un contribuable. On se place dans le cas simplifié d'un contribuable n'ayant que son seul salaire à déclarer :
• on calcule le nombre de parts du salarié nbParts=nbEnfants/2 +1 s'il n'est pas marié, nbEnfants/2+2 s'il est marié, où nbEnfants est son nombre d'enfants.
• s'il a au moins trois enfants, il a une demi-part de plus
• on calcule son revenu imposable R=0.72*S où S est son salaire annuel
• on calcule son coefficient familial QF=R/nbParts • on calcule son impôt I. Considérons le tableau suivant :
12620.0
13190 15640 24740 31810 39970 48360 55790 92970
127860 151250 172040
195000 0
|
0
0.05
0.1
0.15
0.2
0.25
0.3
0.35
0.4
0.45
0.50
0.55
0.60
0.65
|
0
631
1290.5 2072.5
3309.5
4900
6898.5
9316.5
12106
16754.5
23147.5
30710 39312 49062
|
Chaque ligne a 3 champs. Pour calculer l'impôt I, on recherche la première ligne où QF<=champ1. Par exemple, si QF=23000 on trouvera la ligne
24740 0.15 2072.5
0 0.65 49062
ce qui donne l'impôt I=0.65*R - 49062*nbParts.
Les données définissant les différentes tranches d'impôt sont dans une base de données ODBC-MySQL. MySQL est un SGBD du domaine public utilisable sur différentes plate-formes dont Windows et Linux. Avec ce SGBD, une base de données dbimpots a été créée avec dedans une unique table appelée impots. L'accès à la base est contrôlé par un login/motdepasse ici admimpots/mdpimpots. La copie d'écran suivante montre comment utiliser la base dbimpots avec MySQL :
C:\Program Files\EasyPHP\mysql\bin>
Enter password: *********Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use dbimpots;
Database changed
|
mysql -u admimpots -p
|
|
|
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots |
+--------------------+
1 row in set (0.00 sec)
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES | | NULL | |
| coeffR | double | YES | | NULL | |
| coeffN | double | YES | | NULL | |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> select * from impots; +---------+--------+---------+
| limites | coeffR | coeffN |
+---------+--------+---------+
| 12620 | 0 | 0 |
| 13190 | 0.05 | 631 |
| 15640 | 0.1 | 1290.5 |
| 24740 | 0.15 | 2072.5 |
| 31810 | 0.2 | 3309.5 |
| 39970 | 0.25 | 4900 |
| 48360 | 0.3 | 6898 |
| 55790 | 0.35 | 9316.5 |
| 127860 | 0.45 | 16754 |
| 151250 | 0.5 | 23147.5 |
| 172040 | 0.55 | 30710 |
| 195000 | 0.6 | 39312 |
| 0 | 0.65 | 49062 |
+---------+--------+---------+
14 rows in set (0.00 sec) mysql>quit
|
La base de données dbimpots est transformée en source de données ODBC de la façon suivante : • on lance le gestionnaire des sources de données ODBC 32 bits
• on utilise le bouton [Add] pour ajouter une nouvelle source de données ODBC
• le pilote MySQL demande un certain nombre de renseignements :
1le nom DSN à donner à la source de données ODBC - peut-être quelconque
2la machine sur laquelle s'exécute le SGBD MySQL - ici localhost. Il est intéressant de noter que la base de données pourrait être une base de données distante. Les applications locales utilisant la source de données ODBC ne s'en apercevraient pas. Ce serait le cas notamment de notre application Java.
3la base de données MySQL à utiliser. MySQL est un SGBD qui gère des bases de données relationnelles qui sont des ensembles de tables reliées entre-elles par des relations. Ici, on donne le nom de la base gérée. 4le nom d'un utilisateur ayant un droit d'accès à cette base
5son mot de passe
Deux classes ont été définies pour calculer l'impôt : impots et impotsJDBC. Une instance de la classe impots est construite avec les données des tranches d'impôts passées en paramètres dans des tableaux :
// création d'une classe impots
public class impots{
// les données nécessaires au calcul de l'impôt
// proviennent d'une source extérieure
protected double[] limites=null; protected double[] coeffR=null; protected double[] coeffN=null;
// constructeur vide protected impots(){}
// constructeur
|
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
|
// on vérifie que les 3 tableaux ont la même taille
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// c'est bon this.limites=LIMITES; this.coeffR=COEFFR; this.coeffN=COEFFN; }//constructeur
// calcul de l'impôt
|
public long calculer(boolean marié, int nbEnfants, int salaire){
|
// calcul du nombre de parts double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2; else nbParts=(double)nbEnfants/2+1; if (nbEnfants>=3) nbParts+=0.5;
// calcul revenu imposable & Quotient familial double revenu=0.72*salaire; double QF=revenu/nbParts; // calcul de l'impôt
limites[limites.length-1]=QF+1; int i=0;
while(QF>limites[i]) i++; // retour résultat
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]); }//calculer }//classe
|
La classe impotsJDBC dérive de la classe impots précédente. Une instance de la classe impotsJDBC est construite avec les données des tranches d'impôts stockées dans une base de données. Les informations nécessaires pour accéder à cette base de données sont passées en paramètres au constructeur :
// paquetages importés import .*; import .*;
public class impotsJDBC extends impots{
// rajout d'un constructeur permettant de construire
// les tableaux limites, coeffr, coeffn à partir de la table // impots d'une base de données
|
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS) throws SQLException,ClassNotFoundException{
|
// dsnIMPOTS : nom DSN de la base de données
// userIMPOTS, mdpIMPOTS : login/mot de passe d'accès à la base
// les tableaux de données
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// connexion à la base
Class.forName(".JdbcOdbcDriver");
// création d'un objet Statement
Statement S=connect.createStatement();
// requête select
String select="select limites, coeffr, coeffn from impots";
// exécution de la requête
ResultSet RS=S.executeQuery(select); while(()){
// exploitation de la ligne courante (RS.getString("limites")); (RS.getString("coeffr")); (RS.getString("coeffn"));
}// ligne suivante
// fermeture ressources
RS.close();
|
S.close(); connect.close();
// transfert des données dans des tableaux bornés int (); limites=new double[n]; coeffR=new double[n]; coeffN=new double[n]; for(int i=0;i<n;i++){
limites[i]=Double.parseDouble((String)(i)); coeffR[i]=Double.parseDouble((String)(i)); coeffN[i]=Double.parseDouble((String)(i));
}//for
}//constructeur
}//classe
|
Une fois qu'une instance de la classe impotsJDBC a été construite, on peut appeler de façon répétée sa méthode calculer afin de calculer l'impôt :
public long calculer(boolean marié, int nbEnfants, int salaire){
L'acquisition des trois données nécessaires peut se faire de multiple façons. L'intérêt de la classe impotsJDBC est qu'on a juste à se préoccuper de cette acquisition. Une fois les trois information (statut marital, nombre d'enfants, salaire annuel) obtenues, l'appel à la méthode calculer de la classe impotsJDBC nous donne l'impôt à payer.
On se place dans le contexte d'une application web qui présenterait une interface HTML à un utilisateur afin d'obtenir les trois paramètres nécessaires au calcul de l'impôt :
§ l'état marital (marié ou non)
§ le nombre d'enfants
§ le salaire annuel
L'affichage du formulaire est réalisé par la page JSP suivante :
|
<%
// on récupère les attributs passés par la servlet principale
String chkoui=(String)request.getAttribute("chkoui");
String chknon=(String)request.getAttribute("chknon");
String txtEnfants=(String)request.getAttribute("txtEnfants");
String txtSalaire=(String)request.getAttribute("txtSalaire");
String txtImpots=(String)request.getAttribute("txtImpots");
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>
|
impots
|
|
Calcul d'impôts
|
|
|
Etes-vous marié(e) |
|
>oui
>non
|
|
|
|
|
|
|
|
|
|
|
<%
// y-a-t-il des erreurs if(erreurs!=null){ // affichage des erreurs out.println("
");
out.println("");
out.println("Les erreurs suivantes se sont produites ");
out.println("
for(int i=0;i<();i++){
out.println("
}
out.println(""); out.println("");
}
%>
La page JSP se contente d'afficher des informations qui lui sont passées par la servlet principale de l'application :
// on récupère les attributs passés par la servlet principale
String chkoui=(String)request.getAttribute("chkoui");
String chknon=(String)request.getAttribute("chknon");
String txtEnfants=(String)request.getAttribute("txtEnfants");
String txtSalaire=(String)request.getAttribute("txtSalaire");
String txtImpots=(String)request.getAttribute("txtImpots");
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
|
attributs des boutons radio oui, non - ont pour valeurs possibles "checked" ou "" afin d'allumer ou non le bouton radio correspondant
le montant de l'impôt à payer
une liste éventuelle d'erreurs si erreurs!=null
La page envoyée au client contient un script javascript contenant une fonction effacer associée au bouton "Effacer" dont le rôle est de remettre le formulaire dans son état initial : bouton non coché, champs de saisie vides. On notera que ce résultat ne pouvait pas être obtenu avec un bouton HTML de type "reset". En effet, lorsque ce type de bouton est utilisé, le navigateur remet le formulaire dans l'état où il l'a reçu. Or dans notre application, le navigateur reçoit des formulaires qui peuvent être non vides.
La servlet principale de l'application s'appelle et son code est le suivant :
import .*; import javax.servlet.*; import .*; import .regex.*; import .*; public class main extends HttpServlet{
// variables d'instance
String msgErreur=null;
String urlAffichageImpots=null;
String urlErreur=null;
String DSNimpots=null;
String admimpots=null; String mdpimpots=null; impotsJDBC impots=null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
|
// l'initialisation s'est-elle bien passée ? if(msgErreur!=null){
// on passe la main à la page d'erreur request.setAttribute("msgErreur",msgErreur);
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
|
// des attributs de la requête
String chkoui=null;
String chknon=null;
String txtImpots=null;
|
// on récupère les paramètres de la requête
String optMarie=request.getParameter("optMarie"); // statut marital
String txtEnfants=request.getParameter("txtEnfants"); // nbre d'enfants if(txtEnfants==null) txtEnfants="";
String txtSalaire=request.getParameter("txtSalaire"); // salaire annuel if(txtSalaire==null) txtSalaire="";
// a-t-on tous les paramètres attendus
if(optMarie==null || txtEnfants==null || txtSalaire==null){
// on passe la main à l'url d'affichage de l'impôt
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
|
|
// on a tous les paramètres - on les vérifie
ArrayList erreurs=new ArrayList();
// état marital
if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
// erreur
("Etat marital incorrect");
optMarie="non";
}
// nombre d'enfants ();
if(! Pattern.matches("^\\d+$",txtEnfants)){
// erreur
("Nombre d'enfants incorrect");
|
}
// salaire
();
if(! Pattern.matches("^\\d+$",txtSalaire)){
// erreur
("Salaire incorrect");
}
|
// s'il y a des erreurs, on les passe en attribut de la requête if(()!=0){ request.setAttribute("erreurs",erreurs); txtImpots="";
}else{
|
// on peut calculer l'impôt à payer
try{ int nbEnfants=Integer.parseInt(txtEnfants); int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
|
}catch(Exception ex){}
}
// les autres attributs de la requête if(optMarie.equals("oui")){ request.setAttribute("chkoui","checked"); request.setAttribute("chknon","");
}else{ request.setAttribute("chknon","checked"); request.setAttribute("chkoui","");
}
request.setAttribute("txtEnfants",txtEnfants); request.setAttribute("txtSalaire",txtSalaire); request.setAttribute("txtImpots",txtImpots);
|
// on passe la main à l'url d'affichage de l'impôt
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
|
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ doGet(request,response);
}
//-------- INIT public void init(){
|
// on récupère les paramètres d'initialisation
ServletConfig config=getServletConfig();
|
// paramètres ok ? if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){ msgErreur="Configuration incorrecte";
return;
}
|
// on crée une instance d'impotsJDBC
try{ impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
}catch(Exception ex){ msgErreur=ex.getMessage();
}
|
}
}
|
§ la méthode init de la servlet fait deux choses : o elle récupère ses paramètres d'initialisation. Ceux-ci lui permettent de se connecter à la base de données ODBC contenant les données des différentes transches d'impôts (DSNimpots, admimpots, mdpimpots) et les URL des pages associées à l'application : urlAffichageImpots pour le formulaire, urlErreur pour la page d'erreur.
o elle crée une instance de la classe impotsJDBC
Dans les deux cas, les erreurs possibles sont gérées et le message d'erreur placé dans la variable msgErreur.
§ la méthode doGET o vérifie tout d'abord que la servlet s'est initialisée correctement. Si ce n'est pas le cas, la page d'erreur est affichée
o récupère les paramètres attendus du formulaire d'impôts : optMarie, txtEnfants, txtSalaire. Si l'un d'eux manque (==null) un formulaire d'impôts vide est envoyé. On pourrait se dire que la vérification de la validité du paramètre optMarie est inutile. Celui-ci est la valeur d'un bouton radio et ne peut avoir ici que l'une des valeurs "oui" et "non". C'est oublier que rien n'empêche un programme d'interroger directement la servlet en lui envoyant les paramètres qu'il veut. On ne peut jamais être assuré d'être réellement connecté à un navigateur. Oublier ce point peut amener à des trous de sécurité dans l'application et il est d'ailleurs fréquent d'en trouver même dans des applications commerciales.
o les informations nécessaires à l'affichage de la page sont mis en attributs de la requête et le formulaire d'impôts est ensuite affiché
La page JSP d'erreur est la suivante :
<%
// jspService
// une erreur s'est produite
String msgErreur= (String)request.getAttribute("msgErreur"); if(msgErreur==null) msgErreur="Erreur non identifiée"; %>
impots
calcul d'impots
Application indisponible(<%= msgErreur %>)
|
L'aplication web s'appelle impots et est configuré dans le fichier de Tomcat de la façon suivante :
Le dossier de l'application contient les dossiers et fichiers suivants :
Le fichier de configuration de l'application impots est le suivant :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
main
main
urlAffichageImpots
DSNimpots
mysql-dbimpots
admimpots
admimpots
mdpimpots
mdpimpots
urlErreur
main
/main
|
La servlet principale s'appelle main et a l'alias /main. Elle est donc accessible via l'URL http://localhost:8080/impots/main.
Voici quelques exemples d'application :
Pour s'initialiser correctement, la servlet doit avoir accès à la base de données mysql-dbimpots. Voici la page obtenue si par exemple le serveur MySQL n'est pas lancé et la base mysql-dbimpots en conséquence inaccessible :
La page obtenue en cas de saisie incorrecte est la suivante :
Si les saisie sont correctes, l'impôt est calculé :
Dans l'exemple précédent, la validité des paramètres txtEnfants, txtSalaire du formulaire est vérifiée par le serveur. On se propose ici de la vérifier par un script Javascript inclus dans la page du formulaire. C'est alors le navigateur qui fait la vérification des paramètres. Le serveur n'est alors sollicité que si ceux-ci sont valides. On économise ainsi de la "bande passante". La page JSP d'affichage devient la suivante :
impots
..
|
|
|
|
..
|
On notera les changements suivants :
§ le bouton Calculer n'est plus de type submit mais de type button associé à une fonction appelée calculer. C'est celle-ci qui fera l'analyse des champs txtEnfants et txTsalaire. S'ils sont corrects, les valeurs du formulaire seront envoyées au serveur (submit) sinon un message d'erreur sera affiché.
Voici un exemple d'affichage en cas d'erreur :
Nous modifions légèrement l'application pour y introduire la notion de session. Nous considérons maintenant que l'application est une application de simulation de calcul d'impôts. Un utilisateur peut alors simuler différentes "configurations" de contribuable et voir quelle serait pour chacune d'elles l'impôt à payer. La page WEb ci-dessous donne un exemple de ce qui pourrait être obtenu :
La servlet principale est modifiée et s'appelle désormais simulations. Elle est configurée comme suit au sein de l'application impots :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
.
|
simulations
simulations
|
|
urlSimulationImpots
|
DSNimpots
mysql-dbimpots
admimpots
admimpots
mdpimpots
mdpimpots
urlErreur
|
|
simulations
/simulations
|
|
La servlet principale s'appelle simulations et s'appuie sur le fichier classe simulations.class. Elle a l'alias /simulations qui la rend accessible via l'URL http://localhost:8080/impots/simulations. Elle a les mêmes paramètres d'initialisation que la servlet main étudiée précédemment pour ce qui est de l'accès à la base de données. Un nouveau paramètre apparaît, urlSimulationsImpots qui est l'URL de la page JSP de simulation (celle qui vient d'être présentée un peu plus haut).
La servlet est proche de la servlet . Elle en diffère par les points principaux suivants :
§ la servlet simulations calcule de la même façon la valeur txtImpots, enregistre les paramètres (optMarie, txtEnfants, txtsalaire, txtImpots) dans une liste appelée simulations. C'est cette liste qui est passée en paramètre à la page JSP d'affichage. Afin que cette liste contienne toutes les simulations effectuées par l'utilisateur, elle est enregistrée comme attribut de la session courante.
La servlet simulations est la suivante ( n'ont été conservées que les lignes de code qui diffèrent de celles de l'application précédente) :
import .*; .
|
public class simulations extends HttpServlet{
|
// variables d'instance
String msgErreur=null;
|
String urlSimulationImpots=null;
|
String urlErreur=null; ..
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
..
|
// on récupère les simulations précédentes de la session
HttpSession session=request.getSession();
ArrayList simulations=(ArrayList)session.getAttribute("simulations"); if(simulations==null) simulations=new ArrayList(); // on met les simulations dans la requête courante request.setAttribute("simulations",simulations);
|
// d'autres attributs de la requête ..
// a-t-on tous les paramètres attendus
if(optMarie==null || txtEnfants==null || txtSalaire==null){
..
// on passe la main à l'url d'affichage des simulations de calculs d'impôts
|
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
|
}
// on a tous les paramètres - on les vérifie ..
// s'il y a des erreurs, on les passe en attributs de la requête if(()!=0){ request.setAttribute("erreurs",erreurs);
}else{ try{
// on peut calculer l'impôt à payer int nbEnfants=Integer.parseInt(txtEnfants); int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots}; (simulation);
// la nouvelle valeur de simulations est remise dans la session
session.setAttribute("simulations",simulations);
|
}catch(Exception ex){}
}
// autres attributs de la requête .
// on passe la main à l'url d'affichage des simulations
|
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
|
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ doGet(request,response);
}
//-------- INIT public void init(){
// on récupère les paramètres d'initialisation
ServletConfig config=getServletConfig();
|
urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
|
urlErreur=config.getInitParameter("urlErreur"); DSNimpots=config.getInitParameter("DSNimpots"); admimpots=config.getInitParameter("admimpots"); mdpimpots=config.getInitParameter("mdpimpots");
// paramètres ok ?
. }
}
|
La page JSP d'affichage est devenue la suivante (n'a été conservé que le code qui diffère de la page JSP d'affichage de l'application précédente).
(F)
"); for(int i=0;i<();i++){ String[] simulation=(String[])(i);out.println(""); }out.println("
<%@ page import=".*" %>
|
<%
// on récupère les attributs passés par la servlet principale ..
ArrayList simulations=(ArrayList)request.getAttribute("simulations");
%>
|
impots
Calcul d'impôts
|
|
..
|
|
|
<%
// y-a-t-il des erreurs ? if(erreurs!=null){
|
}else if(()!=0){ // résultats des simulations
out.println("
Résultats des simulations
"); out.println("
");
out.println("
%>
4.5
Version 4
Nous allons maintenant créer une application autonome qui sera un client web de l'application /impots/simulations précédente. Cette application aura l'interface graphique suivante :
n°
|
type
|
nom
|
rôle
|
1
|
JTextField
|
txtUrlServiceImpots
|
URL du service de simulation du calcul d'impôts
|
2
|
JRadioButton
|
rdOui
|
coché si marié
|
3
|
JRadioButton
|
rdNon
|
coché si non marié
|
4
|
JSpinner
|
spinEnfants
|
nombre d'enfants du contribuable (minimum=0, maximum=20, increment=1)
|
5
|
JTextField
|
txtSalaire
|
salaire annuel en F du contribuable
|
6
|
JList dans
JScrollPane
|
lstSimulations
|
liste des simulations
|
Le menu Impôts est composé des options suivantes :
option
principale
|
option
secondaire
|
nom
|
rôle
|
Impôts
|
Calculer
|
mnuCalculer
|
calcule l'impôt à payer lorsque toutes les données nécessaires au calcul sont présentes et correctes
|
|
Effacer
|
mnuEffacer
|
remet le formulaire dans son état initial
|
|
Quitter
|
mnuQuitter
|
termine l'application
|
Règles de fonctionnement
§ l'option de menu Calculer reste éteinte si l'un des champs 1 ou 5 est vide
§ une URL syntaxiquement incorrecte en 1 est détectée
§ un salaire incorrect est détecté
§ toute erreur de connexion au serveur est signalée (dans l'exemple 1 ci-dessous, le port est incorrect - dans l'exemple 2, l'URL demandée n'existe pas - dans l'exemple 3 la base de données MySQL n'était pas lancé)
§ Si tout est correct, les simulations sont affichées
Demandons l'URL alors que la base MySQL permettant de créer on objet de type impots n'est pas lancée :
Dos>
|
java clientweb http://localhost:8080/impots/simulations GET
|
|
|
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:31:04 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=9DEC8B27966A1FBE3D4968A7B9DF3331;Path=/impots
|
impots
calcul d'impôts
Application indisponible([TCX][MyODBC]Can't connect to MySQL server on 'localhost' (10061))
|
Pour récupérer l'erreur, le client web devra chercher dans la réponse du serveur web la ligne ayant le texte "Application indisponible". Maintenant lançons la base MySQL et demandons la même URL en lui passant les valeurs qu'elle attend (elle les attend indifféremment sous la forme d'un GET ou d'un POST - cf code java du serveur) :
Dos>java clientweb "http://localhost:8080/impots/simulations?ptMarie=oui&txtEnfants=2&txtSalaire=200000"
|
GET
|
|
|
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:42:36 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=C2A707600E98A37A343611D80DD5C8A2;Path=/impots
|
impots
Calcul d'imp¶ts
|
Résultats des simulations
<table "border="1">
|
Marié |
Enfants |
Salaire annuel (F) |
Impôts à payer (F) |
oui |
2 |
200000 |
22504 |
On retrouve ici la totalité du document HTML envoyé par le serveur. On trouve le résultat des différentes simulations dans l'unique table présente dans ce document. L'expression régulière nous permettant de récupérer les informations pertinentes du document pourra être la suivante :
"\\s*(.*?)\\s*(.*?)\\s*(.*?)\\s*(.*?)\\s*"
où les quatre expressions parenthésées représentent les quatre informations à récupérer.
§ vérifier que toutes les données de l'interface sont valides et éventuellement signaler les erreurs.
§ se connecter à l'URL indiquée dans le champ 1. Pour cela on suivra le modèle du client web générique déjà présenté et étudié
§ dans le flux de la réponse du serveur, utiliser des expressions régulières pour soit : o trouver le message d'erreur s'il y en a un o trouver les résultats des simulations s'il n'y a pas d'erreur
Le code lié au menu calculer est le suivant :
|
void mnuCalculer_actionPerformed(ActionEvent e) {
|
// calcul de l'impôt
// vérification URL service
URL urlImpots=null; try{ urlImpots=new URL(txtURLServiceImpots.getText().trim()); String query=urlImpots.getQuery(); if(query!=null) throw new Exception();
}catch (Exception ex){
// msg d'erreur
JOptionPane.showMessageDialog(this,"URL incorrecte.
Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// focus sur champ erroné
txtURLServiceImpots.requestFocus();
// retour à l'interface return;
}
// vérification salaire int salaire=0; try{
salaire=Integer.parseInt(txtSalaire.getText().trim()); if(salaire<0) throw new Exception();
}catch (Exception ex){
// msg d'erreur
JOptionPane.showMessageDialog(this,"Salaire incorrect.
Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// focus sur champ erroné txtSalaire.requestFocus(); // retour à l'interface return;
}
// nbre d'enfants
Integer nbEnfants=(Integer)spinEnfants.getValue();
|
try{
// on calcule l'impôt
calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
}catch (Exception ex){
// on affiche l'erreur
JOptionPane.showMessageDialog(this,"L'erreur suivante s'est produite : " + ex.getMessage(),"Erreur",JOptionPane.ERROR_MESSAGE);
}
|
}//mnuCalculer
// calcul de l'impôt
// urlImpots : URL du service des impôts
// marié : true si marié, false sinon
// nbEnfants : nombre d'enfants
// salaire : salaire annuel
|
// on retire d'urlImpots les infos nécessaire à la connexion au serveur d'impôts
String path=urlImpots.getPath(); if(path.equals("")) path="/";
|
String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
|
String host=urlImpots.getHost(); int port=urlImpots.getPort();
if(port==-1) port=urlImpots.getDefaultPort();
|
\\s*\\s*\\s*\\s*
td>\\s*
");// le modèle d'une ligne de la liste des erreursPattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
// données locales
Socket client=null; // le client
BufferedReader IN=null; // le flux de lecture du client
PrintWriter OUT=null; // le flux d'écriture du client
String réponse=null; // réponse du serveur
|
// le modèle recherché dans les entêtes HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// le modèle d'une réponse correcte
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
|
// le résultat de la comparaison au modèle
Matcher résultat=null;
try{
// on se connecte au serveur client=new Socket(host,port);
// on crée les flux d'entrée-sortie du client TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// on demande l'URL - envoi des entêtes HTTP
OUT.println("GET " + path + query + " HTTP/1.1"); OUT.println("Host: " + host + ":" + port); if(! JSESSIONID.equals("")){
OUT.println("Cookie: JSESSIONID="+JSESSIONID);
}
OUT.println("Connection: close");
OUT.println("");
|
// on lit la 1ère ligne de la réponse réponse=IN.readLine();
if(! ré()){
// on a un problème d'URL
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(résultat)
// on lit la réponse jusqu'à la fin des entêtes en cherchant l'éventuel cookie while((réponse=IN.readLine())!=null){
// ligne vide ? if(réponse.equals("")) break;
// ligne HTTP non vide
// si on n'a pas le jeton de la session on le cherche
if (JSESSIONID.equals("")){
// on compare la ligne HTTP au modèle du cookie
résultat=modèleCookie.matcher(réponse); if(ré()){
// on a trouvé le cookie du jeton
JSESSIONID=résultat.group(1);
}//if(résultat)
}//if(JSESSIONID)
}//while
|
// c'est fini pour les entêtes HTTP - on passe au code HTML
// pour récupérer les simulations
ArrayList listeSimulations=getSimulations(IN,OUT,simulations); simulations.clear();
for (int i=0;i<();i++){ simulations.addElement((i));
}
// c'est fini client.close();
}catch (Exception ex){ throw new Exception(ex.getMessage()); }
}//calculerImpots
|
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations)
throws Exception{
|
|
// le modèle d'une ligne du tableau des simulations
Pattern ptnSimulation=Pattern.compile("
|
(.*?) |
(.*?) |
(.*?) |
(.*?) |
// le résultat de la comparaison au modèle
Matcher résultat=null;
|
// les simulations
ArrayList listeSimulations=new ArrayList();
// on lit toutes les lignes jusqu'à la fin String ligne=null;
boolean simulationRéussie=false; while((ligne=IN.readLine())!=null){
// suivi
// on compare la ligne au modèle d'erreur si la partie simulations n'a pas encore été rencontrée if(! simulationRéussie){ résultat=ptnErreur.matcher(ligne); if(ré()){
// msg d'erreur
JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
// c'est fini
return listeSimulations;
}//if
}//if
// on a trouvé une ligne de la table
(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
":"+résultat.group(4)); // la simulation a été réussie simulationRéussie=true;
}//if
}//while // fin
return listeSimulations; }
|
Explicitons un peu ce code :
§ la procédure mnuCalculer_actionPerformed vérifie que les données de l'interface sont valides. Si elles ne le sont pas un message d'erreur est émis et la procédure terminée. Si elles le sont, la procédure calculerImpots est exécutée.
§ la procédure calculerImpots commence par construire l'URL qu'elle doit demander
// on retire d'urlImpots les infos nécessaire à la connexion au serveur d'impôts
String path=urlImpots.getPath(); if(path.equals("")) path="/";
|
String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
|
String host=urlImpots.getHost(); int port=urlImpots.getPort();
if(port==-1) port=urlImpots.getDefaultPort();
|
§ puis se connecte à cette URL en envoyant les entêtes HTTP adéquats :
// on se connecte au serveur client=new Socket(host,port);
// on crée les flux d'entrée-sortie du client TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
|
// on demande l'URL - envoi des entêtes HTTP
OUT.println("GET " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
if(! JSESSIONID.equals("")){
OUT.println("Cookie: JSESSIONID="+JSESSIONID);
}
OUT.println("Connection: close");
OUT.println("");
// le modèle recherché dans les entêtes HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// le modèle d'une réponse correcte
Pattern réponseOK=Pattern.compile("^.*? 200 OK"); .
// on lit la 1ère ligne de la réponse réponse=IN.readLine();
// on compare la ligne HTTP au modèle de la réponse correcte résultat=réponseOK.matcher(réponse); if(! ré()){ // on a un problème d'URL
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(résultat)
// on lit la réponse jusqu'à la fin des entêtes en cherchant l'éventuel cookie while((réponse=IN.readLine())!=null){
// ligne vide ? if(réponse.equals("")) break;
// ligne HTTP non vide
// si on n'a pas le jeton de la session on le cherche
if (JSESSIONID.equals("")){
// on compare la ligne HTTP au modèle du cookie résultat=modèleCookie.matcher(réponse);
if(ré()){
// on a trouvé le cookie du jeton
JSESSIONID=résultat.group(1);
}//if(résultat)
}//if(JSESSIONID) }//while
|
§ une fois les entêtes HTTP exploités, on passe à la partie HTML de la réponse
// c'est fini pour les entêtes HTTP - on passe au code HTML
// pour récupérer les simulations
ArrayList listeSimulations=getSimulations(IN,OUT,simulations); simulations.clear();
for (int i=0;i<();i++){ simulations.addElement((i)); }
|
§ la procédure getSimulations rend la liste des simulations si elles existent, liste qui sera vide si le serveur renvoie un message d'erreur. Dans ce cas, celui-ci est affiché dans une boîte de message. Si la liste est non vide, elle est affichée dans la liste déroulante de l'interface graphique.
\\s*\\s*\\s*\\s*
");
// le modèle d'une ligne de la liste des erreurs
Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations)
throws Exception{
// le modèle d'une ligne du tableau des simulations
Pattern ptnSimulation=Pattern.compile("
|
(.*?) |
(.*?) |
(.*?) |
(.*?)\\s* |
4.6
|
Version 5
|
Ici, nous transformons l'application graphique autonome précédente en applet java. L'interface graphique est légèrement différente. Avec l'application autonome l'utilisateur donnait lui-même l'URL du service de simulation de calculs d'impôts et ensuite l'application se connectait à cette URL. Ici, l'application cliente est un navigateur et l'utilisateur demandera l'URL du document HTML contenant l'applet. Il faut se rappeler maintenant qu'une applet Java ne peut ouvrir une connexion réseau qu'avec le serveur d'où elle a été téléchargée. L'URL du service de simulation sera donc sur le même serveur que le document HTML contenant l'applet. Dans notre exemple, ce sera un paramètre d'initialisation de l'applet qui sera mis dans le champ txtUrlServiceImpots, champ qui sera non éditable par l'utilisateur. On aura ainsi le client suivant :
Le document HTML contenant l'applet s'appelle et est le suivant :
Simulations de calculs d'impôts
Simulations de calculs d'impôts
|
|
|
L'applet a un paramètre urlServiceImpots qui est l'URL du service de simulations du calcul d'impôts. Cette URL est relative et mesurée par rapport à l'URL du document HTML . Ainsi si un navigateur obtient ce document avec l'URL , l'URL du service de simulation sera http://localhost:8080/impots/simulations. Si cette URL avait été l'URL du service de simulation aurait été http://stahe:8080/impots/simulations.
|
public class appletImpots extends JApplet {
|
// les composants de la fenêtre
JPanel contentPane;
JMenuBar jMenuBar1 = new JMenuBar();
JMenu jMenu1 = new JMenu();
JMenuItem mnuCalculer = new JMenuItem();
.
|
//Construire le cadre
|
public void init() {
|
try { jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
// autres initialisations
moreInit(); }
// initialisation formulaire private void moreInit(){
|
// on récupère le paramètre urlServiceImpots
String urlServiceImpots=getParameter("urlServiceImpots"); if(urlServiceImpots==null){
// paramètre manquant
JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
// fin return;
}
// on met l'URL dans son champ
String codeBase=""+getCodeBase(); if(codeBase.endsWith("/"))
txtURLServiceImpots.setText(codeBase+urlServiceImpots); else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
|
// menu Calculer inhibé mnuCalculer.setEnabled(false);
// spinner Enfants - entre 0 et 20 enfants
spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1)); spinEnfants.setBounds(new Rectangle(130,140,50,27)); (spinEnfants); }//moreInit
//Initialiser le composant
private void jbInit() throws Exception { contentPane = (JPanel) this.getContentPane(); contentPane.setLayout(null);
}
Nous avons montré différentes versions de notre application client-serveur de calcul d'impôts :
§ version 1 : le service est assuré par un ensemble de servlets et pages JSP, le client est un navigateur. Il fait une seule simulation et n'a pas de mémoire des précédentes.
§ version 2 : on ajoute quelques capacités côté navigateur en mettant des scripts javascript dans le document HTML chargé par celui-ci. Il contrôle la validité des paramètres du formulaire.
§ version 3 : on permet au service de se souvenir des différentes simulations opérées par un client en gérant une session. L'interface HTML est modifiée en conséquence pour afficher celles-ci.
§ version 4 : le client est désormais une application graphique autonome. Cela nous permet de revenir sur l'écriture de clients web programmés.
§ version 5 : le client se transforme en applet java. On a alors une application client-serveur entièrement programmée en Java que ce soit côté serveur ou côté client.
A ce point, on peut faire quelques remarques :
§ les versions 1 à 3 autorisent des navigateurs sans capacité autre que celle de pouvoir exécuter des scripts javascript. On notera qu'un utilisateur a toujours la possibilité d'inhiber l'exécution de ces derniers. L'application ne fonctionnera alors que partiellement dans sa version 1 (option Effacer ne fonctionnera pas) et pas du tout dans ses versions 2 et 3 (options Effacer et Calculer ne fonctionneront pas). Il pourrait être intéressant de prévoir une version du service n'utilisant pas de scripts javascript.
§ la version 4 nécessite que le poste client ait une machine virtuelle Java 2.
§ la version 5 nécessite que le poste client ait un navigateur ayant une machine virtuelle Java 2.
Les versions 4 et 5 sont des clients web qui retrouvent l'information dont ils ont besoin au sein du flux HTML envoyé par le serveur. Très souvent on ne maîtrise pas ce flux. C'est le cas lorsqu'on a écrit un client pour un service web existant sur le réseau et géré par quelqu'un d'autre. Prenon un exemple. Supposons que notre service de simulations de calcul d'impôts ait été écrit par une société X. Actuellement le service envoie les simulations dans un tableau HTML et notre client exploite ce fait pour les récupérer. Il compare ainsi chaque ligne de la réponse du serveur à l'expression régulière :
\\s*\\s*\\s*\\s*
");
// le modèle d'une ligne du tableau des simulations
Pattern ptnSimulation=Pattern.compile("
|
(.*?) |
(.*?) |
(.*?) |
(.*?)\\s* |
Supposons maintenant que le concepteur de l'application change l'apparence visuelle de la réponse en mettant les simulations non pas dans un tableau mais dans une liste sous la forme :
Dans ce cas, notre client web devra être réécrit. C'est là la menace permanente pesant sur les clients web d'applications qu'on ne maîtrise pas soi-même. XML peut apporter une solution à ce problème :
§ au lieu de générer du HTML, le service de simulations va générer du XML. Dans notre exemple, cela pourrait être
§ une feuille de style pourrait être associée à cette réponse indiquant aux navigateurs la forme visuelle à donner à cette réponse XML
§ les clients web programmés ignoreraient cette feuille de style et récuperaient l'information directement dans le flux XML de la réponse
§ version 6 : le service fournit une réponse XML accompagnée d'une feuille de style à destination des navigateurs
§ version 7 : le client est une application graphique autonome exploitant la réponse XML du serveur
§ version 8 : le client est une applet java exploitant la réponse XML du serveur
5. XML et JAVA
Dans ce chapitre, nous introduisons l'utilisation de documents XML avec Java. Nous le ferons dans le contexte de l'application impôts étudiée dans le chapitre précédent.
5.1
|
Fichiers XML et feuilles de style XSL
|
Considérons le fichier XML suivant qui pourrait représenter le résultat de simulations de calculs d'impôts :
Si on le visualise avec IE 6, on obtient le résultat suivant :
IE6 reconnaît qu'il a affaire à un fichier XML (grâce au suffixe .xml du fichier) et le met en page d'une façon qui lui est propre.
Avec Netscape on obtient une page vide. Cependant si on regarde le code source (View/Source) on a bien le fichier XML d'origine
:
Pourquoi Netscape n'affiche-t-il rien ? Parce qu'il lui faut une feuille de style qui lui dise comment transformer le fichier XML en fichier HTML qu'il pourra alors afficher. Il se trouve que IE 6 a lui une feuille de style par défaut lorsque le fichier XML n'en propose pas ce qui était le cas ici.
La commande XML
désigne le fichier comme un feuille de style (xml-stylesheet) de type text/xsl c.a.d. un fichier texte contenant du code XSL. Cette feuille de style sera utilisée par les navigateurs pour transformer le texte XML en document HTML. Voici le résultat obtenu avec Netscape 7 lorsqu'on charge le fichier XML :
Lorsque nous regardons le code source du document (View/Source) nous retrouvons le document XML initial et non le document HTML affiché :
Netscape a utilisé la feuille de style pour transformer le document XML ci-dessus en document HTML affichable. Il est maintenant temps de regarder le contenu de cette feuille de style :
|
|
Simulations de calculs d'impôts
Simulations de calculs d'impôts
|
marié |
enfants |
salaire |
impôt |
§ une feuille de style XSL est un fichier XML et en suit donc les règles. Il doit être en autres choses "bien formé" c'est à dire que toute balise ouverte doit être fermée.
§ le fichier commence par deux commandes XML qu'on pourra garder dans toute feuille de style XSL :
L'attribut encoding="ISO-8859-1" permet d'utiliser les caractères accentués dans la feuille de style.
§ La balise indique à l'interpréteur XSL qu'on veut produire du HTML "indenté".
§ La balise sert à définir l'élément du document XML sur lequel vont s'appliquer les instructions que l'on va trouver entre et .
Dans l'exemple ci-dessus l'élément "/" désigne la racine du document. Cela signifie que dès que le début du document XML va être rencontré, les commandes XSL situées entre les deux balises vont être exécutées.
§ Tout ce qui n'est pas balise XSL est mis tel quel dans le flux de sortie. Les balises XSL elles sont exécutées. Certaines d'entre-elles produisent un résultat qui est mis dans le flux de sortie. Etudions l'exemple suivant :
Simulations de calculs d'impôts
Dès le début du document XML analysé (match="/"), l'interpréteur XSL va produire en sortie le texte
Simulations de calculs d'impôts
Simulations de calculs d'impôts
mariéenfantssalaireimpôt
On remarquera que dans le texte initial on avait
et non pas
. Dans le texte initial on ne pouvait pas écrire
qui si elle est une balise HTML valide est une balise XML invalide. Or nous avons affaire ici à un texte XML qui doit être "bien formé", c.a.d que toute balise doit être fermée. On écrit donc
et parce qu'on a écrit l'interpréteur XSL transformera le texte
en
. Derrière ce texte, viendra ensuite le texte produit par la commande XSL :
Nous verrons ultérieurement quel est ce texte. Enfin l'interpréteur ajoutera le texte :
La commande demande qu'on exécute le "template" (modèle) de l'élément /simulations/simulation. Elle sera exécutée à chaque fois que l'interpréteur XSL rencontrera dans le texte XML analysé une balise .. ou à l'intérieur d'une balise ... A la rencontre de la balise , l'interpréteur exécutera les instructions du modèle suivant :
Considérons les lignes XML suivantes :
La ligne correspond au modèle de l'instruction XSL ".
L'interpréteur XSL va donc chercher à lui appliquer les instructions qui correspondent à ce modèle. Il va trouver le modèle et va l'exécuter. Rappelons que ce qui n'est pas une commande XSL est repris tel quel par l'interpréteur XSL et que les commandes XSL sont elles remplacées par le résultat de leur exécution. L'instruction XSL est ainsi remplacé par la valeur de l'attribut "champ" du noeud analysé (ici un noeud ). L'analyse de la ligne XML précédente va produire en sortie le résultat suivant :
XSL
|
sortie
|
|
|
|
|
|
oui
|
|
|
|
|
|
2
|
|
|
|
|
|
200000
|
|
|
|
|
|
22504
|
|
Au total, la ligne XML
oui220000022504
Toutes ces explications sont un peu rudimentaires mais il devrait apparaître maintenant au lecteur que le texte XML suivant :
accompagné de la feuille de style XSL suivante :
Simulations de calculs d'impôts
Simulations de calculs d'impôts
|
marié |
enfants |
salaire |
impôt |
produit le texte HTML suivant :
Simulations de calculs d'impôts
Simulations de calculs d'impots
mariéenfantssalaireimpôt
oui220000022504
non220000033388
Le fichier XML accompagné de la feuille de style lu par un navigateur récent (ici Netscape 7) est alors affiché comme suit :
5.2
|
Application impôts : version 6
|
5.2.1 Les fichiers XML et feuilles de style XSL de l'application impôts
Revenons à l'application web impôts et modifions la afin que la réponse faite aux clients soit une réponse au format XML plutôt qu'une réponse HTML. Cette réponse XML sera accompagnée d'une feuille de style XSL afin que les navigateurs puissent l'afficher. Dans le paragraphe précédent, nous avons présenté :
§ le fichier qui est le prototype d'une réponse XML comportant des simulations de calculs d'impôts
§ le fichier qui sera la feuille de style XSL qui accompagnera cette réponse XML
Il nous faut prévoir également le cas de la réponse avec des erreurs. Le prototype de la réponse XML dans ce cas sera le fichier suivant :
La feuille de style permettant d'afficher ce document XML dans un navigateur sera la suivante :
Simulations de calculs d'impôts
Simulations de calculs d'impôts
Les erreurs suivantes se sont produites :
|
Cette feuille de style introduit une commande XSL non encore rencontrée : . Cette commande produit en sortie la valeur du noeud analysé, ici un noeud texte. La valeur de ce noeud est le texte compris entre les deux balises d'ouverture et de fermeture, ici texte.
Simulations de calculs d'impots
Simulations de calculs d'impots
Les erreurs suivantes se sont produites :
Le fichier accompagné de sa feuille de style est affiché par un navigateur de la façon suivante :
5.2.2 La servlet xmlsimulations
Nous créons un fichier que nous mettons dans le répertoire de l'application impots. La page visualisée est la suivante :
Ce document HTML est un document statique. Son code est le suivant :
oui
non
Nombre d'enfants Salaire annuel
impots
|
|
Calcul d'impôts
|
|
|
Etes-vous marié(e) |
|
On notera que les données du formulaire sont postées à l'URL /impots/xmlsimulations. Cette application est une servlet Java configurée de la façon suivante dans le fichier de l'application impots :
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"">
..
xmlsimulations
xmlsimulations
|
xslSimulations
xslErreurs
|
|
DSNimpots
mysql-dbimpots
admimpots
admimpots
mdpimpots
mdpimpots
|
..
|
xmlsimulations
/xmlsimulations
§ la servlet s'appelle xmlsimulations et s'appuie sur la classe xmlsimulations.class.
§ elle a pour paramètres les paramètres DSNimpots, admimpots, mdpimpots nécessaires pour accéder à la base de données des impôts. Par ailleurs, elle admet deux autres paramètres :
o xslSimulations qui est le nom du fichier de style qui doit accompagner la réponse XML contenant les simulations
§ elle a un alias xmlsimulations qui la rend accessible via l'URL http://localhost:8080/impots/xmlsimulations.
Le squelette de la servlet xmlsimulations est semblable à celui de la servlet simulations déjà étudiée. La principale différence vient du fait qu'elle doit générer du XML au lieu du HTML. Cela va entraîner la suppression des fichiers JSP utilisés dans les applications précédentes. Leur rôle principal était d'améliorer la lisibilité du code HTML généré en évitant que celui-ci soit noyé dans le code Java de la servlet. Ce rôle n'a maintenant plus lieu d'être. La servlet a deux types de code XML à générer :
§ celui pour les simulations § celui pour les erreurs
Nous avons précédemment présenté et étudié les deux types de réponse XML à fournir dans ces deux cas ainsi que les feuilles de style qui doivent les accompagner. Le code de la servlet est le suivant :
import .*; import javax.servlet.*; import .*; import .regex.*; import .*; public class xmlsimulations extends HttpServlet{
// variables d'instance
String msgErreur=null;
String xslSimulations=null;
String xslErreurs=null;
String DSNimpots=null;
String admimpots=null; String mdpimpots=null; impotsJDBC impots=null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
// on récupère le flux d'écriture vers le client
PrintWriter out=response.getWriter();
|
// on précise le type de la réponse response.setContentType("text/xml");
|
// la liste des erreurs
ArrayList erreurs=new ArrayList();
// l'initialisation s'est-elle bien passée ? if(msgErreur!=null){
// c'est fini - on envoie la réponse avec erreurs au serveur (msgErreur);
|
sendErreurs(out,xslErreurs,erreurs);
|
// c'est fini return;
}
// on récupère les simulations précédentes de la session
HttpSession session=request.getSession();
// on récupère les paramètres de la requête courante
String optMarie=request.getParameter("optMarie"); // statut marital
String txtEnfants=request.getParameter("txtEnfants"); // nbre d'enfants
String txtSalaire=request.getParameter("txtSalaire"); // salaire annuel
// a-t-on tous les paramètres attendus
if(optMarie==null || txtEnfants==null || txtSalaire==null){ // il manque des paramètres
|
// on envoie la réponse avec erreurs
("Demande incomplète. Il manque des paramètres");
|
sendErreurs(out,xslErreurs,erreurs);
|
// c'est fini return;
}
// on a tous les paramètres - on les vérifie
// état marital
if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
// erreur
("Etat marital incorrect");
}
// nombre d'enfants ();
if(! Pattern.matches("^\\d+$",txtEnfants)){
// erreur
("Nombre d'enfants incorrect");
}
// salaire
();
if(! Pattern.matches("^\\d+$",txtSalaire)){
// erreur
("Salaire incorrect");
}
if(()!=0){
// s'il y a des erreurs, on les signale
|
sendErreurs(out,xslErreurs,erreurs);
|
}else{
// pas d'erreurs try{
// on peut calculer l'impôt à payer int nbEnfants=Integer.parseInt(txtEnfants); int salaire=Integer.parseInt(txtSalaire);
String txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
// on ajoute le résultat courant aux simulations précédentes
String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots}; (simulation);
|
// on envoie la réponse avec simulations sendSimulations(out,xslSimulations,simulations);
|
}catch(Exception ex){}
}//if-else
// on remet la liste des simulations dans la session session.setAttribute("simulations",simulations);
}//GET
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{ doGet(request,response);
}//POST
// on récupère les paramètres d'initialisation
ServletConfig config=getServletConfig();
xslSimulations=config.getInitParameter("xslSimulations"); xslErreurs=config.getInitParameter("xslErreurs"); DSNimpots=config.getInitParameter("DSNimpots"); admimpots=config.getInitParameter("admimpots"); mdpimpots=config.getInitParameter("mdpimpots");
// paramètres ok ? if(xslSimulations==null || DSNimpots==null || admimpots==null || mdpimpots==null){ msgErreur="Configuration incorrecte"; return;
}
// on crée une instance d'impotsJDBC try{ impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
}catch(Exception ex){ msgErreur=ex.getMessage();
}
}//init
|
|
//-------- sendErreurs
private void sendErreurs(PrintWriter out,String xslErreurs,ArrayList erreurs){
String réponse=""
+ "\n"
|
+"\n";
for(int i=0;i<();i++){ réponse+=""+(String)(i)+"\n";
}//for
réponse+="\n"; // on envoie la réponse out.println(réponse);
}
|
|
//-------- sendSimulations
private void sendSimulations(PrintWriter out, String xslSimulations, ArrayList simulations){
String réponse=""
+ "\n"
+ "\n";
String[] simulation=null;
for(int i=0;i<();i++){
// simulation n° i
simulation=(String[])(i); réponse+="<simulation "
+"marie=\""+(String)simulation[0]+"\" "
+"enfants=\""+(String)simulation[1]+"\" "
+"salaire=\""+(String)simulation[2]+"\" "
+"impot=\""+(String)simulation[3]+"\" />\n";
}//for
réponse+="\n";
// on envoie la réponse out.println(réponse);
}
|
}
|
Détaillons les principales nouveautés de ce code par rapport à ce que nous connaissions déjà :
dos>dir E:\data\serge\Servlets\impots\*.xsl
27/08/2002 08:15 1 030
27/08/2002 09:23 795
§ la procédure GET commence par regarder s'il y a eu une erreur lors de l'initialisation. Si oui, elle appelle la procédure sendErreurs qui génère la réponse XML adaptée à ce cas puis s'arrête. Dans cette réponse XML est insérée l'instruction désignant la feuille de style à utiliser.
§ s'il n'y a pas eu d'erreurs, la procédure GET analyse les paramètres de la requête du client. Si elle trouve une erreur quelconque elle le signale en utilisant là aussi la procédure sendErreurs. Sinon, elle calcule la nouvelle simulation, l'ajoute aux anciennes mémorisées dans la session courante et termine en envoyant sa réponse XML via la procédure sendSimulations. Cette dernière procède de façon analogue à la procédure sendErreurs.
§ on remarquera que la servlet annonce sa réponse comme étant de type text/xml :
// on précise le type de la réponse response.setContentType("text/xml");
Voici des exemples d'exécution. Le formulaire de départ est rempli de la façon suivante :
La base de données MySQL n'a pas été lancée rendant impossible la construction de l'objet impots dans la procédure init de la servlet. La réponse de celle-ci est alors la suivante :
Le code reçu par le navigateur (View/Source) est le suivant :
Si maintenant on refait deux simulations après avoir lancé la base de données MySQL, on obtient le résultat suivant :
Le navigateur a cette fois reçu le code suivant :
5.3
|
Analyse d'un document XML en Java
|
Les versions 7 et 8 de notre application impots vont être des clients programmés de la servlet précédente xmlsimulations. Ceux-ci vont recevoir du code XML qu'ils vont devoir analyser pour en retirer les informations qui les intéressent. Nous allons faire ici une pause dans nos différentes versions et apprendre comment on peut analyser un document XML en Java. Nous le ferons à partir d'un exemple livré avec JBuilder 7 appelé MySaxParser. Le programme s'appelle de la façon suivante :
dos>java MySaxParser Usage: java MySaxParser [URI]
L'application MySaxParser admet un paramètre : l'URI (Uniform Resource Identifier) du document XML à analyser. Dans notre exemple, cette URI sera simplement le nom d'un fichier XML placé dans le répertoire de l'application MySaxParser. Considérons deux exemples d'exécution. Dans le premier exemple, le fichier XML analysé est le fichier :
L'analyse donne les résultats suivants :
dos> java MySaxParser
Début du document
Début élément
Début élément
[erreur 1]
Fin élément
Début élément
[erreur 2]
Fin élément
Fin élément
Fin du document
Nous n'avions pas encore indiqué ce que faisait l'application MySaxParser mais l'on voit ici qu'elle affiche la structure du document XML analysé. Le second exemple analyse le fichier XML :
L'analyse donne les résultats suivants :
dos>java MySaxParser
Début du document
Début élément
Début élément
marie = oui enfants = 2 salaire = 200000 impot = 22504
|
Fin élément
Début élément
marie = non enfants = 2 salaire = 200000 impot = 33388
Fin élément
Fin élément
Fin du document
import .IOException;
|
import .*; import .helpers.*;
import org.apache.xerces.parsers.SAXParser;
|
import .regex.*;
// la classe
|
public class MySaxParser extends DefaultHandler {
|
// valeur d'un élement de l'arbre XML
private StringBuffer valeur=new StringBuffer();
// une expression régulière de la valeur d'un élément lorsqu'on veut ignorer
// les "blancs" qui la précèdent ou la suivent private static Pattern ptnValeur=null; private static Matcher résultats=null;
|
// -------- main
public static void main(String[] argv) {
// vérification du nombre de paramètres
if (argv.length != 1) {
.println("Usage: java MySaxParser [URI]");
(0);
}
// on récupère l'URI du fichier XML à analyser
String uri = argv[0];
try {
// création d'un analyseur XML (parseur)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// on indique au parseur l'objet qui va implémenter les méthodes
// startDocument, endDocument, startElement, endElement, characters
MySaxParser MySaxParserInstance = new MySaxParser(); parser.setContentHandler(MySaxParserInstance);
// on initialise le modèle de valeur d'un élément ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// on indique au parseur le document XML à analyser
parser.parse(uri);
}
catch(Exception ex) {
// erreur
.println("Erreur : " + ex);
// trace
ex.printStackTrace();
}
}//main
|
|
// -------- startDocument
public void startDocument() throws SAXException {
// procédure appelée lorsque le parseur rencontre le début du document
.println("Début du document");
}//startDocument
|
|
// -------- endDocument
public void endDocument() throws SAXException {
// procédure appelée lorsque le parseur rencontre la fin du document
.println("Fin du document");
}//endDocument
|
|
// -------- startElement
public void startElement(String uri, String localName, String qName,
|
// procédure appelée par le parseur lorsqu'il rencontre un début de balise // uri : URI du document analysé ?
// localName : nom de l'élément en cours d'analyse
// qName : idem mais "qualifié" par un espace de noms s'il y en a un
// attributes : liste des attributs de l'éélment
// suivi
.println("Début élément <"+localName+">");
// l'élément a-t-il des attributs ? for (int i = 0; i < attributes.getLength(); i++) {
.println(attributes.getLocalName(i) + " = " + attributes.getValue(i));
}//for
}//startElement
|
|
// -------- characters
public void characters(char[] ch, int start, int length) throws SAXException {
// procédure appelée de façon répétée par le parseur lorsqu'il rencontre du texte
// entre deux balises texte
// le texte est dans ch à partir du caractère start sur length caractères
// le texte est ajouté au buffer valeur valeur.append(ch, start, length);
}//characters
|
|
// -------- endElement public void endElement(String uri, String localName, String qName) throws SAXException {
// procédure appelée par le parseur lorsqu'il rencontre une fin de balise // uri : URI du document analysé ?
// localName : nom de l'élément en cours d'analyse
// qName : idem mais "qualifié" par un espace de noms s'il y en a un
// on affiche la valeur de l'élément
String strValeur=valeur.toString();
if (ptnValeur==null) .println("null"); résultats=ptnValeur.matcher(strValeur);
if (ré() && ! résultats.group(1).equals("")){
.println("["+résultats.group(1)+"]");
}//if
// on met la valeur de l'élément à vide valeur.setLength(0);
// suivi
.println("Fin élément <"+localName+">");
}//endElement
|
}//classe
L'application importe un certain nombre de paquetages :
import .*; import .helpers.*;
import org.apache.xerces.parsers.SAXParser;
|
Les deux premiers viennent avec le JDK 1.4, le troisième non. Le paquetage est disponible sur le site du serveur Web Apache. Il vient avec JBuilder 7 mais aussi avec Tomcat 4.x :
Si donc on veut compiler l'application précédente en-dehors de JBuilder 7 et qu'on dispose du JDK 1.4 et de Tomcat 4.x on pourra écrire :
dos>javac -classpath ".;E:\Program Files\Apache Tomcat 4.0\common\lib\" A l'exécution, on fera de même :
dos>java -classpath ".;E:\Program Files\Apache Tomcat 4.0\common\lib\" MySaxParser
La classe MySaxParser dérive de la classe DefaultHandler. Nous y reviendrons. Etudions le code de la procédure main :
// on récupère l'URI du fichier XML à analyser
String uri = argv[0]; try {
// création d'un analyseur XML (parseur)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// on indique au parseur l'objet qui va implémenter les méthodes
// startDocument, endDocument, startElement, endElement, characters
MySaxParser MySaxParserInstance = new MySaxParser(); parser.setContentHandler(MySaxParserInstance); // on initialise le modèle de valeur d'un élément ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$"); // on indique au parseur le document XML à analyser parser.parse(uri);
}
catch(Exception ex) {
// erreur
.println("Erreur : " + ex);
// trace
ex.printStackTrace();
}
|
Pour analyser un document XML, notre application a besoin d'un analyseur de code XML appelé "parseur".
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
Le parseur XML utilisé est celui fourni par le paquetage . L'objet récupéré est de type XMLReader. XMLReader est une interface dont nous utilisons ici deux méthodes :
void setContentHandler(ContentHandler handler)
void parse(InputSource input) démarre l'analyse du document XML passé en paramètre
Lorsque le parseur va analyser le document XML, il va émettre des événements tels que : "j'ai rencontré le début du document, le début d'une balise, un attribut de balise, le contenu d'une balise, la fin d'une balise, la fin du document, ". Il transmet ces événements à l'objet ContentHandler qu'on lui a donné. ContentHandler est une interface qui définit les méthodes à implémenter pour traiter tous les événements que le parseur XML peut générer. DefaultHandler est une classe qui fait une implémentation par défaut de ces méthodes. Les méthodes implémentées dans DefaultHandler ne font rien mais elles existent. Lorsqu'on doit indiquer au parseur quel objet va traiter les événements qu'il va générer au moyen de l'instruction
void setContentHandler(ContentHandler handler)
il est alors pratique de passer en paramètre un objet de type DefaultHandler. Si on s'en tenait là, aucun événement du parseur ne serait traité mais notre programme serait syntaxiquement correct. Dans la pratique, on passe en paramètre au parseur, un objet dérivé de la classe DefaultHandler, dans lequel sont redéfinies les méthodes traitant les seuls événements qui nous intéressent. C'est ce qui est fait ici :
// on indique au parseur l'objet qui va implémenter les méthodes
// startDocument, endDocument, startElement, endElement, characters
MySaxParser MySaxParserInstance = new MySaxParser(); parser.setContentHandler(MySaxParserInstance); // on indique au parseur le document XML à analyser parser.parse(uri);
|
On passe au parseur un exemplaire de la classe mySaxParser qui est notre classe et qui a été définie plus haut par la déclaration
public class MySaxParser extends DefaultHandler {
événement émis par le parseur
|
méthode de traitement
|
début du document
|
void startDocument()
|
fin du document
|
void endDocument()
|
|
public void startElement(String uri, String localName, String qName, Attributes attributes)uri : ?
localName : nom de l'élément analysé. Si l'élément rencontré est , on aura localName="simulations". qName : nom qualifié par un espace de noms de l'élément analysé. Un document XML peut définir un espace de noms, XX par exemple. Le nom qualifié de la balise précédente serait alors XX:simulations.
attributes : liste des attributs de la balise
|
valeur d'un élément : valeur
|
|
public void characters(char[] ch, int start, int length)ch : tableau de caractères
start : indice du 1er caractère à utiliser dans le tableau ch length : nombre de caractères à prendre dans le tableau ch
La méthode characters peut être appelée de façon répétée. Pour construire la valeur d'un élément, on utilise alors un buffer qu'on :
§ vide au départ de l'élément
§ complète à chaque nouvel appel de la méthode characters § termine à la fin de l'élément
|
fin d'un élément : ou
/>
|
void endElement(String uri, String localName, String qName)
les paramètres sont ceux de la méthode startElement.
|
La méthode startElement permet de récupérer les attributs de l'élément grâce au paramètre attributes de type Attributes :
§ le nombre d'attributs est disponible dans attributes.getLength()
§ le nom de l'attribut i est disponible dans attributes.getLocalName(i)
§ la valeur de l'attribut i est disponible dans attributes.getValue(i)
§ la valeur de l'attribut de nom localName dans attributes.getValue(localName)
erreur 1
donne pour valeur associée à la balise le texte erreur 1 débarrassé des espaces et sauts de lignes qui pourraient le précéder et/ou le suivre.
5.4
|
Application impôts : version 7
|
Nous avons maintenant tous les éléments pour écrire des clients programmés pour notre service d'impôts qui délivre du XML. Nous reprenons la version 4 de notre application pour faire le client et nous gardons la version 6 pour ce qui est du serveur. Dans cette application client-serveur :
§ le service de simulations du calcul d'impôts est fait la servlet xmlsimulations. La réponse du serveur est donc au format XML comme nous l'avons vu dans la version 6.
§ le client n'est plus un navigateur mais un client java autonome. Son interface graphique est celle de la version 4.
Voici quelques exemples d'exécution. Tout d'abord un cas d'erreur : le client interroge la servlet xmlsimulations alors que celle-ci n'a pas pu s'initialiser correctement du fait que le SGBD MySQL n'était pas lancé :
On lance MySQL et on fait quelques simulations :
Le client de cette nouvelle version ne diffère du client de la version 4 que par la façon dont il traite la réponse du serveur. Rien d'autre ne change. Dans la version 4, le client recevait du code HTML dans lequel il allait chercher les informations qui l'intéressaient en utilisant des expressions régulières. Ici le client reçoit du code XML dans lequel il va récupérer les informations qui l'intéressent à l'aide d'un parseur XML.
Rappelons les grandes lignes de la procédure liée au menu Calculer de la version 4 de notre client puisque c'est principalement là que se font les changements :
|
void mnuCalculer_actionPerformed(ActionEvent e) {
.
try{
// on calcule l'impôt
calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
}catch (Exception ex){
// on affiche l'erreur
} .
}//mnuCalculer_actionPerformed
|
|
public void calculerImpots(URL urlImpots,boolean marié, int nbEnfants, int salaire)
|
throws Exception{
// calcul de l'impôt
// urlImpots : URL du service des impôts
// marié : true si marié, false sinon
// nbEnfants : nombre d'enfants
// salaire : salaire annuel
// on retire d'urlImpots les infos nécessaire à la connexion au serveur d'impôts .
try{
// on se connecte au serveur .
// on crée les flux d'entrée-sortie du client TCP .
// on demande l'URL - envoi des entêtes HTTP .
// on lit la 1ère ligne de la réponse .
// on lit la réponse jusqu'à la fin des entêtes en cherchant l'éventuel cookie while((réponse=IN.readLine())!=null){
. }//while
|
// c'est fini pour les entêtes HTTP - on passe au code HTML
|
// pour récupérer les simulations
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<();i++){ simulations.addElement((i));
}
|
// c'est fini .
}//calculerImpots
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations)
throws Exception{ .
}
|
Tout ce code reste valide dans la nouvelle version. Seul le traitement de la réponse HTML du serveur (partie encadrée ci-dessus) et son affichage doivent être remplacés par le traitement de la réponse XML du serveur et son affichage :
|
// c'est fini pour les entêtes HTTP - on passe au code XML
// pour récupérer les simulations ou les erreurs
ImpotsSaxParser parseur=new ImpotsSaxParser(IN);
ArrayList listeErreurs=parseur.getErreurs();
ArrayList listeSimulations=parseur.getSimulations();
|
// fermeture connexion au serveur client.close();
// nettoyage liste d'affichage simulations.clear();
|
// erreurs
if(()!=0){
// on concatène toutes les erreurs
}
// affichage erreurs
throw new Exception(msgErreur);
}//if
|
|
// simulations
for (int i=0;i<();i++){ simulations.addElement((i));
}
|
return;
|
Que fait la portion de code ci-dessus ?
§ elle crée un parseur XML et lui passe le flux IN qui contient le code XML envoyé par le serveur. Ce flux contenait aussi les entêtes HTTP mais ceux-ci ont déjà été lus et traités. Ne reste donc que la partie XML de la réponse. Le parseur produit deux listes de chaînes de caractères : la liste des erreurs s'il y en a eu, sinon celle des simulations. Ces deux listes sont exclusives l'une de l'autre.
§ si la liste des erreurs est non vide, les messages contenus dans la liste sont concaténés en un seul message d'erreur et une exception est lancée avec comme paramètre ce message. Cette exception fait l'objet d'un affichage dans la procédure mnuCalculer_actionPeformed qui a appelé calculerImpots.
§ si la liste des simulations est non vide, elle fait l'objet d'un affichage dans le composant jList de l'interface graphique.
Découvrons maintenant le parseur de la réponse XML du serveur, parseur qui découle directement de l'étude que nous avons faite précédemment sur la façon d'analyser un document XML en java :
import .IOException;
|
import .*; import .helpers.*;
import org.apache.xerces.parsers.SAXParser;
|
import .regex.*; import .*; import .*; import javax.swing.*;
// la classe
|
public class ImpotsSaxParser extends DefaultHandler {
|
|
// valeur d'un élement de l'arbre XML
private StringBuffer valeur=new StringBuffer();
// une expression régulière de la valeur d'un élément lorsqu'on veut ignorer
// les "blancs" qui la précèdent ou la suivent private Pattern ptnValeur=null; private Matcher résultats=null; // les listes d'éléments XML
private ArrayList listeSimulations=new ArrayList(); private ArrayList listeErreurs=new ArrayList();
// éléments XML
|
// -------- constructeur
public ImpotsSaxParser(BufferedReader IN) throws Exception{
// création d'un analyseur XML (parseur)
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
// on indique au parseur l'objet qui va implémenter les méthodes
// startDocument, endDocument, startElement, endElement, characters
parser.setContentHandler(this);
// on initialise le modèle de valeur d'un élément ptnValeur=Pattern.compile("^\\s*(.*?)\\s*$");
// au départ pas d'élément XML courant
élé("");
// on analyse le document
parser.parse(new InputSource(IN));
}//constructeur
|
|
// -------- startElement
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// procédure appelée par le parseur lorsqu'il rencontre un début de balise // uri : URI du document analysé ?
// localName : nom de l'élément en cours d'analyse
// qName : idem mais "qualifié" par un espace de noms s'il y en a un
// attributes : liste des attributs de l'éélment
// on note le nom de l'élément élément=localName.toLowerCase(); élé(élément);
// l'élément a-t-il des attributs ? if(élément.equals("simulation") && attributes.getLength()==4){
// c'est une simulation - on récupère les attributs
String simulation=attributes.getValue("marie")+","+ attributes.getValue("enfants")+","+ attributes.getValue("salaire")+","+ attributes.getValue("impot");
// on ajoute la simulation à la liste des simulations
(simulation);
}//if
}//startElement
|
|
// -------- characters
public void characters(char[] ch, int start, int length) throws SAXException {
// procédure appelée de façon répétée par le parseur lorsqu'il rencontre du texte
// entre deux balises texte
// le texte est dans ch à partir du caractère start sur length caractères
// le texte est ajouté au buffer valeur s'il s'agit de l'élément erreur
}//characters
|
|
// -------- endElement public void endElement(String uri, String localName, String qName) throws SAXException {
// procédure appelée par le parseur lorsqu'il rencontre une fin de balise // uri : URI du document analysé ?
// localName : nom de l'élément en cours d'analyse
// qName : idem mais "qualifié" par un espace de noms s'il y en a un
// cas de l'erreur
if(élément.equals("erreur")){
// on récupère la valeur de l'élément erreur
String strValeur=valeur.toString();
// on la débarrasse de ses "blancs" inutiles et on l'enregistre dans la liste des
// erreurs si elle est non vide
|
résultats=ptnValeur.matcher(strValeur);
if (ré() && ! résultats.group(1).equals("")){ (résultats.group(1));
}//if
}
// on met la valeur de l'élément à vide
valeur.setLength(0);
// on réinitialise le nom de l'élément éléments.remove(élé()-1);
élément=(String)élé(élé()-1);
}//endElement
|
// --------- getErreurs public ArrayList getErreurs(){ return listeErreurs;
}
// --------- getSimulations public ArrayList getSimulations(){ return listeSimulations;
}
}//classe
|
§ le constructeur reçoit le flux XML IN à analyser et fait immédiatement cette analyse. Celle-ci terminée, l'objet a été construit ainsi que les listes (ArrayList) d'erreurs (listeErreurs) et de simulations (listeSimulations) ont été construites. Il ne reste plus à la procédure qui a construit l'objet qu'à récupérer les deux listes à l'aide des méthodes getErreurs et getSimulations.
§ seuls trois événements générés par le parseur XML nous intéressent ici :
o début d'un élément XML, événement qui sera traité par la procédure startElement. Celle-ci aura à traiter les balises et .
o valeur d'un élément XML, événement qui sera traité par la procédure characters.
o fin d'un élément XML, événement qui sera traité par la procédure endElement.
§ la procédure characters est identique à celle qui avait été étudiée dans un exemple précédent. On prend simplement soin de vérifier que l'élément courant est bien l'élément , précaution inutile normalement ici. Ce type de précaution avait été également pris dans la procédure startElement pour vérifier qu'on avait affaire à un élément .
Grâce à sa réponse XML l'application impots est devenue plus facile à gérer à la fois pour son concepteur et les concepteurs des application clientes.
§ la conception de l'application serveur peut maintenant être confiée à deux types de personnes : le développeur Java de la servlet et l'infographiste qui va gérer l'apparence de la réponse du serveur dans les navigateurs. Il suffit à ce dernier de connaître la structure de la réponse XML du serveur pour construire les feuilles de style qui vont l'accompagner. Rappelons que celles-ci font l'objet de fichiers XSL à part et indépendants de la servlet java. L'infographiste peut donc travailler indépendamment du développeur Java.
§ les concepteurs des applications clientes ont eux aussi simplement besoin de connaître la structure de la réponse XML du serveur. Les modifications que pourrait apporter l'infographiste aux feuilles de style n'ont aucune répercussion sur cette réponse XML qui reste toujours la même. C'est un énorme avantage.
6. A suivre
Nous avons donné dans ce document suffisamment d'informations pour démarrer sérieusement la programmation web en Java. Certains thèmes n'ont été qu'effleurés et mériteraient des approfondissements :
§ XML (eXtended Markup Language), Feuilles de style CSS (Cascading Style Sheets) et XSL (eXtended
Stylesheet Language)
Nous avons abordé ces thèmes mais beaucoup reste à faire. Ils méritent à eux trois un polycopié.
§ Javascript
Nous avons utilisé ici et là quelques scripts Javascript exécutés par le navigateur sans jamais s'y attarder. Nous ne sommes par exemple jamais entrés dans les détails du langage Javascript. Par manque de place et de temps. Ce langage peut lui aussi nécessiter un livre à lui tout seul. Nous avons rajouté en annexe, trois exemples significatifs en javascript là encore sans les expliquer. Néanmoins ils sont compréhensibles par la simple lecture de leurs commentaires et peuvent servir d'exemples. § Java beans, balises JSP
Nous avons montré comment nous pouvions séparer le code Java et le code HTML dans une application Web. Le code Java est rassemblé dans une ou plusieurs servlets et le code HTML dans une ou plusieurs pages JSP. Néanmoins dans nos exemples, il restait souvent quelques lignes de code Java dans les pages JSP. On peut améliorer les choses en utilisant dans les pages JSP des composants appelés Java beans et des balises JSP prédéfinies ou à créer soi-même (extensions de balises). Cet aspect des pages JSP n'est pas indispensable (on s'en est bien passé dans nos exemples qui sont réalistes) mais cependant utile à connaître.
§ Enterprise Java Beans (EJB)
Tous ces thèmes non développés ici le sont excellemment dans le livre "Programmation J2EE" aux éditions WROX et distribué par Eyrolles.
Annexes
7. Les outils du développement web
Nous indiquons ici où trouver et comment installer les outils nécessaires au développement web. Certains outils ont vu leurs versions évoluer et il se peut que les explications données ici ne conviennent plus pour les versions les plus récentes. Le lecteur sera alors amené à s'adpater Dans le cours de programmation web, nous utiliserons essentiellement les outils suivants, tous disponibles gratuitement :
Ø un navigateur récent capable d'afficher du XML. Les exemples du cours ont été testés avec Internet Explorer 6.
Ø un JDK (Java Development Kit) récent. Les exemples du cours ont été testés avec le JDK 1.4. Ce JDK amène avec lui le Plug-in Java 1.4 pour les navigateurs ce qui permet à ces derniers d'afficher des applets Java utilisant le JDK 1.4.
Ø un environnement de développement Java pour écrire des servlets Java. Ici c'est JBuilder 7.
Ø des serveurs web : Apache, PWS (Personal Web Server), Tomcat. o Apache sera utilisé pour le développement d'applications web en PERL (Practical Extracting and Reporting
Language) ou PHP (Personal Home Page) o PWS sera utilisé pour le développement d'applications web en ASP (Active Server Pages) ou PHP o Tomcat sera utilisé pour le développement d'applications web à l'aide de servlets Java ou de pages JSP (Java Server pages)
Ø une application de gestion de base de données : MySQL
Ø EasyPHP : un outil qui amène ensemble le serveur Web Apache, le langage PHP et le SGBD MySQL
7.1
|
Serveurs Web, Navigateurs, Langages de scripts
|
1. Serveurs Web principaux
§ Apache (Linux, Windows)
§ Interner Information Server IIS (NT), Personal Web Server PWS (Windows 9x)
2. Navigateurs principaux
§ Internet Explorer (Windows)
3. Langages de scripts côté serveur
§ VBScript (IIS, PWS)
§ JavaScript (IIS, PWS)
§ Perl (Apache, IIS, PWS)
§ PHP (Apache, IIS, PWS)
§ Java (Apache, Tomcat)
§ Langages .NET
4. Langages de scripts côté navigateur
§ VBScript (IE)
§ Javascript (IE, Netscape)
§ Perlscript (IE) § Java (IE, Netscape)
7.2
|
|
Où trouver les outils
|
(lien downloads)
(Windows Binaries)
Vbscript, (suivre le lien windows script)
Javascript
JAVA
(JSE)
inclus dans NT 4.0 Option pack for Windows 95 inclus dans le CD de Windows 98
p
IIS
(windows NT/2000)
Tomcat
Cette application est très pratique en ce qu'elle amène dans un même paquetage :
Ø le serveur Web Apache (1.3.x)
Ø le langage PHP (4.x)
Ø le SGBD MySQL (3.23.x)
Ø un outil d'administration de MySQL : PhpMyAdmin
L'application d'installation se présente sous la forme suivante :
L'installation d'EasyPHP ne pose pas de problème et une arborescence est créée dans le système de fichiers :
l'exécutable de l'application l'arborescence du serveur apache l'arborescence du SGBD mysql l'arborescence de l'application phpmyadmin l'arborescence de php racine de l'arborescence des pages web délivrées par le serveur apache d'EasyPHP arborescence où l'on peut palcer des script CGI pour le serveur Apache
L'intérêt principal d'EasyPHP est que l'application arrive préconfigurée. Ainsi Apache, PHP, MySQL sont déjà configurés pour travailler ensemble. Lorsqu'on lance EasyPhp par son lien dans le menu des programmes, une icône se met en place en bas à droite de l'écran.
C'est le E avec un point rouge qui doit clignoter si le serveur web Apache et la base de données MySQL sont opérationnels. Lorsqu'on clique dessus avec le bouton droit de la souris, on accède à des options de menu :
7.3.1 Administration PHP
Le bouton infos php doit vous permettre de vérifier le bon fonctionnement du couple Apache-PHP : une page d'informations PHP doit apparaître :
Le bouton extensions donne la liste des extensions installées pour php. Ce sont en fait des bibliothèques de fonctions.
L'écran ci-dessus montre par exemple que les fonctions nécessaires à l'utilisation de la base MySQL sont bien présentes.
Le bouton paramètres donne le login/motdepasse de l'administrateur de la base de données MySQL.
L'utilisation de la base MySQL dépasse le cadre de cette présentation rapide mais il est clai ici qu'il faudrait mettre un mot de passe à l'administrateur de la base.
7.3.2 Administration Apache
Toujours dans la page d'administration d'EasyPHP, le lien vos alias permet de définir des alias associés à un répertoire. Cela permet de mettre des pages Web ailleurs que dans le répertoire www de l'arborescence d'easyPhp.
Si dans la page ci-dessus, on met les informations suivantes :
et qu'on utilise le bouton valider les lignes suivantes sont ajoutées au fichier \apache\conf\ :
Alias /st/ "e:/data/serge/web/"
Options FollowSymLinks Indexes
AllowOverride None Order deny,allow allow from 127.0.0.1 deny from all
|
désigne le répertoire d'installation d'EasyPHP. est le fichier de configuration du serveur Apache. On peut donc faire la même chose en éditant directement ce fichier. Une modification du fichier est normalement prise en compte immédiatement par Apache. Si ce n'était pas le cas, il faudrait l'arrêter puis le relancer, toujours avec l'icône d'easyphp :
Pour terminer notre exemple, on peut maintenant placer des pages web dans l'arborescence e:\data\serge\web :
C:\winnt\system32>dir e:\data\serge\web\html\
14/07/2002 17:02 3 767
et demander cette page en utilisant l'alias st :
7.3.3 Le fichier de configuration d'Apache
Lorsqu'on veut configurer un peu finement Apache, on est obligé d'aller modifier "à la main" son fichier de configuration situé ici dans le dossier \apache\conf :
Voici quelques points à retenir de ce fichier de configuration :
ligne(s)
|
rôle
|
ServerRoot "D:/Program Files/Apache Group/Apache"
|
indique le dossier où se trouve l'arborescence de Apache
|
|
indique sur quel port va travailler le serveur Web. Classiquement
|
c'est 80. En changeant cette ligne, on peut faire travailler le serveur Web sur un autre port l'adresse email de l'administrateur du serveur Apache le nom de la machine sur laquelle "tourne" le serveur Apache
le répertoire d'installation du serveur Apache. Lorsque dans le fichier de configuration, apparaissent des noms relatifs de fichiers, ils sont relatifs par rapport à ce dossier.
le dossier racine de l'arborescence des pages Web délivrées par le serveur. Ici, l'url correspondra au fichier E:\Program Files\EasyPHP\www \rep1\ fixe les propriétés du dossier précédent
dossier des logs, donc en fait \logs\ : E:\Program Files\EasyPHP\apache\logs\. C'est le fichier à consulter si vous constatez que le serveur Apache ne fonctionne pas.
E:\Program Files\EasyPHP\cgi-bin sera la racine de l'arborescence où l'on pourra mettre des scripts CGI. Ainsi l'URL sera l'url du script CGI E:\Program
Files\EasyPHP\cgi-bin \rep1\. fixe les propriétés du dossier ci-dessus
lignes de chargement des modules permettant à Apache de travailler avec PHP4.
fixe les suffixes des fichiers à considérer comme des fichiers comme devant être traités par PHP
7.3.4 Administration de MySQL avec PhpMyAdmin
Sur la page d'administration d'EasyPhp, on clique sur le bouton PhpMyAdmin :
Si on clique sur le lien Afficher de user :
Nous n'en dirons pas plus sur PhpMyAdmin qui est un logiciel riche et qui mériterait un développement de plusieurs pages.
Nous avons vu comment obtenir PHP au travers de l'application EasyPhp. Pour obtenir PHP directement, on ira sur le site .
PHP n'est pas utilisable que dans le cadre du Web. On peut l'utiliser comme langage de scripts sous Windows. Créez le script suivant et sauvegardez-le sous le nom :
Dans une fenêtre DOS, placez-vous dans le répertoire de et exécutez-le :
E:\data\serge\php\essais>"e:\program files\easyphp\php\"
X-Powered-By: PHP/4.2.0
Content-type: text/html
Nous sommes le 18/07/02, 09:31:01
Il est préférable que Internet Explorer soit déjà installé. S'il est présent, Active Perl va le configurer afin qu'il accepte des scripts PERL dans les pages HTML, scripts qui seront exécutés par IE lui-même côté client. Le site de Active Perl est à l'URL A l'installation, PERL sera installé dans un répertoire que nous appelerons . Il contient l'arborescence suivante :
DEISL1 ISU 32 403 23/06/00 17:16
BIN 23/06/00 17:15 bin
LIB 23/06/00 17:15 lib
HTML 23/06/00 17:15 html
EG 23/06/00 17:15 eg
SITE 23/06/00 17:15 site
HTMLHELP 28/06/00 18:37 htmlhelp
L'exécutable est dans \bin. Perl est un langage de scripts fonctionnant sous Windows et Unix. Il est de plus utilisé dans la programmation WEB. Écrivons un premier script :
# script PERL affichant l'heure
# modules use strict;
# programme
my ($secondes,$minutes,$heure)=localtime(time); print "Il est $heure:$minutes:$secondes\n";
E:\data\serge\Perl\Essais>e:\perl\bin\
Il est 9:34:21
7.6
|
Vbscript, Javascript, Perlscript
|
Ces langages sont des langages de script pour windows. Ils peuvent fonctionner dans différents conteneurs tels
Ø Windows Scripting Host pour une utilisation directe sous Windows notamment pour écrire des scripts d'administration système
Ø Internet Explorer. Il est alors utilisé au sein de pages HTML auxquelles il amène une certaine interactivité impossible à atteindre avec le seul langage HTML.
Ø Internet Information Server (IIS) le serveur Web de Microsoft sur NT/2000 et son équivalent Personal Web Server (PWS) sur Win9x. Dans ce cas, vbscript est utilisé pour faire de la programmation côté serveur web, technologie appelée ASP (Active Server Pages) par Microsoft.
On récupère le fichier d'installation à l'URL : et on suit les liens Windows Script. Sont installés :
Ø le conteneur Windows Scripting Host, conteneur permettant l'utilisation de divers langages de scripts, tels Vbscript et Javascript mais aussi d'autres tel PerlScript qui est amené avec Active Perl.
Ø un interpréteur VBscript
Ø un interpréteur Javascript
Présentons quelques tests rapides. Construisons le programme vbscript suivant :
' une classe class personne
Dim nom
Dim age
End class
' création d'un objet personne
|
Set p1=new personne
With p1
.nom="dupont"
.age=18
End With
' affichage propriétés personne p1 With p1
"nom=" & .nom "age=" & .age End With
Ce programme utilise des objets. Appelons-le (le suffixe vbs désigne un fichier vbscript). Positionnons-nous sur le répertoire dans lequel il se trouve et exécutons-le :
E:\data\serge\windowsScripting\vbscript\poly\objets>cscript
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.
nom=dupont age=18
// tableau dans un variant
// tableau vide tableau=new Array(); affiche(tableau);
// tableau croît dynamiquement for(i=0;i<3;i++){ (i*10);
}
// affichage tableau affiche(tableau);
// encore for(i=3;i<6;i++){ (i*10);
}
affiche(tableau);
// tableaux à plusieurs dimensions
("-----------------------------");
tableau2=new Array(); for(i=0;i<3;i++){
(new Array()); for(j=0;j<4;j++){
tableau2[i].push(i*10+j);
}//for j }// for i affiche2(tableau2);
// fin
(0);
// --------------------------------------------------------function affiche(tableau){ // affichage tableau
for(i=0;i<tableau.length;i++){
("tableau[" + i + "]=" + tableau[i]);
}//for
}//function
// --------------------------------------------------------function affiche2(tableau){ // affichage tableau for(i=0;i<tableau.length;i++){ for(j=0;j<tableau[i].length;j++){
("tableau[" + i + "," + j + "]=" + tableau[i][j]);
}// for j
}//for i
}//function
|
Ce programme utilise des tableaux. Appelons-le (le suffixe js désigne un fichier javascript). Positionnons-nous sur le répertoire dans lequel il se trouve et exécutons-le :
E:\data\serge\windowsScripting\javascript\poly\tableaux>cscript
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. Tous droits réservés.
tableau[0]=0
tableau[1]=10 tableau[2]=20 tableau[0]=0 tableau[1]=10 tableau[2]=20 tableau[3]=30 tableau[4]=40 tableau[5]=50
----------------------------tableau[0,0]=0 tableau[0,1]=1 tableau[0,2]=2 tableau[0,3]=3 tableau[1,0]=10 tableau[1,1]=11 tableau[1,2]=12 tableau[1,3]=13 tableau[2,0]=20 tableau[2,1]=21 tableau[2,2]=22 tableau[2,3]=23
|
Un dernier exemple en Perlscript pour terminer. Il faut avoir installé Active Perl pour avoir accès à Perlscript.
E:\data\serge\windowsScripting\perlscript\essais>cscript
Microsoft (R) Windows Script Host Version 5.6
Copyright (C) Microsoft Corporation 1996-2001. Tous droits réservés.
clé=philippe, valeur=marianne clé=maurice, valeur=juliette
juliette marianne
|
Perlscript peut utiliser les objets du conteneur dans lequel il s'exécute. Ici c'était des objets du conteneur Windows Script. Dans le contexte de la programmation Web, les scripts VBscript, Javascript, Perlscript peuvent être exécutés soit au sein du navigateur IE, soit au sein d'un serveur PWS ou IIS. Si le script est un peu complexe, il peut être judicieux de le tester hors du contexte Web, au sein du conteneur Windows Script comme il a été vu précédemment. On ne pourra tester ainsi que les fonctions du script qui n'utilisent pas des objets propres au navigateur ou au serveur. Même avec cette restriction, cette possibilité reste intéressante car il est en général assez peu pratique de déboguer des scripts s'exécutant au sein des serveurs web ou des navigateurs.
Java est disponible à l'URL : (cf début de ce document) et s'installe dans une arborescence qu'on appellera qui contient les éléments suivants :
22/05/2002 05:51
22/05/2002 05:51
22/05/2002 05:51
22/05/2002 05:51
07/02/2002 12:52 8 277
07/02/2002 12:52 13 853 LICENSE
07/02/2002 12:52 4 516 COPYRIGHT
07/02/2002 12:52 15 290
22/05/2002 05:51
22/05/2002 05:51
22/05/2002 05:51
07/02/2002 12:52 10 377 848
11/02/2002 12:55
1. Écrire le script suivant :
//programme Java affichant l'heure
import .*; import .*;
public class heure{
public static void main(String arg[]){
// on récupère date & heure
Date maintenant=new Date();
// on affiche
.println("Il est "+maintenant.getHours()+
":"+maintenant.getMinutes()+":"+maintenant.getSeconds());
}//main
}//class
|
2. Sauvegarder ce programme sous le nom . Ouvrir une fenêtre DOS. Se mettre dans le répertoire du fichier et le compiler :
D:\data\java\essais>c:\jdk1.3\bin\javac Note: uses or overrides a deprecated API. Note: Recompile with -deprecation for details.
Dans la commande ci-dessus c:\jdk1.3\bin\javac doit être remplacé par le chemin exact du compilateur . Vous devez obtenir dans le même répertoire que un fichier heure.class qui est le programme qui va maintenant être exécuté par la machine virtuelle .
3. Exécuter le programme :
D:\data\java\essais>c:\jdk1.3\bin\java heure
Il est 10:44:2
Nous avons vu que l'on pouvait obtenir le serveur Apache avec l'application EasyPhp. Pour l'avoir directement, on ira sur le site d'Apache : . L'installation crée une arborescence où on trouve tous les fichiers nécessaires au serveur. Appelons ce répertoire. Il contient une arborescence analogue à la suivante :
UNINST ISU 118 805 23/06/00 17:09
HTDOCS 23/06/00 17:09 htdocs
APACHE~1 DLL 299 008 25/02/00 21:11
ANNOUN~1 3 000 23/02/00 16:51 Announcement
ABOUT_~1 13 197 31/03/99 18:42 ABOUT_APACHE
APACHE EXE 20 480 25/02/00 21:04
KEYS 36 437 20/08/99 11:57 KEYS
LICENSE 2 907 01/01/99 13:04 LICENSE
MAKEFI~1 TMP 27 370 11/01/00 13:47
README NT 3 223 19/03/99 9:55
WARNIN~1 TXT 339 21/09/98 13:09
BIN 23/06/00 17:09 bin
MODULES 23/06/00 17:09 modules
ICONS 23/06/00 17:09 icons
LOGS 23/06/00 17:09 logs
CONF 23/06/00 17:09 conf
CGI-BIN 23/06/00 17:09 cgi-bin
PROXY 23/06/00 17:09 proxy
INSTALL LOG 3 779 23/06/00 17:09
conf dossier des fichiers de configuration d'Apache logs dossier des fichiers de logs (suivi) d'Apache bin les exécutables d'Apache
7.8.1 Configuration
Dans le dossier \conf, on trouve les fichiers suivants : , , . Dans les dernières versions d'Apache, les trois fichiers ont été réunis dans . Nous avons déjà présenté les points importants de ce fichier de configuration. Dans les exemples qui suivent c'est la version Apache d'EasyPhp qui a servi aux tests et donc son fichier de configuration. Dans celui-ci DocumentRoot qui désigne la racine de l'arborescence des pages Web est e:\program files\easyphp\www.
7.8.2 Lien PHP - Apache
Pour tester, créer le fichier avec la seule ligne suivante :
et le mettre à la racine des pages du serveur Apache(DocumentRoot ci-dessus). Demander l’URL . On doit voir une liste d’informations php :
Le script PHP suivant affiche l'heure. Nous l'avons déjà rencontré :
Plaçons ce fichier texte à la racine des pages du serveur Apache (DocumentRoot ) et appelons-le date.php. Demandons avec un navigateur l’URL . On obtient la page suivante :
Si besoin est, modifiez la ligne ScriptAlias /cgi-bin/ ""et relancez le serveur Apache. Faites ensuite le test suivant :
1. Écrire le script :
#!c:\perl\bin\
# script PERL affichant l'heure
# modules use strict;
# programme
my ($secondes,$minutes,$heure)=localtime(time); print <
Content-Type: text/html
heure
Il est $heure:$minutes:$secondes
FINHTML
;
|
2. Mettre ce script dans \ où est le dossier pouvant recevoir des scripts CGI (cf ). La première ligne #!c:\perl\bin\ désigne le chemin de l'exécutable . Le modifier si besoin est.
3. Lancer Apache si ce n'est fait
4. Demander avec un navigateur l'URL . On obtient la page suivante :
7.9.1 Installation
Le serveur PWS (Personal Web Server) est une version personnelle du serveur IIS (Internet Information server) de Microsoft. Ce dernier est disponible sur les machines NT et 2000. Sur les machines win9x, PWS est normalement disponible avec le paquetage d'installation Internet Explorer. Cependant il n'est pas installé par défaut. Il faut prendre une installation personnalisée d'IE et demander l'installation de PWS. Il est par ailleurs disponibles dans le NT 4.0 Option pack for Windows 95.
7.9.2 Premiers tests
La racine des pages Web du serveur PWS est lecteur:\inetpub\wwwroot où lecteur est le disque sur lequel vous avez installé PWS. Nous supposons dans la suite que ce lecteur est D. Ainsi l'url correspondra au fichier d:\inetpub\wwwroot\rep1\. Le serveur PWS interprète tout fichier de suffixe .asp (Active Server pages) comme étant un script qu'il doit exécuter pour produire une page HTML.
7.9.3 Lien PHP - PWS
1. Ci-dessous on trouvera un fichier .reg destiné à modifier la base de registres. Double-cliquer sur ce fichier pour modifier la base. Ici la dll nécessaire se trouve dans d:\php4 avec l'exécutable de php. Modifier si besoin est. Les \ doivent être doublés dans le chemin de la dll.
REGEDIT4
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\w3svc\parameters\Script Map] ".php"="d:\\php4\\"
2. Relancer la machine pour que la modification de la base de registres soit prise en compte.
3. Créer un dossier php dans d:\inetpub\wwwroot qui est la racine du serveur PWS. Ceci fait, activez PWS et prendre l’onglet « Avancé ». Sélectionner le bouton « Ajouter » pour créer un dossier virtuel :
Répertoire/Parcourir : d:\inetpub\wwwroot\php
Alias : php
Cocher la case exécuter.
4. Valider le tout et relancer PWS. Mettre dans d:\inetpub\wwwroot\php le fichier ayant la seule ligne suivante :
5. Demander au serveur PWS l’URL . On doit voir la liste d’informations php déjà présentées avec Apache.
7.10
|
Tomcat : servlets Java et pages JSP (Java Server Pages)
|
Tomcat est un serveur Web permettant de générer des pages HTML grâce à des servlets (programmes Java exécutés par le serveur web) où des pages JSP (Java Server Pages), pages mélangeant code Java et code HTML. C'est l'équivalent des pages ASP (Active Server Pages) du serveur IIS/PWS de Microsoft où là on mélange code VBScript ou Javascript avec du code HTML.
consiste simplement à décompresser cette archive dans un répertoire. Prenez un répertoire ne contenant dans son chemin que des noms sans espace (pas par exemple "Program Files"), ceci parce qu'il y a un bogue dans le processus d'installation de Tomcat. Prenez par exemple C:\tomcat ou D:\tomcat. Appelons ce répertoire . On y trouvera dedans un dossier appelé jakartatomcat et dans celui-ci l'arborescence suivante :
LOGS 15/11/00 9:04 logs
LICENSE 2 876 18/04/00 15:56 LICENSE
CONF 15/11/00 8:53 conf
DOC 15/11/00 8:53 doc
LIB 15/11/00 8:53 lib
SRC 15/11/00 8:53 src
WEBAPPS 15/11/00 8:53 webapps
BIN 15/11/00 8:53 bin
WORK 15/11/00 9:04 work
7.10.2 Démarrage/Arrêt du serveur Web Tomcat
Tomcat est un serveur Web comme l'est Apache ou PWS. Pour le lancer, on dispose de liens dans le menu des programmes :
Start Tomcat pour lancer Tomcat Stop Tomcat pour l'arrêter
Lorsqu'on lance Tomcat, une fenêtre Dos s'affiche avec le contenu suivant :
On peut mettre cette fenêtre Dos en icône. Elle restera présente pendant tant que Tomcat sera actif. On peut alors passer aux premiers tests. Le serveur Web Tomcat travaille sur le port 8080. Une fois Tomcat lancé, prenez un navigateur Web et demandez l'URL http://localhost:8080. Vous devez obtenir la page suivante :
Suivez le lien Servlet Examples :
Cliquez sur le lien Execute de RequestParameters puis sur celui de Source. Vous aurez un premier aperçu de ce qu'est une servlet Java. Vous pourrez faire de même avec les liens sur les pages JSP.
Pour arrêter Tomcat, on utilisera le lien Stop Tomcat dans le menu des programmes.
Jbuilder est un environnement de développement d'applications Java. Pour construire des servlets Java où il n'y a pas d'interfaces graphiques, il n'est pas indispensable d'avoir un tel environnement. Un éditeur de textes et un JDK font l'affaire. Seulement JBuilder apporte avec lui quelques plus par rapport à la technique précédente :
Ø facilité de débogage : le compilateur signale les lignes erronées d'un programme et il est facile de s'y positionner
Ø suggestion de code : lorsqu'on utilise un objet Java, JBuilder donne en ligne la liste des propriétés et méthodes de celui-ci. Cela est très pratique lorsqu'on sait que la plupart des objets Java ont de très nombreuses propriétés et méthodes qu'il est difficile de se rappeler.
On trouvera JBuilder sur le site . Il faut remplir un formulaire pour obtenir le logiciel. Une clé d'activation est envoyée par mél. Pour installer JBuilder 7, il a par exemple été procédé ainsi :
Ø trois fichiers zip ont été obtenus : pour l'application, pour la documentation, pour les exemples. Chacun de ces zip fait l'objet d'un lien séparé sur le site de JBuilder.
Ø on a installé d'abord l'application, puis la documentation et enfin les exemples
Ø lorsqu'on lance l'application la première fois, une clé d'activation est demandée : c'est celle qui vous a été envoyée par mél. Dans la version 7, cette clé est en fait la totalité d'un fichier texte que l'on peut placer, par exemple, dans le dossier d'installation de JB7. Au moment où la clé est demandée, on désigne alors le fichier en question. Ceci fait, la clé ne sera plus redemandée.
Ø lancer JBuilder
Ø activer l'option Tools/Configure JDKs
Dans la partie JDK Settings ci-dessus, on a normalement dans le champ Name un JDK 1.3.1. Si vous avez un JDK plus récent, utilisez le bouton Change pour désigner le répertoire d'installation de ce dernier. Ci-dessus, on a désigné le répertoire E:\Program Files\jdk14 où avait installé un JDK 1.4. Désormais, JBuilder utilisera ce JDK pour ses compilations et exécutions. Dans la partie (Class, Siurce, Documentation) on a la liste de toutes les bibliothèques de classes qui seront explorées par JBuilder, ici les classes du JDK 1.4. Les classes de celui-ci ne suffisent pas pour faire du développement web en Java. Pour ajouter d'autres bibliothèques de classes on utilise le bouton Add et on désigne les fichiers .jar supplémentaires que l'on veut utiliser. Les fichiers .jar sont des bibliothèques de classes. Tomcat 4.x amène avec lui toutes les bibliothèques de classes nécessaires au développement web. Elles se trouvent dans \common\lib où est le répertoire d'installation de Tomcat :
Avec le bouton Add, on va ajouter ces bibliothèques, une à une, à la liste des bibliothèques explorées par JBuilder :
A partir de maintenant, on peut compiler des programmes java conformes à la norme J2EE, notamment les servlets Java. Jbuilder ne sert qu'à la compilation, l'exécution étant ultérieurement assurée par Tomcat selon des modalités expliquées dans le cours.
8. Code source de programmes
8.1
|
Le client TCP générique
|
Beaucoup de services créés à l'origine de l'Internet fonctionnent selon le modèle du serveur d'écho étudié précédemment : les échanges client-serveur se font pas échanges de lignes de texte. Nous allons écrire un client tcp générique qui sera lancé de la façon suivante : java cltTCPgenerique serveur port
Ce client TCP se connectera sur le port port du serveur serveur. Ceci fait, il créera deux threads :
2. un thread chargé de lire les réponses du serveur et de les afficher à l'écran
Pourquoi deux threads? Chaque service TCP-IP a son protocole particulier et on trouve parfois les situations suivantes :
• le client doit envoyer plusieurs lignes de texte avant d'avoir une réponse
• la réponse d'un serveur peut comporter plusieurs lignes de texte
Aussi la boucle envoi d'une unique ligne au serveur - réception d'une unique ligne envoyée par le serveur ne convient-elle pas toujours. On va donc créer deux boucles dissociées :
• une boucle de lecture des commandes tapées au clavier pour être envoyées au serveur. L'utilisateur signalera la fin des commandes avec le mot clé fin.
• une boucle de réception et d'affichage des réponses du serveur. Celle-ci sera une boucle infinie qui ne sera interrompue que par la fermeture du flux réseau par le serveur ou par l'utilisateur au clavier qui tapera la commande fin.
Pour avoir ces deux boucles dissociées, il nous faut deux threads indépendants. Montrons un exemple d'excécution où notre client tcp générique se connecte à un service SMTP (SendMail Transfer Protocol). Ce service est responsable de l'acheminement du courrier électronique à leurs destinataires. Il fonctionne sur le port 25 et a un protocole de dialogue de type échanges de lignes de texte.
Dos>java clientTCPgenerique 25 Commandes :
Subject: test
ligne1 ligne2 ligne3
.
quit
[fin du thread de lecture des réponses du serveur] fin
[fin du thread d'envoi des commandes au serveur]
|
Commentons ces échanges client-serveur :
• le service SMTP envoie un message de bienvenue lorsqu'un client se connecte à lui :
• certains services ont une commande help donnant des indications sur les commandes utilisables avec le service. Ici ce n'est pas le cas. Les commandes SMTP utilisées dans l'exemple sont les suivantes :
o rcpt to:destinataire, pour indiquer l'adresse électronique du destinataire du message. S'il y a plusieurs destinataires, on ré-émet autant de fois que nécessaire la commande rcpt to: pour chacun des destinataires.
o data qui signale au serveur SMTP qu'on va envoyer le message. Comme indiqué dans la réponse du serveur, celui-ci est une suite de lignes terminée par une ligne contenant le seul caractère point. Un message peut avoir des entêtes séparés du corps du message par une ligne vide. Dans notre exemple, nous avons mis un sujet avec le mot clé Subject:
• une fois le message envoyé, on peut indiquer au serveur qu'on a terminé avec la commande quit. Le serveur ferme alors la connexion réseau. Le thread de lecture peut détecter cet événement et s'arrêter.
• l'utilisateur tape alors fin au clavier pour arrêter également le thread de lecture des commandes tapées au clavier.
Si on vérifie le courrier reçu, nous avons la chose suivante (Outlook) :
On remarquera que le service SMTP ne peut détecter si un expéditeur est valide ou non. Aussi ne peut-on jamais faire confiance au champ from d'un message. Ici l'expéditeur n'existait pas.
Ce client tcp générique peut nous permettre de découvrir le protocole de dialogue de services internet et à partir de là construire des classes spécialisées pour des clients de ces services. Découvrons le protocole de dialogue du service POP (Post Office Protocol) qui permet de retrouver ses méls stockés sur un serveur. Il travaille sur le port 110.
Dos> java clientTCPgenerique 110 Commandes :
user st
<--
[fin du thread de lecture des réponses du serveur] fin
[fin du thread d'envoi des commandes au serveur]
|
Les principales commandes sont les suivantes :
• userlogin, où on donne son login sur la machine qui détient nos méls
• passpassword, où on donne le mot de passe associé au login précédent
• quit, pour arrêter le dialogue.
Découvrons maintenant le protocole de dialogue entre un client et un serveur Web qui lui travaille habituellement sur le port 80 :
Dos> java clientTCPgenerique 80 Commandes :
GET HTTP/1.0
<--
|
<--
Bienvenue a l'ISTIA - Universite d'Angers
.
- Dernire mise jour le 10 janvier 2002
<--
[fin du thread de lecture des réponses du serveur] fin
[fin du thread d'envoi des commandes au serveur]
|
Un client Web envoie ses commandes au serveur selon le schéma suivant : commande1 commande2
commanden
[ligne vide]
Ce n'est qu'après avoir reçu la ligne vide que le serveur Web répond. Dans l'exemple nous n'avons utilisé qu'une commande :
qui demande au serveur l'URL et indique qu'il travaille avec le protocole HTTP version 1.0. La version la plus récente de ce protocole est 1.1. L'exemple montre que le serveur a répondu en renvoyant le contenu du fichier puis qu'il a fermé la connexion puisqu'on voit le thread de lecture des réponses se terminer. Avant d'envoyer le contenu du fichier , le serveur web a envoyé une série d'entêtes terminée par une ligne vide :
La ligne est la première ligne du fichier . Ce qui précède s'appelle des entêtes HTTP (HyperText Transfer Protocol). Nous n'allons pas détailler ici ces entêtes mais on se rappellera que notre client générique y donne accès, ce qui peut être utile pour les comprendre. La première ligne par exemple :
indique que le serveur Web contacté comprend le protocole HTTP/1.1 et qu'il a bien trouvé le fichier demandé (200 OK), 200 étant un code de réponse HTTP. Les lignes
disent au client qu'il va recevoir 11251 octets représentant du texte HTML (HyperText Markup Language) et qu'à la fin de l'envoi, la connexion sera fermée.
// paquetages importés import .*; import .*; public class clientTCPgenerique{
// reçoit en paramètre les caractéristiques d'un service sous la forme // serveur port
// se connecte au service
// crée un thread pour lire des commandes tapées au clavier
// celles-ci seront envoyées au serveur
// crée un thread pour lire les réponses du serveur
// celles-ci seront affichées à l'écran
// le tout se termine avec la commande fin tapée au clavier
// variable d'instance private static Socket client;
public static void main(String[] args){
// syntaxe
final String syntaxe="pg serveur port";
// nombre d'arguments if(args.length != 2) erreur(syntaxe,1);
// on note le nom du serveur String serveur=args[0];
// le port doit être entier >0 int port=0;
boolean erreurPort=false;
Exception E=null; try{ port=Integer.parseInt(args[1]);
}catch(Exception e){ E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0; if(erreurPort) erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
client=null;
// il peut y avoir des problèmes try{
// on se connecte au service client=new Socket(serveur,port);
}catch(Exception ex){
// erreur
erreur("Impossible de se connecter au service ("+ serveur +","+port+"), erreur : "+ex.getMessage(),3);
// fin return;
}//catch
// on crée les threads de lecture/écriture new ClientSend(client).start(); new ClientReceive(client).start();
// fin thread main return;
}// main
// affichage des erreurs
public static void erreur(String msg, int exitCode){
// affichage erreur
.println(msg);
// arrêt avec erreur
(exitCode);
}//erreur }//classe
class ClientSend extends Thread {
// classe chargée de lire des commandes tapées au clavier
// et de les envoyer à un serveur via un client tcp passé en paramètre private Socket client; // le client tcp
// constructeur
public ClientSend(Socket client){ // on note le client tcp this.client=client;
}//constructeur
// données locales
PrintWriter OUT=null; // flux d'écriture réseau BufferedReader IN=null; // flux clavier
|
String commande=null; // commande lue au clavier
// gestion des erreurs try{
// création du flux d'écriture réseau
OUT=new PrintWriter(client.getOutputStream(),true);
// création du flux d'entrée clavier
IN=new BufferedReader(new InputStreamReader());
// boucle saisie-envoi des commandes .println("Commandes : "); while(true){
// lecture commande tapée au clavier commande=IN.readLine().trim();
// fini ? if (commande.toLowerCase().equals("fin")) break;
// envoi commande au serveur
OUT.println(commande);
// commande suivante
}//while
}catch(Exception ex){
// erreur
.println("Envoi : L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// fin - on ferme les flux try{
OUT.close();client.close();
}catch(Exception ex){}
// on signale la fin du thread
.println("[Envoi : fin du thread d'envoi des commandes au serveur]");
}//run }//classe
class ClientReceive extends Thread{
// classe chargée de lire les lignes de texte destinées à un
// client tcp passé en paramètre private Socket client; // le client tcp
// constructeur
public ClientReceive(Socket client){
// on note le client tcp this.client=client;
}//constructeur
// méthode Run du thread public void run(){
// données locales
BufferedReader IN=null; // flux lecture réseau
String réponse=null; // réponse serveur
// gestion des erreurs try{
// création du flux lecture réseau
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
// boucle lecture lignes de texte du flux IN while(true){
// lecture flux réseau réponse=IN.readLine();
// flux fermé ? if(réponse==null) break;
// affichage
.println("<-- "+réponse); }//while
}catch(Exception ex){
// erreur
}//catch
// fin - on ferme les flux try{
IN.close();client.close();
}catch(Exception ex){}
// on signale la fin du thread
.println("[Réception : fin du thread de lecture des réponses du serveur]");
}//run
}//classe
|
8.2
|
Le serveur Tcp générique
|
Maintenant nous nous intéressons à un serveur
• qui affiche à l'écran les commandes envoyées par ses clients
• leur envoie comme réponse les lignes de texte tapées au clavier par un utilisateur. C'est donc ce dernier qui fait office de serveur.
Le programme est lancé par : java serveurTCPgenerique portEcoute, où portEcoute est le port sur lequel les clients doivent se connecter. Le service au client sera assuré par deux threads :
• un thread se consacrant exclusivement à la lecture des lignes de texte envoyées par le client
• un thread se consacrant exclusivement à la lecture des réponses tapées au clavier par l'utilisateur. Celui-ci signalera par la commande fin qu'il clôt la connexion avec le client.
Le serveur crée deux threads par client. S'il y a n clients, il y aura 2n threads actifs en même temps. Le serveur lui ne s'arrête jamais sauf par un Ctrl-C tapé au clavier par l'utilisateur. Voyons quelques exemples.
Le serveur est lancé sur le port 100 et on utilise le client générique pour lui parler. La fenêtre du client est la suivante :
E:\data\serge\MSNET\c#\réseau\client tcp générique>
Commandes : commande 1 du client 1
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
|
java clientTCPgenerique localhost 100
|
|
|
Les lignes commençant par
Dos>
1
<--
1
1 fin [
|
java serveurTCPgenerique 100
|
1]
1]
|
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
:
: [fin du Thread de lecture des demandes du client fin du Thread de lecture des réponses du serveur au client
|
Les lignes commençant par N : sont les lignes envoyées du serveur au client n° N. Le serveur ci-dessus est encore actif alors que le client 1 est terminé. On lance un second client pour le même serveur :
Dos> java clientTCPgenerique localhost 100
Commandes : commande 3 du client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
|
La fenêtre du serveur est alors celle-ci :
Dos>
1
<--
1
1 fin [
2
<--
|
java serveurTCPgenerique 100
|
1]
1]
|
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
: Thread de lecture des demandes du client 1 lancé commande 1 du client 1 réponse 1 au client 1
:
: [fin du Thread de lecture des demandes du client
fin du Thread de lecture des réponses du serveur au client
Thread de lecture des réponses du serveur au client 2 lancé
: Thread de lecture des demandes du client 2 lancé commande 3 du client 2 réponse 3 au client 2
|
2 : [fin du Thread de lecture des demandes du client 2] fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C
|
Simulons maintenant un serveur web en lançant notre serveur générique sur le port 88 :
Dos>
|
java serveurTCPgenerique 88
|
|
Serveur générique lancé sur le port 88
|
Prenons maintenant un navigateur et demandons l'URL . Le navigateur va alors se connecter sur le port 88 de la machine localhost puis demander la page :
Regardons maintenant la fenêtre de notre serveur :
Dos>java serveurTCPgenerique 88
Serveur générique lancé sur le port 88
Thread de lecture des réponses du serveur au client 2 lancé
914)
<--
On découvre ainsi les entêtes HTTP envoyés par le navigateur. Cela nous permet de découvrir peu à peu le protocole HTTP. Lors d'un précédent exemple, nous avions créé un client Web qui n'envoyait que la seule commande GET. Cela avait été suffisant. On voit ici que le navigateur envoie d'autres informations au serveur. Elles ont pour but d'indiquer au serveur quel type de client il a en face de lui. On voit aussi que les entêtes HTTP se terminent par une ligne vide.
Elaborons une réponse à notre client. L'utilisateur au clavier est ici le véritable serveur et il peut élaborer une réponse à la main. Rappelons-nous la réponse faite par un serveur Web dans un précédent exemple :
Essayons de donner une réponse analogue :
<--
2 : HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html 2 :
|
2 :
2 : Serveur generique
2 :
2 :
2 :
Reponse du serveur generique
2 :
2 :
2 :
2 : fin L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des réponses du serveur au client 2]
Les lignes commençant par 2 : sont envoyées du serveur au client n° 2. La commande fin clôt la connexion du serveur au client. Nous nous sommes limités dans notre réponse aux entêtes HTTP suivants :
HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html 2 :
2 :
2 : Serveur generique
2 :
2 :
2 :
Reponse du serveur generique
2 :
2 :
2 :
L'utilisateur ferme ensuite la connexion au client en tapant la commande fin. Le navigateur sait alors que la réponse du serveur est terminée et peut alors l'afficher :
Si ci-dessus, on fait View/Source pour voir ce qu'a reçu le navigateur, on obtient :
c'est à dire exactement ce qu'on a envoyé depuis le serveur générique.
Le code du serveur tcp générique est le suivant :
// paquetages import .*; import .*; public class serveurTCPgenerique{
// programme principal
public static void main (String[] args){
// reçoit le port d'écoute des demandes des clients
// crée un thread pour lire les demandes du client
// celles-ci seront affichées à l'écran
// crée un thread pour lire des commandes tapées au clavier
// celles-ci seront envoyées comme réponse au client // le tout se termine avec la commande fin tapée au clavier
final String syntaxe="Syntaxe : pg port";
// variable d'instance
// y-a-t-il un argument if(args.length != 1) erreur(syntaxe,1);
// le port doit être entier >0 int port=0;
boolean erreurPort=false;
Exception E=null; try{ port=Integer.parseInt(args[0]);
}catch(Exception e){ E=e;
erreurPort=true;
}
erreurPort=erreurPort || port <=0; if(erreurPort) erreur(syntaxe+"\n"+"Port incorrect ("+E+")",2);
// on crée le servive d'écoute
ServerSocket ecoute=null;
int nbClients=0; // nbre de clients traités try{
// on crée le service
ecoute=new ServerSocket(port);
// suivi
.println("Serveur générique lancé sur le port " + port);
// boucle de service aux clients Socket client=null;
while (true){ // boucle infinie - sera arrêtée par Ctrl-C
// attente d'un client client=ecoute.accept();
// le service est assuré des threads séparés nbClients++;
// on retourne à l'écoute des demandes
}// fin while
}catch(Exception ex){ // on signale l'erreur
erreur("L'erreur suivante s'est produite : " + ex.getMessage(),3);
}//catch
}// fin main
// affichage des erreurs
public static void erreur(String msg, int exitCode){
// affichage erreur
.println(msg);
// arrêt avec erreur
(exitCode);
}//erreur
}//classe
class ServeurSend extends Thread{
// classe chargée de lire des réponses tapées au clavier
// et de les envoyer à un client via un client tcp passé au constructeur
Socket client;// le client tcp int numClient; // n° de client
// constructeur
public ServeurSend(Socket client, int numClient){
// on note le client tcp this.client=client;
// et son n°
|
this.numClient=numClient;
}//constructeur
// méthode Run du thread public void run(){
// données locales
PrintWriter OUT=null; // flux d'écriture réseau
String réponse=null; // réponse lue au clavier BufferedReader IN=null; // flux clavier
// suivi
.println("Thread de lecture des réponses du serveur au client "+ numClient + " lancé");
// gestion des erreurs try{
// création du flux d'écriture réseau
OUT=new PrintWriter(client.getOutputStream(),true);
// création du flux clavier
IN=new BufferedReader(new InputStreamReader());
// boucle saisie-envoi des commandes while(true){
// identification client
.print("--> " + numClient + " : ");
// lecture réponse tapée au clavier réponse=IN.readLine().trim();
// fini ? if (réponse.toLowerCase().equals("fin")) break;
// envoi réponse au serveur
OUT.println(réponse);
// réponse suivante
}//while
}catch(Exception ex){
// erreur
.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// fin - on ferme les flux try{
OUT.close();client.close();
}catch(Exception ex){}
// on signale la fin du thread
.println("[fin du Thread de lecture des réponses du serveur au client "+ numClient+ "]");
class ServeurReceive extends Thread{
// classe chargée de lire les lignes de texte envoyées au serveur // via un client tcp passé au constructeur
Socket client;// le client tcp int numClient; // n° de client
// constructeur
public ServeurReceive(Socket client, int numClient){
// on note le client tcp this.client=client; // et son n°
this.numClient=numClient;
}//constructeur
// méthode Run du thread public void run(){
// données locales
BufferedReader IN=null; // flux lecture réseau
String réponse=null; // réponse serveur
// suivi
.println("Thread de lecture des demandes du client "+ numClient + " lancé");
// gestion des erreurs try{
// création du flux lecture réseau
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
// boucle lecture lignes de texte du flux IN while(true){
// lecture flux réseau réponse=IN.readLine();
// flux fermé ? if(réponse==null) break;
// affichage
.println("
}catch(Exception ex){
// erreur
.println("L'erreur suivante s'est produite : " + ex.getMessage());
}//catch
// fin - on ferme les flux try{
|
IN.close();client.close();
}catch(Exception ex){}
// on signale la fin du thread
.println("[fin du Thread de lecture des demandes du client "+ numClient+"]");
}//run
}//classe
|
9. JAVASCRIPT
Nous montrons dans cette partie trois exemples d'utilisation de Javascript dans les pages WEB. Nous nous centrons sur la gestion des formulaires mais Javascript peut faire bien davantage.
9.1
|
Récupérer les informations d'un formulaire
|
L'exemple ci-dessous montre comment récupérer au sein du navigateur les données entrées par l'utilisateur au sein d'un formulaire. Cela permet en général de faire des pré-traitements avant de les envoyer au serveur.
9.1.2 Le code
Un formulaire traité par Javascript
|
|
|
|
Un formulaire traité par Javascript
|
|
Un champ textuel simple |
|
|
Un champ textuel sur plusieurs lignes |
Ce texte est modifiable
Des boutons radio : GO PO Des choix multiples : un deux trois
Un menu déroulant :
50 F
60 F
70 F
100 F
RenaultCitroënPeugeotFiatAudi Un mot de passe : Un champ de contexte caché :
Informations du formulaire
9.2
Les expressions régulières en Javascript
Côté navigateur, Javascript peut être utilisé pour vérifier la validité des données entrées par l'utilisateur avant de les envoyer au serveur. Voici un programme de test de ces expressions régulières.
9.2.1 La page de test
9.2.2 Le code de la page
Les expressions régulières en Javascript
|
|
|
Les expressions régulières en Javascript
|
Expression régulière |
Chaîne de test |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Résultats de l'instruction champs=expression réguliè(chaine)
Exemples
Modèles
Chaînes
^\d+$
^(\d+) (\d+)$
^(\d+)(.*)(\d+)$
^(\d+)(\s+)(\d+)$
67
56 84
45abcd67
45 67
9.3
Gestion des listes en JavaScript
9.3.1 Le formulaire
9.3.2 Le code
Les listes en Javascript
1.1LESCOMPOSANTESD'UNEAPPLICATIONWEB..6 1.2LESÉCHANGESDEDONNÉESDANSUNEAPPLICATIONWEBAVECFORMULAIRE.. 7
1.3QUELQUESRESSOURCES .. 7
1.4NOTATIONS ..8
1.5PAGESWEBSTATIQUES, PAGESWEBDYNAMIQUES..9 1.5.1 PAGE STATIQUE HTML (HYPERTEXT MARKUP LANGUAGE) 9
1.5.2 UNEPAGE ASP (ACTIVE SERVER PAGES) ..9 1.5.3 UN SCRIPT PERL (PRACTICAL EXTRACTING AND REPORTING LANGUAGE) 10 1.5.4 UNSCRIPT PHP (PERSONAL HOME PAGE) ..11 1.5.5 UNSCRIPT JSP (JAVA SERVER PAGES) .11 1.5.6 CONCLUSION ..12
1.6SCRIPTSCÔTÉNAVIGATEUR .12
1.6.1 UNE PAGE WEB AVEC UN SCRIPT VBSCRIPT, CÔTÉ NAVIGATEUR . 13 1.6.2 UNE PAGE WEB AVEC UN SCRIPT JAVASCRIPT, CÔTÉ NAVIGATEUR ..14
1.7LESÉCHANGESCLIENT-SERVEUR 15
1.7.1 LEMODÈLE OSI ..16 1.7.2 LEMODÈLE TCP/IP 17 1.7.3 LEPROTOCOLE HTTP 19
1.8LELANGAGE HTML 26 1.8.1 UNEXEMPLE ..26
1.8.2 ENVOI À UN SERVEUR WEB PAR UN CLIENT WEB DES VALEURS D'UN FORMULAIRE 36
2. INTRODUCTION AUX SERVLETS JAVA ET PAGES JSP.. 42
2.1SERVLETS JAVA .. 42
2.1.1 ENVOYER UN CONTENU HTML À UN CLIENT WEB 42 2.1.2 RÉCUPÉRER LES PARAMÈTRES ENVOYÉS PAR UN CLIENT WEB .43 2.1.3 RÉCUPÉRER LES ENTÊTES HTTP ENVOYÉS PAR UN CLIENT WEB ..45 2.1.4 RÉCUPÉRER DES INFORMATIONS D'ENVIRONNEMENT .46 2.1.5 CRÉER UNE SERVLET AVEC JBUILDER, LA DÉPLOYER AVEC TOMCAT ..48 2.1.6 EXEMPLES ..51
2.2PAGES JSP .. 64
2.2.1 RÉCUPÉRER DES INFORMATIONS D'ENVIRONNEMENT .65 2.2.2 RÉCUPÉRER LES PARAMÈTRES ENVOYÉS PAR LE CLIENT WEB . 66 2.2.3 LESBALISES JSP . 67 2.2.4 LESOBJETSIMPLICITES JSP ..67 2.2.5 LA TRANSFORMATION D'UNE PAGE JSP EN SERVLET 68 2.2.6 LES MÉTHODES ET VARIABLES GLOBALES D'UNE PAGE JSP 70 2.2.7 DÉPLOIEMENT ET DÉBOGAGE DES PAGES JSP AU SEIN DU SERVEUR TOMCAT 73 2.2.8 EXEMPLES ..73
2.4CYCLEDEVIEDESSERVLETSETPAGESJSP .94
2.4.1 LECYCLEDEVIE ..94 2.4.2 SYNCHRONISATION DES REQUÊTES À UNE SERVLET .. 96
3. SUIVI DE SESSION..101
3.1 LEPROBLÈME 101 3.2L'API JAVAPOURLESUIVIDESESSION ..103 3.3EXEMPLE 1 .103 3.4EXEMPLE 2 .110 3.5EXEMPLE 3 .113 3.6EXEMPLE 4 .119 3.7EXEMPLE 5 .120
4. L'APPLICATION IMPOTS. 128
4.1 INTRODUCTION ..128 4.2VERSION 1 ..132 4.3VERSION 2 ..138 4.4VERSION 3 ..139 4.5VERSION 4 ..143 4.6VERSION 5 ..150 4.7CONCLUSION ..153
5. XML ET JAVA.155
5.1FICHIERS XML ETFEUILLESDESTYLE XSL 155 5.2APPLICATIONIMPÔTS : VERSION 6 . 159
5.2.1 LES FICHIERS XML ET FEUILLES DE STYLE XSL DE L'APPLICATION IMPÔTS .159 5.2.2 LASERVLETXMLSIMULATIONS 161 5.3ANALYSED'UNDOCUMENT XML EN JAVA .167
5.4APPLICATIONIMPÔTS : VERSION 7 . 172 5.5CONCLUSION ..176
6. A SUIVRE. 177
ANNEXES 178
7. LES OUTILS DU DÉVELOPPEMENT WEB.178
7.1SERVEURSWEB, NAVIGATEURS, LANGAGESDESCRIPTS178 7.2OÙTROUVERLESOUTILS .. 178 7.3EASYPHP ..179 7.3.1 ADMINISTRATION PHP 180 7.3.2 ADMINISTRATION APACHE ..181
7.3.3 LE FICHIER DE CONFIGURATION D'APACHE .183 7.3.4 ADMINISTRATION DE MYSQL AVEC PHPMYADMIN ..184
7.4PHP .186 7.5PERL .186 7.6VBSCRIPT, JAVASCRIPT, PERLSCRIPT187 7.7JAVA .189 7.8SERVEUR APACHE 190 7.8.1 CONFIGURATION .190
7.8.2 LIEN PHP - APACHE 190 7.8.3 LIEN PERL-APACHE .191 7.9LESERVEUR PWS ..192 7.9.1 INSTALLATION .192 7.9.2 PREMIERSTESTS .192 7.9.3 LIEN PHP - PWS 193
7.11JBUILDER ..195
8. CODE SOURCE DE PROGRAMMES. 197
8.1LECLIENT TCP GÉNÉRIQUE 197 8.2LESERVEUR TCPGÉNÉRIQUE ..202
9. JAVASCRIPT208
9.1RÉCUPÉRERLESINFORMATIONSD'UNFORMULAIRE..208
9.1.1 LEFORMULAIRE . 208 9.1.2 LECODE .. 208
9.2LESEXPRESSIONSRÉGULIÈRESENJAVASCRIPT..210
9.2.1 LAPAGEDETEST 211 9.2.2 LECODEDELAPAGE 211
9.3GESTIONDESLISTESENJAVASCRIPT213
9.3.1 LEFORMULAIRE . 213 9.3.2 LECODE .. 213
| | | | | | | | | | | | | | |