Start - Publikationen - Wissen - TOGAF - Impressum -

Servlets und der Servlet Lifecycle


Servlets sind Javaklassen, die nach den Vorgaben der Servletspezifikation implementiert sind und in einem Servlet-Container leben. Sie nehmen Anfragen von HTTP-Clients entgegen und beantworten diese in Einklang mit der HTTP-Spezifikation. Eigene Funktionalität wird in erster Linie durch das Überschreiben der Servicemethoden der Servlet-Stubklassen erreicht.

Ein Servlet wird einmalig beim Initialisieren der Webanwendung geladen und verbleibt als Singleton im Webccontainer, bis die Webanwendung beendet wird. Im Einzelnen:

  1. servlet class loading - Die Servletklasse wird geladen
  2. servlet instantiation - Eine Instanz wird erzeugt, der Servletcontainer ist nicht involviert und ein ServletConfig steht nicht zur Verfügung
  3. call the init method - init(ServletConfig) wird gerufen, das übergebene ServletConfig repräsentiert die Umgebung des Servlets (ServletContext-Objekt) und die Initialisierungsparameter aus der web.xml. Die Initialisierungsparameter werden gelesen und verarbeitet, das ServletContext-Objekt steht auch später bei jedem Request zur Verfügung: das Ablegen des ServletConfig zwar demnach möglich, aber unnötig.
  4. call the service method - service(ServletRequest, ServletResponse) wird für jeden Clientrequest gerufen, der Servletcontainer stellt sicher, dass dem verarbeitenden Thread die Request und Response Instanzen exklusiv zur Verfügung stehen, Methodenaufrufe an diesen Objekten müssen also nicht synchronisiert werden.
  5. call destroy method - destroy() wird gerufen kurz bevor die Servletinstanz vom Server verworfen wird. Nach dem Verlassen der Methode steht kein ServletConfig mehr zur Verfügung.
  6. Die Servletinstanz ist nicht mehr zu gebrauchen und wird von der GC früher oder später verworfen.

Vom Container wird also per VM immer nur eine Instanz des Servlets erzeugt. Alle Aufrufe am Servlet oder an dessen Umgebung (ServletConfig) müssen demnach synchronisiert werden, da sie potenziell nebenläufig passieren können.

Deprecated: SingleThreadModel


Implementiert ein Servlet das Interface SingleThreadModel signalisiert das dem Container, dass dieses Servlet nicht nebenläufig genutzt werden darf. Leider (besser: erwartungsgemäß) gibt es keinen verlässlichen, allgemein anwendbaren und gleichzeitig skalierenden Mechanismus für diese Anforderung. Aus diesem Grund muss auf die Verwendung von SingleThreadModel verzichtet werden, diese Funktion ist 'deprecated' und wird in aktuellen Spezifikationen nicht weiterentwickelt. Jedes Servlet ist konsequent für nebenläufige Verwendung zu designen.

Request Parameter


Form-Parameter oder URL-Parameter vom Request lesen:

req.getParameter("name"); // liefert den Parameter diese Namens
req.getParameterNames();  // liefert alle Parameternamen

In einer Form können jedoch auch mehrere Parameter mit dem gleichen Namen exisiteren, diese werden dann als String-Array geliefert:

req.getParameterValues("name"); // ein String[] mit allen Parametern dieses Namens
req.getParameterMap(); // eine Map mit key-String und value-String[] 

Ruft man req.getParameter("name") an einem Parameter-Array, so wird (ohne Warnung) der erste Parameterwert zurückgegeben.

HTTP Header


Auslesen des Request Header:

req.getHeader("name");
req.getHeaderNames();

Auch hier kann zu einem Header mehr als ein Eintrag existieren, dann hilft

req.getHeaders("name"); // liefert eine Enumeration aller Headereinträge zu diesem Namen

Für ganze Zahlen und Datumsangaben gibt es typsichere Methoden zum Auslesen:

req.getIntHeader("name"); // liefert einen Header als int
req.getDateHeader("name"); // liefert einen Header als Date

Header können im Response auch gesetzt werden. Einige davon, zum Beispiel 'Date', werden dabei automatisch gesetzt:

// setzt eine Wert im Header, der vorhandene Wert wird überschrieben
resp.setHeader("name", "value");
resp.setDateHeader("name", 12345678); 
resp.setIntHeader("name", 666);
// fügt einen Wert dem Header hinzu
resp.addHeader("name", "value");
resp.addDateHeader("name", 12345678);
resp.addIntHeader("name", 666);

Cookies


Auslesen von Cookies aus dem Request

req.getCookies(); // liefert ein Cookie-Array mit allen Cookies des Requests
..
cookie.getValue(name);

Setzen von Cookies im Response:

Cookie cookie = new Cookie(name, value); // Ein Cookie wird erzeugt 
cookie.setMaxAge(seconds);
resp.addCookie(cookie); // ein Cookie wird dem Response hinzugefügt

Bei der Arbeit mit Cookies muss man sich allerdings an gewisse Limitierungen halten, hier ein paar konkrete Zahlen:

  • maximal 300 Cookies
  • maximal 20 Cookies per Server!
  • 4K Daten per Cookie, dabei sind Name und Wert relevant
  • Weder Name noch Wert des Cookie darf eines dieser Zeichen enthalten: [ ] ( ) = , " / ? @ : ;

Content Type


Der Content Type informiert den anfragenden Client über die Natur des Inhalts, der im Response geschickt wird. Setzen des Content Type am Response:

resp.setContentType("text/html;charset=UTF-8");
resp.setContentType("application/pdf");
// resp.setContentType("application/java");
// resp.setContentType("application/jar");
:

Response Streams


Responses können formatiert (characterbasiert) oder unformatiert (bytebasiert) erfolgen. Beispiel: Für einen Fileupload wird (1) zunächst ein PrintWriter besorgt. Als nächstes setzt man Contenttype (2), Cache-Optionen (3) und Filename (4). Dann wird ein char-Stream auf den PrintWriter geschrieben (5).

1 PrintWriter out = response.getWriter();
  // für den Browser unbekannter Datentyp (verhindert ungewolltes Öffnen)
2 response.setContentType("application/x-download");
  // Der Name, unter dem clientseitig abgespeichert werden soll
3 response.setHeader("Content-Disposition", "attachment; filename=myName.txt");
  // siehe Cache-Kapitel
4 response.setDateHeader("Expires", System.currentTimeMillis(  ) + someTimes);
  //
5 Reader in = new BufferedReader(new FileReader(myFile));
  char[] buf = new char[4096];
  int r;
  while ((r = in.read(buf)) != -1) {
    out.write(buf, 0, r);
  }

Für unformatierte Daten dann statt des PrintWriter ein OutputStream benutzen:

1 OutputStream out = response.getOutputStream();
  // für den Browser hoffentlich bekannter Datentyp
2 response.setContentType("application/zip");
  // Der Name, unter dem clientseitig abgespeichert werden soll
3 response.setHeader("Content-Disposition", "attachment; filename=myName.txt");
  // siehe Cache-Kapitel
4 response.setDateHeader("Expires", System.currentTimeMillis() + someMoreMillis);
  //
5 InputStream in = getServletContext().getResourceAsStream("/foobar.zip");
  int r = 0;
  byte[] chunk = new byte[1024];
  while ((r = in.read(chunk)) != -1) {
      out.write(chunk, 0, r);
  }

Request Encoding


Bei GET und DELETE Requests werden die URL und HTTP Header übertragen, dank HTTP Spezifikation müssen sich Client und Server dabei nicht über das verwendete Encoding dieser Daten austauschen, denn die HTTP Spec regelt das. Bei POST und PUT Requests jedoch gibt es neben der URL und dem Header noch Nutzdaten, deren Encoding der Client bestimmt. Dieses Encoding muss der Server beim Lesen dieser Daten (also beim Lesen der Request-Parameter) kennen, damit deren Inhalt korrekt interpretiert wird. Das Attribut enctype in der HTML Form regelt dies nicht. Es bestimmt lediglich wie die Inhalte der Form als POST Request verpackt werden und wird hier nicht diskutiert.

