Willkommen zum zweiten Teil: Heute werfen wir einen näheren Blick auf Tomcat und wie PHP unter Tomcat funktioniert.
Der offensichtlichste Unterschiede zwischen dem Apache und Tomcat ist: Apache ist in C geschrieben, Tomcat dagegen in Java. Das hat grundsätzlich erst einmal nichts zu sagen. Einige mögen zwar einwerfen, dass Tomcat deswegen langsamer ist, allerdings ist Java nicht unmittelbar der Grund dafür. Der grundlegende Unterschied ist zu suchen in der unterschiedlichen Funktionsweise und der Zielstellung.
Das Grundprinzip des Apache ist es einen HTTP-Request entgegen zu nehmen und die angeforderte Ressource zurückzuliefern. Klassischerweise wird dabei eine URL unmittelbar auf eine Verzeichnisstruktur übertragen und eine Ressource ist zumeist nichts anderes als eine Datei. Dieses Konzept ist durch CGI und entsprechende Module wie mod_rewrite (und eben auch PHP) aufgebohrt worden, am Funktionsprinzip selber hat sich nichts geändert.
Dieses Prinzip ist zwar praktisch und effizient für die Auslieferung von statischen Inhalten und beim Einsatz von klassischen Kontakt-Formularen, wird aber zum Pferdefuss bei Webanwendungen. In solchen Fällen schlagen die Schwächen des Protokolls HTTP wie die Zustandslosigkeit voll durch, und müssen durch den Programmierer der Webanwendung entsprechend beachtet, bzw. durch die Programmiersprachen unterstützt werden, wie eben PHP es z.B. mit seinem Session-Support tut.
Bei der Entwicklung von Tomcat standen Webanwendungen im Fokus. Deshalb unterscheidet sich dessen Funktionsweise deutlich vom Apache. URLs versteht Tomcat nicht als eine Datei-Angabe, sondern als eine Benachrichtigung über einer Benutzeraktion. Das ist vergleichbar mit dem ereignisgesteuerten Programmierung in der klassischen GUI-Programmierung: Ein Klick auf einen Button erzeugt ein Ereignis, dass vom Window-Manager an das Programm gemeldet wird. Analog ist der Aufruf einer URL sei es in Form eines Links oder Formular-Aufrufes.
Technisch wird dies dadurch umgesetzt, dass der Programmier entsprechende Java-Klassen bereitstellt, um auf Ereignisse zu reagieren - Servlets. Dieser Ansatz ist an sich äußerst mächtig, den innerhalb von Servlets stehen einem sämtlichen Möglichkeiten von Java zur Verfügung. Allerdings ist die Programmierung von Java-basierten Webanwendungen damit auch aufwendig. Deswegen entstand das Konzept der Java Server Pages (JSP). JSP-Seiten und darin enthaltenen JSP-Tags werden in Servlets übersetzt und dann zur Laufzeit in Java-Bytecode kompiliert. Vergleichbar ist dieser Vorgang z.B. mit dem Template-System Smarty, bei dem Templates in lauffähiges PHP umgewandelt wird.
Aus dem anwendungsorientierten Ansatz heraus unterscheidet sich auch die Verzeichnislogik des Tomcat deutlich vom Apache. Jede Anwendung von Tomcat ist innerhalb eines Context organisiert. Ein Context fasst alle zusammengehörigen Java-Klassen, JSP-Seiten usw. einer Anwendung zusammen. Ein Tomcat kann eine Vielzahl von Contexten enthalten, alle sind aber voneinander separiert und können ihre eigenen, individuellen Einstellungen definieren. Das bedeutet gleichfalls, dass es unter Tomcat keinen zentralen Document-Root gibt. Stattdessen muss in der server.xml ein entsprechender Context-Eintrag definiert werden, der das Basis-Verzeichnis, den Context-Root, definiert. Dies kann z.B. so aussehen:
<Context docBase="c:/tomcat/webapps/myapp" path="/test" />Das docBase-Attribute bestimmt, wo unsere Webanwendung liegt. Das Attribute path bestimmt die URL unter der die Anwendung erreichbar ist, hier http://localhost:8800/test/.
Zwar existiert das webapps-Verzeichnis, aber es ist nicht vergleichbar mit dem htdoc-Verzeichnis des Apaches. Denn auch Anwendungen und Verzeichnisse werden nur beachtet, wenn explizit ein Context-Eintrag definiert wird, oder im WEB-INF-Verzeichnis eine eigene web.xml-Datei vorhanden ist.
Zur Erinnerung: Tomcat ist eine Plattform für Webanwendungen, dementsprechend bietet er dem Programmierer verschiedene Techniken, um der Probleme bei der Programmierung von Webanwendungen zu verstecken bzw. zu entschärfen. Eine Folge davon ist, dass der Session-Support bereits Bestandteil der Request-Auswertung ist. Eine andere Folge ist für uns der wesentlich interessantere Application-Scope und der Page-Scope.
Diese Programmier-Unterstützung ist letztendlich auch Ursache dafür, dass Tomcat langsamer ist als Apache. Den Tomcat macht wesentlich mehr als einfach nur eine Datei einzulesen und an den Client zu schicken.
Tomcat kennt drei Sichtbarkeitsbereiche (Scopes) bzw. Container, in denen Variablen abgelegt werden können: der Page-Scope, der Session-Scope und der Application-Scope.
Bereits von PHP dürften Sie bereits mit dem Session-Scope vertraut sein: Sie legen eine Variable, z.B. einen Benutzernamen innerhalb einer Session ab.
Der Page-Scope ist ein Container, dessen Inhalte nur innerhalb einer Seite gültig sind. Das klingt zwar zuerst etwas seltsam, man sollte aber Bedenken, dass Java keine globalen Variablen kennt. Der Page-Scope ist daher ein Weg, um klassen- und sprachübergreifend Werte zu setzen und auszutauschen.
Der Application-Scope ist ein anwendungsweiter Container für Variablen. Z.B. können Sie hier Konfigurationswerte oder Templates hinterlegen, ohne sie bei jedem Seitenrequest neu einlesen zu müssen. In JSP-Seiten können damit aber auch Datenbankverbindungen persistent verwendet werden, ohne Sie bei jedem Seitenaufruf neu öffnen zu müssen. Der Application-Scope ist unabhängig von Sessions!
Werfen wir einen Blick darauf, wie der Aufruf von PHP technisch umgesetzt ist. Im ersten Teil haben wir in die web.xml einen Eintrag gesetzt, der dafür sorgt, dass Dateien mit der Endung .php von einem speziellen Servlet ausgewertet werden soll. Dieses Servlet wiederum ruft den PHP-Interpreter mit dem entsprechenden Skript auf.
Obwohl Bestandteil der JSR 233-Installation, wird aber nicht das PHP-Executable aufgerufen. Stattdessen bindet das Servlet über das Java Native Interface (JNI) das mitgelieferte Embedded PHP ein, dass in Form einer DLL unter Windows bzw. als Shared Library (php5.so) unter Linux.
Dieser Ansatz ist vergleichbar mit der Einbindung von mod_php in Apache. Deshalb sind die Laufzeit-Unterschiede zwischen PHP&Tomcat und PHP&Apache auch geringer als man erwarten würde.
Neben der Übergabe des auszuführenden PHP-Skriptes stellt das Servlet zusätzlich einige Variablen bereit, denen wir uns in den nächsten Artikeln ausführlich widmen werden. Über diese können wir die obengenannten Möglichkeiten nutzen.
Die PHP-Version, die mit der Referenz-Implementierung mitgeliefert wird, ist eine etwas ältliche Version 5.0.1, und sehr sparsam mit Extensions ausgestattet. Die php.ini befindet sich im Verzeichnis php5/lib im Verzeichnis Ihrer Installation der JSR223-Implementierung. Wenn Sie weitere Extensions einbinden wollen, können Sie diese für 5.0.1 von http://www.php.net/releases.php bekommen.
Alternativ können Sie natürlich auf eine neue Version updaten. Sie müssen darauf achten, dass Sie die php5.dll bzw. php5.so updaten. Unter Windows ist das nicht ganz einfach, denn die php5.dll ist nicht Bestandteil der originalen PHP-Distributionen. Als Alternative kann man beispielsweise die Windows-Binaries von http://kromann.info/php.php benutzen. Unter Linux&Co müssen Sie bei der Kompilierung von PHP einfach den Schalter --enable-embed angeben, um eine entsprechende SO-Datei zu erhalten.
Grundsätzlich gibt es keine funktionalen Unterschiede bei der Ausführung eines PHP-Skriptes im Vergleich zu anderen Webservern. PHP-Sessions funktionieren auch weiterhin, hier gibt es keinen Konflikt mit Tomcat. Es ist sogar bedenkenlos möglich, Tomcat- und PHP-Sessions parallel in einem Skript zu verwenden.
Zwei Probleme gibt es allerdings, die Showstopper sind, wenn man existierende PHP-Anwendungen installiert.
Erstens: die Server-Variablen wie $_SERVER['REQUEST_URI'] oder $_SERVER['QUERY_STRING'] sind nicht definiert. Stattdessen befinden sich z.B. die entsprechenden Request-Werte in $_SERVER['argv']. Davon nicht betroffen sind aber $_POST und $_GET; diese stehen ganz normal zur Verfügung.
Zweitens: Alle Include- und Datei-Pfade sind relativ zum Context-Root!
Gehen wir z.B. von folgendem Verzeichnislayout aus:Context/ admin/admin.php includes/… includes/subincludes/… index.phpIncludieren wir in index.php eine Datei, z.B.:
<?php include_once 'include/test.php'; ?>funktioniert das ohne Problem. Wenn wir aber die Datei admin.php aufrufen, die z.B. Folgendes enthält:
<?php include_once '../include/test.php'; ?>geht das schief.
Wie kann man das Problem lösen? Wenn man eine neue Anwendung entwickelt, kann man das natürlich berücksichtigen. Will man hingegen eine bestehende Anwendung benutzen, oder nicht Tomcat-spezifisch arbeiten will, wird es aufwendiger.
Die einfachste Lösung ist in der php.ini den include_path entsprechend anzupassen, und alle Unterverzeichnis darin aufzunehmen. Im Beispiel wäre das also includes und includes/subincludes.
Bei vielen Verzeichnissen oder bei Dateien gleichen Namens in unterschiedlichen Verzeichnissen, muss eine andere Lösung her. Die kompatibelste Lösung besteht darin, eine entsprechende Variable oder Konstante zu definieren, die den absoluten Pfad zum Context-Root enthält. Diese wird dann in den Include-, Require- und bei Datei-Anweisungen wie file_exists() entsprechend ergänzt. Aufpassen muss man natürlich bei relativen Angaben mit ".." am Anfang. Die Include-Anweisung für admin.php muss also z.B. so umgeändert werden:
<?php define (ABS_PATH, "c:\tomcat\webapps\myapp"); include_once ABS_PATH.‚/include/test.php' ?>
Das war es für heute erst einmal. Nächste Woche werden wir auf die bereits oben angesprochenen Variablen eingehen, die uns Tomcat zur Verfügung stellt und unsere erste kleine Webseite mit PHP unter Tomcat bauen.
1. Teil: Tomcat&PHP - Installation