Indianer von innen
Mit über 50 Prozent Marktanteil ist Apache der am häufigsten benutzte Webserver. Kein Wunder, dass es die passenden Hilfsmittel gibt, um ihn via Perl zu beeinflussen.
- Gerald Richter
Bereits 1997 hat die iX beschrieben, wie das Apache-Modul mod_perl die Ausführung von CGI-Scripts wesentlich beschleunigt [1]. Es kann aber weit mehr, indem es Perl-Scripts Zugriff auf die internen Datenstrukturen des Servers und den Eingriff in alle Phasen eines Request ermöglicht.
Apache ist kein monolithischer Block, sondern modular aufgebaut. Um größtmögliche Flexibilität zu erreichen, bearbeitet er jeden einkommenden Request in mehreren Phasen. Für jede Phase sind ein oder mehrere Handler zuständig, die die eigentliche Aktion ausführen. Apache-Module können sich in jeder von ihnen einklinken, um das Verhalten des Servers zu beeinflussen. Eine vollständige Liste der Phasen und der Namen der dazugehörigen Perl-Handler enthält die Tabelle ‘Request-Phasen und Perl-Handler’.
| Request-Phasen und Perl-Handler | |
| PerlPostReadRequestHandler | Aufruf nach dem Lesen des Request; es hat noch keinerlei Verarbeitung stattgefunden |
| PerlTransHandler | Zuordnung der URI zu einem Dateinamen |
| PerlHeaderParserHandler | Ermöglicht das Untersuchen des HTTP-Header sowie deren Änderung; hier kann der Request auch schon abgebrochen werden, um zum Beispiel Roboter auszusperren. |
| PerlAccessHandler | prüfen, ob der Zugriff erlaubt ist |
| PerlAuthenHandler | Authentizität (Identität) des Anfragenden feststellen |
| PerlAuthzHandler | Ist der Anfragende authorisiert für diesen Zugriff? |
| PerlTypeHandler | MIME Typ feststellen |
| PerlFixupHandler | letzte Korrekturen an den Request-Daten vornehmen |
| PerlHandler | erzeugen des eigentlichen Inhalts |
| PerlLogHandler | Abfrage protokollieren |
| PerlCleanupHandler | aufräumen |
In der URI-Translation-Phase versucht der Daemon die URI einer Ressource auf dem Server zuzuordnen. Meist ist dies eine Datei, es kann jedoch auch ein virtuelles Dokument sein, das zum Beispiel von einem CGI-Script oder von einem internen Modul generiert wird. Da der Server nun eine ‘Vorstellung’ davon hat, in welchem Bereich das Dokument liegt, können Handler für spätere Phasen gezielt für bestimmte Bereiche des Servers festgelegt werden, gesteuert durch die Konfigurationsanweisungen <Directory>, <Location> und <Files>.
Nächste Station ist die Access-Control-Phase. Hier entscheidet sich, ob der Zugriff überhaupt erlaubt ist. Der Handler kann dazu alle bereits bekannten Daten heranziehen, zum Beispiel die IP-Adresse oder die Methode der Anfrage. Dies schließt die Identität des Benutzer ausdrücklich aus, da noch nicht bekannt. Als Rückgabewerte sind OK, FORBIDDEN oder DECLINED möglich. Im ersten Fall geht der Daemon zur nächsten Phase über, im zweiten bricht er mit einer Fehlermeldung ab. DECLINED schließlich bedeutet, dass der Handler über die Zulässigkeit der Anfrage nicht entscheiden kann, und Apache ruft, so vorhanden, den nächsten Access-Control-Handler auf.
Authentifizierung und Autorisierung
Ist der Zugriff grundsätzlich zulässig, folgt als nächster Schritt die Authentifizierung. Sie und die folgende Autorisierungsphase sind nur bedeutsam, wenn eine benutzerbezogene Zugriffskontrolle stattfinden soll. In der Authentifizierungsphase versucht Apache, die Identität des Benutzers festzustellen. Im einfachsten Fall (‘Basic Authentication’), prüft der Handler, ob der HTTP-Header den Benutzernamen und das Passwort enthält. Fehlen sie, bricht er den Request mit dem Fehler 401 (UNAUTHORIZED) ab. Das veranlasst den Browser, Namen und Passwort zu erfragen und die Anfrage mit diesen Daten erneut zu stellen. Durch die modulare Architektur ist man hier aber nicht auf Basic Authentification beschränkt, Benutzerdaten könnten genauso gut einem Cookie oder anderen Quellen entstammen. Beliebige eigene Module lassen sich an dieser Stelle einhängen. Auch hier ist es wieder möglich, mehrere Handler der Reihe nach abzufragen, bis einer Namen und Passwort ermitteln konnte.
Weiter geht es mit der Autorisierung. Sie kontrolliert, ob dieser Benutzer auf die Ressource zugreifen darf. Das kann zum Beispiel durch Vergleich von Namen und Passwort mit einer Datenbank geschehen. Kann ein Handler dies nicht entscheiden, kümmert sich der nächste darum - so lange, bis ein Handler einen Fehler meldet oder den Benutzer identifiziert hat.
Nachdem geklärt ist, dass der Benutzer die nötigen Berechtigungen besitzt, bestimmt der Daemon im nächsten Schritt die Art des zu sendenden Inhalts (MIME-Typ). Standardmäßig betrachtet er dazu die Dateierweiterung, zum Beispiel leitet er aus der Endung .html den MIME-Typ text/html ab.
In der nun folgenden Fixup-Phase sind letzte Korrekturen an den Request-Daten möglich, bevor in der Content-Phase der Inhalt entsteht. Welcher Handler sich um diese Phase kümmert, richtet sich zum einen danach, wo die Ressource liegt, zum anderen nach ihrem MIME-Typ. Ein Handler kann dabei für einen oder mehrere MIME-Typen zuständig sein. Content-Handler müssen die HTTP-Header und den eigentlichen Inhalt der Serverantwort erzeugen. Letzterer kann eine Datei, das Ergebnis eines Programms wie bei CGI-Scripts oder eine modifizierte Datei wie bei Server-Side-Includes sein. Beispiele für Content-Handler in Perl sind Apache::Registry, der das Ausführen von CGI-Scripts unter mod_perl erlaubt, oder HTML::Embperl [2], das es ähnlich wie SSI gestattet, Perl-Code in HTML-Dokumente einzubetten.
Damit ist die Client/Server-Kommunikation beendet. Apache hat jedoch noch zwei weitere Phasen zu bearbeiten, bis er die nächste Anfrage entgegennehmen kann. Dies ist zum einen das Logging, das die Anfrage protokolliert. Der eingebaute Handler schreibt dazu je eine Zeile in die Log-Datei, denkbar sind jedoch andere Implementierungen, um beispielsweise das Ergebnis in einer Datenbank abzulegen. Als Letztes muss noch aufgeräumt werden: Dateien schließen, Speicher und andere Ressourcen freigeben.
Zu jedem Request gehören Daten, die Apache in drei Strukturen verwaltet. Der Request-Record enthält alle zur Anfrage gehörenden Informationen wie den Request selbst, die Methode (GET, POST), die HTTP-Header et cetera. Die zweite Struktur ist der Connection-Record mit den Verbindungsdaten, zum Beispiel die IP-Adresse des Clients. Schließlich gibt es noch den Serverrecord, der Daten über den (virtuellen) Server zur Verfügung stellt. Er enthält unter anderem die Portnummer, die IP-Adresse und den Servernamen. Die Handler der einzelnen Phasen füllen diese Strukturen mit Inhalt oder können Teile ändern, um das Verhalten folgender Handler zu beeinflussen. In Perl kann man die drei Datenstrukturen mit den Objekten Apache, Apache::Connection und Apache::Server verwenden. Entsprechende Methoden erlauben den Zugriff auf die Daten beziehungsweise das Aufrufen von Funktionen aus der Apache-API.
Client-Adresse statt Proxy-IP
Was aber kann man in der Praxis damit anfangen? Angenommen, ein Webserver ist aus Performancegründen nur über einen Proxy erreichbar, das heißt, die Clients greifen nie direkt auf die Seiten zu. Dadurch sieht der HTTP-Server statt der IP-Adresse des Clients immer die des Proxy. Dies ist weder informativ in den Log-Dateien, noch kann man damit Zugriffsbeschränkungen aufgrund der IP-Adresse durchführen. Ein kleiner Perl-Handler löst dieses Problem, indem er die IP-Adresse des Clients dem vom Proxy eingefügten HTTP-Header X-Forwarded-For entnimmt und sie als Absender-Adresse einsetzt (siehe Listing 1). Alle darauf folgenden Operationen, die die IP-Adresse auswerten, beziehen sich jetzt auf den tatsächlichen Client.
Listing 1
ProxyAddr.pm ersetzt die IP-Adresse des Proxy durch die des Clients.
package Apache::ProxyAddr ;
use Apache::Constants qw(:common) ;
sub handler {
my $r = shift ;
if ($r -> header_in('X-Forwarded-For') =~ /([^,\s]+)$/) {
$r -> connection -> remote_ip($1);
}
return OK;
}
1;
Die Datei ProxyAddr.pm muss entweder im Suchpfad für Perl-Module stehen oder mittels require geladen werden. Die folgenden Beispiele verwenden die zweite Methode, da sie für anfängliche Versuche schneller zum Ziel führt. Um das erste Beispiel zum Leben zu erwecken, sind folgende Zeilen in die httpd.conf einzufügen:
PerlRequire /pfad/zu/ProxyAddr.pm
PerlPostReadRequestHandler Apache::ProxyAddr
Sie können entweder auf oberster Ebene oder innerhalb eines <Virtual>-Abschnitts stehen. Außerdem ist zu beachten, dass der PerlPostReadRequestHandler beim Übersetzen von mod_perl freigeschaltet sein muss (siehe ‘Voraussetzungen und Installation’).
Ruft man jetzt eine Seite auf und beobachtet die Ausgabe in der Log-Datei, wird dort statt der IP-Adresse des Proxy nun die des Clients erscheinen. Was passiert im Einzelnen? mod_perl sucht das in der PerlHandler-Anweisung angegebene Modul, lädt es, wenn nicht schon geschehen, und ruft die Funktion handler mit dem Request-Record-Objekt als Parameter auf. Der Handler legt die Referenz auf dieses Objekt, das ihm Zugriff auf alle Request-Daten bietet, in der Variablen $r ab. Eine ausführliche Dokumentation dieses Objekts liefert perldoc Apache. Die folgende Zeile prüft mittels der Methode header_in, ob ein X-Forward-For-Header vorhanden ist und extrahiert gegebenenfalls die letzte dort angegebene IP-Adresse. Nun kann die Remote-IP-Adresse im Connection-Record auf diesen Wert gesetzt werden. Als Letztes gibt der Handler den Wert OK zurück und signalisiert so dem HTTP-Daemon, dass er seine Arbeit erledigt hat und die Verarbeitung des Request mit der nächten Phase weitergehen kann.
Apache enthält mit mod_rewrite bereits ein leistungsfähiges Modul zur Manipulation von URIs und Umwandlung in Dateinamen. Manchmal jedoch ist dessen Konfiguration zu kompliziert, oder bestimmte Konvertierungen lassen sich gar nicht durchführen. Hier kann ein Perl-Handler wertvolle Dienste leisten. Das zweite Beispiel zeigt einen Handler, der alle mit /alt beginnenden URIs auf /neu abbildet. In httpd.conf müssen diese beiden Zeilen stehen:
PerlRequire /pfad/zu/PerlRewrite.pm
PerlTransHandler Apache::PerlRewrite
Das Script ermittelt die URI des Request mit der Methode uri, ändert sie wie gewünscht und setzt die URI auf den neuen Wert. Selbstverständlich sind beliebige Transformationen möglich. Anschließend liefert der Handler DECLINED zurück, was Apache dazu veranlasst, die übliche Transformation von URIs zu Dateinamen durchzuführen (siehe Listing 2).
Listing 2
Das Script PerlRewrite.pm ändert den Anfang des Dokumentenpfads von /alt in /neu, überlässt aber dem Apache weitere URI-Transformationen.
package Apache::PerlRewrite ;
use Apache::Constants qw(:common) ;
sub handler {
my $r = shift ;
my $uri = $r -> uri ;
$uri =~ s#^/alt#/neu# ;
$r -> uri ($uri) ;
return DECLINED ;
}
1;
Pfade automatisch anpassen
Im Gegensatz dazu erledigt das nächste Exempel die Übersetzung in einen Dateinamen selbst. Hier signalisiert der Rückgabewert OK Apache, dass die URI-Translation-Phase abgeschlossen ist. In httpd.conf wird diesmal eingetragen
PerlRequire /pfad/zu/PerlAlias.pm
PerlTransHandler Apache::PerlAlias
PerlAlias.pm (Listing 3) prüft, ob die URI mit /files/ beginnt. In diesem Fall ändert der Handler den Pfad in /tmp/ und setzt den Dateinamen. Geht es um eine andere URI, liefert er DECLINED zurück, damit Apache den Request weiterbearbeitet, als gäbe es diesen Handler nicht.
Listing 3
PerlAlias.pm liefert statt der verlangten Dateien in /files die gleichnamigen in /tmp aus.
package Apache::PerlAlias ;
use Apache::Constants qw(:common) ;
sub handler {
my $r = shift ;
my $uri = $r -> uri ;
if ($uri =~ s#^/files/#/tmp/#) {
$r -> filename ($uri) ;
return OK ;
}
return DECLINED ;
}
1;
Bleibt noch das obligatorische ‘Hello World’. Während die bisherigen Beispiele sich auf alle Requests (oder auf die eines virtuellen Hosts) ausgewirkt haben, hat dies für einen Content-Handler meist wenig Sinn. Es ist eher zweckmäßig, ihn nur für betimmte Dateien (mittels <Files>) oder bestimmte Bereiche des Servers (mittels <Location> oder <Directory>) zu aktivieren. Da es mehrere unterschiedliche Content-Handler je nach MIME-Typ geben kann, muss man Apache außerdem mitteilen, dass mod_perl der zuständige Content-Handler ist. Dies geschieht durch die Konfigurationsanweisung SetHandler. PerlHandler informiert mod_perl darüber, welches Modul es zum Erzeugen des Inhalts aufrufen soll. Um etwa den Inhalt für alle Zugriffe auf die URI /hello durch den Beispiel-Handler erzeugen zu lassen, ist folgende Konfiguration nötig:
PerlRequire /pfad/zu/Hello.pm
<Location /hello>
SetHandler perl-script
PerlHandler Apache::PerlAlias
</Location>
Der eigentliche Handler (Listing 4) setzt zuerst den Content-Type auf text/html, anschließend veranlasst er das Senden der HTTP-Header, um schließlich den Inhalt auszugeben.
Listing 4
Das klassische Hello-World als Perl-Modul für den Apache.
package Apache::Hello ;
use Apache::Constants qw(:common) ;
sub handler {
my $r = shift ;
$r -> content_type ('text/html') ;
$r -> send_http_header ;
print "<HTML><HEAD><TITLE>Hello world \
</TITLE></HEAD>\n" ;
print "<BODY><H1>Hello world</H1></BODY></HTML>\n" ;
return OK ;
}
1;
Dieser Artikel konnte die vielfältigen Möglichkeiten, die sich durch die Kombination von Apache und mod_perl ergeben, nur anreißen. So können noch andere Handler, die nicht direkt im Request-Zyklus vorkommen, definiert werden. Auch die Möglichkeit, die Apache-Konfigurationsdateien in Perl zu schreiben, das heißt dynamisch zu erzeugen, bleibt unbesprochen. Wer sich weiter informieren möchte, kann dies auf der mod_perl-Website tun. Ausgezeichnete und detaillierte Informationen zu Apache und mod_perl finden sich auch im ‘Eagle Book’ [3].
Gerald Richter
ist Programmierer und Netzadministrator bei der Firma ecos eletronic communication services GmbH.
Literatur
[1] Michael Schilli; Donnerbüchse; Apaches CGI-Ausführung beschleunigen
[2] Gerald Richter; In einem Rutsch; Kompakte Datenbankabfragen mit Embperl
[3] Lincoln Stein, Doug MacEachern; Writing Apache Modules with Perl and C, O’Reilly & Associates 1999, ISBN 1-56592-567-X
[4] Linda Mui, Gerald Richter; ‘CGI - kurz & gut’; O’Reilly Verlag 1999; ISBN 3-89721-218-8
iX-TRACT
- mod_perl ist ein Apache-Modul, das zum einen die Ausführung von Perl-Scripts beschleunigt.
- Zum anderen gestattet es, die Arbeitsweise des HTTP-Daemon in jeder Phase durch Perl-Programme zu beeinflussen und zu modifizieren.
- Mit Hilfe von mod_perl können Webadministratoren beispielsweise eigene Autorisierungsprozeduren festlegen oder ganze Hierarchien automatisch umschichten.