Servlet-Architektur

Wie auch bei Swing-Komponenten handelt es sich beim Servlet-API um ein Extension-API, also um eine Erweiterung der Java_Klassenbibliothek. Dieses API ist allerdings im Gegensatz zum Swing-API kein Standardteil von JDK in der Version 1.2.

Servlets werden bei Bedarf, also während der Laufzeit des Webservers, als Java-Bytecode nachgeladen. Das einzige Bindeglied zwischen einem Servlet und einem Webserver besteht hierbei in einem speziellen API, dem Servlet-API. Dieses API ist allgemein gehalten und macht daher weder Annahmen über die Art und Weise, wie ein Servlet geladen wird, noch über die Server_Umgebung oder das Netzwerkprotokoll, mit dessen Hilfe Client und Server kommunizieren. Servlets sind daher auch nicht auf das Hypertext Transfer Protocol (HTTP) beschränkt, auch wenn dieses in den meisten Fällen zum Einsatz kommt. Beim Servlet-API handelt es sich um lediglich zwei Pakete:

  • Package javax.servlet
    Dieses Paket enthält das von jedem Servlet zu implementierende
    Servlet-Interface und zusätzlich zwei Interfaces, die, um eine bessere Übersicht zu erhalten, den Datenaustausch zwischen Server und Servlet kapseln: Das Interface ServletRequest (für die Abfrage) und das Interface ServletResponse (für die Antwort). Da die Programmierung von Servlets nicht allein auf HTTP-Server beschränkt ist, sondern auch für andere Aufgabenbereiche einsetzbar ist, die auf dem Abfrage-Antwort-Paradigma bzw. auf der Client-Server-Architektur beruhen, ist das Package javax.servlet eher allgemein gehalten.
  • Package javax.servlet.http
    Dieses Package enthält im Wesentlichen HTTP-Spezialisierungen des oben genannten Packages
    javax.servlet. Die wichtigste Klasse dieses Packages ist die Klasse HttpServlet, die eine Erweiterung der Klasse GenericServlet darstellt, die das Interface Servlet implementiert.

Jedes Servlet implementiert das Interface javax.servlet.Servlet entweder direkt oder durch Erweiterung der abstrakten Klassen GenericServlet oder HTTPServlet, die selbst dieses Interface implementieren.

kap135 

Abb. 13.5: Implementierungshierarchie eines Servlets

In Tab. 13-2, Tab. 13-3 und in Tab. 13-4 sind die Klassen und Interfaces des Packages javax.servlet dargestellt.

Interface

Bedeutung

Servlet

Das Servlet-Interface deklariert die Methoden init(), service() und destroy(). Diese Methoden werden auch als Lebenszyklus eines Servlets bezeichnet.

ServletConfig

Dieses Interface stellt Servlets Konfigurationsinformationen beim ersten Start zur Verfügung.

ServletContext

Das Interface ServletContext liefert dem Servlet Informationen über den Kontext (Umgebung), in dem es ausgeführt wird.

ServletRequest

Dieses Interface definiert die Methoden, die zur Bearbeitung einer Abfrage notwendig sind.

ServletResponse

Dieses Interface definiert die Methoden, die zur Generierung einer Antwort gemäß der Definition der MIME-Datentypen an den Client notwendig sind. Das Interface ServletResponse erlaubt unter anderem den Zugriff auf einen Ausgabestrom, mit dessen Hilfe ein Servlet eine Antwort an einen Client schicken kann.

ServletThreadModel

Die Implementierung dieses Interface führt zu einem einzigen parallelen Ablauf eines Servlets. Folglich wird das Servlet seiteneffektfrei (Thread-Safe).

Tab. 13.2: Interfaces des Packages javax.servlet

Klasse

Bedeutung

GenericServlet

Diese Klasse stellt eine Implementierung des Interfaces Servlet dar. Wenn ein zu implementierendes Servlet eine andere Oberklasse benötigt, muss sie das Interface Servlet direkt implementieren. Wird diese Klasse erweitert, so muss die abstrakte Methode service überschrieben werden, die als Parameter sowohl ein Objekt der Klasse ServletRequest als auch ein Objekt der Klasse ServletResponse akzeptiert.

ServletInputStream

Diese Klasse erweitert die Klasse InputStream und stellt einen effizienten Weg zum Lesen von Datenströmen zur Verfügung.

ServletOutputStream

Diese Klasse erweitert die Klasse OutputStream und stellt einen effizienten Weg zum Schreiben von Datenströmen zur Verfügung.

Tab. 13.3: Klassen des Packages javax.servlet

Klasse

Bedeutung

ServletException

Diese Ausnahme wird generiert, um ein Servlet-Problem anzuzeigen.

UnavailableException

Diese Ausnahme wird erzeugt, wenn das Servlet nicht vorhanden ist. Das Fehlen eines Servlets kann permanent aber auch temporär sein.

Tab. 13.3: Exceptions des Packages javax.servlet

Servlet-Lebenszyklus

Das im Folgenden angegebene Interface Servlet deklariert die Methoden init() zur Initialisierung eines Servlets, service() zur Bedienung von Anfragen von Clients und destroy() zur Beendigung von Servlets.

code 

public interface Servlet {

    public abstract void init(ServletConfig config) throws ServletException;

    public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    public abstract void destroy();

...

}

kap136 

Abb. 13.6: Lebenszyklus eines Servlets

Diese Methoden, die zu verschiedenen Zeitpunkten der Lebensdauer eines Servlets aufgerufen werden, stellen den sog. Lebenszyklus eines Servlets dar. Abb. 13-6 veranschaulicht den Lebenszyklus eines Servlets.

Initialisierung von Servlets

Die init-Methode eines Servlets wird genau einmal aufgerufen, wenn der Byte-code des Servlets von einem Webserver geladen und eine Instanz des Servlets erzeugt wird. Dies geschieht spätestens dann, wenn die erste Abfrage eines Clients bezüglich des Servlets beim Server erfolgt. Innerhalb dieser Methode wird, wie auch bei einem Applet, die Initialisierung des Servlets vorgenommen. Eventuell benötigte Initialisierungsparameter können, analog zu der getParameter-Methode von Applets, mit der Methode getInitParameter eingelesen werden. Falls die Initialisierung aufgrund eines Fehlers nicht ordnungsgemäß durchgeführt werden kann, löst die init_Methode eine UnavailableException-Ausnahme aus. Ein Beispiel einer init-Methode ist im Folgenden angegeben.

code 

import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;
import java.util.*;
public class HeaderSnoop extends HttpServlet {  

    public void init(ServletConfig config) throws ServletException {

      super.init(config);

    }

Bearbeitungsphase des Servlets

Nachdem die Initialisierung abgeschlossen ist, kann ein Servlet beliebig viele Abfragen bearbeiten. Ein Servlet kann nun Anfragen von Clients entgegennehmen und sie in der service-Methode bearbeiten. Da die service_Methode bei jeder Anfrage in einem eigenen Prozess (Thread) ausgeführt wird, können mehrere service-Methoden zeitlich parallel ablaufen. Die Implementierung dieser Methode sollte möglichst frei von Seiteneffekten sein. Zugriffe auf eine gemeinsam benutze Ressource dürfen daher keine Seiteneffekte auf einen anderen, parallel ablaufenden Prozess verursachen. Aus diesem Grund ist es wichtig, Zugriffe auf gemeinsame Daten innerhalb der service_Methode zu synchronisieren. Falls die service-Methode aufgrund eines Fehlers nicht ordnungsgemäß durchgeführt werden kann, löst sie eine ServletException-Ausnahme oder eine IOException-Ausnahme aus. Ein Beispiel hierfür ist im Folgenden angegeben. In diesem Beispiel werden die Header-Informationen einer HTML-Seite geladen und in einer Tabelle dargestellt.

code 

    public void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

      res.setContentType("text/html");
      PrintWriter out = res.getWriter();
      out.println("<html><title>Header-Lesen</title>");
      out.println("Request Headers:");
      out.println("<br><br>");
      out.println("<table border=1 cellpadding=2 cellspacing=2>");
      out.println("<tr>");
      out.println("<td><Center><b>Header Name</b></Center></td>");
      out.println("<td><Center><b>Wert</b></Center></td>");
      out.println("</tr>");
      Enumeration enum = req.getHeaderNames();
      while(enum.hasMoreElements()) {

        String name = (String)enum.nextElement();
        String value = req.getHeader(name);
        out.println("<tr>");
        out.println("<td>" +name+ "</td>");
        if(value != null)

          out.println("<td>" +value+ "</td>" );

        else

          out.println("<td><i>Kein Wert</i></td>" );

        out.println("</tr>");

      }
      out.println("</table>");
      out.println("</html>");

    }

Im Beispiel wird eine HTML-Seite dynamisch generiert. Diese Seite liest mit Hilfe der Methode getHeaderNames() alle Header-Informationen aus. Diese Informationen werden einer Tabelle hinzugefügt, die dann als Ergebnis der Abfrage an den Client zurückgesendet wird.

Beenden eines Servlets

Wenn ein Servlet initialisiert ist, läuft es auf dem Server und wartet so lange auf Client-Anfragen, bis es explizit beendet wird. Um ein Servlet zu beenden, muss der Webserver die destroy-Methode aufrufen. Das Beenden eines Servlets erfolgt bspw. dann, wenn der Server heruntergefahren wird oder wenn der Server explizit die Anweisung bekommen hat, das Servlet zu beenden. Beim Aufruf der destroy_Methode werden die bei der Initialisierung reservierten Ressourcen freigegeben und an die Garbage Collection übergeben. Das Servlet hat die Möglichkeit, seinen Zustand für einen erneuten Aufruf von init() zu speichern. Erfolgt nach dem Aufruf der destroy-Methode eine weitere Anfrage an das Servlet, so wird es erneut geladen und initialisiert.

code 

    public void destroy() {

      super.destroy();

    }

kap137 

Abb. 13.7:  Ausgabe des HeaderSnoop-Servlets

 

Servlet-Beschreibung

Zusätzlich zu den Aufgaben, die ein Servlet ausführen kann, kann es mit Hilfe der Methode getServletInfo Informationen über sich selbst als Ergebnis übergeben. Wird diese Methode nicht überschrieben, so liefert sie ein null-Objekt zurück. Die Servlet-Beschreibung wird als String-Objekt erzeugt, das bspw. den Autor und die Versionsnummer eines Servlets zurückliefern kann.

code 

    public String getServletInfo() {

      return "Header-Informationen abfragen (c) openjava 1999.";

    }

}

Werden die Code-Teile, die zur Erläuterung des Lebenszyklus von Servlets benutzt wurden, zusammengefügt, übersetzt und anschließend das entstehende Servlet aufgerufen, so ergibt sich die in Abb. 13-7 dargestellte Ausgabe. Es ist zu beachten, dass die Header-Informationen, die hier gelesen werden, Browser-abhängig sind und daher von Browser zu Browser variieren können.

Konfiguration eines Servlets

Das Interface ServletConfig wird implementiert, um ein Servlet konfigurieren zu können, wenn es zum ersten Mal geladen wird. Die Klasse GenericServlet implementiert dieses Interface und dementsprechend die folgenden drei Methoden:

  • getInitParameter(String)
    liefert ein String-Objekt mit den Namen der Initialisierungsparameter zurück. Falls es keine Parameter gibt, wird ein null-Objekt zurückgeliefert.
  • getInitParameterNames()
    liefert den Namen der Initialisierungsparameter eines Servlets als Enumaration-Objekt zurück.
  • getServletContext()
    liefert den Kontext des Servlets zurück.

Schnittstellen zur Servlet-Engine

Das Interface ServletContext erlaubt es einem Servlet, Informationen über dessen Laufzeitumgebung abzufragen und somit Ereignisse zu protokollieren. Servlets erzeugen ein ServletContext-Objekt durch Aufruf der getServletContext-Methode des Interfaces ServletConfig. Ein ServletConfig-Objekt steht dem Servlet zur Verfügung, sobald es initialisiert wird bzw. sobald es mittels der getServletConfig-Methode erzeugt wurde. Das Interface ServletContext stellt einige Methoden zur Verfügung, bspw. die im Folgenden aufgezählten:

  • getMimeType(String)
    Diese Methode liefert den MIME-Typ der als Argument übergebenen Datei zurück.
  • getServerInfo()
    Diese Methode liefert den Namen und die Version des Netzwerkdienstes (bspw. HTTP/1.1) zurück.
  • log(Exception, String)
    Diese Methode protokolliert die als Argument übergebene Exception und die entsprechende Meldung in der Log-Datei des Servlets.
  • log(String)
    Diese Methode schreibt eine Meldung in die Log-Datei des Servlets.

Interface ServletRequest

