5. Die Anwendung IMPOTS
5.1. Einleitung
Wir greifen hier die Anwendung IMPOTS wieder auf, die im Java-Skript desselben Autors mehrfach verwendet wird. Erinnern wir uns an die Problemstellung. Es geht darum, eine Anwendung zu schreiben, mit der die Steuer eines Steuerpflichtigen berechnet werden kann. Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur sein Gehalt anzugeben hat:
- Wir berechnen die Anzahl der Anteile des Arbeitnehmers nbParts = nbEnfants/2 + 1, wenn er unverheiratet ist, nbEnfants/2+2, wenn er verheiratet ist, wobei nbEnfants die Anzahl seiner Kinder ist.
- Wenn er mindestens drei Kinder hat, erhält er einen halben Anteil zusätzlich
- sein steuerpflichtiges Einkommen R wird berechnet als R = 0,72 * S, wobei S sein Jahresgehalt ist
- Man berechnet seinen Familienkoeffizienten QF = R / nbParts
- Man berechnet seine Steuer I. Betrachten wir die folgende Tabelle:
12620,0 | 0 | 0 |
13.190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24.740 | 0,15 | 2072,5 |
31.810 | 0,2 | 3309,5 |
39.970 | 0,25 | 4900 |
48360 | 0,3 | 6898,5 |
55.790 | 0,35 | 9316,5 |
92.970 | 0,4 | 12106 |
127.860 | 0,45 | 16.754,5 |
151250 | 0,50 | 23147,5 |
172040 | 0,55 | 30710 |
195.000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, wird die erste Zeile gesucht, in der QF <= Feld 1 gilt. Wenn beispielsweise QF = 23000 ist, wird die Zeile
Die Steuer I beträgt dann 0,15*R – 2072,5*nbParts. Wenn QF so ist, dass die Beziehung QF <= Feld1 niemals erfüllt ist, werden die Koeffizienten der letzten Zeile verwendet. Hier:
was die Steuer I = 0,65 * R – 49062 * nbParts ergibt.
Die Daten zur Definition der verschiedenen Steuerklassen befinden sich in einer ODBC-MySQL-Datenbank. MySQL ist ein gemeinfreies SGBD, das auf verschiedenen Plattformen, darunter Windows und Linux, verwendet werden kann. Mit diesem SGBD wurde eine Datenbank namens „dbimpots“ angelegt, die eine einzige Tabelle namens „impots“ enthält. Der Zugriff auf die Datenbank wird über einen Benutzernamen und ein Passwort gesteuert, in diesem Fall „admimpots“ und „mdpimpots“. Der folgende Screenshot zeigt, wie die Datenbank „dbimpots“ mit MySQL verwendet wird:
C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
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> 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 |
| 92970 | 0.4 | 12106 |
| 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
Die Datenbank dbimpots wird wie folgt in die Datenquelle ODBC umgewandelt:
- Der 32-Bit-Datenquellenmanager ODBC wird gestartet

- über die Schaltfläche [Add] wird eine neue Datenquelle ODBC hinzugefügt

- Man wählt den Treiber MySQL aus und führt [Terminer] aus
54321

- Der Treiber MySQL fordert eine Reihe von Angaben an:
1 | den Namen DSN, der der Datenquelle ODBC zugewiesen werden soll – dieser kann beliebig sein |
2 | der Maschine, auf der SGBD und MySQL ausgeführt werden – in diesem Fall localhost. Interessanterweise könnte es sich bei der Datenbank um eine entfernte Datenbank handeln. Lokale Anwendungen, die die Datenquelle ODBC verwenden, würden davon nichts bemerken. Dies wäre insbesondere bei unserer Java-Anwendung der Fall. |
3 | die zu verwendende Datenbank MySQL. MySQL ist ein SGBD, das relationale Datenbanken verwaltet, bei denen es sich um Gruppen von Tabellen handelt, die durch Beziehungen miteinander verbunden sind. Hier wird der Name der verwalteten Datenbank angegeben. |
4 | den Namen eines Benutzers, der Zugriffsrechte auf diese Datenbank hat |
5 | sein Passwort |
Zur Berechnung der Steuer wurden zwei Klassen definiert: „impots“ und „impotsJDBC“. Eine Instanz der Klasse „impots“ wird mit den Daten der Steuerklassen erstellt, die als Parameter in Arrays übergeben werden:
// Anlegen einer Steuerklasse
public class impots{
// Die für die Steuerberechnung erforderlichen Daten
// stammen aus einer externen Quelle
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// leerer Konstruktor
protected impots(){}
// Konstruktor
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// Es wird überprüft, ob alle drei Tabellen dieselbe Größe haben
boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// alles in Ordnung
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//Hersteller
// Steuerberechnung
public long calculer(boolean marié, int nbEnfants, int salaire){
// Berechnung der Anzahl der Anteile
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// Berechnung des steuerpflichtigen Einkommens und des Familienquotienten
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// Steuerberechnung
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// Ergebnis zurückgeben
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//berechnen
}//Klasse
Die Klasse impotsJDBC leitet sich von der vorherigen Klasse „impots“ ab. Eine Instanz der Klasse impotsJDBC wird mit den in einer Datenbank gespeicherten Daten zu den Steuerklassen erstellt. Die für den Zugriff auf diese Datenbank erforderlichen Informationen werden als Parameter an den Konstruktor übergeben:
// importierte Pakete
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// Hinzufügen eines Konstruktors zum Erstellen
// die Tabellen „limites“, „coeffr“ und „coeffn“ aus der Tabelle
// Steuerdaten aus einer Datenbank
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: Name der Datenbank DSN
// userIMPOTS, mdpIMPOTS: Benutzername/Passwort für den Zugriff auf die Datenbank
// die Datentabellen
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// Verbindung zur Datenbank
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// Erstellung eines Statement-Objekts
Statement S=connect.createStatement();
// SELECT-Abfrage
String select="select limites, coeffr, coeffn from impots";
// Ausführung der Abfrage
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// Auswertung der aktuellen Zeile
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// Nächste Zeile
// Ressourcen schließen
RS.close();
S.close();
connect.close();
// Übertragung der Daten in begrenzte Arrays
int n=aLimites.size();
limites=new double[n];
coeffR=new double[n];
coeffN=new double[n];
for(int i=0;i<n;i++){
limites[i]=Double.parseDouble((String)aLimites.get(i));
coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
}//for
}//Konstruktor
}//Klasse
Sobald eine Instanz der Klasse impotsJDBC erstellt wurde, kann ihre Methode calculer wiederholt aufgerufen werden, um die Steuer zu berechnen:
Die Erfassung der drei erforderlichen Daten kann auf verschiedene Arten erfolgen. Der Vorteil der Klasse impotsJDBC besteht darin, dass man sich lediglich um diese Erfassung kümmern muss. Sobald die drei Informationen (Familienstand, Anzahl der Kinder, Jahresgehalt) vorliegen, liefert uns der Aufruf der Methode calculer der Klasse impotsJDBC die zu zahlende Steuer.
5.2. Version 1
Wir befinden uns im Kontext einer Webanwendung, die einem Benutzer eine Schnittstelle HTML bereitstellt, um die drei für die Steuerberechnung erforderlichen Parameter zu erfassen:
- den Familienstand (verheiratet oder unverheiratet)
- die Anzahl der Kinder
- das Jahreseinkommen

Die Anzeige des Formulars erfolgt über die folgende Seite JSP:
<%@ page import="java.util.*" %>
<%
// Hier werden die vom Haupt-Servlet übergebenen Attribute abgerufen
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");
%>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
// Formular zurücksetzen
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//mit
}//Löschen
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" action="/impots/main" method="POST">
<table>
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="optMarie" value="oui" <%= chkoui %>>oui
<input type="radio" name="optMarie" value="non" <%= chknon %>>non
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td><input type="text" size="5" name="txtEnfants" value="<%= txtEnfants %>"></td>
</tr>
<tr>
<td>Salaire annuel</td>
<td><input type="text" size="10" name="txtSalaire" value="<%= txtSalaire %>"></td>
</tr>
<tr>
<td><font color="green">Impôt</font></td>
<td><input type="text" size="10" name="txtImpots" value="<%= txtImpots %>" readonly></td>
</tr>
<tr></tr>
<tr>
<td><input type="submit" value="Calculer"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
</tr>
</table>
</form>
</center>
<%
// Gibt es Fehler?
if(erreurs!=null){
// Anzeige der Fehler
out.println("<hr>");
out.println("<font color=\"red\">");
out.println("Les erreurs suivantes se sont produites<br>");
out.println("<ul>");
for(int i=0;i<erreurs.size();i++){
out.println("<li>"+(String)erreurs.get(i));
}
out.println("</ul>");
out.println("</font>");
}
%>
</body>
</html>
Die Seite JSP zeigt lediglich Informationen an, die ihr vom Haupt-Servlet der Anwendung übergeben werden:
// Die vom Haupt-Servlet übergebenen Attribute werden abgerufen
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");
Attribute der Optionsfelder „Ja“ und „Nein“ – mögliche Werte sind "checked" oder "", um das entsprechende Optionsfeld zu aktivieren bzw. nicht zu aktivieren | |
die Anzahl der Kinder des Steuerpflichtigen | |
sein Jahresgehalt | |
die Höhe der zu zahlenden Steuer | |
eine eventuelle Fehlerliste, falls „fehler“ != null |
Die an den Kunden gesendete Seite enthält ein JavaScript-Skript mit einer Funktion effacer, die der Schaltfläche „Effacer“ zugeordnet ist und dazu dient, das Formular in seinen Ausgangszustand zurückzusetzen: Schaltfläche nicht markiert, Eingabefelder leer. Es ist zu beachten, dass dieses Ergebnis mit einer Schaltfläche vom Typ „HTML“ nicht erzielt werden konnte. Denn wenn dieser Schaltflächentyp verwendet wird, versetzt der Browser das Formular in den Zustand zurück, in dem er es empfangen hat. In unserer Anwendung erhält der Browser jedoch Formulare, die möglicherweise nicht leer sind.
Das Haupt-Servlet der Anwendung heißt main.java und sein Code lautet wie folgt:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.regex.*;
import java.util.*;
public class main extends HttpServlet{
// Instanzvariablen
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{
// Ist die Initialisierung erfolgreich verlaufen?
if(msgErreur!=null){
// Es wird an die Fehlerseite weitergeleitet
request.setAttribute("msgErreur",msgErreur);
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
// Attribute der Anfrage
String chkoui=null;
String chknon=null;
String txtImpots=null;
// Die Parameter der Anfrage werden abgerufen
String optMarie=request.getParameter("optMarie"); // Familienstand
String txtEnfants=request.getParameter("txtEnfants"); // Anzahl der Kinder
if(txtEnfants==null) txtEnfants="";
String txtSalaire=request.getParameter("txtSalaire"); // Jahresgehalt
if(txtSalaire==null) txtSalaire="";
// Sind alle erwarteten Parameter vorhanden?
if(optMarie==null || txtEnfants==null || txtSalaire==null){
// Es fehlen Parameter
request.setAttribute("chkoui","");
request.setAttribute("chknon","checked");
request.setAttribute("txtEnfants","");
request.setAttribute("txtSalaire","");
request.setAttribute("txtImpots","");
// Weiterleitung an die URL zur Anzeige der Steuer
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
// Alle Parameter sind vorhanden – sie werden überprüft
ArrayList erreurs=new ArrayList();
// Familienstand
if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
// Fehler
erreurs.add("Etat marital incorrect");
optMarie="non";
}
// Anzahl der Kinder
txtEnfants=txtEnfants.trim();
if(! Pattern.matches("^\\d+$",txtEnfants)){
// Fehler
erreurs.add("Nombre d'enfants incorrect");
}
// Gehalt
txtSalaire=txtSalaire.trim();
if(! Pattern.matches("^\\d+$",txtSalaire)){
// Fehler
erreurs.add("Salaire incorrect");
}
// Falls Fehler auftreten, werden diese als Attribut der Abfrage übergeben
if(erreurs.size()!=0){
request.setAttribute("erreurs",erreurs);
txtImpots="";
}else{
// die zu zahlende Steuer kann berechnet werden
try{
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
}catch(Exception ex){}
}
// die übrigen Attribute der Abfrage
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);
// die Kontrolle wird an die URL zur Anzeige der Steuer übergeben
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// Die Initialisierungsparameter werden abgerufen
ServletConfig config=getServletConfig();
urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
urlErreur=config.getInitParameter("urlErreur");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// Parameter in Ordnung?
if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){
msgErreur="Configuration incorrecte";
return;
}
// Es wird eine Instanz von impotsJDBC erstellt
try{
impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
}catch(Exception ex){
msgErreur=ex.getMessage();
}
}
}
- Die init-Methode des Servlets führt zwei Schritte aus:
- Sie ruft ihre Initialisierungsparameter ab. Diese ermöglichen es ihr, eine Verbindung zur Datenbank ODBC herzustellen, die die Daten der verschiedenen Steuerklassen (DSNimpots, admimpots, mdpimpots) sowie die URL der zur Anwendung gehörenden Seiten enthält: urlAffichageImpots für das Formular, urlErreur für die Fehlerseite.
- Sie erstellt eine Instanz der Klasse impotsJDBC
In beiden Fällen werden mögliche Fehler behandelt und die Fehlermeldung in die Variable msgErreur gespeichert.
- Die Methode doGET
- überprüft zunächst, ob das Servlet korrekt initialisiert wurde. Ist dies nicht der Fall, wird die Fehlerseite angezeigt
- und ruft die erwarteten Parameter des Steuerformulars ab: optMarie, txtEnfants, txtSalaire. Fehlt einer davon (==null), wird ein leeres Steuerformular gesendet. Man könnte meinen, dass die Überprüfung der Gültigkeit des Parameters optMarie überflüssig ist. Dieser ist der Wert eines Optionsfelds und kann hier nur einen der Werte „oui“ oder „non“ annehmen. Dabei wird jedoch übersehen, dass nichts ein Programm daran hindert, das Servlet direkt abzufragen, indem es die gewünschten Parameter an es sendet. Man kann nie sicher sein, dass tatsächlich eine Verbindung zu einem Browser besteht. Das Übersehen dieses Punktes kann zu Sicherheitslücken in der Anwendung führen, und es kommt übrigens häufig vor, dass solche Lücken sogar in kommerziellen Anwendungen zu finden sind.
- Die Gültigkeit jedes der drei abgerufenen Parameter wird überprüft. Jeder gefundene Fehler wird einer Fehlerliste hinzugefügt (ArrayList Fehler). Liegen keine Fehler vor, wird der Steuerbetrag berechnet, andernfalls nicht.
- Die für die Anzeige der Seite erforderlichen Informationen werden als Attribute der Anfrage festgelegt, und anschließend wird das Steuerformular angezeigt.
Die Fehlerseite JSP sieht wie folgt aus:
<%
// jspService
// Es ist ein Fehler aufgetreten
String msgErreur= (String)request.getAttribute("msgErreur");
if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- Anfang der Seite HTML -->
<html>
<head>
<title>impots</title>
</head>
<body>
<h3>calcul d'impots</h3>
<hr>
Application indisponible(<%= msgErreur %>)
</body>
</html>
Die Webanwendung heißt „impots“ und ist in der Tomcat-Datei server.xml wie folgt konfiguriert:
Der Anwendungsordner enthält die folgenden Ordner und Dateien:




Die Konfigurationsdatei „web.xml“ der Anwendung „impots“ lautet wie folgt:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>main</servlet-name>
<servlet-class>main</servlet-class>
<init-param>
<param-name>urlAffichageImpots</param-name>
<param-value>/impots.jsp</param-value>
</init-param>
<init-param>
<param-name>DSNimpots</param-name>
<param-value>mysql-dbimpots</param-value>
</init-param>
<init-param>
<param-name>admimpots</param-name>
<param-value>admimpots</param-value>
</init-param>
<init-param>
<param-name>mdpimpots</param-name>
<param-value>mdpimpots</param-value>
</init-param>
<init-param>
<param-name>urlErreur</param-name>
<param-value>/erreur.jsp</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/main</url-pattern>
</servlet-mapping>
</web-app>
Das Haupt-Servlet heißt main und hat den Alias /main. Es ist daher über URL und http://localhost:8080/impots/main erreichbar.
Hier sind einige Anwendungsbeispiele:
Um sich korrekt zu initialisieren, muss das Servlet Zugriff auf die Datenbank mysql-dbimpots haben. Hier ist die Seite, die angezeigt wird, wenn beispielsweise der Server MySQL nicht gestartet ist und die Datenbank mysql-dbimpots daher nicht erreichbar ist:

Bei einer falschen Eingabe wird folgende Seite angezeigt:

Bei korrekter Eingabe wird die Steuer berechnet:

5.3. Version 2
Im vorangegangenen Beispiel wird die Gültigkeit der Formularparameter txtEnfants und txtSalaire vom Server überprüft. Hier wird vorgeschlagen, die Gültigkeit mithilfe eines in die Formularseite eingebundenen JavaScript-Skripts zu überprüfen. Die Überprüfung der Parameter erfolgt dann durch den Browser. Der Server wird somit nur dann angefragt, wenn die Parameter gültig sind. Auf diese Weise wird „Bandbreite“ eingespart. Die Anzeigeseite JSP sieht dann wie folgt aus:
<%@ page import="java.util.*" %>
............
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
.......
}//Löschen
function calculer(){
// Überprüfung der Parameter vor dem Senden an den Server
with(document.frmImpots){
//Anzahl der Kinder
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// Das Modell wurde nicht überprüft
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//if
//Gehalt
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// Das Modell wurde nicht überprüft
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//if
// Alles in Ordnung – das Formular wird an den Server gesendet
submit();
}//mit
}//berechnen
</script>
</head>
<body background="/impots/images/standard.jpg">
........
<td><input type="button" value="Calculer" onclick="calculer()"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
.....
</body>
</html>
Es sind folgende Änderungen zu beachten:
- Die Schaltfläche Calculer hat nicht mehr den Typ submit, sondern den Typ button, der mit einer Funktion namens calculer verknüpft ist. Diese Funktion führt die Analyse der Felder txtEnfants und txTsalaire durch. Sind diese korrekt, werden die Werte des Formulars an den Server gesendet (Submit), andernfalls wird eine Fehlermeldung angezeigt.
Hier ein Beispiel für die Anzeige im Fehlerfall:

5.4. Version 3
Wir passen die Anwendung leicht an, um das Konzept der Sitzung einzuführen. Wir betrachten die Anwendung nun als eine Simulation zur Steuerberechnung. Ein Benutzer kann dann verschiedene „Konfigurationen“ von Steuerpflichtigen simulieren und sehen, wie hoch die zu zahlende Steuer für jede dieser Konfigurationen wäre. Die untenstehende Seite WEb zeigt ein Beispiel dafür, was dabei herauskommen könnte:

Das Haupt-Servlet wurde geändert und heißt nun „simulations“. Es ist innerhalb der Anwendung „impots“ wie folgt konfiguriert:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
....
</servlet>
<servlet>
<servlet-name>simulations</servlet-name>
<servlet-class>simulations</servlet-class>
<init-param>
<param-name>urlSimulationImpots</param-name>
<param-value>/simulationsImpots.jsp</param-value>
</init-param>
<init-param>
<param-name>DSNimpots</param-name>
<param-value>mysql-dbimpots</param-value>
</init-param>
<init-param>
<param-name>admimpots</param-name>
<param-value>admimpots</param-value>
</init-param>
<init-param>
<param-name>mdpimpots</param-name>
<param-value>mdpimpots</param-value>
</init-param>
<init-param>
<param-name>urlErreur</param-name>
<param-value>/erreur.jsp</param-value>
</init-param>
</servlet>
......
<servlet-mapping>
<servlet-name>simulations</servlet-name>
<url-pattern>/simulations</url-pattern>
</servlet-mapping>
</web-app>
Das Haupt-Servlet heißt „simulations“ und basiert auf der Klassendatei simulations.class. Es hat den Alias „/simulations“, wodurch es über die Servlets URL und http://localhost:8080/impots/simulations erreichbar ist. Es verfügt über dieselben Initialisierungsparameter wie das zuvor behandelte Haupt-Servlet, was den Zugriff auf die Datenbank betrifft. Es taucht ein neuer Parameter auf: urlSimulationsImpots, bei dem es sich um das URL der Simulationsseite JSP handelt (die etwas weiter oben vorgestellt wurde).
Das Servlet simulations.java ähnelt dem Servlet main.java. Es unterscheidet sich von diesem in folgenden wesentlichen Punkten:
- Das Haupt-Servlet berechnet einen Wert txtImpots aus den Parametern optmarie, txtEnfants und txtSalaire und übergibt diesen Wert an die Anzeigeseite JSP
- Das Servlet „simulations“ berechnet auf die gleiche Weise den Wert txtImpots, speichert die Parameter (optMarie, txtEnfants, txtsalaire, txtImpots) in einer Liste namens „simulations“ ab. Diese Liste wird als Parameter an die Anzeigeseite JSP übergeben. Damit diese Liste alle vom Benutzer durchgeführten Simulationen enthält, wird sie als Attribut der aktuellen Sitzung gespeichert.
Das Servlet „simulations“ sieht wie folgt aus (es wurden nur die Codezeilen beibehalten, die sich von denen der vorherigen Anwendung unterscheiden):
import java.io.*;
.......
public class simulations extends HttpServlet{
// Instanzvariablen
String msgErreur=null;
String urlSimulationImpots=null;
String urlErreur=null;
...........
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
...........
// die vorherigen Simulationen der Sitzung werden abgerufen
HttpSession session=request.getSession();
ArrayList simulations=(ArrayList)session.getAttribute("simulations");
if(simulations==null) simulations=new ArrayList();
// die Simulationen werden in die aktuelle Abfrage übernommen
request.setAttribute("simulations",simulations);
// weitere Attribute der Abfrage
...........
// Sind alle erwarteten Parameter vorhanden?
if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
// die Steuerung wird an die URL zur Anzeige der Steuerberechnungssimulationen übergeben
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
}
// Alle Parameter sind vorhanden – sie werden überprüft
...........
// Falls Fehler vorliegen, werden diese als Attribute der Anfrage übergeben
if(erreurs.size()!=0){
request.setAttribute("erreurs",erreurs);
}else{
try{
// Die zu zahlende Steuer kann berechnet werden
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
// Das aktuelle Ergebnis wird zu den vorherigen Simulationen hinzugefügt
String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
simulations.add(simulation);
// Der neue Simulationswert wird in die Sitzung zurückgeschrieben
session.setAttribute("simulations",simulations);
}catch(Exception ex){}
}
// weitere Attribute der Abfrage
..........
// die Kontrolle wird an die URL zur Anzeige der Simulationen übergeben
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// Die Initialisierungsparameter werden abgerufen
ServletConfig config=getServletConfig();
urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
urlErreur=config.getInitParameter("urlErreur");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// Sind die Parameter in Ordnung?
.........................
}
}
Die Anzeigeseite JSP der Seite simulationsImpots.jsp sieht nun wie folgt aus (es wurde nur der Code beibehalten, der sich von der Anzeigeseite JSP der vorherigen Anwendung unterscheidet).
<%@ page import="java.util.*" %>
<%
// Die vom Haupt-Servlet übergebenen Attribute werden abgerufen
...........
ArrayList simulations=(ArrayList)request.getAttribute("simulations");
%>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
........
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" action="/impots/simulations" method="POST">
.......................
</form>
</center>
<hr>
<%
// Gibt es Fehler?
if(erreurs!=null){
..................
}else if(simulations.size()!=0){
// Ergebnisse der Simulationen
out.println("<h3>Résultats des simulations<h3>");
out.println("<table \"border=\"1\">");
out.println("<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>");
for(int i=0;i<simulations.size();i++){
String[] simulation=(String[])simulations.get(i);
out.println("<tr><td>"+simulation[0]+"</td><td>"+simulation[1]+"</td><td>"+simulation[2]+"</td><td>"+simulation[3]+"</td></tr>");
}
out.println("</table>");
}
%>
</body>
</html>
5.5. Version 4
Wir werden nun eine eigenständige Anwendung erstellen, die als Web-Client für die vorherige Anwendung /impots/simulations dient. Diese Anwendung wird folgende grafische Benutzeroberfläche haben:
![]() |
Nr. | Typ | Name | Rolle |
1 | JTextField | txtUrlServiceImpots | URL des Dienstes zur Simulation der Steuerberechnung |
2 | JRadioButton | rdOui | ankreuzen, wenn verheiratet |
3 | JRadioButton | rdNon | ankreuzen, wenn unverheiratet |
4 | JSpinner | spinEnfants | Anzahl der Kinder des Steuerpflichtigen (Minimum=0, Maximum=20, Schrittweite=1) |
5 | JTextField | txtSalaire | Jahresgehalt des Steuerpflichtigen in F |
6 | JList in JScrollPane | lstSimulations | Liste der Simulationen |
Das Menü „Steuern“ umfasst folgende Optionen:
Option Hauptoption | Nebenoption Nebenoption | Name | Rolle |
Steuern | |||
Berechnen | mnuCalculer | berechnet die zu zahlende Steuer, wenn alle für die Berechnung erforderlichen Daten vorhanden und korrekt sind | |
Löschen | mnuEffacer | Setzt das Formular in seinen Ausgangszustand zurück | |
Beenden | mnuQuitter | beendet die Anwendung |
Funktionsregeln
- Der Menüpunkt Calculer bleibt deaktiviert, wenn eines der Felder 1 oder 5 leer ist
- eine syntaktisch fehlerhafte URL in Feld 1 erkannt wird

- ein falscher Lohn erkannt wird

- Jeder Fehler bei der Verbindung zum Server wird gemeldet (im Beispiel 1 unten ist der Port falsch – im Beispiel 2 existiert die angeforderte URL nicht – im Beispiel 3 war die Datenbank MySQL nicht gestartet)



- Wenn alles korrekt ist, werden die Simulationen angezeigt

Beim Schreiben eines programmierten Web-Clients muss man genau wissen, was der Server als Antwort auf die verschiedenen möglichen Anfragen eines Clients sendet. Der Server sendet eine Reihe von Zeilen HTML, die nützliche Informationen sowie andere enthalten, die nur dem Layout dienen HTML. Reguläre Ausdrücke in Java können uns dabei helfen, die nützlichen Informationen in der Flut der vom Server gesendeten Zeilen zu finden. Dazu müssen wir das genaue Format der verschiedenen Serverantworten kennen. Hier verwenden wir den bereits vorgestellten Webclient, mit dem sich die Antwort eines Servers auf die Anfrage nach einer URL auf dem Bildschirm anzeigen lässt. Die angeforderte URL ist die unseres Dienstes zur Simulation von Steuerberechnungen http://localhost:8080/impots/simulations, an den wir Parameter in der Form http://localhost:8080/impots/simulations?param1=vam1¶m2=val2&... übergeben können
Rufen wir nun die URL auf, während die Basis MySQL, mit der ein Objekt vom Typ impots erstellt werden kann, nicht gestartet ist:
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
<!-- dÚbut von der Seite HTML -->
<html>
<head>
<title>impots</title>
</head>
<body>
<h3>calcul d'impôts</h3>
<hr>
Application indisponible([TCX][MyODBC]Can't connect to MySQL server on 'localhost' (10061))
</body>
</html>
Um den Fehler abzurufen, muss der Webclient in der Antwort des Webservers nach der Zeile mit dem Text „Anwendung nicht verfügbar“ suchen. Starten wir nun die Datenbank MySQL und fragen wir dieselbe Datenbank URL ab, indem wir ihr die erwarteten Werte übergeben (sie erwartet diese Werte entweder in Form von GET oder POST – siehe Java-Code des Servers):
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
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
...................................................
}//löschen
function calculer(){
...................................................
}//berechnen
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Calcul d'imp¶ts
<hr>
<form name="frmImpots" action="/impots/simulations" method="POST">
...................................................
</form>
</center>
<hr>
<h3>Résultats des simulations<h3>
<table "border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
</table>
</body>
</html>
Hier finden wir das gesamte vom Server gesendete Dokument HTML. Die Ergebnisse der verschiedenen Simulationen sind in der einzigen Tabelle dieses Dokuments enthalten. Der reguläre Ausdruck, mit dem wir die relevanten Informationen aus dem Dokument extrahieren können, könnte wie folgt lauten:
wobei die vier Ausdrücke in Klammern die vier abzurufenden Informationen darstellen.
Wir haben nun die Leitlinien dafür, was zu tun ist, wenn der Benutzer über die vorherige grafische Benutzeroberfläche die Berechnung der Steuer anfordert:
- Überprüfen, ob alle Daten der Benutzeroberfläche gültig sind, und gegebenenfalls Fehler melden.
- eine Verbindung zu dem in Feld 1 angegebenen URL herstellen. Dazu folgen wir dem bereits vorgestellten und untersuchten Modell des generischen Web-Clients
- Im Antwortstrom des Servers reguläre Ausdrücke verwenden, um entweder:
- die Fehlermeldung zu finden, falls vorhanden
- die Simulationsergebnisse zu finden, falls kein Fehler vorliegt
Der Code für das Menü calculer lautet wie folgt:
void mnuCalculer_actionPerformed(ActionEvent e) {
// Steuerberechnung
// Überprüfung 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){
// Fehlermeldung
JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// Fokus auf fehlerhaftes Feld
txtURLServiceImpots.requestFocus();
// Zurück zur Benutzeroberfläche
return;
}
// Gehaltsüberprüfung
int salaire=0;
try{
salaire=Integer.parseInt(txtSalaire.getText().trim());
if(salaire<0) throw new Exception();
}catch (Exception ex){
// Fehlermeldung
JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// Fokus auf fehlerhaftes Feld
txtSalaire.requestFocus();
// Zurück zur Benutzeroberfläche
return;
}
// Anzahl der Kinder
Integer nbEnfants=(Integer)spinEnfants.getValue();
try{
// Die Steuer wird berechnet
calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
}catch (Exception ex){
// der Fehler wird angezeigt
JOptionPane.showMessageDialog(this,"L'erreur suivante s'est produite : " + ex.getMessage(),"Erreur",JOptionPane.ERROR_MESSAGE);
}
}//mnuCalculer
public void calculerImpots(URL urlImpots,boolean marié, int nbEnfants, int salaire)
throws Exception{
// Steuerberechnung
// urlImpots: URL vom Finanzamt
// verheiratet: true, wenn verheiratet, andernfalls false
// nbEnfants: Anzahl der Kinder
// Gehalt: Jahresgehalt
// Aus urlImpots werden die für die Verbindung zum Steuer-Server erforderlichen Informationen entnommen
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();
// lokale Daten
Socket client=null; // der Client
BufferedReader IN=null; // der Lesestrom des Kunden
PrintWriter OUT=null; // Schreib-Stream des Clients
String réponse=null; // Antwort des Servers
// das in den Headern gesuchte Muster HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// das Muster einer korrekten Antwort
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
// Ergebnis des Vergleichs mit dem Muster
Matcher résultat=null;
try{
// Verbindung zum Server wird hergestellt
client=new Socket(host,port);
// Die Ein- und Ausgabeströme des Clients werden erstellt: TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// Anfrage an URL – Senden der Header 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("");
// Die erste Zeile der Antwort wird gelesen
réponse=IN.readLine();
// Die Zeile HTTP wird mit dem Muster der korrekten Antwort verglichen
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// Es gibt ein Problem mit URL
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(Ergebnis)
// Die Antwort wird bis zum Ende der Header gelesen, um nach einem möglichen Cookie zu suchen
while((réponse=IN.readLine())!=null){
// Leere Zeile?
if(réponse.equals("")) break;
// Zeile HTTP ist nicht leer
// Wenn das Session-Token fehlt, wird danach gesucht
if (JSESSIONID.equals("")){
// die Zeile HTTP wird mit dem Cookie-Muster verglichen
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// Das Token-Cookie wurde gefunden
JSESSIONID=résultat.group(1);
}//if(Ergebnis)
}//if(JSESSIONID)
}//while
// Das war's mit den Headern HTTP – weiter geht's mit dem Code HTML
// um die Simulationen abzurufen
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
// Das war’s
client.close();
}catch (Exception ex){
throw new Exception(ex.getMessage());
}
}//calculerImpots
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// das Muster einer Zeile aus der Simulationstabelle
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// das Muster einer Zeile aus der Fehlerliste
Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
// das Ergebnis des Vergleichs mit dem Modell
Matcher résultat=null;
// die Simulationen
ArrayList listeSimulations=new ArrayList();
// Es werden alle Zeilen bis zum Ende gelesen
String ligne=null;
boolean simulationRéussie=false;
while((ligne=IN.readLine())!=null){
// Weiter
// die Zeile wird mit dem Fehlermodell verglichen, sofern der Abschnitt „Simulationen“ noch nicht erreicht wurde
if(! simulationRéussie){
résultat=ptnErreur.matcher(ligne);
if(résultat.find()){
// Fehlermeldung
JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
// Fertig
return listeSimulations;
}//if
}//if
// Die Zeile wird mit dem Simulationsmodell verglichen
résultat=ptnSimulation.matcher(ligne);
if(résultat.find()){
// Es wurde eine Zeile aus der Tabelle gefunden
listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
":"+résultat.group(4));
// Die Simulation war erfolgreich
simulationRéussie=true;
}//if
}//while
// Ende
return listeSimulations;
}
Lassen Sie uns diesen Code etwas näher erläutern:
- Die Prozedur mnuCalculer_actionPerformed überprüft, ob die Daten der Schnittstelle gültig sind. Sind sie es nicht, wird eine Fehlermeldung ausgegeben und die Prozedur beendet. Sind sie es, wird die Prozedur calculerImpots ausgeführt.
- Die Prozedur calculerImpots beginnt damit, die Prozedur URL zu erstellen, die sie aufrufen muss
// Aus urlImpots werden die für die Verbindung zum Steuerserver erforderlichen Informationen entnommen
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();
........................
- und stellt dann eine Verbindung zu diesem URL her, indem es die entsprechenden HTTP-Header sendet:
// Es wird eine Verbindung zum Server hergestellt
client=new Socket(host,port);
// Die Ein- und Ausgangsströme des Kunden werden angelegt: TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// Anforderung des URL – Versand der Kopfdaten 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("");
- Sobald die Anfrage gestellt wurde, wartet unser Webclient auf die Antwort. Zunächst erhält er die Header HTTP aus der Antwort des Servers. Er durchsucht diese Header nach dem Sitzungstoken. Dieses muss er nämlich an den Server zurücksenden, damit dieser die verschiedenen durchgeführten Simulationen nachverfolgen kann. An dieser Stelle sei angemerkt, dass es einfacher gewesen wäre, wenn der Client die verschiedenen vom Benutzer durchgeführten Simulationen selbst gespeichert hätte. Dennoch behalten wir das Konzept der Rücksendung des Tokens bei, um ein weiteres Beispiel für die Sitzungsverwaltung zu liefern. Die erste Zeile der Antwort wird gesondert behandelt. Sie muss nämlich die Form „HTTP/Version 200 OK“ haben, um anzuzeigen, dass das angeforderte „URL“ tatsächlich existiert. Wenn sie also nicht diese Form hat, wird daraus geschlossen, dass der Benutzer ein falsches URL angefordert hat, und dies wird ihm mitgeteilt.
// Das gesuchte Muster wird in den Kopfzeilen gesucht HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// das Muster einer korrekten Antwort
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........
// die erste Zeile der Antwort wird gelesen
réponse=IN.readLine();
// Die Zeile HTTP wird mit dem Muster der korrekten Antwort verglichen
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// Es liegt ein Problem mit URL vor
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(Ergebnis)
// Die Antwort wird bis zum Ende der Header gelesen, wobei nach einem eventuellen Cookie gesucht wird
while((réponse=IN.readLine())!=null){
// Leere Zeile?
if(réponse.equals("")) break;
// Zeile HTTP nicht leer
// Wenn das Session-Token fehlt, wird danach gesucht
if (JSESSIONID.equals("")){
// die Zeile HTTP wird mit dem Cookie-Muster verglichen
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// Das Token-Cookie wurde gefunden
JSESSIONID=résultat.group(1);
}//if(Ergebnis)
}//if(JSESSIONID)
}//while
- Nachdem die Kopfzeilen HTTP ausgewertet wurden, geht es weiter mit dem Teil HTML der Antwort
// Das war's mit den Headern HTTP – weiter geht's mit dem Code HTML
// um die Simulationen abzurufen
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
- Die Prozedur getSimulations gibt die Liste der Simulationen zurück, sofern vorhanden. Diese Liste ist leer, wenn der Server eine Fehlermeldung zurückgibt. In diesem Fall wird die Fehlermeldung in einem Meldungsfeld angezeigt. Ist die Liste nicht leer, wird sie in der Dropdown-Liste der grafischen Benutzeroberfläche angezeigt.
- Die Prozedur getSimulations vergleicht jede Zeile der Antwort von HTML mit dem regulären Ausdruck, der die Fehlermeldung („Anwendung nicht verfügbar...“) darstellt, sowie mit dem regulären Ausdruck, der eine Simulation darstellt. Wird die Fehlermeldung gefunden, wird sie angezeigt und die Prozedur beendet. Wird das Ergebnis einer Simulation gefunden, wird es der Liste der Simulationen hinzugefügt. Am Ende der Prozedur wird diese Liste als Ergebnis zurückgegeben.
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// das Muster einer Zeile aus der Simulationstabelle
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// die Vorlage für eine Zeile der Fehlerliste
Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
5.6. Version 5
Hier wandeln wir die vorherige eigenständige grafische Anwendung in ein Java-Applet um. Die grafische Benutzeroberfläche unterscheidet sich geringfügig. Bei der eigenständigen Anwendung gab der Benutzer selbst die URL des Dienstes zur Simulation von Steuerberechnungen ein, woraufhin die Anwendung eine Verbindung zu dieser URL herstellte. Hier ist die Client-Anwendung ein Browser, und der Benutzer fordert die URL des Dokuments HTML an, das das Applet enthält. Man muss nun bedenken, dass ein Java-Applet nur eine Netzwerkverbindung zu dem Server herstellen kann, von dem es heruntergeladen wurde. Das URL des Simulationsdienstes befindet sich daher auf demselben Server wie das Dokument HTML, das das Applet enthält. In unserem Beispiel wird ein Initialisierungsparameter des Applets in das Feld txtUrlServiceImpots eingefügt, das für den Benutzer nicht bearbeitbar ist. Damit ergibt sich folgender Client:

Das Dokument HTML, das das Applet enthält, heißt simulations.htm und sieht wie folgt aus:
<html>
<head>
<title>Simulations de calculs d'impôts</title>
</head>
<body background="/impots/images/standard.jpg">
<center>
<h3>Simulations de calculs d'impôts</h3>
<hr>
<applet code="appletImpots.class" width="400" height="360">
<param name="urlServiceImpots" value="simulations">
</applet>
</center>
</body>
</html>
Das Applet verfügt über einen Parameter urlServiceImpots, der dem URL des Dienstes zur Simulation der Steuerberechnung entspricht. Diese URL ist relativ und wird im Verhältnis zur URL des Dokuments HTML simulations.htm gemessen. Wenn also ein Browser dieses Dokument mit dem URL „http://localhost:8080/impots/simulations.htm“ abruft, dann wäre der URL des Simulationsdienstes http://localhost:8080/impots/simulations.. Wäre dieser URL http://stahe gewesen:8080/impots/simulations.htm, wäre die URL des Simulationsdienstes http://stahe:8080/impots/simulations gewesen.
Das Applet appletImpots.java übernimmt den Code der vorherigen eigenständigen grafischen Anwendung vollständig und beachtet dabei die Regeln für die Umwandlung einer grafischen Anwendung in ein Applet.
public class appletImpots extends JApplet {
// die Komponenten des Fensters
JPanel contentPane;
JMenuBar jMenuBar1 = new JMenuBar();
JMenu jMenu1 = new JMenu();
JMenuItem mnuCalculer = new JMenuItem();
.............
//Rahmen erstellen
public void init() {
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
// weitere Initialisierungen
moreInit();
}
// Initialisierung des Formulars
private void moreInit(){
// wird der Parameter urlServiceImpots abgerufen
String urlServiceImpots=getParameter("urlServiceImpots");
if(urlServiceImpots==null){
// Parameter fehlt
JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
// Ende
return;
}
// URL wird in das entsprechende Feld eingefügt
String codeBase=""+getCodeBase();
if(codeBase.endsWith("/"))
txtURLServiceImpots.setText(codeBase+urlServiceImpots);
else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
// Menü „Berechnen“ deaktiviert
mnuCalculer.setEnabled(false);
// Auswahlfeld „Kinder“ – zwischen 0 und 20 Kindern
spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
spinEnfants.setBounds(new Rectangle(130,140,50,27));
contentPane.add(spinEnfants);
}//moreInit
//Komponente initialisieren
private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
contentPane.setLayout(null);
...............
}
Wenn ein Browser ein Applet lädt, führt er zunächst dessen init-Prozedur aus. In dieser haben wir den Wert des Parameters urlServiceImpots des Applets abgerufen, anschließend den vollständigen Namen des Simulationsdienstes URL berechnet und diesen Wert in das Feld txtURLServiceImpots eingegeben, so als hätte der Benutzer ihn selbst eingegeben. Damit gibt es keinen Unterschied mehr zwischen den beiden Anwendungen. Insbesondere ist der dem Menü Calculer zugeordnete Code identisch. Hier ein Ausführungsbeispiel:

5.7. Fazit
Wir haben verschiedene Versionen unserer Client-Server-Anwendung zur Steuerberechnung vorgestellt:
- Version 1: Der Dienst wird durch eine Reihe von Servlets und Seiten (JSP) bereitgestellt, der Client ist ein Browser. Er führt nur eine einzige Simulation durch und speichert die vorherigen nicht.
- Version 2: Es werden einige browserbasierte Funktionen hinzugefügt, indem JavaScript-Skripte in das vom Browser geladene Dokument HTML eingebunden werden. Diese überprüfen die Gültigkeit der Formularparameter.
- Version 3: Durch die Verwaltung einer Sitzung wird es dem Dienst ermöglicht, sich die verschiedenen von einem Client durchgeführten Simulationen zu merken. Die Schnittstelle HTML wird entsprechend angepasst, um diese anzuzeigen.
- Version 4: Der Client ist nun eine eigenständige grafische Anwendung. Dies ermöglicht es uns, wieder auf die Entwicklung programmierter Web-Clients zurückzugreifen.
- Version 5: Der Client wird zu einem Java-Applet. Damit verfügen wir über eine vollständig in Java programmierte Client-Server-Anwendung, sowohl auf der Server- als auch auf der Client-Seite.
An dieser Stelle lassen sich einige Anmerkungen machen:
- Die Versionen 1 bis 3 unterstützen Browser, die lediglich über die Fähigkeit verfügen, JavaScript-Skripte auszuführen. Es ist zu beachten, dass ein Benutzer jederzeit die Möglichkeit hat, die Ausführung dieser Skripte zu unterbinden. Die Anwendung funktioniert dann in Version 1 nur teilweise (die Option „Löschen“ funktioniert nicht) und in den Versionen 2 und 3 überhaupt nicht (die Optionen „Löschen“ und „Berechnen“ funktionieren nicht). Es könnte interessant sein, eine Version des Dienstes zu entwickeln, die ohne JavaScript-Skripte auskommt.
- Für Version 4 muss der Client-Rechner über eine Java 2-Virtual Machine verfügen.
- Für Version 5 muss der Client-Rechner über einen Browser mit einer Java-2-Virtual-Machine verfügen.
Wenn man einen Webdienst entwickelt, muss man sich fragen, welche Arten von Clients man ansprechen möchte. Wenn man möglichst viele Clients ansprechen möchte, sollte man eine Anwendung schreiben, die an die Browser ausschließlich HTML sendet (kein JavaScript und keine Applets). Arbeitet man in einem Intranet und hat die Kontrolle über die Konfiguration der dortigen Rechner, kann man sich höhere Anforderungen an den Client leisten, und die vorherige Version 5 kann dann akzeptabel sein.
Die Versionen 4 und 5 sind Web-Clients, die die benötigten Informationen aus dem vom Server gesendeten Datenstrom „HTML“ beziehen. Sehr oft hat man keinen Einfluss auf diesen Datenstrom. Dies ist der Fall, wenn man einen Client für einen im Netzwerk vorhandenen Webdienst geschrieben hat, der von jemand anderem verwaltet wird. Nehmen wir ein Beispiel. Nehmen wir an, unser Dienst zur Simulation von Steuerberechnungen wurde von einem Unternehmen X entwickelt. Derzeit sendet der Dienst die Simulationen in einer Tabelle vom Typ HTML, und unser Client nutzt diese Tatsache, um sie abzurufen. Er vergleicht dabei jede Zeile der Serverantwort mit dem regulären Ausdruck:
// Das Modell einer Zeile der Simulationstabelle
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
Nehmen wir nun an, der Entwickler der Anwendung ändert das Erscheinungsbild der Antwort, indem er die Simulationen nicht mehr in einem Array, sondern in einer Liste in folgender Form bereitstellt:
In diesem Fall muss unser Webclient neu geschrieben werden. Das ist die ständige Gefahr, der Webclients von Anwendungen ausgesetzt sind, die man nicht selbst kontrolliert. XML kann eine Lösung für dieses Problem bieten:
- Anstatt HTML zu generieren, wird der Simulationsdienst XML generieren. In unserem Beispiel könnte dies lauten
<simulations>
<entetes marie="marié" enfants="enfants" salaire="salaire" impot="impôt"/>
<simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
<simulation marie="non" enfants="2" salaire="200000" impot="33388" />
</simulations>
- Dieser Antwort könnte ein Stylesheet zugeordnet werden, das den Browsern vorgibt, wie diese Antwort visuell dargestellt werden soll XML
- Die programmierten Web-Clients würden dieses Stylesheet ignorieren und die Informationen direkt aus dem Datenstrom „XML“ der Antwort abrufen
Möchte der Entwickler des Dienstes die visuelle Darstellung der bereitgestellten Ergebnisse ändern, muss er das Stylesheet anpassen und nicht den Datenstrom XML. Dank des Stylesheets zeigen die Browser die neue visuelle Darstellung an, und die programmierten Web-Clients müssen nicht geändert werden. So könnten neue Versionen unseres Simulationsdienstes entwickelt werden:
- Version 6: Der Dienst liefert eine Antwort XML zusammen mit einem Stylesheet für Browser
- Version 7: Der Client ist eine eigenständige grafische Anwendung, die die Antwort XML des Servers nutzt
- Version 8: Der Client ist ein Java-Applet, das die Antwort XML des Servers verarbeitet