Intuitiv erwartet man also, dass ein HTTP Client verpflichtet ist, den Server über das Encoding der Request-Parameter zu informieren. In der HTTP Spezifikation ist nichts in dieser Richtung zu finden. Hier soll es deshalb darum gehen, wie Web Container mit diesem Problem umgehen und wie der Anwendungsentwickler den Ablauf steuern kann:

  1. Zunächst versucht der Container den Content-Type Header des Requests auszuwerten. Findet er darin entsprechende Angaben, werden diese für das Encoding beim Lesen der Requestparameter benutzt.
  2. Findet der Container keinen Content-Type, so muss er ISO-8859-1 als Encoding voraussetzen.

Da die HTTP Spezifikation keine Anforderung für das Senden des Request-Parameter Encoding stellt wundert es nicht, dass Webbrowser diese Information auch nicht liefern. Dabei wäre es doch so einfach, beispielsweise das Encoding der HTML Seite die die FORM beinhaltet als das Request-Parameter Encoding zu nutzen!

Der Anwendungsentwickler muss sich anders behelfen. Im ServletRequest kann er die Methode

servletRequest.getCharacterEncoding()
rufen. Wenn diese null zurückgibt, dann hat der Container kein Encoding ermitteln können - der Client hat keinen Content-Type Header gesetzt. In diesem Fall kann man das Encoding mit
servletRequest.setCharacterEncoding("UTF-8")
explizit setzen. Eingesetzt wird das gleiche Encoding, das auf der die FORM beinhaltende HTML Seite benutzt wurde. In den meisten Fällen kennt das der Entwickler, weil er es selbst bestimmt hat. Leider kann man das Encoding nicht in der FORM, zum Beispiel als Hidden Field, definieren: setCharacterEncoding muss VOR dem Lesen der Requestparameter erfolgen, sonst ist es wirkungslos. Nachdem man also ein hypothetisches Hidden Field mit dem Encoding Angaben gelesen hat, kann man das Encoding zum Lesen der Parameter nicht mehr ändern. Schade eigentlich.

Der RequestDispatcher - include und forward


Der RequestDispatcher repräsentiert das Handle auf eine Ressource des Webservers.

// Holt sich das Handle vom Request - hier sind relative und absolute Pfade gestattet 
RequestDispatcher handle = request.getRequestDispatcher("view.jsp");
RequestDispatcher handle = request.getRequestDispatcher("/xyz/view.jsp");
alternativ:
//
// Holt sich das Handle vom ServletContext - hier sind nur absolute Pfade gestattet 
RequestDispatcher handle = getServletContext().getRequestDispatcher("/xyz/view.jsp");
Am Handle kann nun ein include oder forward gerufen werden. Ein include leitet den Request und den gesamten Context an die angegebene Ressource weiter. Danach wird im aufrufenden Servlet die Verarbeitung fortgesetzt:

Im Servlet:

:
handle.include(request, response);
:
Mit Includes, sie können beliebig tief verschachtelt sein, lassen sich hervorragend modulare Webseiten realisieren. Hinweis: die JSP Include-Direktive leistet das nicht. Sie fügt zur JSP-Übersetzungszeit statische Textfragmente ein.

Ein forward übergibt komplett die Kontrolle an die gegebene Ressource. Dabei darf noch keinerlei Response an den Client gesendet worden sein (auch nicht Teile). Technisch gesehen durfte somit bis zu Forward also weder explizit noch implizit ein flush gerufen sein und response.isCommitted() liefert false:

: bis hier darf kein flush auf den Response gegeben sein, sonst Fehler, alles
: bisher Geschriebene wird ansonsten verworfen
if (!reponse.isCommitted()) {
  handle.forward(request, response);
}
: diese Codezeile wird nie erreicht 

HTTP Redirect


