17.20 Das HttpServletResponse-Objekt
Das HttpServletResponse-Objekt ist die Rückrichtung vom Servlet zum Client. Bei den JSPs haben wir das implizite Objekt response, das für die Rückrichtung gedacht ist, schon kennen gelernt
17.20.1 Wir generieren eine Web-Seite
Unser folgendes Beispiel führt uns noch einmal zu einer einfachen Web-Seite:
Listing 17.21 FirstServlet2.java
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class FirstServlet2 extends HttpServlet
{
public void doGet( HttpServletRequest request, HttpServletResponse response )
throws ServletException, IOException
{
PrintWriter out = response.getWriter();
out.println( "Oho. Nun mit doGet()!" );
}
}
Das Objekt HttpServletRequest beinhaltet die Informationen vom Browser. HttpServletResponse ist für die Ausgabe zuständig, die das Servlet liefert und zum Client schickt. Beide Klassen liegen zusammen mit HttpServlet im Paket javax.servlet.http. Das Paket javax.servlet.* wird nur wegen ServletException eingebunden. Im Paket java.io liegt die Klasse Writer. Die IOException müssen wir auffangen, da getWriter() eine Ausnahme auslösen kann.
Eine HTML-Seite statt einer Textseite erzeugen
In den ersten Beispielen haben wir kein spezielles Ausgabeformat gewählt, es war Plain-Text. Damit andere Typen gesendet werden können, beispielsweise HTML oder Binärdaten für Bilder, müssen wir zwei Schritte machen. Zuerst müssen wir dem Browser mitteilen, um welches Ausgabeformat es sich handelt, und dann müssen wir den Datenstrom speziell formatieren. Das Ausgabeformat wird dabei mit Hilfe von MIME-Type definiert. (MIME-Types sind in RFC 2045 definiert.) Dazu schickt das Servlet eine spezielle Kennung, die Content-Type heißt. Da wir diese zum Browser schicken, wenden wir uns an das HttpServletResponse-Objekt. Dies bietet setHeader() an, um beliebige Header zu setzen:
response.setHeader( "Content-Type", "text/html");
Die Methode erhält als Parameter zwei Zeichenketten: den Header und den dazugehörigen Wert. Da jedoch der Header Content-Type so häufig benötigt wird, bietet die Schnittstelle HttpServletResponse dafür die eigene Methode setContentType() an:
response.setContentType( "text/html" );
Um reine (Nur-)Textausgaben zu erzeugen, schreiben wir den Header text/ plain.
Nach dem Setzen folgt der zweite Schritt. Wir schreiben HTML-Text über den Ausgabestrom, nachdem der Header gesetzt ist:
PrintWriter out = response.getWriter();
out.println( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " +
"Transitional//EN\">\n" +
"<HTML>\n" +
"<HEAD><TITLE>Der Kopf</TITLE></HEAD>\n" +
"<BODY>\n" +
"<H1>Das ist HTML in Perfektion</H1>\n" +
"</BODY></HTML>");
Wie wir sehen, ist es ein sehr aufwändiges Unterfangen, HTML-Code zu erzeugen. Daher wird in diesem Tutorial meistens darauf verzichtet, da wir viele Eigenschaften der Server auch einfach durch Text zeigen können.
Eine wohlgeformte HTML-Datei besteht aus einer Anweisung zu Beginn, die den XML-Typ über das Tag DOCTYPE angibt. Anschließend folgen die HTML-Anweisungen. Unterschiedliche Hersteller bieten Bibliotheken an, mit denen die Generierung von Web-Seiten über spezielle Hilfsklassen einfacher wird. Der Aufbau der Seite wird dabei als baumartig organisiertes Objekt verwaltet und anschließend in HTML übersetzt. Der Content-Type sollte vor der ersten Ausgabe gesetzt sein.
17.20.2 Binärdaten senden
Um Binärdaten zu senden, muss lediglich der passende Typ im Content-Type eingestellt sein. Dabei kann es sich um Daten handeln, die der Browser interpretieren kann oder nicht.
Beispiel Content-Type bei Bildern
response.setContentType( "image/jpeg" );
response.setContentType( "image/gif" );
|
Wenn wir den Inhaltstyp - wie oben geschehen - setzen und einen binären Datenstrom mit den Bildinformationen schicken, ist schnell ein Bilder-Servlet geschrieben. Wieder gilt, dass der Dateiname natürlich als Parameter eingeführt werden kann.
Listing 17.22 BinarySender.java
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class BinarySender extends HttpServlet
{
public void doGet( HttpServletRequest request,
HttpServletResponse response )
throws ServletException, IOException
{
String filename = "C:/WINNT/Profiles/Administrator/"+
"Desktop/wirelessduke.jpg";
InputStream in = new BufferedInputStream(
new FileInputStream(filename) );
String s = URLConnection.guessContentTypeFromStream(in);
response.setContentType( s );
byte pic[]= new byte[in.available()];
in.read( pic );
OutputStream out = response.getOutputStream();
out.write( pic );
}
}
In den Eingabestrom schreiben wir dieses Mal keine Unicode-Zeichen, sondern binäre Daten. Daher ist ein Writer nicht nötig. Stattdessen holen wir uns direkt einen OutputStream mit getOutputStream(). Falls wir uns jedoch schon vorher einen Writer mit get Writer() geholt haben, bestraft uns Tomcat mit einer netten Seite und dem Fehler 500: Es dürfen nicht gleichzeitig Writer und Stream offen sein.
Beherzigen wir dies, können wir einfach mit write() Daten schreiben, und der Client versucht, diese zu interpretieren. Der Content-Type und die Binärdaten müssen zusammenpassen, andernfalls ist das Verhalten nicht immer vorhersehbar.
Falls uns der Datentyp selber nicht klar ist, lässt sich eine kaum bekannte statische Methode aus der Klasse URLConnection nutzen: guessContentTypeFromStream(). Sie versucht mit Hilfe der ersten Bytes auf den Inhalt zu schließen. Da die Methode einen Stream mit mark/reset benötigt (damit die Daten gelesen und dann wieder zurückgesetzt werden können), geben wir ihm einen markierungsfähigen Datenstrom in Form des Buffered InputStream. Findet er den Typ (wenn nicht, liefert die Methode null), wird dieser sofort in setContentType() eingesetzt. Somit können wir die Datei leicht ändern, und die Klasse sucht sich automatisch den richtigen Typ für verbreitete Binärdaten. Unter der Servlet-API gibt es auch die Methode ServletContext.getMimeType(), die das Erkennen des Typs vornimmt.
17.20.3 Komprimierte Daten mit Content-Encoding
Wir wollen unser Wissen über die Header etwas erweitern und noch andere Möglichkeiten des Protokolls HTTP kennen lernen. Ein spezieller Header Content-Encoding setzt den Typ der Kodierung für die Seite. Hier lässt sich eine Komprimierung wie gzip einsetzen. Da dies in Java problemlos über eine spezielle Klasse abgewickelt werden kann, wollen wir ein einfaches Beispiel konstruieren, welches eine komprimierte Textdatei verschickt. Wir setzen dabei Content-Encoding mit folgender Anweisung:
response.setHeader( "Content-Encoding ", "gzip" );
Bevor die Datei vom Browser dargestellt wird, entpackt dieser die Datei selbstständig. Das ist angenehm, denn auf diese Weise lässt sich der Datenverkehr für gut komprimierbare Daten, beispielweise Plain-Text oder HTML, deutlich reduzieren. Der Content-Type bleibt unangetastet.
Listing 17.23 GzipFile.java
import java.io.*;
import java.util.zip.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class GzipFile extends HttpServlet
{
public void doGet( HttpServletRequest request,
HttpServletResponse response )
throws ServletException, IOException
{
response.setContentType( "text/html" );
response.setHeader( "Content-Encoding", "gzip" );
String filename = "C:/WINNT/Profiles/Administrator/"+
"Desktop/tel.html";
InputStream in = new FileInputStream( filename );
byte txt[]= new byte[in.available()];
in.read( txt );
OutputStream out = new GZIPOutputStream(
response.getOutputStream() );
out.write( txt );
out.close();
}
}
Kann der Browser entpacken?
Da nicht jeder Browser automatisch die Daten entpackt, sollte ein schlaues Programm dies vorher erfragen. Glücklicherweise schickt der Browser bei jeder Anfrage ein spezielles Feld Accept-Encoding mit, das sich zum Beispiel nach der Kodierung durchsuchen lässt. Die Zeile
boolean c = request.getHeader("Accept-Encoding").indexof("gzip") >= 0;
erledigt dies und testet, ob Kompression erlaubt ist. Hier nehmen wir den Header der Anfrage auseinander. Dieses Vorgehen beleuchten wir später noch intensiver.
Was wir jedoch jetzt schon sagen können, ist Folgendes: Falls der Client die Kompression unterstützt, wie Netscape unter Unix und IE ab Version 4, so sollten wir dies für HMTL- oder Text-Dateien nutzen. Einem Browser, der die Daten nicht entpacken kann, liefern wir einfach eine ungepackte Version, die er darstellen kann. Andernfalls würde der Browser die komprimierte Version zum Abspeichern anbieten.
17.20.4 Noch mehr über Header, die der Server setzt
Bisher kennen wir von der Klasse HttpServletResponse die Methode setHeader() für beliebige Header.1
Beispiel Setze den Header pragma6, damit vom Browser keine Daten im Cache gehalten werden.
response.setHeader( "pragma", "no-cache" );
|
Mit dieser Aufforderung soll der Browser die Seite jedes Mal neu laden. Das ist bei dynamischen Seiten besonders wichtig, da sie bei jedem Aufruf neu generiert werden und sich Werte ändern können, wie es zum Beispiel bei Warenkorbsystemen der Fall ist. Da wir uns als Applikationsentwickler nicht immer mit dem Namen der Header herumärgern wollen, bietet die Bibliothek einige Spezialfunktionen an.
Beispiel Für den Header Content-Type gibt es die spezielle Methode setContentType().
response.setHeader( "Content-Type", "text/html");
response.setContentType( "text/html" );
|
Daneben gibt es setContentLength(), die den Header Content-Length setzt. Diese Länge muss nicht gesetzt werden und wird automatisch berechnet. Falsche Längen könnten zu Ausnahmesituationen führen. Der Gebrauch ist jedoch nützlich, wenn vorher die gesamte Web-Seite in einem StringBuffer sb gesammelt und in einem Rutsch übertragen wird. Dann können wir setContentLength(sb.length()) aufrufen.
Um einen Datums-Header zu setzen, existiert setDateHeader(String, long). Der Parameter ist eine beliebige Zeichenkette, die mit einem Datumswert verbunden wird. Das long gibt die Millisekunden seit dem 1.1.1970 an. Die Ausgabe, die erzeugt wird, schreibt einen GMT-String.
Eine weitere Hilfsfunktion ist setIntHeader(), die Zahlenwerte mit Schlüsseln in den Header schreibt. Hier übernimmt die Methode die Konvertierung von String in eine Ganzzahl.
Neben diesen setXXX()-Methoden, die möglicherweise gesetzte Header überschreiben, lässt sich mit containsHeader(String) abfragen, ob Wertepaare schon gesetzt sind. Neben den setXXX()-Methoden gibt es auch entsprechende addXXX()-Methoden, die die Werte nicht überschreiben, sondern hinzufügen. Für Cookies existiert zusätzlich eine Methode mit dem Namen addCookie(), die einen Cookie im Header setzt.
1 Den Header pragma gibt es schon seit HTTP 1.0. In HTTP 1.1 wurde die Cache-Fähigkeit verfeinert, etwa mit cache-control, die den Header pragma ersetzt. Dennoch sollten beide Header gesetzt werden.
|