Start - Projekte - Wissen - Impressum -
Prozesse & Systeme - Optimierung von Entwicklungsprozessen

Servlets


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.

Servlet Lifecycle


  1. servlet class loading - Die Servletklasse wird geladen
  2. servlet instantiation - Eine Instanz wird erzeugt, ServletConfig wird gesetzt
  3. call the init method - init() wird gerufen
  4. call the service method - service() wird für jeden Clientrequest gerufen
  5. call destroy method - destroy() wird gerufen kurz bevor die Servletinstanz vom Server verworfen wird
Vom Container wird also per VM immer nur eine Instanz des Servlets erzeugt (Ausnahme: SingleThreadModel).

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[] 

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. Viele davon werden dabei automatisch (vom GenericServlet) 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
Setzen von Cookies im Response:
resp.addCookie(cookie); // ein Cookie wird dem Response hinzugefügt
// Ein Cookie wird erzeugt 
Cookie cookie = new Cookie(name, value);
cookie.setMaxAge(seconds);
cookie.getValue(name); // TODO stimmt das?  
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 Character 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 war 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 Request Parameter benutzt.
  2. Findet der Container keinen Content-Type, so muss er ISO-8859-1 als Encoding voraussetzen.
Da die HTTP Spec 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 Request-Parameter erfolgen, sonst ist es wirkungslos. Nachdem man also ein hypothetisches Hidden Field mit dem Encoding Angaben gelesen hat, kann man es nicht mehr ändern. Schade eigentlich.

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 © 2002-2012 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved