<jsptutorial />

Newsletter vom 02.11.2006


Hallo,

mit etwas Verspätung gibt es wieder mal einen Newsletter des JSP-Tutorials. Wie immer berichten wir zunächst über Neuerungen in der Java-Welt, wobei wir natürlich unser Haupt-Augenmerk auf Projekte und Entwicklungen richten, die für die Erstellung webbasierter Anwendungen Bedeutung haben. Und zudem haben wir einen weiteren Beitrag zum Jakarta-Subprojekt "Commons" der Apache Software Foundation.

Seit dem letzten Newsletter hat sich im Java-Umfeld sehr viel getan. Wir beschränken uns daher auf ein paar News, die sich mit OpenSource-Projekten beschäftigen, die bei Web-Projekten häufig genutzt werden.


Nicht nur im allgemeinen Java-Umfeld hat sich im letzten Monat einiges getan, auch wir haben seit dem Erscheinen des letzten Newsletters einige Artikel freigegegen:
Wir denken, mit diesen Kapiteln wesentliche Bausteine zum Tutorial hinzugefügt zu haben. Die Übersichtsseite der aktuell bearbeiteten und der weiteren geplanten Themen ist entsprechend aktualisiert worden.
Nachdem wir im ersten Newsletter kurz eine allgemeine Einführung in das Projekt Jakarta-Commons der Apache Software Foundation gegeben und in unserem zweiten Newsletter dann ausführlicher die Nutzung von Commons Lang beschrieben haben, stellen wir in diesem Newsletter eine kleine, aber ungemein nützliche, Bibliothek des Commons-Projektes vor: Commons HttpClient. Infos zum Download der Bibliothek befinden sich in dem einleitenden Beitrag des ersten Newsletters.

Zum SeitenanfangZum Seitenanfang

Commons HttpClient

Wozu überhaupt einen Http-Client?

Commons HttpClient ist eine Bibliothek, mit der Java-Anwendungen auf einfache Weise über das HTTP-Protokoll auf entfernte Ressourcen zugreifen können. Dies ist natürlich erst einmal ungewöhnlich. Wird doch normalerweise auf unsere Webanwendungen von entfernten Clients aus zugegriffen. Allerdings wird man bei der Erstellung von Web-Anwendungen häufig auf die Notwendigkeit stoßen, aus der Anwendung heraus externe Datenquellen über HTTP anzusprechen. Beispielsweise bieten Dienstleister Pseudo-Webservices an, bei denen über HTTP Daten transferiert werden, ohne indes das übliche WebService-Protokoll SOAP zu nutzen. Auch Seiten, wie bspw. Netvibes, die die Aggregation von RSS-Feeds ergänzt um kleinere Tools wie Online-Bookmarks oder ToDo-Listen anbieten, müssen Teile des Contents mittels HTTP von anderen Diesntleistern abholen. Für alle diese Dinge ist HttpClient das unumstrittene Java-Tool.

Was kann Commons HttpClient?

Commons HttpClient bietet eine Java-basierte Unterstützung des HTTP-Protokolls in der Version 1.0 und 1.1 an (Informationen zum HTTP-Protokoll finden Sie im Anhang I des JSP-Tutorials). Dabei werden von HttpClient alle Methoden wie bspw. GET, HEAD oder POST unterstützt. SSL-verschlüsselte Verbindungen oder Verbindungen über einen Proxy funktionieren umstandslos und für Cookies gibt es ein Standardverhalten und einen Plugin-Mechanismus für anwendungsspezifisches Verhalten.

Dependencies

Commons-HttpClient ist lediglich von zwei weiteren Bibliotheken des Jakarta Commons-Projektes abhängig: Commons-Codec und Commons-Logging.

Logging

Während der Entwicklung empfiehlt es sich, das Logging möglichst hoch zu setzen und den gesamten Datentransfer mitzuloggen (letzteres wird im Commons-Logging-Slang Wire-Log genannt). Wir geben hier für log4j eine beispielhafte Konfiguration an1:
log4j.rootLogger=DEBUG, stdout

# logs to standard out
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%c] %m%n

# the following line ensures that the wire-log is added to the log
log4j.logger.httpclient.wire=DEBUG
# alternative settings if we are interested only in 
# header or content logging:
#log4j.logger.httpclient.wire.header=DEBUG
#log4j.logger.httpclient.wire.content=DEBUG

# the next line ensures that the normal logging messages of
# commons httpclient are logged
log4j.logger.org.apache.commons.httpclient=INFO

Das Log sieht dann bspw. wie folgt aus:

DEBUG [httpclient.wire.header] >> "GET / HTTP/1.1[\r][\n]"
DEBUG [httpclient.wire.header] >> "User-Agent: Jakarta Commons-HttpClient/3.0[\r][\n]
DEBUG [httpclient.wire.header] >> "Host: java.net[\r][\n]"
DEBUG [httpclient.wire.header] >> "[\r][\n]"
DEBUG [httpclient.wire.header] << "HTTP/1.1 200 OK[\r][\n]"
DEBUG [httpclient.wire.header] << "Date: Sun, 23 Apr 2006 16:30:56 GMT[\r][\n]"
DEBUG [httpclient.wire.header] << "Server: Apache[\r][\n]"
DEBUG [httpclient.wire.header] << "Vary: Host[\r][\n]"
DEBUG [httpclient.wire.header] << "Content-Type: text/html; charset=ISO-8859-1[\r][\n]"
DEBUG [httpclient.wire.header] << "X-Cache: MISS from www.java.net[\r][\n]"
DEBUG [httpclient.wire.header] << "Transfer-Encoding: chunked[\r][\n]"

Hier erkennt man das Wire-Log an den Zeilen, deren Log-Informationen mit ">>" bzw. "<<" anfangen und die damit die jeweils ausgehende und eingehende Protokolldaten kennzeichnen.

Ein einfaches Beispiel

Die zentralen Klassen, die man zur Absetzung von HTTP-Anfragen benötigt, sind die Klassen HttpClient und HttpMethod des Packages org.apache.commons.httpclient.
Diese kann man einfach wie folgt nutzen, um eine simple GET-Anfrage an eine URL zu richten:
package org.jsptutorial.examples.servlets;

import java.io.*;
import javax.servlet.http.*;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;

public class HttpClientSimpleExampleServlet extends HttpServlet {
   
   public void doGet(HttpServletRequest request, HttpServletResponse response) 
         throws IOException {
      doPost(request, response);
   }

   public void doPost(HttpServletRequest request, HttpServletResponse response)
         throws IOException {
             
      HttpMethod method = null;
      try {
            
         // step 1: create an HttpClient-object
         HttpClient httpClient = new HttpClient();
         
         // step 2: create an appropriate method and point it to a URL
         method = new GetMethod("http://java.net");
         
         // step 3: execute the request
         int statusCode = httpClient.executeMethod(method);
         
         // step 4: handle the response
         if (statusCode != 200) {
            // request was not successfully answered
            // so just pass this information towards the client
            response.sendError(statusCode, "client responded: " +
                    method.getStatusText());
         } else {
            // success, so get the result:
            String responseText = method.getResponseBodyAsString();
            // now do with this response whatever is needed
            PrintWriter out = response.getWriter();
            out.println("<html><body><pre>");
            out.println(responseText.replaceAll("<", "<").replaceAll(">", ">"));
            out.println("</pre></body></html>");
         }
      }
      finally {
         // should always be executed as the final step: release the connection 
         if (method != null) {
            method.releaseConnection();
         }
      }
   }
}

Dieses Beispiel zeigt die Grundstruktur jeder Nutzung von HttpClient: Als erstes erzeugt man ein HttpClient-Objekt, wobei dieses, wie wir im folgenden noch sehen werden, je nach Bedarf konfiguriert werden kann. Dann erzeugt man ein HttpMethod-Objekt und gibt diesem die gewünschte URL mit. Man setzt die Anfrage durch Aufruf von executeMethod(HttpMethod) ab und fragt anschließend das HttpMethod-Objekt nach dem Ergebnis der Anfrage ab. Im obigen Code wurden bspw. der StatusCode mit getStatusCode() und im Fehlerfall die Statusmeldung mit getStatusText() bzw. im Erfolgsfall der Body der Antwort mit getResponseBodyAsString() ausgelesen.

Proxy-Server nutzen

Das oben gezeigte Beispiel geht für die Verbindung durchweg von Standard-Werten aus. So wird davon ausgegangen, dass die Connection nicht für mehrere Anfragen gleichzeitig benötigt, kein Proxy-Server genutzt und für Timeouts der Standardwert genutzt wird. Diese und andere Werte kann man allerdings spezifizieren.
Als erstes zeigen wir, wie man eine Anfrage über einen Proxy-Server stellen kann. Dazu benötigen wir ein org.apache.commons.httpclient.HostConfiguration-Objekt. Mit diesem legt man die Verbindungsregeln zu einem Host fest. Mittels der Methode setProxy(String, int) können wir einem HostConfiguration-Objekt den Proxy-Server und den Port, auf dem dieser lauscht, mitgeben. Anschließend geben wir dem HttpConnection-Objekt bekannt, dass es diese HostConfiguration nutzen soll. Im Folgenden zeigen wir den Ausschnitt aus dem Code, der die wesentlichen Ergänzungen des obigen Beispiels enthält:
// just the relevant lines of code:
HttpClient httpClient = new HttpClient();
HostConfiguration hostConfig = new HostConfiguration();
hostConfig.setProxy("proxy.yourhost.com", 8080);
httpClient.setHostConfiguration(hostConfig);

Eine Authentifizierung an einem Proxy-Server ist auch möglich. Dies geschieht aber nicht mit dem eben erzeugten HostConfiguration-Objekt, sondern mittels eines org.apache.commons.httpclient.Credentials-Objekts. Dazu wird ein UsernamePasswordCredentials-Objekt erzeugt, das der gewünschten Authentifizierung entspricht. Zudem muss noch ein Gültigkeitsbereich in Form eines org.apache.commons.httpclient.AuthScope-Objektes angegeben werden. Im folgenden Beispiel verwenden wir die Konstante Authscope.ANY, da der Proxy für alle Http-Zugriffe verwendet werden soll. Mit diesen Objekten können wir nun unsere Berechtigung für den Proxy-Zugriff am HttpState-Objekt setzen. Das HttpState-Objekt ist ein Objekt, das Verbindungsinformationen über mehrere Requests aufrecht erhält. Das macht bei einem Proxy durchaus Sinn. Dennoch erleichtert es das intuitive Arbeiten mit der HttpClient-Bibliothek nicht gerade, dass der Proxy-Server selber einem HostConfiguration-Objekt bekanntzugeben ist, die Authentifizierung hingegen am HttpState-Objekt zu konfigurieren ist.
Im Folgenden der Code, der die zusätzlichen Zeilen zur Proxy-Konfiguration enthält:
HttpState state = httpClient.getState();
Credentials credentials = new UsernamePasswordCredentials("user", "pwd");
state.setProxyCredentials(AuthScope.ANY, credentials);
httpClient.setState(state);

Connection-Timeouts definieren

Bisher haben wir das HttpClient-Objekt ohne Parameter erzeugt. Doch wir können dem Konstruktor auch ein org.apache.commons.httpclient.HttpClientParams-Objekt übergeben. Dieses HttpClientParams-Objekt eignet sich zum Beispiel zum Setzen eines anderen Charsets für den Body der HTTP-Methode, zur Angabe eines Timeouts oder um eine etwas laschere, aber bei den gängigen Browsern übliche Handhabung des HTTP-Protokolls zu erlauben, falls angesprochene Server dieses Browser-Fehlverhalten erwarten.
Der folgende Code zeigt, wie man einen Timeout für Connections setzen kann. Um über alle Betriebssysteme und über virtuelle Maschinen unterschiedlicher Hersteller (IBM, SUN, Kaffe.org, BEA) ein gleichartiges Verhalten zu erreichen, ist dies ohnehin ein Muss und daher der vermutlich am häufigsten gesetzte Parameter:
HttpClientParams params = new HttpClientParams();
params.setConnectionManagerTimeout(1000); //timeout in milliseconds
HttpClient httpClient = new HttpClient(params);

Zu beachten ist, dass die beiden Zeilen des obigen Code-Fragments anstelle der Erzeugung des HttpClient-Objektes mit einem leeren Konstruktor aus dem ersten Code-Beispiel zur HttpClient-Bibliothek verwendet werden müssen.

Wichtige Methoden der PostMethod- und GetMethod-Objekte

