17.13 Kleine Kekse: die Klasse Cookies
 
Jeder Auftrag an den Web-Server wird unabhängig von anderen Aufträgen verwaltet. Wenn wir beispielsweise eine Seite neu laden oder einen Verweis verfolgen, 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 nicht-existente Server-seitige Information. 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:
|
Ein Warenkorb für den Einkauf
In Online-Systemen wird ein Einkaufswagen gefüllt, und unterschiedliche Web-Seiten informieren Kaufwillige über die Produkte. Wenn der Server allerdings die Seitenanfrage dem Client nicht zuordnen kann, ist es nicht möglich, den Warenkorb zu füllen. |
|
Individualisierung
Bei privaten Seiten muss sich ein Benutzer anmelden, um die Angebote nutzen zu können. Es ist unpraktisch, wenn er sich bei jedem Seitenwechsel neu authentifizieren muss. Verlässt ein Kunde das System auf einer bestimmten Seite, kann Ersteres nach einem erneuten Anmelden den Benutzer wieder zurück auf diese Seite führen. Hat der Kunde per Suchmaschine eine Ware gesucht, die nicht verfügbar war, 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. |
|
Demoskopie
Das System eignet sich auch für die Benutzerüberwachung. Besucht ein Benutzer eine Seite mehrmals, 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 lang zu speichern. Betritt der Client die Seite des Anbieters, schickt er dem Server den Cookie als Kennung. Dieser kann anhand der Cookie-Kennung die Sitzung erkennen, sofern er die Information gesichert hat. Name und Technologie der Cookies wurden von Netscape geprägt, als die Firma noch den Browser-Markt revolutionierte. 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, was aber nicht beabsichtigt ist. 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.
17.13.1 Cookies erzeugen und setzen
 
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
|
|
public void addCookie( Cookie cookie )
Fügt der Anwort einen angefüllten Cookie-Header zu. |
17.13.2 Cookies vom Servlet einlesen
 
Bei jeder weiteren Kommunikation mit einem Server werden die mit der Server-URL assoziierten Cookie-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.17
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() %>"
<br>
<%
}
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, 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, sodass der Server diesen Wert auslesen kann. Cookies sind für andere Server nicht sichtbar, sodass sie keine direkte Sicherheitslücke darstellen.
17.13.3 Kleine Helfer für Cookies
 
Setzen wir mehrere Cookies im Programm, 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. Das folgende 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().
17.13.4 Cookie-Status ändern
 
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
|
|
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. |
|
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. |
|
void setMaxAge( int expiry ) |
|
int getMaxAge()
setMaxAge() setzt das Alter in Sekunden, in denen der Cookie existieren soll. Ist der Wert negativ, wird der Cookie nicht gespeichert, sondern nach der Sitzung, also beim Schließen des Browsers, entfernt. getMaxAge() liefert die Lebensdauer eines Cookies, wobei die oben getätigten Aussagen auch hier zutreffen. |
|
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. |
|
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. |
|
void setName( String name ) |
|
String getName()
Der Name des Cookies, der nach der Erzeugung nicht mehr geändert werden kann. |
|
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. |
|
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. |
17.13.5 Langlebige Cookies
 
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.18
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 );
}
}
1 Das ist richtig für das Amerikanische, die Engländer können damit meist nicht viel anfangen, dort heißt ein Keks Biscuit.
|