Galileo Computing < openbook >
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.
Galileo Computing - Professionelle Buecher. Auch fuer Einsteiger.


Java ist auch eine Insel von Christian Ullenboom
Buch: Java ist auch eine Insel (Galileo Computing)
gp Kapitel 17 Servlets und Java Server Pages
gp 17.1 Dynamische Web-Seiten und Servlets
gp 17.1.1 Was sind Servlets?
gp 17.1.2 Was sind Java Server Pages?
gp 17.1.3 Vorteil von JSP/Servlets gegenüber CGI-Programmen
gp 17.2 Vom Client zum Server und wieder zurück
gp 17.2.1 Der bittende Client
gp 17.2.2 Was erzeugt ein Web-Server für eine Antwort?
gp 17.2.3 Wer oder was ist MIME?
gp 17.3 Servlets und Java Server Pages entwickeln und testen
gp 17.3.1 Servlet-Container
gp 17.3.2 Web-Server mit Servlet-Funktionalität
gp 17.3.3 Tomcat
gp 17.4 Java Server Pages in Tomcat und Eclipse
gp 17.4.1 Erster Ablageort für eigene JSP-Seiten
gp 17.4.2 Das Sysdeo-Plugin
gp 17.5 Skript-Elemente
gp 17.5.1 Scriptlets
gp 17.5.2 Ausdrücke
gp 17.5.3 Deklarationen
gp 17.5.4 Kommentare und Quoting
gp 17.6 Web-Applikationen
gp 17.7 Implizite Objekte
gp 17.8 Entsprechende XML-Tags
gp 17.9 Was der Browser mit auf den Weg gibt - HttpServletRequest
gp 17.9.1 Verarbeiten der Header
gp 17.9.2 Hilfsfunktion im Umgang mit Headern
gp 17.9.3 Übersicht der Browser-Header
gp 17.10 Formulardaten
gp 17.11 Das HttpServletResponse-Objekt
gp 17.11.1 Automatisches Neuladen
gp 17.11.2 Seiten umlenken
gp 17.12 JSP-Direktiven
gp 17.12.1 page-Direktiven im Überblick
gp 17.12.2 include-Direktive
gp 17.13 Aktionen
gp 17.13.1 Aktion include
gp 17.13.2 Aktion forward
gp 17.13.3 Aktion plugin
gp 17.14 Beans
gp 17.14.1 Beans in JSP-Seiten anlegen, Attribute setzen und erfragen
gp 17.14.2 Der schnelle Zugriff auf Parameter
gp 17.15 Kleine Kekse: die Klasse Cookies
gp 17.15.1 Cookies erzeugen und setzen
gp 17.15.2 Cookies vom Servlet einlesen
gp 17.15.3 Kleine Helfer für Cookies
gp 17.15.4 Cookie-Status ändern
gp 17.15.5 Langlebige Cookies
gp 17.15.6 Ein Warenkorbsystem
gp 17.16 Sitzungsverfolgung (Session Tracking)
gp 17.16.1 Das mit einer Sitzung verbundene Objekt HttpSession
gp 17.16.2 Werte mit einer Sitzung assoziieren und auslesen
gp 17.16.3 URL-Rewriting
gp 17.16.4 Zusätzliche Informationen
gp 17.17 Tag-Libraries
gp 17.17.1 Standard Tag Library (JSTL)
gp 17.18 Servlets
gp 17.18.1 Servlets compilieren
gp 17.18.2 Wohin mit den Servlets: das classes-Verzeichnis
gp 17.18.3 Servlets mit dem Sysdeo-Plugin unter Eclipse
gp 17.18.4 Servlet-Mapping
gp 17.19 Der Lebenszyklus eines Servlets
gp 17.19.1 Initialisierung in init()
gp 17.19.2 Abfragen bei service()
gp 17.19.3 Mehrere Anfragen beim Servlet und die Thread-Sicherheit
gp 17.19.4 Das Ende eines Servlets
gp 17.20 Das HttpServletResponse-Objekt
gp 17.20.1 Wir generieren eine Web-Seite
gp 17.20.2 Binärdaten senden
gp 17.20.3 Komprimierte Daten mit Content-Encoding
gp 17.20.4 Noch mehr über Header, die der Server setzt
gp 17.21 Objekte und Dateien per POST verschicken
gp 17.21.1 Datei-Upload
gp 17.22 Servlets und Sessions
gp 17.23 Weiterleiten und Einbinden von Servlet-Inhalten
gp 17.24 Inter-Servlet-Kommunikation
gp 17.24.1 Daten zwischen Servlets teilen
gp 17.25 Internationalisierung
gp 17.25.1 Die Länderkennung des Anfragers auslesen
gp 17.25.2 Länderkennung für die Ausgabe setzen
gp 17.25.3 Westeuropäische Texte senden
gp 17.26 Tomcat: Spezielles
gp 17.26.1 Tomcat als Service unter Windows NT ausführen
gp 17.26.2 MIME-Types mit Tomcat verbinden
gp 17.26.3 Servlets beim Start laden
gp 17.27 Ein Servlet generiert WAP-Seiten für das Handy
gp 17.27.1 Ein WAP-Handy simulieren
gp 17.27.2 Übersicht der wichtigsten Tags
gp 17.27.3 Der Gateway
gp 17.27.4 WML-Seiten aufbauen
gp 17.27.5 Interessante Links zum Thema Servlets/JSP