Wie schon im einfachen Beispiel erläutert, stößt man durch Ausführen der Http-Methoden-Objekte die eigentliche Anfrage an. Dabei bietet HttpClient für alle Methoden des HTTP-Protokolls entsprechende Klassen an. In aller Regel wird man aber wohl mit Objekten der Klassen org.apache.commons.httpclient.methods.GetMethod und org.apache.commons.httpclient.methods.PostMethod auskommen. Beide Klassen erben von org.apache.commons.httpclient.HttpMethodBase und bieten daher eine Fülle gemeinsamer Methoden. Diese betreffen vor allem das Setzen von Request-Headern oder Authentifizierungs-Informationen, sowie andererseits Methoden zum Auslesen des Status-Codes, der Response-Header und des Response-Body. Letzterer kann mittels getResponseBodyAsStream() als InputStream-Objekt, mittels getResponseBodyAsString() als String oder mittels getResponseBody() als Byte-Array ausgelesen werden.
Wir zeigen zunächst ein Beispiel, in dem wir einen zusätzlichen Request-Header verwenden und die Antwort als String auslesen:
HttpMethod method = null;
try {
   HttpClient httpClient = new HttpClient();
   method = new GetMethod("http://www.oreillynet.com/pub/feed/7");

   // wait no longer than 10 seconds (using a socket timeout)
   httpClient.getParams().setParameter(HttpClientParams.SO_TIMEOUT, 10000);

   // identify yourself
   method.addRequestHeader("User-Agent", "yourCompany.com FeedReader beta");

   // execute method
   httpClient.executeMethod(method);
   int ergebnis = method.getStatusCode();
   String feed = method.getResponseBodyAsString(); 
   System.out.println("feed: " + feed);
   // ...      
}
catch (IOException e) {
   // ...
}
finally {
   if (method != null) {
      method.releaseConnection();
   }
}

Beim PostMethod-Objekt hat man zusätzlich zu den gemeinsamen Methoden aus HttpMethodBase noch eine Reihe von Methoden zur Verfügung, mit denen man Parameter im Request-Body setzen, löschen oder ggf. - bspw. für's Logging - auslesen kann. Wir ändern obiges Beispiel an lediglich zwei Stellen so um, dass wir nun einen POST-Request verwenden und noch einen Parameter im Body mitgeben:
PostMethod method = null;
try {
   HttpClient httpClient = new HttpClient();
   method = new PostMethod("http://www.oreillynet.com/pub/feed/7");

   // wait no longer than 10 seconds (using a socket timeout)
   httpClient.getParams().setParameter(HttpClientParams.SO_TIMEOUT, 10000);

   // identify yourself
   method.addRequestHeader("User-Agent", "yourCompany.com FeedReader beta");

   // add a parameter (format=rss2) - without it we'd get an ATOM-feed
   method.setParameter("format", "rss2");
   
   // execute method
   httpClient.executeMethod(method);
   int ergebnis = method.getStatusCode();
   String feed = method.getResponseBodyAsString(); 
   System.out.println("feed: " + feed);
   // ...      
}
catch (IOException e) {
   // ...
}
finally {
   if (method != null) {
      method.releaseConnection();
   }
}

Bei dem GET-Beispiel erhält man als Response einen ATOM-Feed (ATOM ist ein W3C-Standard für Newsfeeds), im Falle der POST-Anfrage aufgrund des mitgegebenen Parameters einen RSS-Feed.2

Multithreading

Bei Webanwendungen muss großer Wert darauf gelegt werden, dass zahlreiche parallel eintreffende Anforderungen sowohl performant beantwortet werden können als auch unabhängig voneinander ausgeführt werden. Wenn aus ohnehin schon nebenläufigen Webanwendungen wiederum auf entfernte Ressourcen mit HttpClient zugegriffen wird, muss sichergestellt werden, dass diese Anfragen sich nicht gegenseitig behindern.
HttpClient nutzt hierzu intern einen org.apache.commons.httpclient.HttpConnectionManager. Dieser ist für die Verwaltung der Connections zuständig und kann entweder bei der Erzeugung des HttpClient-Objektes mitgegeben oder später gesetzt werden. Wird kein spezieller HttpConnectionManager gesetzt, wird von der Bibliothek ein Standard-HttpConnectionManager benutzt. Der verwendete HttpConnectionManager bestimmt, ob überhaupt mehrere Connections sicher parallel ablaufen können (bei Standalone-Anwendungen kann man sich ggf. einigen Overhead zur Wahrung der Thread-Sicherheit sparen) und wie viele Connections insgesamt und jeweils zu bestimmten Hosts gleichzeitig parallel abgearbeitet werden können.
Der org.apache.commons.httpclient.SimpleHttpConnectionManager ist ausschließlich für Requests geeignet, die nicht parallel ablaufen. Wer diesen Manager für parallele Anfragen nutzt, wird bspw. auf IOExceptions wegen geschlossener Streams oder IllegalStateExceptions wegen bereits geschlossener Verbindungen stoßen. Für Webanwendungen ist dieser Manager daher nicht geeignet.
Hingegen bietet der org.apache.commons.httpclient.MultiThreadedHttpConnectionManager Unterstützung für mehrere parallele Threads. Zur Konfiguration nutzt der MultiThreadedHttpConnectionManager ein Objekt vom Typ org.apache.commons.httpclient.params.HttpConnectionManagerParams. Diesem kann man mitgeben, wie viele parallele Connections zu beliebigen Hosts maximal möglich sein sollen. Zudem kann man für einzelne Hosts definieren, wie viele Connections jeweils maximal zu diesen gleichzeitig geöffnet werden sollen.
Im folgenden zeigen wir kurz die Konfiguration eines MultiThreadedHttpConnectionManagers:
MultiThreadedHttpConnectionManager connManager = new MultiThreadedHttpConnectionManager();

// define some HostConfigurations for which to use the ConnectionManager
HostConfiguration hostConfiguration = new HostConfiguration();
// a first host
hostConfiguration.setHost(new HttpHost("someHost.com"));
HttpConnectionManagerParams connManagerParams = new HttpConnectionManagerParams();
connManagerParams.setMaxConnectionsPerHost(hostConfiguration, 100);
// another host
hostConfiguration.setHost(new HttpHost("someOtherHost.org"));
connManagerParams.setMaxConnectionsPerHost(hostConfiguration, 100);
// any further hosts - if necessary ...

// set maximum number of all possible connections
connManagerParams.setMaxTotalConnections(250);

// set params and create HttpClient-object
connManager.setParams(connManagerParams);
httpClient = new HttpClient(connManager);

Cookies setzen und auslesen

Cookies wurden erstmals von Netscape eingeführt und sind eine Möglichkeit, auf dem Client kleinste Mengen von Daten vorrätig zu halten, die dieser bei jeder Anfrage an den Ursprungsserver wieder mitsendet. Genutzt werden Cookies v.a. zum Speichern der Session-ID oder allgemeiner zum Speichern von einem Identifier, mit denen der Server eine Nutzerin eindeutig wieder erkennen kann. Amazon bspw. nutzt langlebige Cookies, um die Nutzerin beim nächsten Besuch der Webseite mit ihrem Namen zu begrüßen und dieser direkt eine Artikelauswahl anzubieten, die aufgrund des vorherigen Kaufverhaltens von Amazons Personalisierungssystem als geeignete Kaufkandidaten eingeschätzt werden. Bei der hohen Bedeutung, die Cookies haben, wundert es nicht, dass diese auch von der HttpClient-Bibliothek umfassend unterstützt werden.
Wie so häufig gibt es verschiedene konkurrierende Cookie-Formate - und natürlich auch einen offiziellen Standard. HttpClient unterstützt alle bekannten Formate. Allerdings muss die Bibliothek wissen, welcher Standard genutzt werden soll. Dazu gibt es die Klasse org.apache.commons.httpclient.cookie.CookiePolicy, die Konstanten für die gebräuchlichen Cookie-Formate enthält. Fast immer ist die Konstante CookiePolicy.BROWSER_COMPATIBILITY die geeignete Wahl. Ggf. lohnt sich aber ein Blick in die API von Commons HttpClient.3.
Cookies liest man am einfachsten aus, indem man die Methode getCookies() des HttpState-Objektes aufruft. Der Rückgabewert dieser Methode ist ein Array von org.apache.commons.httpclient.Cookie-Objekten zurück. Diese einzelne Cookie-Objekte kann man dann wiederum auf Namen, Wert, Lebensdauer oder Pfad abfragen, wie im Beispiel gezeigt.
HttpMethod method = null;
try {
   HttpClient httpClient = new HttpClient();
   method = new GetMethod("http://www.jsptutorial.org/examples/cookies.jsp");
   HttpMethodParams params = method.getParams();
   System.out.println("policy - before: " + params.getCookiePolicy());
   params.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
   System.out.println("policy - afterwards: " + params.getCookiePolicy());
   int statusCode = httpClient.executeMethod(method);
   System.out.println("\nafterwards:\n");
   HttpState state = httpClient.getState();
   Cookie[] cookies = state.getCookies();
   for (Cookie cookie: cookies) { // JAVA 5 for-loop; change if your use an older JVM
      System.out.println("Cookie [" + cookie.getName() + " - " + cookie.getValue() + "]");
   }
   Header[] headers = method.getResponseHeaders("Set-Cookie");
   for (Header header: headers) { // print the headers to see cookie formatting
      System.out.println("Header [" + header.getName() + " - " + header.getValue());
   }
}
finally {
   if (method != null) {
      method.releaseConnection();
   }
}

Etwas unschön ist die Zurückgabe eines Arrays. Schöner wäre es, wenn man gezielt Cookies anhand des Namens abfragen könnte und ansonsten eine Enumeration oder einen Iterator über die Namen bekäme - wie es bei der Abfrage der Request-Parameter oder der Attribute der Lebenszyklus-Objekte üblich ist.

Exceptions von HttpClient

Bei Anfragen an entfernte Webserver kann bekanntlich alles Mögliche passieren. Der Server kann aus verschiedenen Gründen nicht erreichbar sein, die Response kann fehlerhaft im Sinne der HTTP-Spezifikation formatiert oder aufgrund von Problemen auf der Transportstrecke unvollständig sein.
Hier reagiert HttpClient mit zahlreichen Exceptions, die detaillierte Rückschlüsse auf das aufgetretene Problem erlauben.
Alle im folgenden beschriebenen Exceptions sind direkt oder über eine Superklasse indirekt von java.io.IOException abgeleitet:

ExceptionVerwendung
HttpExceptionDie Superklasse einiger im folgenden besprochenen Exceptions, die aber auch direkt von HttpClient geworfen werden kann. Wird direkt HttpException und nicht etwa eine geerbte Exception geworfen, so kann man davon ausgehen, dass der Fehler durch eine Änderung der Protokoll-Version oder anderere Anpassungen des Requests nicht behoben werden kann.
ProtocolExceptionDies ist die Exception, die anzeigt, dass die Antwort nicht der Spezifikation entspricht. Unter Umständen kann hier bereits eine Anfrage gemäß einer anderen Spezifikation (1.0 statt 1.1) helfen.
RedirectExceptionAbgeleitete Exception von ProtocolException. Hier konnte das RedirectHandling nicht korrekt durchgeführt werden. Dies kann darauf hindeuten, dass das automatische Handling von Redirects aufgrund fehlerhafter Antworten nicht funktioniert und die Anwendung um ein selbstgeschriebens RedirectHandling ergänzt werden muss.
ConnectionPoolTimeoutExceptionEs konnte in diesem Fall die angeforderte gepoolte Connection nicht rechtzeitig zur Verfügung gestellt werden. Wenn dieser Fehler vermehrt auftritt, empfiehlt es sich, die Konfigurations-Einstellungen für den MultiThreadedHttpConnectionManager hochzusetzen (s. dazu den Abschnitt Mutlithreading weiter oben).
ConnectTimeoutExceptionIm Unterschied zur vorhergehenden Exception erfolgte hier nicht die Bereitstellung der Connection zu spät, sondern die Anfrage dauert länger als im Timeout-Parameter spezifiziert.
URIExceptionHier ist die URI des Requests nicht korrekt geformt. Dieser Fehler dürfte zumeist auf eine falsche Eingabe seitens der Nutzerin (bspw. beim Angeben einer Feed-Adresse) beruhen und legt eine spezifische Fehlermeldung nahe, die im Nutzerinnen-Interface der Anwendung anzuzeigen ist.


Übrigens: Die Spezifikation ist bekanntlich das eine. Etwas ganz Anderes ist das, was die gängigen Browser (und ebenso die Webserver) letztlich umsetzen. An einigen Stellen kann man HttpClient so konfigurieren, dass dieser toleranter gegenüber Protokollabweichungen wird. Wir gehen hier nicht darauf ein. Wer allerdings Abweichungen zu kämpfen hat, der sollte sich die Liste der Konfigurationsparameter von HttpMethod und HttpClient ansehen.

Performance beachten

In unseren Beispielen haben wir den Inhalt der Antwort häufig direkt in Form eines Strings ausgelesen, indem wir die Methode getResponseBodyAsString() des jeweiligen Method-Objekts genutzt haben. Dies ist akzeptabel, wenn nur kurze Antworten erwartet werden und das Ergebnis nicht weitergegeben wird (bspw. zur Speicherung in einer DB oder zur Generierung einer Antwort an die Nutzerin unserer Webseite). Gibt man das Objekt an andere Handler weiter, oder hat man es mit großen Datenmengen zu tun, so sollte man lieber die Antwort als Stream auslesen. Dazu nutzt man die Methode getResponseBodyAsStream(), die einen InputStream zurückgibt.
Der folgende Code-Ausschnitt zeigt, wie man ein Streaming-Servlet schreiben kann, dass nichts anderes macht, als eine entfernte Ressource einfach durchzureichen (wie es bspw. bei Portalen oder auch den oben genannten AJAX-basierten Feed-Readern häufig vorkommt).
HttpMethod method = null;
try {
    HttpClient httpClient = new HttpClient();
    method = new GetMethod(url);
    int statusCode = httpClient.executeMethod(method);
    if (statusCode == 200) {
       InputStream in = method.getResponseBodyAsStream();
       int currentByte;
       while ((currentByte = in.read()) != -1) {
          out.write(currentByte);
       }
    }
    else {
       // some kind of trouble shooting
    }
    out.flush();
}
finally {
   // should always be executed as the final step: release the connection 
   if (method != null) {
      method.releaseConnection();
   }
}

Alternativ hätte man im obigen Beispiel auch den InputStream nutzen und bspw. an die Datenbankschicht weiterreichen können. Die Klasse java.sql.PreparedStatement hat zum Beispiel die Methode setBinaryStream(int index, InputStream stream, int length) und Hibernate erzeugt Blobs in der createBlob(InputStream stream)-Methode.
Ein weiterer performance-kritischer Aspekt ist das Instanziieren des HttpClient-Objektes. Wie im Abschnitt zum Multithreading schon dargelegt, ist dessen Erzeugung relativ teuer. Auf den Seiten des HttpClient-Projekts selbst wird daher empfohlen, möglichst nur ein einziges HttpClient-Objekt für alle Anfragen zu nutzen, oder doch zumindest pro Kommunikationseinheit (falls bspw. verschiedene Typen von Ressourcen abgerufen werden und in unterschiedlichen Komponenten behandelt werden). Daher ist die Nutzung obiger Multithreading-Empfehlungen fast schon Pflicht, will man eine hinreichende Performance erzielen.
Schlussendlich sollte man darauf achten, dass man die Requests unter Angabe eines Timeouts startet (s. dazu den Abschnitt weiter oben). In den Voreinstellungen setzt HttpClient keinerlei Timeout, und so kann es zu erheblichen Wartezeiten kommen, wenn die Verbindung gestört ist. Das Verhalten von HttpClient hängt hier im Übrigen vom Verhalten des zugrunde liegenden Betriebssystems ab. Dieses reagiert möglicherweise nach einiger Zeit mit einem Fehler - evtl. aber auch nicht. Nichts dürfte für die Nutzerin einer Site ärgerlicher sein, als geduldig zu warten, um am Ende doch eine Timeout-Exception zu bekommen. Und wie oft diese die Anwendung danach noch nutzt, kann man sich selber ausrechnen.

Zum SeitenanfangZum Seitenanfang

Anmerkungen:

1) Im Produktivbetrieb sollte natürlich auf keinen Fall der Datentransfer mitgeloggt werden, da dies die Performance bei größerem Transfer-Aufkommen schnell erheblich beeinträchtigen kann. (zurück)

2) POST wurde in dem Fall nur verwendet, um zu zeigen, wie ein Body aussieht, der Parameter enthält. Lt. HTTP-Spezifikation sollte POST eigentlich nur verwendet werden, wenn eine Anfrage vermutlich eine Status-Änderung nach sich zieht. Im Falle einer Anfrage nach einem RSS-Feed ist das sicher nicht der Fall und GET wäre hier angebracht gewesen. (zurück)

3) Standardmäßig orientiert sich HttpClient beim CookieHandling am Standard RFC 2109 (zurück)


www.jsptutorial.org
© 2005, 2006, 2007