Includes und Forwards werden serverseitig abgewickelt, der Client bekommt vom Dispatching nichts mit. Das HTTP Protokoll bietet aber noch eine weitere Möglichkeit, auf eine andere Ressource zu verweisen. Der Client bekommt eine entsprechende Anweisung in Form eines HTTP Response-Status nebst einer Zieladresse geliefert. Diese Technik ist im Zusammenhang mit dem PRG-Pattern sogar unabdingbar. Servletseitig ist einfach ein entsprechender Status zu setzen und die Location des Verweises zu setzen:

resp.setHeader("Location", encodeRedirectURL(location));
resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
// Ende, keine Ausgaben auf den Response

Der HttpServletResponse bietet auch eine Hilfsmethode dafür an:

resp.sendRedirect(encodeRedirectURL(location));   
sendet der Server dem Browser die HTTP-Nachricht, die angegebene URL abzuarbeiten. Das darf nicht geschehen, nachdem auf den response irgendwas geschrieben wurde.

Die Location kann man relativ, Application-relativ (beginnend mit "/")oder absolut (beginnend mit "http..") angeben. Gesendet wird der HTTP Statuscode 302 (die Ressource ist vorübergehend an einer anderen Stelle zu finden). Der Client wird damit aufgefordert, auch alle zukünftigen Anfragen wieder an diese URL zu richten.

URI Bestandteile einer Webanwendung und relative URLs


Pfadangeben beim Umgang mit Servlets folgen einer strikten Grundregel:

requestURI = contextPath + servletPath + pathInfo
  • contextPath: der Server empfängt eingehende Requests und wird den führenden Bestandteil der URI auswerten, um den Request an die richtige Anwendung weiterzuleiten. Der contextPath hat immer die Form /applicationName und kann mit request.getContextPath() ermittelt werden.
  • servletPath: der Servlet-Container aktiviert Servletaufrufe anhand der Mapping-Regeln in der web.xml. Der bei dieser Regel verwendete Pfadbestandteil ist der servletPath. Er kann mit request.getServletPath() ermittelt werden. Der Servlet Path startet entweder mit einem '/', oder er ist für die URL Mapping Pattern: '/*' und '' ein leerer String.
  • pathInfo: der Rest der URI inklusive aller Parameter. Er kann mit request.getPathInfo() ermittelt werden und ist null, wenn es keine Pathinfo gibt.

Pfadangaben im HTML können auch relativ erfolgen und funktionieren, da HTTP Clients diese Angaben nach festen Regeln auswerten:

  • Server-relativ - der Pfad startet mit '/': die Angabe wird vom Client mit der Adresse des Hosts ergänzt, der die entsprechende Seite geliefert hat. So wird aus /icons/myicon.png der Aufrauf http://host:port/icons/myicon.png. Solche Server-relativen Pfadangaben eignen sich für die Adressierung statischer, von vielen Anwendungen genutzter Ressourcen.
  • Seiten-relativ - der Pfad staret nicht mit '/': die Angabe wird vom Client mit dem Pfad derjenigen Seite ergänzt, auf der diese URL gefunden wurde. So wird aus icons/myicon.png auf der Seite /home/pages/index.html die URL http://host:port/home/pages/icons/myicon.png.

Request/Response Wrapper


Oftmals ist es notwendig, den vom Servlet generierten Response zu manipulieren. Ein populäres Beispiel ist die Kompression des Outputs bevor er zum Client gesendet wird. Das HttpServletResponse Objekt repräsentiert in letzter Instanz eine Strom auf einen Socket, es ist im allgemeinen dann aus Sicht des Filters zu spät für die Manipulation dieses Stroms. Es muss also eine eigene Implementierung des HttpServletResponse Interfaces übergeben werden, welches keinen Socket dekoriert. Diese Implementierung wird erleichtert Dank der Wrapperklassen:

HttpServletRequestWrapper 
ServletRequestWrapper 
HttpServletResponseWrapper 
ServletResponseWrapper

In ihrem Konstruktor übernehmen sie das originale Request/Response Objekt und delegieren alle Methoden an dieses "gewrappte" Objekt weiter. Sie funktionieren also exakt wie das Original (Decorator-Pattern). In einer eigenen Implementierung erbt man von einer Wrapperklasse und überschreibt eine oder mehrere Methoden.

copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved