Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 18 Servlets und Java Server Pages
  gp 18.1 Dynamische Web-Seiten und Servlets
    gp 18.1.1 Was Servlets sind
    gp 18.1.2 Vorteil von Servlets gegenüber CGI-Programmen
    gp 18.1.3 Das erste Servlet
  gp 18.2 Vom Client zum Server und wieder zurück
    gp 18.2.1 Der bittende Client
    gp 18.2.2 Was ein Webserver erzeugt
    gp 18.2.3 Wer oder was ist MIME?
  gp 18.3 Servlets entwickeln und testen
    gp 18.3.1 Servlet-Container
    gp 18.3.2 Webserver mit Servlet-Funktionalität
    gp 18.3.3 Tomcat
    gp 18.3.4 Das erste Servlet compilieren und ausführen
  gp 18.4 Der Lebenszyklus
    gp 18.4.1 Initialisierung in init()
    gp 18.4.2 Abfragen bei service()
    gp 18.4.3 Mehrere Anfragen beim Servlet und Threadsicherheit
    gp 18.4.4 Das Ende eines Servlets
  gp 18.5 Das HttpServletResponse-Objekt
    gp 18.5.1 Wir generieren eine Web-Seite
    gp 18.5.2 Binärdaten senden
    gp 18.5.3 Automatisches Neuladen
    gp 18.5.4 Komprimierte Daten mit Content-Encoding
    gp 18.5.5 Noch mehr über Header, die der Server setzt
    gp 18.5.6 Seiten umlenken
  gp 18.6 Was der Browser mit auf den Weg gibt – HttpServletRequest
    gp 18.6.1 Hilfsfunktion im Umgang mit Headern
    gp 18.6.2 Übersicht der Browser-Header
    gp 18.6.3 Formulardaten auslesen
  gp 18.7 Kleine Kekse: die Klasse Cookies
    gp 18.7.1 Kekse erzeugen und setzen
    gp 18.7.2 Cookies vom Servlet einlesen
    gp 18.7.3 Kleine Helfer für Cookies
    gp 18.7.4 Cookiestatus ändern
    gp 18.7.5 Langlebige Cookies
    gp 18.7.6 Ein Warenkorbsystem
  gp 18.8 Sitzungsverfolgung (Session Tracking)
    gp 18.8.1 Das mit einer Sitzung verbundene Objekt HttpSession
    gp 18.8.2 Werte mit einer Sitzung assoziieren und auslesen
    gp 18.8.3 Zusätzliche Informationen
  gp 18.9 URL-Rewriting
  gp 18.10 Weiterleitung und Einbinden von Servlet-Inhalten
  gp 18.11 Inter-Servlet-Kommunikation
    gp 18.11.1 Daten zwischen Servlets teilen
  gp 18.12 Internationalisierung
    gp 18.12.1 Die Länderkennung des Anfragers auslesen
    gp 18.12.2 Länderkennung für die Ausgabe setzen
    gp 18.12.3 Westeuropäische Texte senden
  gp 18.13 Java Server Pages (JSP)
    gp 18.13.1 Was Java Server Pages sind
  gp 18.14 Skript-Elemente
    gp 18.14.1 Kommentare und Quoting
    gp 18.14.2 Scriptlets
    gp 18.14.3 Vordefinierte Variablen
    gp 18.14.4 Ausdrücke
    gp 18.14.5 Deklarationen
  gp 18.15 Entsprechende XML-Tags
  gp 18.16 Direktiven
    gp 18.16.1 Wichtige page-Direktiven im Überblick
  gp 18.17 Aktionen
    gp 18.17.1 Beans
  gp 18.18 Sonstiges zu den Servern
    gp 18.18.1 Den internen Compiler bei Tomcat für JSP ändern
  gp 18.19 Tomcat: Spezielles
    gp 18.19.1 Tomcat als Service unter Windows NT ausführen
    gp 18.19.2 MIME-Types mit Tomcat verbinden
    gp 18.19.3 Servlets beim Start laden
  gp 18.20 Ein Servlet generiert WAP-Seiten für das Handy
    gp 18.20.1 Ein WAP-Handy simulieren
    gp 18.20.2 Übersicht der wichtigsten Tags
    gp 18.20.3 Der Gateway
    gp 18.20.4 WML-Seiten aufbauen
    gp 18.20.5 Interessante Links zum Thema Servlets/JSP
  gp 18.21 Text in HTML-konformen Text umwandeln

Kapitel 18 Servlets und Java Server Pages

Lebensfreude entsteht durch Frieden, der nicht statisch, sondern dynamisch ist.
– Henry Miller


Galileo Computing

18.1 Dynamische Web-Seiten und Servlets  downtop

In der ersten Generation von Internetseiten war jede Seite statisch auf dem Webserver abgelegt und durch einen eindeutigen Namen identifiziert. Doch dies reichte für viele Anwendungen nicht aus und beschränkte die Interaktionsfähigkeit. Es gibt mehrere gute Gründe, warum Webinhalte dynamisch generiert werden sollten:

gp  Die Seite ist abhängig von Benutzereingaben
Wenn ein Kunde sich beispielsweise für ein Produkt und dessen Preis interessiert, dann war es kaum möglich, für jedes Produkt eine aktuelle statische Web-Seite zu bereitzustellen. Zudem sieht ja jede Seite anders aus, und so gäbe es sehr viele Seiten. Falls sich die Produktbeschreibung ändert, muss der Benutzer immer eine aktuelle Seite sehen. In diesem Fall ist es günstig, die Web-Seiten bei Bedarf zu erzeugen. Für Einkaufssysteme kommt noch eine weitere Eigenschaft hinzu: Der Benutzer bewegt sich über mehrere Seiten und verwaltet einen Warenkorb, der anwächst oder schrumpfen kann.
gp  Daten ändern sich oft
Eine weitere Anwendung ergibt sich, wenn sich die Seiteninformationen ändern. Wie könnten wir die Anzahl der Benutzer, die bis dato auf eine Seite zugegriffen haben, darstellen? Oder was wollen wir machen, wenn Nachrichten oder Börseninformationen von einer Datenbank kommen und auf einer Web-Seite aktuell gehalten werden sollen? Dies würde mit statischen Seiten nur mit großen Verrenkungen möglich sein.