Galileo Computing

17.15 Kleine Kekse: die Klasse Cookiesdowntop

Jeder Auftrag an den Web-Server wird unabhängig von anderen Aufträgen verwaltet. Wenn wir beispielweise eine Seite neu laden oder einen Verweis verfolgen, dann weiß der Server nicht (beziehungsweise interessiert sich nicht dafür), dass die Anfrage von uns kam. Was an diesem Verhalten deutlich wird, ist das Fehlen eines Zustands. Es fehlt also die Möglichkeit, dass ein Client vom Server identifiziert wird und einem aktuellen Zustand des bidirektionalen Kommunikationsverlaufes zugeordnet werden kann. Der Zustand bezieht sich hier auf eine Server-seitige Information, die nicht existiert. Aus diesem Grund wird HTTP auch als zustandsloses Protokoll bezeichnet. Dass dies aber nicht immer wünschenswert ist und sogar einen Nachteil darstellen kann, sehen wir an unterschiedlichen Anforderungen:

gp Ein Warenkorb für den Einkauf
In Online-Systemen wird ein Einkaufswagen gefüllt, unterschiedliche Web-Seiten informieren die Kaufwilligen über die Produkte. Wenn allerdings der Server die Seitenanfrage dem Client nicht zuordnen kann, so kann der Warenkorb nicht gefüllt werden.
gp Individualisierung
Bei privaten Seiten muss sich ein Benutzer anmelden, damit er die Angebote nutzen kann. Es ist unpraktisch, wenn er sich bei jedem Seitenwechsel neu authentifizieren muss. Verlässt ein Kunde das System auf einer bestimmten Seite, so kann das System nach einem erneuten Anmelden den Benutzer wieder zurück auf diese Seite führen. Wurde vom Kunden in der Suchmaschine eine Ware gesucht, die nicht verfügbar war, so kann sich dies nach einer Zeit geändert haben. Das System sollte dem Benutzer dann die Information geben, dass seine Ware nun verfügbar ist.
gp Demoskopie
Das System eignet sich auch für die Benutzerüberwachung. Besucht ein Benutzer eine Seite mehrmals, so kann der Betreiber dies erkennen und diese Information mit einem »Ist-Beliebt-Faktor« verbinden. Diese Information lässt sich natürlich kommerziell gut nutzen.

Es ist also ein System gesucht, das es dem Server erlaubt, den Client zu identifizieren. Dazu dienen kleine Informationseinheiten, die Cookies. Der Server kann den Client veranlassen, diese Information eine bestimmte Zeit zu speichern. Betritt der Client die Seite des Anbieters, so schickt er dem Server den Cookie als Kennung. Dieser kann anhand der Cookie-Kennung die Sitzung erkennen, sofern er die Information gesichert hat. Der Name und die Technologie der Cookies wurde von Netscape geprägt, als diese noch den Browser-Markt revolutionierten. Mittlerweile kümmert sich die HTTP Working Group der Internet Engineering Task Force (IETF) um die Weiterentwicklung.

Das Wort »Cookie« wird gerne mit Keksen1 assoziiert, dies ist aber nicht beabsichtigt. Informatiker kennen den Begriff und meinen damit einfach nur kleine Informationseinheiten. Mehr Informationen rund um Cookies hat David Whalen auf seiner Seite http://www.cookiecentral.com/ gesammelt.


Galileo Computing

17.15.1 Cookies erzeugen und setzendowntop

Cookies werden für den Benutzer durch die Klasse Cookie verwaltet. Sie bietet Methoden zur Bearbeitung der Informationen, die der Cookie speichert. Damit wir auf der Client-Seite Cookies setzen können, müssen wir zunächst ein Cookie-Objekt erzeugen. Dazu bietet die Klasse genau einen Konstruktor mit zwei Parametern an, die dem Cookie einen Namen und einen Wert geben. Der Name muss nach RFC 2109 geformt sein, das heißt vereinfacht aus Buchstaben und Ziffern. Nun muss der Cookie beim Client gesetzt werden. Dies führt die Methode addCookie() auf dem HttpServletResponse-Objekt durch:

Cookie cookie = new Cookie( "key", "value" );
response.addCookie( cookie );

Da es mehrere Einträge geben kann, darf die Methode auch mehrmals aufgerufen werden.


interface javax.servlet.http.HttpServletResponse
extends ServletResponse

gp public void addCookie( Cookie cookie )
Fügt der Anwort einen angefüllten Cookie-Header zu.

Galileo Computing

17.15.2 Cookies vom Servlet einlesendowntop

Bei jeder weiteren Kommunikation mit einem Server, werden die mit der Server-URL assoziierten Cookies-Daten automatisch mitgeschickt. Um sie zu erfragen, bemühen wir die Methode getCookies() des HttpServletRequest-Objekts. Der Rückgabewert der Methode ist ein Feld von Cookie-Objekten. Jeder Cookie bietet als Objektmethode getName() und getValue() an, um an die Schlüssel/Werte-Paare zu gelangen. Wenn die getCookies()-Methode null liefert, so war noch kein Cookie angelegt, und wir müssen darauf reagieren.

Listing 17.16 CookieDemo.jsp

<%@ page import="java.util.*" %>
<%
  String myCookieName = "visisted";
  Cookie cookies[] = request.getCookies();
  if ( cookies == null )
    out.println( "Kein Cookie gesetzt!" );
  else
  {
    boolean visited = false;
    
    for ( int i = 0; i < cookies.length; i++ )
    {
      String cookieName = cookies[i].getName();
      
      if ( cookieName.equals(myCookieName) )
          visited = true;
%>
Cookie "<%= cookieName %>" hat den Wert "<%= cookies[i].getValue() %>"

<%
    }
    if ( !visited )
    {
      Cookie visCookie = new Cookie( myCookieName, new java.util.Date().
        toString() );
      response.addCookie( visCookie );
      out.println( "Cookie gesetzt" );
    }
  }
%>    

Bekommt der Server eine Anforderung vom Client, so kennt der Client natürlich die Server-Adresse. Er schaut in seinem Cookie-Speicher nach, ob mit diesem Server ein Cookie assoziiert ist. Dann schickt er diesen automatisch in einem speziellen Cookie-Feld mit, so dass der Server diesen Wert auslesen kann. Cookies sind für andere Server nicht sichtbar, so dass sie keine direkte Sicherheitslücke darstellen.


Galileo Computing

17.15.3 Kleine Helfer für Cookiesdowntop

Setzen wir mehrere Cookies im Programm, so liefert getCookies() lediglich ein Feld von Cookie-Objekten. Wollen wir einen Keks mit einem bestimmen Namen ansprechen, so müssen wir durch das Feld wandern und nach dem Cookie suchen. Dafür bietet sich eine vorteilhafte Hilfsmethode an, die das Feld nach dem Cookie durchsucht. Wir wollen die Methode getCookieValue() nennen.

public static String
getCookieValue( Cookie[] cookies, String name, String default )
{
  for( int i = 0; i < cookies.length; i++ )
    return name.equals( cookies[i].getName()) ?
                        cookies[i].getValue() : default;
}

Diese Methode hat noch einen weiteren Vorteil: Sie übergibt dem Aufrufer einen Standardwert, falls der Cookie nicht gesetzt wurde.

Eine andere Lösung besteht darin, die Cookies in ein Map-Objekt abzulegen. Dann erfolgt die Anfrage immer aus dem Assoziativspeicher und nicht mehr aus dem Feld. Der Vorteil liegt darin, dass wir einmal die Map erstellen und dann den Cookie über die Methode get() erfragen. Folgendes Programmstück erzeugt aus dem HttpServletRequest selbstständig ein HashMap-Objekt mit den Schlüssel/Werte-Paaren:

public Map getCookies( HttpServletRequest request )
{
  Cookie cookies[] = request.getCookies();
  Map m = new HashMap();
  for ( int i = 0; i < cookies.length; i++ )
    m.put( cookies[i].getName(), cookies[i].getValue() );
  return m;
}

Jetzt ist es leicht, nach einem Cookie zu fragen:

Map m = getCookies( request );
if ( !m.isEmpty() )
  String s = (String) m.get( key );

Der Test, ob Cookies überhaupt gesetzt sind, ist einfach. Dies ist ein Aufruf von m.isEmpty().


Galileo Computing

17.15.4 Cookie-Status änderndowntop

Im Cookie werden neben einem Namen und dem damit verbundenen Wert noch weitere Informationen gespeichert. Die nachfolgende Aufzählung zeigt die Zugriffsmethoden für Cookies:


class javax.servlet.http.Cookie
implements java.lang.Cloneable

gp void setComment( String purpose )
String getComment()
Eine zusätzliche Beschreibung für den Cookie, der nicht von jedem Browser unterstützt wird (beispielsweise von Netscape). Bei der Abfragemethode bekommen wir null, falls dem Cookie kein Kommentar zugewiesen wurde.
gp setDomain( String pattern )
String getDomain()
Der Gültigkeitsbereich eines Cookies. Der Domänenname beginnt mit einem Punkt (etwa .kuchenfuerulli.com) und gilt dann für alle direkten Rechner dieser DNS-Adresse, also etwa www.kuchenfuerulli.com, aber nicht a.b.kuchenfuerulli.com.
gp void setMaxAge( int expiry )
int getMaxAge()
setMaxAge()
setzt das Alter in Sekunden, in denen der Cookie existieren soll. Ist der Wert negativ, so wird der Cookie nicht gespeichert. Er wird nach der Sitzung, also beim Schließen des Browsers, entfernt. getMaxAge() liefert die Lebensdauer eines Cookies, dabei treffen die oben getätigten Aussagen auch hier zu.
gp void setPath( String uri )
public String getPath()
Der Pfad gibt den Ort für den Client an, an dem der Cookie sichtbar ist. Die Sichtbarkeit gilt für das angegebene Verzeichnis und alle Unterverzeichnisse. Zusätzliche Informationen sind in der RFC 2109 abgelegt.
gp void setSecure( boolean flag )
public boolean getSecure()
Mit einer sicheren Verbindung lassen sich Cookies nur über ein sicheres Protokoll wie HTTPS oder SSL übertragen. setSecure(true) sendet den Cookie daher nur, wenn ein sicheres Protokoll verwendet wird. getSecure() liefert false, wenn der Browser den Cookie durch ein beliebiges Protokoll senden kann.
gp void setName( String name )
String getName()
Der Name des Cookies, der nach der Erzeugung nicht mehr geändert werden kann.
gp void setValue( String newValue )
String getValue()
Jeder Cookie speichert einen Wert, der mit setValue() neu gesetzt werden kann, sofern das Cookie existiert. Bei einem binären Wert müssen wir selbstständig eine ASCII-Kodierung finden, zum Beispiel eine BASE64-Kodierung. Mit Cookies der Version 0 sind die Zeichen ' ', '(', ')', '[', ']', '=', ',', '\'', '»', '\', '?', '@', ':', ';' nicht zugelassen. Nicht gesetzte Werte können unterschiedliche Rückgaben des Browsers provozieren.
gp int getVersion()
void setVersion( int v )
Die Version des Cookies, wie in RFC 2109 beschrieben. Version 0 hält sich an die Originalspezifikation von Netscape. Die Version 1 wird im RFC 2109 beschrieben; die Variante ist noch etwas experimentell.

Galileo Computing

17.15.5 Langlebige Cookiesdowntop

Für Cookies, die länger als eine Sitzung halten sollen, lässt sich mit setMaxAge() eine Zeit setzen, zu der sie gültig sein sollen. Eine praktische Klasse ist MaxAgeCookie, die im parametrisierten Konstruktor das Alter auf die Höchstzahl von einem Jahr setzt. Dies müssen aber nicht alle Browser so implementieren.

Listing 17.17 MaxAgeCookie.java

import javax.servlet.http.*;
public class MaxAgeCookie extends Cookie
{
  public MaxAgeCookie( String name, String value )
  {
    super( name, value );
    setMaxAge( 60*60*24*365 );
  }
}

Galileo Computing

17.15.6 Ein Warenkorbsystemtoptop

Der Schritt von unserem Programm zu einem Warenkorbsystem für den Einkauf ist nicht mehr weit. Das Servlet verwaltet für jede Sitzung in internen Datenstrukturen alle bisher bestellten Waren. Meldet sich ein Kunde im System an, wird eine Kennung erzeugt und diese zum Client geschickt. Die Kommunikation über die folgenden Seiten geht über diese Kennung, an der der Server den Client erkennen kann. Damit wir für die Kennung nicht eine beliebige Zahl selbst erzeugen müssen, können wir Zufallszahlen nutzen. Diese liefert etwa UID().toString(), die uns ein RMI-Server zur Verfügung stellt. Folgende Funktion erzeugt einen gültigen URL-String aus dieser Kennung:

public String createID()
{
  return URLEncoder.encode( new UID().toString() );
}

Nun müssen wir unsere Sitzungskennung nur noch mit den Waren verbinden, die im Korb sind. Sie können etwa in einer Liste verwaltet werden. Dazu können wir folgende Hilfsmethoden nutzen:

static public synchronized void saveItem( String id, String item )
{
  List list = (List) map.get( id );
  if ( list == null )
    map.put( id, list = new ArrayList() );
  list.add( item );
}
static Map map = new HashMap();

Wir verwalten in einem Assoziativspeicher alle IDs zusammen mit einer Liste der Waren. (Wir könnten dies auch anders realisieren, indem wir jedem Servlet eine eigene Liste geben, aber so teilen sich alle Servlets über static die Datenstruktur. Dies hat den Vorteil, dass beim Herunterfahren des Systems prinzipiell die Map serialisiert werden kann, was wir hier aber nicht implementiert haben.) Wird zum ersten Mal eine Ware eingelegt, so muss getestet werden, ob schon eine Liste angelegt wurde. Dann müssen wir nur noch die Ware hinzufügen. Das Abfragen gestaltet sich ebenso einfach:

static public synchronized String getItems( String id )
{
  List list = (List) map.get( id );
  return list != null ? list.toString() : "[]";
}

Aus der HashMap holen wir zuerst die Liste zur ID. Zu Testzwecken geben wir eine String-Repräsentation der Liste zurück, die bekanntermaßen alle Werte umfasst. Die Liste müsste eigentlich immer existieren, doch zur Sicherheit (defensiv programmieren und auf Wiederverwendung trimmen) testen wir auch diesen Fall.

Mit diesen Helfern gelingt es uns, Informationen unseres Warenkorbs zu sichern. Die Sitzungsdauer ist nur davon abhängig, wie lange der Cookie gespeichert ist. Über Cookies haben wir erreicht, dass wir für die Dauer einer Sitzung - oder auch länger, doch das ist für das Warensystem in der Regel nicht so wichtig - Informationen speichern können. Wir werden im folgenden Kapitel sehen, dass uns die Java-API eine schönere Lösung für dieses Problem liefert.






1 Das ist richtig für das Amerikanische, die Engländer können damit meist nicht viel anfangen, dort heißt ein Keks Biscuit.





Copyright (c) Galileo Press GmbH 2004
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de