Dieses Interface wird dazu verwendet, um Informationen bzw. eine Anfrage vom Client zum Servlet zu senden. Ein ServletRequest-Objekt wird hierbei als Argument der service-Methode eines Servlets zurückgegeben. Daten, die ein ServletRequest-Objekt enthalten kann, sind bspw. die Namen von Servlet-Parametern und deren Werte. Unterklassen dieses Interfaces können zusätzliche Daten beinhalten, wie bspw. Daten, die spezifisch für das HTTP-Protokoll sind. Das Interface HttpServletRequest, das die Klasse ServletRequest erweitert, beinhaltet daher auch HTTP-spezifische Informationen. Zu den wichtigsten Methoden dieses Interfaces gehören unter anderem:

  • getContentLength()
    Diese Methode liefert die Größe der angefragten Daten zurück.
  • getContentType()
    Diese Methode liefert den MIME-Datentyp zurück.
  • getInputStream()
    Diese Methode liefert ein InputStream-Objekt zurück, um binäre Daten der Anfrage lesen zu können.
  • getProtocol()
    Diese Methode liefert das verwendete Protokoll und dessen Version als String-Objekt zurück. Das Ergebnis hat die folgende Form: <protocol>/<major version>.<minor version>, bspw. HTTP/1.0.
  • getRemoteAddr()
    Diese Methode liefert die IP-Adresse des Senders der Anfrage zurück.
  • getRemoteHost()
    Diese Methode liefert den Namen des Senders der Anfrage zurück.
  • getScheme()
    Diese Methode liefert das Schema des verwendeten Protokolls der Anfrage zurück. Beispiele hierfür sind die Werte http, https oder ftp.
  • getServerName()
    Diese Methode liefert den Namen des Servers zurück.
  • getServerPort()
    Diese Methode liefert die Port-Nummer des Servers zurück.

Interface ServletResponse

Dieses Interface wird implementiert, um MIME-Datentypen aus der service-Methode des Servlets an die Clients zu senden. Wie auch bei ServletRequest wird hier ein ServletResponse-Objekt als Argument der service-Methode zurückgegeben.

Um einen MIME-Inhalt aus binären Eingabedaten zu erzeugen, wird die getOutputStream-Methode verwendet, die ein OutputStream-Objekt liefert. Um dagegen einen MIME-Inhalt aus Textdaten zu erzeugen, wird die getWriter-Methode verwendet, die ein PrintWriter-Objekt liefert. Die gleichzeitige Verwendung beider Datentypen ist hierbei ohne weiteres möglich. Das Interface ServletResponse beinhaltet folgende Methoden:

  • getCharacterEncoding()
    Diese Methode liefert die Zeichenkodierung (Character Set Encoding), die dem zurückgelieferten MIME-Typ der Daten entspricht, zurück bspw. gzip für mittels gzip komprimierte Dateien.
  • getOutputStream()
    Diese Methode liefert ein
    OutputStream-Objekt zurück, um binäre Daten zu schreiben.
  • getWriter()
    Diese Methode liefert ein
    PrintWriter-Objekt zurück, um Textdaten zu schreiben.
  • setContentLength(int)
    Diese Methode setzt die Größe der zu sendenden Antwort.
  • setContentType(String)
    Diese Methode setzt den MIME-Datentyp der zu sendenden Antwort.

Servlet-Ausnahmen

Die Servlet-Ausnahmebehandlung beinhaltet zwei Klassen: javax.servlet.ServletException, mittels derer allgemeine Aussagen über eine Exception eines Servlets getroffen werden können und javax.servlet.UnavailableException, die angezeigt wird, wenn ein Servlet nicht vorhanden ist. Das Fehlen eines Servlets kann permanent, aber auch lediglich temporär sein. Die Methoden dieser Klasse sind:

  • getUnavailableSeconds()
    Diese Methode liefert die Zeit zurück, in der das Servlet unerreichbar bleiben wird.
  • isPermanent()
    Diese Methode liefert den Wert true, falls das Servlet permanent unerreichbar ist, ansonsten den Wert false.

Synchronisation von Servlets

Es bestehen zwei Möglichkeiten, Servlets frei von Seiteneffekten ablaufen lassen zu können: Zum einen mit dem Interface SingleThreadModel (JSDK 2.0) und zum Anderen mit dem Schlüsselwort synchronized (JSDK 1.0).

code 

public class ThreadSafeServlet extends Servlet {

    protected synchronized void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {

      // Diese Methode ist synchronisiert
      // und kann daher niemals parallel ablaufen.

    }

}

In JSDK 2.0 wird das Interface javax.servlet.SingleThreadModel von dem jeweiligen Servlet implementiert, das frei von Seiteneffekten ablaufen soll. Die Implementierung dieser Klasse, die keine Methoden und keine Konstruktoren beinhaltet, garantiert, dass die service-Methode des Servlets niemals parallel aufgerufen werden kann:

code 

public class ThreadSafeServlet extends Servlet implements  SingleThreadModel {

    protected void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {

      // Diese Methode ist automatisch durch die Implementierung
      // von SingleThreadModel frei von Seiteneffekten
      // und kann daher niemals parallel ablaufen.

    }

}


SPNavRight SPNavRight SPNavRight
BuiltByNOF