Aus diesen Gründen wurden Schnittstellen definiert, wobei die bekannteste das Common Gateway Interface (kurz CGI) ist. Andere Hersteller haben für ihre Server eigene Schnittstellen definiert. So etwas Netscape NSAPI oder Microsoft ISAPI (Internet Information Server). Alle Schnittstellen erlauben dem Webserver externe Programme aufzurufen, die dann eine HTML-Seite generieren, die zurück zum Client geschickt wird. Mittels der CGI-Schittstelle kann der Browser dem Server-Daten übergeben, wie etwa ein Produkt, nach dem das Programm suchen soll. Über den Austausch der Daten haben wir uns schon im Kapitel über die URL-Klasse Gedanken gemacht.


Galileo Computing

18.1.1 Was Servlets sind  downtop

Unsere URL-Klasse definiert lediglich die Client-Seite, also den Aufrufer, der den Browser ersetzt. Auf der Server-Seite laufen dann meist keine Java-Programme. Häufig übernehmen Skriptsprachen wie Python oder Perl die Aufbereitung der Daten. Servlets sind nun die Antwort auf CGI-Programme. Dabei sind Servlets aber nicht einfache Java-Programme, die über die CGI-Schnittstelle mit dem Server kommunizieren, sondern eine eigenständige Entwicklung. Wenn wir Java-Programme als normale Applikationen auf der Server-Seite nutzen würden, müsste der Webserver immer dann, wenn eine dynamische Seite generiert wird, die JVM aufrufen und dann das Programm ausführen. Die Laufzeit wäre – dass können wir uns denken – ziemlich schlecht. Eine Verbesserung würde darin bestehen, dass der Webserver eine JVM integriert, die immer läuft und Objekte einzelne Verbindungen innerhalb der Java-Maschine bedienen. Genau das sind Servlets. Der Name ist vergleichbar mit dem Begriff »Applet«. Ein Applet ist ein Javaprogramm auf der Clientseite (im Browser) und ein Servlet ist ein Programm auf der Server-Seite (im Server).


Galileo Computing

18.1.2 Vorteil von Servlets gegenüber CGI-Programmen  downtop

Im Gegensatz zu herkömmlichen CGI-Programmen hat diese Einbettung in den Server viele Vorteile:

gp  Effizienz: Ein wichtiger Vorteil, der für Servlets und gegen externe CGI-Programme spricht, ist die gute Performance. Sie ergibt sich nicht daraus, dass die Programmiersprache Java verwendet wird, sondern daraus, dass die JVM im Server integriert ist und dass somit keine externen Programme gestartet werden müssen. Dies ist bei der CGI-Lösung ein großes Problem, denn jede Anfrage, die ein externes Programm aufruft, startet einen externen Prozess. Bei einer Anfrage am Tag, bei der die Antwortzeit nicht so wichtig ist, spielt das sicherlich keine große Rolle; wollen jedoch viele Clients bedient werden, fällt die Antwortzeit ins Gewicht. Bei Servlets läuft permanent die JVM im Hintergrund. Dabei wird jede Verbindung durch ein Thread-Objekt gehandhabt, dessen Erzeugung und Speicherverbrauch wesentlich optimaler ist als der Verwendung externer Programme. Für CGI-Programme wurde FastCGI definiert, sodass die Geschwindigkeit auch hier besser wird, da keine externen Prozessaufrufe mehr nötig sind.
gp  Datenaustausch und Kommunikation mit dem Webserver: Ein CGI-Programm kann mit anderen CGI-Programmen nur mühsam Daten teilen. Jedes Programm läuft unabhängig von anderen und verwaltet eigene Zustände. Da aber Servlets in einem gemeinsamen Maschinen-Kontext laufen, können sie Daten teilen. Zwecks Optimierung lassen sich beispielsweise Datenbankverbindungen oder vorberechnete Daten gemeinsam nutzen.
gp  Einfachheit: Programmierer lieben zwar unterschiedliche Sprachen, doch ist es sehr praktisch, eine gemeinsame Sprache zu nutzen. Warum sollte eine Firma eine Sprache nur für Webapplikationen verwenden? Nur deshalb, weil sich vielleicht spezielle Probleme in zehn Zeilen lösen lassen und nicht in fünfzig? Für den kommerziellen Erfolg spielt das keine Rolle. Wer einmal versucht hat, eine Benutzerverwaltung oder Aktionsbörse in Perl zu verstehen, der weiß, was ich meine. Wartung und die Möglichkeit, die Software einfach zu erweitern, spielen eine größere Rolle als kurze Entwicklungszeiten für Hacker gegenüber langen Pflegezeiten. Zudem bietet Java alle Eigenschaften der objektorientierten Welt. Für Servlets gibt es ordentliche Klassenbibliotheken, die sowohl den Zugriff auf die Felder der Formulare erlauben als auch Session-Tracking (das Verfolgen der Seiten, die ein Benutzer während einer Sitzung anwählt) oder Cookies. Java bietet die Infrastruktur für parallele Programme mit Threads, eine einfache Möglichkeit auf Datenbanken mittels JDBC zuzugreifen und verteilte Programme mit RMI/CORBA zu verwenden, die vorher mit dem Namensdienst JNDI gefunden wurden.

Galileo Computing

18.1.3 Das erste Servlet  downtop

Um eine Vorstellung zu bekommen, um was es in diesem Kapitel geht, werfen wir einen Blick auf ein einfaches Servlet:

import java.io.*;
import javax.servlet.*;

public class FirstServlet extends GenericServlet
{
public void service( ServletRequest request,
ServletResponse response )
throws ServletException, IOException
{
PrintWriter out = response.getWriter();
out.println( "'Chr! Schnarch! Razong! Chr! Chr! Rapüh!'"+
"(Disneys beste Comics, Band 5, S. 218)" );
out.close();
}
}

Dieses Programm erzeugt eine einfache Ausgabe auf der Web-Seite. Wir werden uns im Folgenden damit beschäftigen, wie diese Programme compiliert und vom Webserver ausgeführt werden können. Dann sehen wir uns einige Möglichkeiten an, wie ein Servlet mit der Umgebung sprechen kann und Daten austauscht.


Galileo Computing

18.2 Vom Client zum Server und wieder zurück  downtop

Bevor wir den Fokus auf servergenerierte Web-Seiten lenken, wollen wir den Weg der Anfrage einmal genauer verfolgen.


Galileo Computing

18.2.1 Der bittende Client  downtop

Richtet der Web-Browser oder eine Applikation (wie wir dies schon im Netzwerkkapitel gesehen haben) eine Anfrage an Webserver, so schickt er eine Kennung mit der Anfrageart (auch Methode oder Kommando genannt) zusammen mit der angeforderten URI (eine URL ohne Host, Port und Formularinhalte) und der Versionsnummer des zu nutzenden Protokolls zum Server. Die Anfrage (engl. request) kann zum Beispiel so aussehen:

GET /index.html HTTP/1.1

Dann wird vom Server, mit dem eine Verbindung besteht, das Dokument index.html angefordert. Wer die Reaktion des Servers auf diese Anfrage sehen möchte, der kann sich mittels telnet mit dem Server verbinden und das Kommando GET abschicken:

telnet java-tutor.com 80
GET /index.html HTTP/1.0

Nach zweimaligem Bestätigen finden wir in dem Ausgabefester von Telnet die Reaktion des Servers, die zu Beginn wie folgt aussieht:

HTTP/1.1 200 OK
Date: Tue, 08 Aug 2000 17:41:46 GMT
Server: Apache/1.3.4 (Unix) FrontPage/4.0.4.3 PHP/3.0.14
Last-Modified: Sun, 23 Jul 2000 18:06:38 GMT
ETag: "6b00a-2336-397b342e"
Accept-Ranges: bytes
Content-Length: 9014
Connection: close
Content-Type: text/html

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><html>
<head>

In erster Linie ist das HTTP für das Word Wide Web ausgelegt. Dennoch gibt es nicht nur das Kommando GET:

GET /index.html HTTP/1.1

Daneben definiert das Protokoll HTTP eine Reihe von weiteren Methoden. Folgende Tabelle gibt eine Auflistung der Typen seit der HTTP Version 1.1.

Tabelle 18.1   Wichtigste Methoden seit HTTP 1.1
Methode Beschreibung
GET Fordert die Web-Seite
HEAD Fordert lediglich den Header einer Web-Seite
PUT Anfrage, eine Web-Seite zu speichern
POST Anhang zu einer benannten Ressource (z. B. eine Web-Seite)
DELETE Fordert zum Löschen einer Web-Seite auf
TRACE Der Client empfängt seine gesendeten Informationen zurück

Die GET-Anfrage ist die häufigste Methode. Da sie oft mit POST verwechselt wird, wollen wir den Unterschied noch einmal klarstellen.

GET

Anfrage für eine normale Web-Seite, die etwa durch Verfolgen eines Links oder Eintrags in der Browserzeile für die URL gestartet wird. GET wird auch manchmal verwendet, wenn Daten über Formulare an den Server geschickt werden. In diesem Fall werden die Daten URL-kodiert an die URL angehängt. Die so übertragenen Daten können jedoch nicht beliebig lang sein und sind durch das System begrenzt; die Maximalgröße liegt bei etwa 2 Kilobyte. Das schränkt GET-Anfragen für Parameter ein. Herkömmliche CGI-Programme kommen an die Daten über eine Umgebungsvariable. Genau diese sind längenbeschränkt und lassen Daten nicht unendlich groß werden.

Beispiel Eine abgeschickte URL
http://server/servlet?parameter1=wert1&parameter2=wert2

POST

Auch POST überträgt Daten an den Server. Doch POST wird vom Browser nur für Formulare innerhalb von <FORM> mit der Anweisung METHOD="POST" verwendet. Die Daten werden nicht einfach an die URL angehängt, sondern in einem Dateistrom übermittelt. Zuerst wird dazu der HTTP-Server kontaktiert und mit Header-Informationen versorgt. Zum Beispiel steht die Länge der Daten im Request-Header Content-length. Danach werden die Daten gesendet, die CGI-Programmen in einem Dateistrom zur Verfügung stehen. Im Gegensatz zu GET gibt es daher bei POST keine Begrenzung des Informationsumfangs. Diese Methode wird auch meistens zur Übermittlung von Login-Daten verwendet, da bei der GET-Methode das Passwort »unkodiert« in der Browserzeile zu sehen wäre.

Die anderen Methoden werden nicht so häufig eingesetzt. Die HEAD-Methode fragt nach den Headern einer Web-Seite, ohne die Datei selbst zu übertragen. So lässt sich zum Beispiel das Datum der Änderung beziehen und dann feststellen, ob die Datei schon im Cache ist. Aus Sicherheitsgründen werden bei den meisten am Markt angebotenen Servern die Anfragemethoden PUT und DELETE nicht angeboten, da mit ihnen direkt auf den Server geschrieben werden kann.


Galileo Computing

18.2.2 Was ein Webserver erzeugt  downtop

Beantwortet ein Webserver eine Clientanfrage (so genanntes Response), so schickt er das Ergebnis in einer ganz besonderen Form zurück. Diese ist wieder ASCII-basiert und erinnert an die Anfrage:

HTTP/1.1 200 OK
Content-Type: text/plain
Mein erstes Textdokument.

Diese Ausgabe besteht aus einer Statuszeile, einem oder mehreren Antwort-Headern, einer Leerzeile und dem Dokument selbst. Die Statuszeile gliedert sich in drei Bereiche. Als Erstes zeigt die Information dem Browser die Version des HTTP-Protokolls an. Dann folgt eine Ganzzahl, die den Statuscode kodiert. Dieser ist in der Headerbeschreibung der RFC bei http://www.w3.org/Protocols beschrieben. Dann folgt eine kurze Textanzeige, die den Statuscode menschenlesbar macht. Einer der wohl am besten bekannten Statuscodes ist 404. Dieser zeigt an, dass die angeforderte Datei auf dem Server nicht existiert.

Das Servlet erzeugt automatisch die erste und zweite Zeile. Auf diese können wir Einfluss nehmen. Um ordentliche HTML-Seiten zu erstellen, wollen wir zuerst den Content-Type ändern. Danach lernen wir weitere Header kennen und unterschiedliche Statuscodes. Wir werden zudem sehen, dass unterschiedliche Header oft andere Statuscodes bedingen.


Galileo Computing

18.2.3 Wer oder was ist MIMEdowntop

Wenn eine Seite vom Server zum Browser geschickt wird, dann erkennt der Anfrager wie von Zauberhand den Typ der verschickten Datei und kann sie passend anzeigen. Woher weiß also der Browser, dass eine HTML-Datei anders darzustellen ist als eine Grafik oder eine Word-Datei mit der Endung .doc, für das ein externes Anzeigeprogramm gestartet werden muss? Der Hinweis auf den Datentyp kommt vom Server mit einer Mitteilung, die sich MIME nennt. MIME ist ursprünglich für E-Mails entwickelt worden, um binäre Dateien zu versenden, doch MIME hat für das Web mittlerweile eine größere Bedeutung bekommen. MIME geht in Bezug auf elektronische Nachrichten durch E-Mails noch etwas weiter, als es im Protokoll HTTP genutzt wird, denn bei der Web-Kommunikation werden lediglich einige zentrale Teile benutzt.

Möchte der Server dem Client den passenden MIME-Type senden, muss er erst einmal herausfinden, was die Datei für einen Typ besitzt. Dazu kann er zwei Taktiken verfolgen. Er kann in die Datei hineinschauen, die ersten Bytes untersuchen und seine Prognose abgeben oder einfach über die Dateiendung gehen. Die Lösung der Dateiendung wird sehr oft bei den Implementierungen von Webservern angewendet. Wenn wir Servlets programmieren, dann spielen wir selbst Server und müssen den MIME-Typ daher selbst angeben.

Wenn die Datei zum Client kommt, geht das Spielchen »wer-mit-wem« weiter, denn der Client muss nun aufgrund des MIME-Typs das passende Anzeigeprogramm finden. In der Regel kann der Browser die Daten direkt anzeigen, wenn es etwa eine HTML-Seite, Textseite oder Grafik ist. Sonst greift meistens ein ActiveX-Control (Microsoft) oder ein Plugin (Netscape) ein. Wenn er diesen MIME-Typ nicht mitgeschickt bekommt (was bei Servlets allerdings nicht passieren kann, da hier bei fehlender Programmierangabe immer ein Standardwert mitgeschickt wird) muss er genauso raten wie der Server. Doch bleiben wir bei dem Servlet-Fall, dass der Typ immer mitgeschickt wird. Wie sieht dieser dann aus?

Beispiele für MIME-Typen

Jeder MIME-Typ besteht aus zwei Hälften. Einer Kategorie und einem Datentyp. Wichtige Kategorien sind text, audio, image, video, application. Wir wollen einige Beispiele aufführen:

gp  text/html
Dies ist der Standardtyp für Web-Seite. Ein Servlet, welches HTML-Seiten generiert, sollte diesen Typ immer setzen. Somit kann der Browser sofort HTML interpretieren und die Seite aufbauen.
gp  text/plain
Hierbei handelt es sich um eine Textdatei, die nicht vom Browser interpretiert wird. Auch wenn der Text ein HTML-Text ist, baut der Server daraus keine HTML-Seite auf. Typischerweise verbindet der Server normale Textdateien mit dem Typ plain.
gp  image
Die Datentypen für Grafikformate. Beispiel: image/gif, image/jpeg für GIF und JPEG.
gp  audio
Sound-Dateien, zum Beispiel audio/basic oder audio/x-wav.
gp  video
Darstellbare Bewegbilder wie video/mgep oder video/quicktime.
gp  application
Dazu zählen in der Regel Binärdateien, die von anderen Programmen gelesen oder ausgeführt werden, etwa application/pdf für eine PDF-Datei, application/msword für ein Microsoft Word Dokument oder application/vnd.ms-excel für ein Excel Dokument.
gp  multipart
Anzeige, das der Datenstrom unterschiedliche Teile beinhaltet. Oft bei E-Mails, die einen Text und einen Datenanteil für den Anhang besitzen. Beispiel: multipart/mixed, multipart/news.

Mittlerweile gibt es unzählige MIME-Typen und es ist nicht sicher, dass diese immer vom Browser unterstützt werden. Im Zweifelsfall hilft nur ein Blick in die Zuordnungstabelle des Browser. Unter ftp://ftp.isi.edu/in-notes/iana/assignments/media-types lässt sich in Erfahrung bringen, wo welcher MIME-Typ genau definiert ist.


Galileo Computing

18.3 Servlets entwickeln und testen  downtop

Um Servlets entwickeln und testen zu können, benötigen wir einen Servlet-fähigen Webserver, beziehungsweise einen Servlet-Container. Mittlerweile gibt es eine große Anzahl von Herstellern, deren Server Servlets verwalten.


Galileo Computing

18.3.1 Servlet-Container  downtop

Servlets und Applets sind konzeptionell ähnlich. Daher kann ein Vergleich gewagt werden: Applets werden vom Web-Browser geladen und gestartet. Den Browser können wir dabei als Container für Applets sehen, der eine Infrastruktur wie die virtuelle Maschine oder Netzwerkeigenschaften bereitstellt. Innerhalb einer Java-Umgebung im Browser können durchaus mehrere Applets parallel eingebunden sein. Genauso verhält es sich mit Servlets. Auch hier benötigen wir einen Container, der alle Servlets verwaltet. Dieser kann entweder in einem Webserver eingebettet sein oder in einen Applikationsserver. Der Container leitet dann Anfragen an das Servlet weiter. Neben der Kommunikation nach außen, verwaltet der Container den Lebenszyklus eines Servlets, genau wie ein Browser darüber wacht, ob das Applet gerade sichtbar ist oder nicht. Bei Servlets sieht ein solcher Vorgang wie folgt aus: Ein Client richtet eine HTTP-Anfrage an den Webserver. Dieser bemerkt, dass es sich um ein Servlet handelt und gibt die Anfrage an den Container weiter. Dieser wiederum verwaltet alle Servlets und spricht genau das Servlet an, das der Benutzer nutzen wollte und übergibt Datenströme zur Ein- und Ausgabe. Das Servlet liest über den Eingabekanal optional Formularinhalte und generiert über den Ausgabestrom eine HTML-Seite, die der Container an den Client weiterreicht.


Galileo Computing

18.3.2 Webserver mit Servlet-Funktionalität  downtop

Sun verzeichnet auf ihrer Web-Seite http://java.sun.com/products/servlet/industry.html eine Liste von servletfähigen Servern. Ein Server ist genau dann servletfähig, wenn er die Java Servlet- und Java Server Pages (JSP)-Spezifikation erfüllt. Die Spezifikation in der Version 2.2 ist vom 17. Dez. 1999. Die Servlets-Komponente kann dabei integraler Bestandteil des Servers sein oder auch ein Zusatz, der nachträglich zu installieren ist. Hier eine Liste der wichtigsten Server:

Apache Tomcat
Abbildung
gp  Tomcat ist die offizielle Referenzimplementierung. Zum Testen von Servlets und JSP kann Tomcat sowohl als Stand-Alone-Applikation eingesetzt oder als Ergänzung zum Apache-Server eingebunden werden. Tomcat ist ebenso wie Apache frei. Er sitzt allerdings nicht unter der Webadresse tomcat.com, sondern unter http://jakarta.apache.org.
gp  Java Server Web Development Kit (JSWDK)
Das JSWDK ist von Sun und die erste Servlet-Implementierung. Sie ist ebenso wie Tomcat frei verfügbar und unter http://java.sun.com/products/servlet/download.html erreichbar. Sie wird von Sun nicht mehr weiterentwickelt, da nun die Entwicklung in Tomcat weiterläuft.
gp  Allaire JRun
JRun kann als Plugin in den Enterprise/FastTrack von Netscape, Microsoft IIS, Personal Web Server und weiteren weniger verbreiteten Servern eingesetzt werden. Die Software ist kommerziell, eine eingeschränkte Version mit maximal fünf offenen Verbindungen ist frei. Mehr Informationen gibt es unter http://www.allaire.com/products/jrun.
gp  New Atlanta’s ServletExec
ServletExec kann als Zusatz in die meisten Webserver unter Solaris, Windows, MacOS, HP-UX und Linux eingebunden werden. Die Nutzung ist frei, Administrationstools sind jedoch kostenpflichtig. Die Firma New Atlanta hat ebenfalls einen freien Servlet-Debugger im Angebot, der sich in die meisten IDEs einklinken lässt. http://newatlanta.com.

Der Webserver von Sun (Sun’s Java Web Server), eine Implementierung komplett in Java, war der erste Servlet-Server. Mittlerweile hat Sun die Entwicklung eingestellt. Die Entwicklung wird unter dem Netscape/I-Planet-Server fortgesetzt. Weitere Informationen, die Sun zu Servlets bietet, findet sich unter http:/ /java.sun.com/products/servlet/resources.html.

Geschichtliches: JSDK, JSWDK und Tomcat

Der Ursprung für dynamische Web-Seiten geht auf die Zeit zurück, als das JDK noch unter 1.0 ausgeliefert wurde. Sun nannte das Paket mit den Klassen und Programmen für die Servlet-Umgebung anfangs Java Servlet Development Kit (JSDK). Das Paket wurde bis zur Version 2.1 gepflegt. Da allerdings das alte JDK (Java Development Kit) in »Java 2 Software Development Kit« (J2SDK) umgenannt wurde, bestand eine Verwechselungsgefahr mit dem JSDK, weshalb Sun das JSDK in »Java Server Web Development Kit« (JSWDK) umbenannte. Zusätzlich nahmen die Entwickler noch Java Server Pages auf. Das JSWDK begann in der Nummerierung wieder bei 1.0.

Nach langer interner Entwicklung hat Sun den Quellcode für das JSWDK der Apache-Gruppe übergeben, sodass die Implementierung jetzt Open-Source ist. Die Apache-Gruppe entwickelte das JSWDK weiter und nannte es fortan »Tomcat«, das nun damit die einzig gültige Referenzimplementierung bildet. Weitere Freigaben werden offen sein und stehen unter dem Java Community Process. Damit endet die Produktlinie und das JSDK und JSWDK kommen ins Archiv. Der Name »Tomcat« wurde von Apache gewählt, da in frühen Entwicklungstagen »Tomcat« der interne Name für die Servlets bei Sun war. Erschwerend im Versions-Wirrwarr ist, dass die Spezifikation für Servlets und JSP unterschiedlichere Versionen als JSDK, JSWDK und jetzt Tomcat haben.


Galileo Computing

18.3.3 Tomcat  downtop

Jeder Server besitzt natürlich unterschiedliche Vorgehensweise bei der Installation und beim Einbinden von Servlets und Verzeichnissen. Wir halten uns hier an die Implementierung von Tomcat in der Version 4.

Download und Installation

Der Tomcat-Sever liegt unter http://jakarta.apache.org/tomcat als komprimiertes Archiv zum Laden bereit. Nach dem Auspacken liegen im Pfad jakarta-tomcat unterschiedliche Verzeichnisse, wobei wir unter bin/ die ausführbaren Skripte zum Verwalten des Servers finden. Diese liegen einmal als Batch-Datei für Windows vor und als Shell-Skript für Unix. Wir werden wie üblich in der Windows-Welt bleiben.

Damit der Server gestartet werden kann, muss die Umgebungsvariable JAVA_HOME (nicht CLASSPATH) so gesetzt sein, dass sie auf das JDK zeigt. Am einfachsten modifizieren wir dazu die Batch-Datei catalina.bat (oder tomcat.sh für Unix) und tragen dort in den ersten Zeilen das Verzeichnis für die JVM ein. Ein Ausschnitt:


rem
rem $Id: catalina.bat,v 1.14 2001/01/03 
19:55:45 craigmcc Exp $
rem ---------------------------------------------------------
 
set JAVA_HOME=d:\programme\jdk1.3
 
rem --- Save Environment Variables 
That May Change ----------
 
set _CP=%CP%
set _TOMCAT_HOME=%TOMCAT_HOME%

Die Datei catalina.bat ist als eine Art Oberprogramm zu sehen, welches andere Hilfsprogramme aufruft. Befinden wir uns im Installationsverzeichnis, können wir nach dem Anpassen des Pfades den Server starten:

F:\jakarta-tomcat-4.0-b3>bin\catalina 
run
Using CLASSPATH: .\bin\bootstrap.jar;e:\jdk1.3\lib\tools.jar
Starting service Tomcat-Standalone
Apache Tomcat/4.0-b3
Starting service Tomcat-Apache
Hinweis Kommt unter Windows 98/Me die Fehlermeldung »Kein Speicherplatz mehr im Umgebungsbereich«, sollte bei den Dateien im Kontexmenü Eigenschaften unter Speicher und Ursprünglicher Umgebungsspeicher zum Beispiel der Wert 4096 eingetragen werden.

Ohne Veränderung der Voreinstellungen installiert sich der Webserver auf dem lokalen Rechner auf Port 8080. Ist für die Ausgabe ein eigenes Fenster im Hintergrund gewünscht, so macht dies die Option start anstatt run möglich. Ohne Parameter zeigt Tomcat die restlichen Optionen an. Der Aufruf bin\startup aktiviert direkt den Server ohne Parameter.

Im Unterverzeichnis conf liegen diverse XML-Dateien zur Konfiguration. Hier lässt sich beispielsweise der Port anpassen oder das Verzeichnis, in dem die Servlets bzw. JSP-Seiten gesucht werden.

Ein Blick im Browser auf die lokale Adresse http://localhost:8080 zeigt uns die Startseite. Hier finden sich Beispiele und die APIs für das Paket.


Galileo Computing

18.3.4 Das erste Servlet compilieren und ausführen  downtop

Nachdem wir uns über die schönen Servlets gefreut haben, wollen wir natürlich auch eigene programmieren. Dazu müssen wir lediglich den CLASSPATH für den Compiler anpassen, sodass dieser auf die beiden Jar-Archive servlet.jar und jsp.jar zeigt. Die Archive liegen bei Tomcat im Ordner lib. Wenn die Enterprise Version von Java (J2EE) installiert ist, sind die beiden Archive schon im Pfad eingebunden und wir müssen servlet.jar nun nicht mehr extra einbinden.

Tipp Der Download für eine neue Version lohnt sich dennoch, denn die Servlet-API und -Implementierung ändert sich häufig.

Listing 18.1   FirstServlet.java
import java.io.*;
import javax.servlet.*;
 
public class FirstServlet extends 
GenericServlet
{
  public void service( ServletRequest 
request,
                       ServletResponse 
response )
      throws ServletException, 
IOException
  {
    PrintWriter out = response.getWriter();
    out.println ( "'Chr! Schnarch! 
Razong! Chr! Chr! Rapüh!'"+
                  " (Disneys beste 
Comics, Band 5, S. 218)");
  }

Alle Servlets implementieren die Schnittstelle javax.servlet.Servlet. Die Klasse GenericServlet implementiert diese Methoden und stellt uns eine einfache Klasse bereit, von der wir erben können. Verlangt der Client vom Webserver ein Servlet, so bildet der Servlet-Container ein Exemplar der Servlet-Klasse (in unserem Fall FirstServlet) und ruft nach der Initialisierung auf dem Objekt die Methode service() auf. Sie nimmt zwei Argumente an: ein ServletRequest und ein ServletResponse-Objekt.

Über request lassen sich alle Anfrageparameter erfragen. In der Unterklasse HTTPServletRequest kommen zusätzlich Formular-Werte oder ein HTTP-Anfrage-Typ dazu.

Mit response schicken wir die Daten, die zum Webclient zurückgehen, wie HTTP-Response (200, 404), Antwort-Header (Content-Type, Set-Cookie) zurück. Aber am Wichtigsten ist die Methode getWriter(), die uns eine Referenz auf ein Writer-Objekt liefert, damit wir die HTML-Elemente für die Seite abschicken. (Vorsicht! Ein System vor dem out ist diesmal nicht nötig – dieser Fehler schleicht sich schon mal ein.) Für Binärdaten können wir uns auch einen normalen OutputStream besorgen, damit wir zum Beispiel Bilder schicken können.

Hinweis So gut wie alle Servlets erweitern die Unterklasse HttpServlet von GenericServlet Ein HttpServlet enthält wichtige Arbeitsfunktionen für das Protokoll HTTP. Eine Erweiterung von GenericServlet ist eher unüblich, es sei denn, nicht-HTTP Protokolle wie FTP werden angeboten.

Abbildung

Wohin mit dem Servlet?

Mit diesen Informationen lässt sich das Servlet zwar compilieren, aber wir brauchen noch einen Ort, an dem es abgelegt wird. Damit wir nicht erst lange in den Konfigurations-dateien editieren müssen, legen wir unser FirstServlet.class in das Verzeichnis, in dem auch die anderen Testservlets von Tomcat liegen. Sie sind dort mit Quellcode abgelegt und laden direkt zum Anschauen ein. Das Verzeichnis ist

<jakarta-tomcat-Pfad>\webapps\examples\WEB-INF\classes

Um nun den Server zur Ausführung unseres FirstServlets zu bewegen, machen wir die Pfad-Angabe nicht in der URL, sondern wir schreiben

http://localhost:8080/examples/servlet/FirstServlet

Diese Übersetzung von URL-Anfrage in die entsprechende Klasse nennt sich Servlet-Mapping und wird vom Servlet-Container übernommen. Nach der Servlet-Spezifikation lautet die URL pfad/servlet/klassenname. Der Server in der Testinstallation nimmt nur die Angabe examples zur Unterscheidung hinzu. Wo nun die Klassendateien wirklich liegen, ist ganz unterschiedlich. Das definiert eine Konfigurationsdatei. Die Server können dieses Mapping unterschiedlich vornehmen.

Servlet neu starten

Wenn wir das Servlet ändern und neu compilieren, dann reicht es aus, den Inhalt im Browser zu aktualisieren (F5 im Internet Explorer (IE)). Ein vernünftiger Container sollte dann die Klasse automatisch neu laden. Was aber auf den ersten Blick so einfach aussieht, ist beim genaueren Hinsehen schon etwas komplizierter. Denn es ist nicht damit getan, nur Veränderungen zu Überwachen. Das Problem ist, dass der Sevlet-Container die geladen Klassen wieder freigeben und neu laden muss.

Wer das automatische Neuladen vermeiden möchte, der muss bei Tomcat in der Konfigurationdatei conf/server.xml den Eintrag reloadable="true" auf false setzen. Dadurch ergibt sich ein Geschwindigkeitsvorteil, da nicht vor jedem Starten geprüft werden muss, ob sich das Servlet geändert hat.


Galileo Computing

18.4 Der Lebenszyklus  downtop

Der Container für Servlets registriert eine Anfrage durch den Client und lädt das Servlet in den Speicher. Da Servlets normale Klassen sind, übernimmt ein spezieller Klassenlader diese Aufgabe. Die Abarbeitung findet anschließend in einem Thread statt, der die Methoden des Servlet-Objekts aufruft.

Wir wollen nun verfolgen, wie der Container die Arbeit an das Servlet delegiert. Über die Schnittstelle Servlet werden drei elementare Methoden für Initialisierung, Abarbeitung der Anfragen und Beendigung vorgeschrieben. Der Ablauf dieser Methoden nennt sich Lebenszyklus eines Servlets.

Die folgende Aufzählung zeigt alle Methoden, die die Schnittstelle Servlet für alle Java-Servlets vorschreibt.

interface javax.servlet.Servlet

gp  void init( ServletConfig config )
Wird zu Anfang eines Dienstes aufgerufen.
gp  void service( ServletRequest req, ServletResponse res )
Der Container leitetet die Anfrage an das Servlet an diese Stelle.
gp  void destroy()
Wird am Ende eines Servlets vom Container genau einmal aufgerufen.
gp  ServletConfig getServletConfig()
Liefert ein ServletConfig-Objekt, welches Initialisierungs- und Startparameter kapselt.
gp  String getServletInfo()
Liefert Informationen über das Servlet wie Autor, Version und Copyright.
Beispiel Ein Servlet implementiert getServletInfo(), um Informationen an den Servlet-Container zu geben.
public class FirstServlet extends 
GenericServlet
{
  public void service( ServletRequest 
request,
                       ServletResponse 
response )
  {
  }
 
  public String getServletInfo()
  {
    return "Ich bin der erste Erguss 
seiner Servlet-Fähigkeiten";
  }


Galileo Computing

18.4.1 Initialisierung in init()  downtop

Nachdem der Klassenlader genau ein Exemplar der Servlet-Klasse geladen hat, ruft der Container die init()-Methode des Servlets auf. Die Servletspezifikation macht allerdings keine Aussage darüber, wann init() ausgeführt wird. Dies kann der Fall sein, wenn das Servlet geladen wird, aber noch keine Anfrage ansteht, wenn die erste Anfrage kommt oder aufgrund einer globalen Initialisierung. In init() kann das Servlet sich initialisieren und beispielsweise Netzwerk- oder Datenbankverbindungen aufbauen.

Neben init() existiert init(ServletConfig) mit einem Konfigurationsobjekt, vom Typ der Schnittstelle ServletConfig. Die wichtigste Methode ist getServletContext(), die ein ServletContext-Objekt liefert, welches sich zum Beispiel zur Pfadumsetzung eignet. Wenn wir diese parametrisierte Methode überschreiben, dann dürfen wir es nicht versäumen, super() mit dem Parameter ServletConfig aufzurufen und es somit weiterzuleiten:

public void init( ServletConfig 
config ) throws ServletException
{
  super.init( config );
  ...

Die init()-Methode in der Oberklasse über super() anzusprechen, ist die einzige Möglichkeit, die uns Zugang zum Konfigurationsobjekt verschafft. Der Container rückt es nur an dieser Stelle heraus. Daher bleibt uns nichts anders übrig, als entweder init(ServletConfig) mit super() zu verwenden oder init() ohne Parameter zu verwenden, um das ServletConfig-Objekt zu bekommen.

Falls wir das ServletConfig-Objekt in init(ServletConfig) nicht benötigen, ist es einfacher, nur init() zu überschreiben. Das sieht dann in der Klasse GenericServlet (davon erbt HttpServlet) so aus:

public void init(ServletConfig 
config) throws ServletException {
  this.config = config;
  log("init");
  this.init();

Der Container ruft eigentlich nur init(ServletConfig) auf, und wie wir sehen, ruft die Methode dann selbstständig init() auf. Eine Referenz auf das Konfigurations-Objekt wird in GenericServlet gespeichert, auf die über getServletConfig() zugegriffen werden kann. Die Referenz-Variable ist private, so wie es sich gehört.

Thread-Sicherheit

Während externe CGI-Programm beispielsweise Datenbankverbindungen für jede Anfrage neu aufbauen müssen, muss ein Servlet dies nicht. (Dadurch ergibt sich natürlich ein satter Geschwindigkeitsvorteil.) Dies funktioniert aber nur deswegen, da für jede Servlet-Instanz in dessen Lebenszyklus nur einmal die init()-Methode aufgerufen wird. Alle Anfragen werden in einem eigenen Thread behandelt und dieser ruft die Methode service() auf. init() muss nicht threadsicher sein. service() kann von mehreren Threads parallel augerufen werden und threadsicher sein.

Hinweis Obwohl in der Regel der Servlet-Server mehrere Threads initialisiert, die auf die init()-Methode zugreifen, kann die init()-Methode mehrfach aufgerufen werden, um mehrere Exemplare einer Servlet-Klasse zu erzeugen. Dann ist es ungünstig, Ressourcen wie Datenbankverbindungen immer neu anzufordern, obwohl vielleicht schon eine andere init()-Methode dies gemacht hat. Da es mehrere Exemplare der Servlet-Klasse im Servlet-Container geben kann, sollten statische Initialisierungen nicht in der init()-Methode durchgeführt werden.

Beispiel Wenn ein Servlet erwacht, wollen wir eine fiktive Datenbankverbindung für alle Servlets dieser Klasse aufbauen.
public class DBServlet extends 
GenericServlet
{
  static DBConnection con;
 
  public void init()
  {
    if ( con == null )
      con = DB.getConnection();
  }

 public void service( ServletRequest 
request,
                      ServletResponse 
response )
       throws ServletException, 
IOException
  {
    ...
  }

Besser gelöst ist die Implementierung mit einer Fabrik-Methode seitens der Datenbank. Dann kümmert sie sich darum, dass es nur ein Exemplar gibt. Die Referenz wird dann ebenfalls von der Datenbank und nicht vom Servlet gespeichert.


Galileo Computing

18.4.2 Abfragen bei service()  downtop

In unserem ersten Beispiel haben wir uns der Klasse GenericServlet bedient, um eine service()-Methode zu überschreiben, die auf beliebige Anfragen des Clients antwortet. In der Regel wollen wir aber bei unterschiedlichen Anfragen unterschiedlich reagieren. Eine Anfrage ist zum Beispiel GET. Diese Form wird dann benutzt, wenn im Browser vom Benutzer eine URL eingetragen oder ein Verweis verfolgt wird. Neben GET-Anfragen gibt es noch POST-Anfragen, die für Formulare Verwendung finden.

Klasse HttpServlet

Damit wir auf GET-Anfragen anders reagieren können als auf POST-Anfagen, sind zwei Möglichkeiten denkbar:Entweder können wir die service()-Methode überschreiben, den Typ herausfinden und dann die Anfrage behandeln, oder wir verwenden eine andere Klasse, nämlich HttpServlet. Sie implementiert service() so, dass Anfragen an Methoden wie doGet(), doPost() und entsprechende Methoden weitergeleitet werden. Bei eigenen Anfragen wollen wir im Folgenden immer HttpServlet erweitern und service() nicht mehr direkt verwenden, sondern die entsprechenden doXXX()-Methoden.

Abbildung

Implementierung von service() in HttpServlet

Die service()-Methode der Klasse HttpServlet erfragt die verwendete Methode (GET oder POST) mit der Dienstmethode getMethod() vom aktuellen Request. Kurz skizziert hat sie folgendes Format:

protected void service( HttpServletRequest 
req,
                        HttpServletResponse 
resp)
  throws ServletException, IOException
{
  String method = req.getMethod 
();
 
  if (method.equals(METHOD_GET)) 
{
    ...
  } else if (method.equals (METHOD_HEAD)) 
{
    ...
  } else if (method.equals (METHOD_POST)) 
{
    doPost (req, resp);
  } else {
    ...
    resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED,
                   errMsg);