Apache Jakarta Tomcat 4

Architektur und Funktionsweise

Volker Lamp


... [ Seminar "Java und Werkzeuge für das Web" ] ... [ Inhaltsverzeichnis ] ... [ zurück ] ... [ weiter ] ...

Übersicht: Funktionsweise


Startup

Die Klassen Catalina und XmlMapper

Aus den im vorherigen Abschnitt vorgestellten Schnittstellen und deren Standardimplementierungen muß beim Startup ein konkreter Tomcat-Server errichtet und in den Arbeitsspeicher geladen werden. Welche Klassen verwendet werden, und vor allem, wie sie miteinander kombiniert werden sollen, ist ja im Rahmen der Konfiguration in der Datei "server.xml" festgelegt worden. Die darin enthaltenen Angaben müssen folglich interpretiert und die sich daraus ergebende Konstellation von Klassen geladen werden.

Dazu dient die Klasse org.apache.catalina.startup.Catalina, die von der Kommandozeile aus aufgerufen werden kann. Sie delegiert den wesentlichen Teil ihrer Arbeit an eine Instanz von XmlMapper aus dem Util-Paket von Tomcat. Aufgabe des XmlMappers ist es, die server.xml zu lesen und für jedes ihrer Elemente eine entsprechende Klasse zu laden. Welche Klasse für welches Element geladen werden soll, teilt die Klasse Catalina dem XmlMapper in ihrer Methode createStartMapper() mit. In Listing 5 ist auszugsweise zu sehen, wie den einzelnen Elementen der server.xml durch "Rules" Klassennamen zugeordnet werden.

protected XmlMapper createStartMapper() {

	// Initialize the mapper
	XmlMapper mapper = new XmlMapper();
	if (debug)
		mapper.setDebug(999);
		mapper.setValidating(false);

	// Configure the actions we will be using

	mapper.addRule("Server", mapper.objectCreate(
		"org.apache.catalina.core.StandardServer",
		"className"));
	mapper.addRule("Server", mapper.setProperties());
	mapper.addRule("Server", mapper.addChild(
		"setServer", "org.apache.catalina.Server"));

	mapper.addRule("Server/Listener", mapper.objectCreate(
		null, "className"));
	mapper.addRule("Server/Listener", mapper.setProperties());
	mapper.addRule("Server/Listener", mapper.addChild(
		"addLifecycleListener",
		"org.apache.catalina.LifecycleListener"));

	mapper.addRule("Server/Service", mapper.objectCreate(
		"org.apache.catalina.core.StandardService",
		"className"));
	mapper.addRule("Server/Service", mapper.setProperties());
	mapper.addRule("Server/Service", mapper.addChild(
		"addService", "org.apache.catalina.Service"));
...
}

Listing 5: Konfiguration des XmlMappers

Im nächsten Schritt wird die readXml()-Methode des nun fertig konfigurierten XmlMappers aufgerufen, die die server.xml einliest und Default-Objekte der entsprechenden Klassen erzeugt. Die Referenzen der einzelnen Objekte werden dann gegenseitig so eingetragen, daß die Zuordnungen zwischen Server, Service, Connector, Engine, Host und Context (vgl. Abschnitt "Basisarchitektur") wie in der server.xml angegeben gesetzt sind.

Initialisierung und Start

Das so schließlich entstandene Catalina-Gebilde muß nun noch initialisiert werden. Dazu ruft die Klasse Catalina die Methode initialize() der Server-Instanz auf, die wiederum die initialize()-Methode bei jedem ihrer Services aufruft. Jeder Service tut nochmals das gleiche für jeden untergeordneten Connector. Dabei öffnet jeder Connector seinen Server-Socket.

Abbildung 6: Die Klasse org.apache.catalina.startup.Catalina steuert den Startup-Vorgang
Abbildung 6: Die Klasse org.apache.catalina.startup.Catalina steuert den Startup-Vorgang

Nach der Initialisierung folgt nun der Start-Vorgang. Während Server und Service nach dem gleichen Prinzip wie bei initialize() nur die start()-Methode ihrer untergeordneten Komponenten aufrufen, startet sich der HttpConnector als Hintergrund-Thread und erzeugt die eingestellte Mindestanzahl von Prozessoren, die mittels recycle() auf einem Stack abgelegt werden (vgl. Listing 6).

public void start() throws LifecycleException {

	// Validate and update our current state
	if (started)
		throw new LifecycleException
			(sm.getString("httpConnector.alreadyStarted"));
	threadName = "HttpConnector[" + port + "]";
	lifecycle.fireLifecycleEvent(START_EVENT, null);
	started = true;

	// Start our background thread
	threadStart();

	// Create the specified minimum number of processors
	while (curProcessors < minProcessors) {
		if ((maxProcessors > 0) &&
			(curProcessors >= maxProcessors))
			break;
		HttpProcessor processor = newProcessor();
		recycle(processor);
	}

}

Listing 6: Die start()-Methode von HttpConnector

Genau wie HttpConnector implementiert auch HttpProcessor die Schnittstelle java.lang.Runnable, so daß ihre Instanzen als Thread gestartet werden können. Wegen dieser Struktur kann Catalina mehrere Requests gleichzeitig annehmen und verarbeiten.

Abbildung 7: Starten von Connector und Prozessoren
Abbildung 7: Starten von Connector und Prozessoren

Damit ist der Startvorgang abgeschlossen und Tomcat ist bereit für die Requestverarbeitung. Abbildung 7 stellt nochmals den gesamten Startvorgang von StandardService, HttpConnector und HttpProcessor zusammenhängend dar und gibt einen Einblick in die Vorgänge, die im Hintergrund-Thread von HttpConnector ausgeführt werden, und die im nächsten Abschnitt genauer erläutert werden.


Request-Verarbeitung

Parsen des InputStreams

Auch um die Request-Verarbeitung zu erläutern, soll der HttpConnector als Beispiel dienen. In einer Schleife, die durch ein Stop-Flag abgebrochen wird, wartet der Hintergrund-Thread von HttpConnector auf seinem Server-Socket auf eingehende Verbindungen. Sobald eine neue Verbindung eingeht, beschafft er sich durch Aufruf von createProcessor() einen Prozessor. Sollten alle bis zu diesem Zeitpunkt erzeugten Prozessoren bereits Requests bearbeiten, erzeugt und startet createProcessor() einen weiteren, sofern die eingestellte maximale Anzahl von Prozessoren für diesen Connector noch nicht erreicht ist. Sollten alle Prozessoren mit Requests beschäftigt sein, und ist die maximale Anzahl der Prozessoren erreicht, werden neu eintreffende Verbindungen in einer Warteschlange gehalten. Wenn auch deren Limit erreicht ist, werden keine weiteren Verbindungen akzeptiert.

Sobald der HttpConnector sich einen HttpProcessor beschafft hat, weist er diesem den Client-Socket zu, damit dieser die eingehende Verbindung mit seiner process()-Methode weiter bearbeiten kann. Jede Instanz von HttpProcessor enthält ein Request- und ein Response-Objekt, die bei seiner Erzeugung initialisiert werden. In process() wird dem Request-Objekt der InputStream und dem Response-Objekt der OutputStream des Sockets zugewiesen. Mit Hilfe von weiteren Methoden werden die einzelnen Informationen des Requests geparst und entsprechend in dem Request-Objekt gesetzt.

Nun wird das Request-Response-Objektpaar per invoke() zur weiteren Verarbeitung an den Container, im Falle von HttpConnector also normalerweise an die Engine weitergereicht.

Pipelines, Valves und Filter

Die Engine schickt das Request-Response-Objektpaar nun durch eine Pipeline mit mehreren Valves (engl. für "Ventil"). Jedes Valve kann das Request-Response-Objektpaar verändern und es dann ggf. an das nächste Valve in der Pipeline weiterreichen. Es kann aber auch entscheiden, das Paar nicht weiterzuleiten und statt dessen eine vollständigen Response zu erzeugen und diese zurückzuschicken. Das letzte Valve der Pipeline sorgt dafür, daß das Request-Response-Objektpaar an den untergeordneten Container - für die Engine ist das normalerweise ein Host - übergeben wird. Eventuell wiederholt sich dieser Vorgang nun für den Context und dann noch einmal für den Wrapper. Dieses "eigenverantwortliche" Arbeiten der Valves entspricht dem objektbasierten Verhaltensmuster "Verantowrtungskette". Abbildung 8 stellt den Ablauf bis zum Host dar.

Abbildung 8: Erzeugen und Weiterreichen des Request-Response-Objektpaares
Abbildung 8: Erzeugen und Weiterreichen des Request-Response-Objektpaares

Das Pipeline-Valve-Konzept ist eine besondere Eigenschaft von Tomcat, die nicht im Java Servlet 2.3 API spezifiziert ist, und auch bei anderen Web-Application-Containern (bisher) nicht zu finden ist. Allerdings spezifiziert das Java Servlet 2.3 API eine Kette von Filtern. Genau wie ein Valve kann ein Filter einen Request vor der Zustellung an das Servlet verarbeiten. Der Unterschied zwischen Valves und Filters liegt darin, daß ein Valve in der server.xml konfiguriert wird, während ein Filter im Deployment Descriptor (web.xml) beschrieben ist. Das bedeutet, daß Valves Context- und Host-übergreifendend eingesetzt werden können, während Filter immer nur bezüglich einer Web-Application arbeiten. Das bedeutet aber auch, daß neue oder veränderte Valves immer einen Neustart von Tomcat erfordern, während es bei Filtern genügt, die Web-Application neu zu starten.

Nachdem das Request-Response-Paar auch die Kette der Filter passiert hat, wird es der service()-Methode des Servlets übergeben. Wenn das Servlet seine Arbeit erledigt hat, kann das Request-Response-Paar auf dem Rückweg nochmals von Filtern verändert werden.

Der HttpProcessor sorgt schließlich dafür, daß die Response an den Client zurückgeschickt wird, indem die Methode flush() des OutputStreams aufgerufen wird. Damit ist Request-Verarbeitung abgeschlossen, und der HttpProcessor wird wieder auf dem Stack der gerade nicht verwendeten Prozessoren abgelegt.


Jasper

Tomcats JSP-Compiler Jasper läuft gemäß der Java Servlet 2.3 API seinerseits als Servlet. Allerdings existiert mit org.apache.jasper.JspC auch eine Shell, um Jasper eigenständig zu verwenden.

Der Übersetzungsvorgang ist in fünf Phasen aufgeteilt:

  1. Parser: Erzeugen der DOM-Repräsentation (Document Object Model) einer JSP- oder XML-Datei heraus sowie Erzeugen von Debugging-Informationen, um sie eventuell in der Quelle des Servlets unterzubringen.
  2. Transformer: Anwenden von Übersetzungszeit-Transformationen (gemäß JavaServer Pages 1.2 API) auf die DOM-Repräsentation.
  3. Generator: Erzeugen des Quellcodes für das Servlet aus der DOM-Repräsentation heraus.
  4. Translator: Compilieren des vom Generator erzeugten Quellcodes in ein Servlet.
  5. Deployer: Ergänzen des Deployment Descriptors und Hinzufügen der erzeugten class-Datei zum richtigen JAR-Archiv.

Listing 7 zeigt den in der Generator-Phase erzeugten Quellcode der "helloworld.jsp" aus dem Abschnitt "JavaServer Pages":

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;


public class helloworld$jsp extends HttpJspBase {

	// begin [file="/helloworld.jsp";from=(0,3);to=(0,44)]
		String msg = new String("Hello World"); 
	// end

	static {
	}
	public helloworld$jsp( ) {
	}

	private static boolean _jspx_inited = false;

	public final void _jspx_init()
		throws org.apache.jasper.runtime.JspException {
	}

	public void _jspService(HttpServletRequest request,
		HttpServletResponse  response)
		throws java.io.IOException, ServletException {

		JspFactory _jspxFactory = null;
		PageContext pageContext = null;
		HttpSession session = null;
		ServletContext application = null;
		ServletConfig config = null;
		JspWriter out = null;
		Object page = this;
		String  _value = null;
		try {

			if (_jspx_inited == false) {
				synchronized (this) {
					if (_jspx_inited == false) {
						_jspx_init();
						_jspx_inited = true;
					}
				}
			}
			_jspxFactory = JspFactory.getDefaultFactory();
			response.setContentType("text/html;ISO-8859-1");
			pageContext = _jspxFactory.getPageContext(this,
				request, response,"", true, 8192, true);

			application = pageContext.getServletContext();
			config = pageContext.getServletConfig();
			session = pageContext.getSession();
			out = pageContext.getOut();

			//HTML
			//begin 
			//[file="/helloworld.jsp";from=(0,46);to=(7,4)]
			out.write("\r\n\r\n<html>\r\n<head>\r\n" + \
					"<title>Hello World</title>\r\n" + \
					"</head>\r\n<body>\r\n<h1>");
			//end
			//begin 
			//[file="/helloworld.jsp";from=(7,6);to=(7,25)]
			out.println(msg);
			//end
			//HTML
			//begin
			//[file="/helloworld.jsp";from=(7,27);to=(10,0)]
			out.write("</h1>\r\n</body>\r\n</html>\r\n");
			// end
		} catch (Throwable t) {
			if (out != null && out.getBufferSize() != 0)
				out.clearBuffer();
			if (pageContext != null)
				pageContext.handlePageException(t);
		} finally {
			if (_jspxFactory != null)
				_jspxFactory.releasePageContext(
					pageContext);
		}
	}
}

Listing 7: helloworld$jsp.java


... [ Seminar "Java und Werkzeuge für das Web" ] ... [ Inhaltsverzeichnis ] ... [ zurück ] ... [ weiter ] ...