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.
- FindBugs, ein beliebtes Open Source-Tool zur statischen Code-Analyse, ist in der Version 1.1 veröffentlicht worden. Mehr zu Findbugs auf der Projekt-Seite:
http://findbugs.sourceforge.net/index.html - Von Hibernate liegt nun die Version 3.2 vor. Zusammen mit Hibernate Annotations 3.2 und Hibernate EntityManager 3.2 bietet Hibernate damit eine vollständige Unterstützung der Java Persistence API. Zuvor unterstützte Hibernate JPA nur mit Beta- oder Release Candidate-Versionen. Zu Hibernate 3.2 gibt es direkt auf der Hibernate-Homepage Infos:
http://hibernate.org/
Details zur Hibernate-Unterstützung für die Java Persistence API findet man hier:
http://www.hibernate.org/397.html - Das OpenSource-JavaScript-Toolkit dojo hat die Version 0.4 heraus gegeben. Die niedrige Versionsnummer täuscht, dojo ist eine ausgereifte Bibliothek. Beliebt ist dojo wegen seiner Unterstützung für Ajax und seine Integration in diverse Ajax-Frameworks. Downloads und weitere Infos zum Projekt gibt es auf der Projekt-Homepage:
http://dojotoolkit.org/ - Die Version 5.5 von Netbeans ist final. Netbeans 5.5 steht ganz im Zeichen der Enterprise-Entwicklung. Verbesserungen für die Desktop-Entwicklung sind für die Version 6.0 im nächsten Frühjahr geplant. Für JSP-Entwicklerinnen ist die Unterstützung der neuesten Servlet- und JSP-Spezifikationen und der EJB3- und Java Persistence-API durch Netbeans 5.5 besonders hilfreich. Unser Kapitel zu Entwicklungsumgebungen für die Entwicklung javabasierter Webanwendungen ist fast fertig gestellt. Dort werden wir Netbeans 5.5 ausführlich vorstellen.
Die völlig neu überarbeitete Projekt-Website ist unter der folgenden URL zu finden:
http://www.netbeans.org/ - Von JBoss Portal steht die Version 2.6 in einer Alpha-Version bereit. Verbesserungen wurden v.a. bei der Usability des Portals vorgenommen. Ebenso neu ist die Unterstützung des Portlet Repository Protocols.
Infos zu JBoss Portal:
http://labs.jboss.com/portal/jbossportal/
Infos zum Portlet Repository Protocol:
https://prp.dev.java.net/ - Beim Tomcat kann man inzwischen erste Blicke auf die Version 6.0 werfen. Diese steht als Alpha-Version zum Download zur Verfügung. Die für Entwicklerinnen wesentliche Neuerung ist die Unterstützung der neuesten Servlet- und JSP-Spezifikationen (2.5 und 2.1). Die Dokumentation ist derzeit noch nicht auf dem aktuellen Stand, doch erfahrungsgemäß dürfte sich dies bald ändern.
http://tomcat.apache.org/ - Die Bibliothek Commons Lang ist in der Version 2.2 veröffentlicht worden. Im letzten Newsletter haben wir die Bibliothek bereits gründlich vorgestellt.
http://jakarta.apache.org/commons/lang/ - PostgreSQL 8.2 steht als Beta 2 zur Verfügung und die finale Version 8.2 dürfte aller Voraussicht nach noch in diesem Jahr fertig werden. Zahlreiche Verbesserungen betreffen die Performance und das Server-Handling (bspw. verbessertes VACUUM). Zudem wurden Ergänzungen an der SQL-Implementierung vorgenommen. Für DB-Entwickler vielleicht am interessantesten ist die Möglichkeit, mit einer Update-Operation nun mehrere Spalten auf einmal verändern zu können.
Eine Übersicht der Änderungen gibt's hier:
http://developer.postgresql.org/pgdocs/postgres/release-8-2.html
Downloads und weitere Infos zur Datenbank hier:
http://www.postgresql.org/ - Jetty, ein OpenSource Servlet-Container, liegt nun in der Version 6.0.1 vor. Jetty wird u.a. von den Applikations-Servern Jonas und JBoss (als eine weitere Option neben Tomcat) genutzt. Jetty 6 unterstützt bereits die Servlet-Spezifikation 2.5 und die JSP-Spezifikation 2.1 und ist damit Tomcat zeitlich voraus. Jetty 6.0.1 enthält darüber hinausgehend kleinere Verbesserungen (bspw. im Internationalisierungsbereich).
http://jetty.mortbay.org/ - Eclipse, die neben Netbeans zweite große OpenSource Java-IDE, hat ebenfalls eine neue Version herausgegeben, die Version 3.2. Auch Eclipse werden wir in unserem Artikel zu Entwicklungsumgebungen ausführlich vorstellen. Übrigens feiert Eclipse dieses Jahr seinen fünften Geburtstag. Im deutschsprachigen Raum finden dazu zumindest in Stuttgart und in Zürich Parties statt.
Die Projekt-Homepage befindet sich hier:
http://www.eclipse.org/
Infos zu den Fünf-Jahres-Feierlichkeiten gibt es hier:
http://www.eclipse.org/org/press-release/20061025cb_eclipsebirthday5.php - Die wohl wichtigste Nachricht seit unserem letzten Newsletter ist die Ankündigung SUNs gewesen, Java vollständig unter eine von der Open Source Initiative anerkannte Lizenz zu stellen. Nach jüngsten Äußerungen von SUNs CEO Jonathan Schwartz ist die von SUN entwickelte CDDL die wahrscheinlichste Lizenz.
Zu Schwartz' Äußerungen:
http://www.silicon.de/enid/b2b/23404
Mehr zum Prozeß der Java-Freigabe auf der OpenSource-JDK-Seite von java.net:
http://community.java.net/jdk/opensource/
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:
- Im Kapitel "Servlet-Filter" haben wir die Möglichkeit beschrieben, vor und nach der eigentlichen Request-Bearbeitung noch zusätzliche Funktionalität, bspw. zum Logging, in Filterketten unterzubringen. Dabei haben wir dort auch das von Tomcat genutzte Konzept der Valves vorgestellt, das eine ähnliche Funktionalität bietet.
- Das neu aufgenommene Thema "Internationalisierung" haben wir aufgrund seiner Bedeutung und der zahlreichen Probleme, die dabei auftreten können, sehr ausführlich behandelt. Die verschiedenen Fallstricke, die einem die Arbeit mit internationalisierten Webauftritten erschweren können, sind ebenso beschrieben wie die Lösungen mithilfe der Java-Bordmittel, der Servlet-API und spezieller Container-Einstellungen (vorgestellt am Beispiel des Tomcat Servlet-Containers).
- Ebenso haben wir das Kapitel "Architektur webbasierter Anwendungen" fertig gestellt. Dieses beschreibt die architekturellen Grundlagen (Model 1, Model 2 und das Mode View Controller-Muster) und wie diese in Java-basierten Anwendungen umgesetzt werden. Wesentlicher Teil des Kapitels ist dabei die Analyse, wie Struts- und JavaServer Faces-Projekte das an die Web-Besonderheiten angepasste MVC-Muster umsetzen.
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.
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# 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 KlassenHttpClient 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:
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: 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:
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 einorg.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:
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 Klassenorg.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:
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:
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:
// 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.3Cookies 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. 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: | Exception | Verwendung |
| HttpException | Die 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. |
| ProtocolException | Dies 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. |
| RedirectException | Abgeleitete 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. |
| ConnectionPoolTimeoutException | Es 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). |
| ConnectTimeoutException | Im 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. |
| URIException | Hier 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 MethodegetResponseBodyAsString() 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).
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.
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)
