Frei gemaurert

Dynamisch erzeugte Webseiten gehören heute zur Standardausstattung größerer Internetauftritte. Viele wählen eine Lösung mit in HTML eingebettetem Programmcode - oft Perl -, was die Zusammenarbeit von Webdesignern und Entwicklern erleichtern soll.

In Pocket speichern vorlesen Druckansicht 10 Kommentare lesen
Lesezeit: 19 Min.
Von
  • Peter Dintelmann
Inhaltsverzeichnis

Zur Erzeugung dynamischer Websites bietet Perl mehrere Möglichkeiten, von Gerald Richters Embperl über Ralf Engelschalls ePerl bis zu Jochen Wiedmanns Apache::EP - und Jonathan Swartz’ Mason. Letzteres erfreut sich steigender Beliebtheit und wird in einigen größeren Webpräsenzen eingesetzt. Details hierzu kann man der Mason-Homepage (siehe ‘Online-Ressourcen’) entnehmen. Insbesondere findet Mason Anwendung im Content-Management-System Mason-CM sowie im OR-Mapping-Tool Alzabo, auf die dieser Artikel nicht eingeht.

Mehr Infos

Mason ist optimiert für den Einsatz unter Apache mit mod_perl, lässt sich aber mit jedem anderem Webserver auf Basis der CGI-Schnittstelle verwenden. Seine Stärke liegt darin, dynamische Webseiten in komponentenbasierter Architektur zu erstellen. Dies gestattet es, HTML- (und XML-)Seiten aus kleinen Codestücken und Vorlagen zu generieren, wodurch sich große Websites aus wenigen Bausteinen ohne redundante Elemente zusammenbauen lassen.

Aus dem Funktionsumfang dieses mächtigen Werkzeugs kann hier naturgemäß nur ein kleiner Ausschnitt vorkommen. Als Anwendung dient ein CD-Shop mit rudimentären Funktionen. Zusätzliches Material liegt der Mason-Distribution bei. Ferner existiert ein Codearchiv auf der Mason-Website. Entwicklungshilfe erhält man in einer Mailingliste.

Ein HTML-Perl-Gemisch bezeichnet die Mason-Welt als Komponente; die kann sowohl Daten an den Client als auch an eine andere (sie aufrufende) Komponente senden. Alle befinden sich unterhalb eines konfigurierbaren Basisverzeichnisses comp_root. Insgesamt stehen drei syntaktische Möglichkeiten der Einbettung von Perl in HTML zur Verfügung, die Listing 1 zeigt.

Mehr Infos

Listing 1

<!-- 1. syntaktische Variante -->
<%perl>
my $hour = (localtime)[2];
my $greeting;
if ($hour > 18)
{ $greeting = "Guten Abend";
}
elsif ($hour < 12)
{ $greeting = "Guten Morgen";
}
else
{ $greeting = "Guten Tag";
}
</%perl>

<!-- 2. syntaktische Variante -->
<% $greeting %>,
<br>
folgende Umgebungsvariablen stehen zur Verf&uuml;gung:

<!-- 3. syntaktische Variante -->
<table border="1">
<tr>
<th>Variable</th>
<th>Wert</th>
</tr>
% foreach my $key (sort keys %ENV) {
<tr>
<td><% $key %></td>
<td><% $ENV{$key} %></td>
</tr>
% }
</table>

Den in <% ... %> enthaltenen Code wertet Mason aus und fügt das Ergebnis im Dokument ein. Meist verwendet man diese Schreibweise zur Ausgabe einzelner Variablen im laufenden Text, wie bei <% $greeting %>. Ferner werden alle mit einem % beginnenden Zeilen als Perl-Code behandelt. In Listing 1 werden so die Zeilen der Tabelle in einer Schleife erzeugt. Längere Codestücke kann man in den Tags <%perl>...</%perl> einschließen. Solche Blöcke sind äquivalent zu den %-Zeilen, weswegen im Listing auch

<%perl>
foreach my $key (sort keys %ENV)
{
</%perl>
<tr>
<td><% $key %></td>
<td><% $ENV{$key} %></td>
</tr>
<%perl>
}
</%perl>

hätte stehen können. Weitere Mason-spezifische Tags sind in der Regel von der Form <%xxx>...</%xxx>. Kommentare außerhalb von Codeblöcken sind in <%doc>...</%doc> eingeschlossen und in der Ausgabe nicht sichtbar, innerhalb von Codeblöcken wie üblich mit # eingeleitet.

Komponenten werden in anonyme Subroutinen kompiliert - ähnlich wie die Erzeugung von Servlets aus JSPs. Diesen Vorgang steuert ein Apache-Initialisierungsskript (meist handler.pl genannt), das die drei Mason-Objekte Parser, Interpreter sowie ApacheHandler bereitstellt. Der Parser verwandelt die Komponenten in Perl-Subroutinen, die der Interpreter ausführt. Ein Zeitstempel entscheidet darüber, ob eine Komponente neu kompiliert werden muss. Diese Überprüfung ist jedoch abschaltbar. Zusätzlich lassen sich Komponenten beim Serverstart vorkompilieren.

Kompilierte Komponenten liegen in einem separaten Datenverzeichnis data_dir und werden geladen, sowie sie zur Ausführung eines Request erforderlich sind. Sie bleiben zunächst in einem Code-Cache einstellbarer Größe im Speicher. Der ApacheHandler ist schließlich dafür verantwortlich, die Client-Requests weiterzuleiten.

Für das Apache-Initialisierungsskript gelten - wie für den Einsatz von Mason insgesamt - die üblichen mod_perl-Programmiertipps (siehe ‘Online-Ressourcen’). Unter anderem können in diesem zusätzliche Perl-Module ins Shared Memory geladen und Datenbankverbindungen beim Start eines Apache-Kindprozesses hergestellt werden.

Eingangsseite: Die links zu sehenden Links eignen sich gut dafür, durch eine Komponente generiert zu werden (Abb. 1).

Für alle drei Objekte gibt es eine Vielzahl einstellbarer Parameter. Insbesondere können mehrere Komponentenbasisverzeichnisse mit unterschiedlicher Konfiguration nebeneinander existieren.

Die Shop-Anwendung beginnt mit einer Startseite, die in Abbildung 1 zu sehen ist. Die Links zur linken sind aufgrund ihrer einheitlichen Gestaltung ein idealer Kandidat für eine eigene Komponente. Diese soll als Parameter die URL und den Linktext entgegennehmen und daraus das jeweilige graue Tabellenelement erzeugen. Hierfür ist keine eigene Datei notwendig, sondern mit <%def>...</%def> lässt sich eine Unterkomponente .link innerhalb der Startseite erstellen:

<%def .link>
<tr>
<td bgcolor="#b0b0b0" align="center">
<a href="<%$url%>"><%$label%></a>
</td>
</tr>
<%args>
$url => "/"
$label => "Home"
</%args>
</%def>

In den Tags <%args>...<%/args> kann eine Komponente ihre Argumente deklarieren (je ein Argument pro Zeile) und diese unter Verwendung von => mit Default-Argumenten vorbelegen. Die Link-(Unter-)Komponente nimmt eine URL ($url) sowie einen Linktext ($label) entgegen, aus denen eine Tabellenspalte erzeugt wird. Um Fehler mit nicht initialisierten Variablen zu vermeiden, empfiehlt es sich stets, die Vorbelegung einzusetzen.

Für eine eigene Komponente stünde <%perl>...</%perl> anstelle von <%def .link>...</%def>, und der Komponentenname (.link) ergäbe sich aus dem Dateinamen. Der Aufruf in der Startseite geschieht durch

<& .link, 
url => "/mason/search/searchclassic.html",
label => "Klassik" &>

<&...&> ruft eine Komponente auf und schreibt deren (HTML-)Output in das Dokument. Die Argumente werden stets nach dem Namen übergeben. Weitere kann eine Komponente noch über den HTTP-Request erhalten.

Fordert ein Client die Seite an, bindet sie die Ausgabe aller weiteren von ihr aufgerufenen Komponenten (hier .link) ein. Das fertige Dokument sendet der Apache schließlich zurück.

Jeder Request ist in einem eigenen Objekt gekapselt, das als $m zur Verfügung steht und Zugriff auf Komponenten und ihre Eigenschaften erlaubt. Es dient im Wesentlichen der Introspektion. Das Mason-Request-Objekt bietet ferner Zugriff auf das Apache-Request-Objekt durch die Methode apache_req() (beziehungsweise mit cgi_object() auf das unterliegende CGI-Objekt bei Verwendung der CGI-Schnittstelle). Mit diesen gelangt man an die Parameter des HTTP-Request. Das Apache-Request-Objekt ist unter Apache/mod_perl zusätzlich als globale Variable $r ansprechbar. Die zugehörigen Methoden sind in der Dokumentation des Apache-Moduls beschrieben.

Was das Mason-Requestobjekt bietet, soll der Weg zur aktuellen Komponente nachzeichnen. Eine solche lässt sich etwa zu Navigationszwecken einsetzen. Es geht darum, eine URI zu einer Komponente zu ermitteln. Der Webserver kann das nicht leisten, da eine Datei aufgrund von Aliases, virtuellen Hosts und so weiter unter verschiedenen URIs ansprechbar sein kann.

Zur Bearbeitung der URIs und Dateinamen dient die Funktion dirname() aus dem Modul File::Basename, die schon das Apache-Initialisierungsskript lädt. Zunächst sind die Pfadnamen der angefragten URI sowie des dazugehörigen Dateinamens nach dem Separator / zu zerlegen (inklusive Entfernen übereinstimmender Teile), woraus eine Zuordnung eines URI-Pfads zu einem Verzeichnis im Dateisystem entsteht. Dies bestimmt zu jeder in @_ übergebenen Komponentenreferenz den zugehörigen Dateinamen mit der Methode source_file() und ersetzt in ihm den ermittelten Verzeichnisnamen. return gibt das Ergebnis - eine Liste von URIs - zurück. Im Unterschied zu normalen Perl-Subroutinen muss return explizit verwendet werden, da am Ende einer jeden Komponente ein implizites return undef steht.

Die Anfrage-URI sowie den zugehörigen Dateinamen erhält man aus dem Apache-Request-Objekt durch $r->uri() und $r->filename(). Weitere Schreibweisen wären $m->apache_req()->uri() und $m->apache_req()->filename().

Wie erwähnt, müssen Komponenten nicht unbedingt HTML erzeugen. Sie können sich auch wie Perl-Subroutinen verhalten, die Werte ermitteln. Da alle Komponenten im Hintergrund durch normale Subroutinen realisiert sind, ist es nicht weiter verwunderlich, dass sie die vom Aufrufer übergebenen Daten im Array @_ finden können und ihr Ergebnis über das Schlüsselwort return zurückgeben. $m->comp() ergibt die Daten, hier:

$m->comp("/priv/comp2uri", $comp1, $comp2, ...);

Absolute Komponentenpfade beziehen sich stets auf deren Basisverzeichnis, relative auf das Verzeichnis der aufrufenden Komponente. Der Unterschied zwischen <&...&> und $m->comp() besteht darin, dass Ersteres lediglich den Output der Komponente erzeugt und deren Rückgabewert ignoriert, während Letzteres diesen liefert.

Mehr Infos

Listing 2: <comp_root>/priv/comp2uri

<%perl>
my @uriDir = split "/", File::Basename::dirname $r->uri();
my @fileDir = split "/", File::Basename::dirname $r->filename();

while (@uriDir && @fileDir && $uriDir[-1] eq $fileDir[-1])
{ pop @uriDir;
pop @fileDir;
}

my $uriRoot = join "/", @uriDir;
my $fileRoot = join "/", @fileDir;

return grep s/^$fileRoot/$uriRoot/i => map $_->source_file() => @_;
</%perl>

Das Layout der Shop-Anwendung soll durch einen einheitlichen Rahmen bestimmt sein, dessen Inhalt verschiedene Komponenten ausfüllen. Dies ist unter Verwendung so genannter Autohandler realisierbar. Bei Aufruf einer Komponente durch den Client - auch als Top-Level bezeichnet - werden deren Verzeichnis sowie alle über diesem liegenden bis hinauf zur comp_root nach Komponenten mit dem Namen autohandler durchsucht. Die Kontrolle geht anschließend auf den obersten Autohandler über. Üblicherweise ruft dieser nach getaner Arbeit die nächste Komponente vermöge

$m->call_next()

auf. Ob überhaupt eine solche existiert, kann $m->fetch_next() prüfen. Diese Methode gibt eine Referenz auf die folgende Komponente zurück. Auf diese Weise kommen alle Autohandler zum Zuge, bis die Top-Level-Komponente ausgeführt wird. Bei einem Aufruf von index.html (Top-Level) in der Verzeichnisstruktur

<comp_root>
|
+- autohandler
|
+- dir1
|
+- autohandler
|
+- dir2
|
+- autohandler
+- index.html

ist die Ausführungsreihenfolge

1. <comp_root>/autohandler
2. <comp_root>/dir1/autohandler
3. <comp_root>/dir1/dir2/autohandler
4. <comp_root>/dir1/dir2/index.html
5. alle von index.html aufgerufenen Komponenten

Der Autohandler im Basisverzeichnis comp_root kann somit alle Standardlayoutelemente der Website bereitstellen. Dies kann eine framelose Dreispaltigkeit sein. Ein minimaler Autohandler in comp_root wäre

<html>
<head>
<title>Mason Website</title>
</head>
<body>
<% $m->call_next() %>
</body>
</html>

Pro Unterverzeichnis lässt sich dieses Layout verfeinern, beispielsweise durch die Einbettung von Links. Der Vorteil dieses Konzepts liegt darin, dass mit wenigen Änderungen an einem Autohandler die ganze Site oder Teile umgestaltet werden können. Ferner enthalten alle weiteren Komponenten nur noch den HTML-Code, den sie zur Gesamtseite beitragen, was sowohl deren Dateigröße als auch die Zahl der potenziellen Fehlerquellen reduziert.

Die Abfolge der Autohandler bis zur Top-Level-Komponente kann man verfolgen - durch navPath (Listing 3).

Mehr Infos

Listing 3: <comp_root>/navPath

<%perl>
my @uris = $m->comp("/priv/comp2uri", $m->fetch_next_all());
my @titles = map $_->name() => $m->fetch_next_all();
return unless $#uris == $#titles;
</%perl>

% for (my $i=0; $i<@uris-1; $i++) {
<a href="<%$uris[$i]%>"><%$titles[$i]%></a> ->
% }

<%$titles[-1]%>

$m->fetch_next_all() ist das Array aller aus Sicht der aktuellen Komponente noch aufzurufenden Arrays. comp2uri kann die zugehörigen URIs ermitteln. Eine for-Schleife erzeugt die Links, wobei die letzte Komponente (die aktuelle Seite im Browser) nicht verlinkt wird. Link-Texte sind die Default-Titel aus $comp->name(). Hier sollen später die echten Dokumententitel stehen. An geeigneter Stelle (etwa im Root-Autohandler) ist die Navigationskomponente durch <& navPath &> einzubinden.

Auf ähnliche Weise lassen sich andere Navigationselemente wie Linklisten und Ähnliches zur Laufzeit erzeugen, wobei es sich anbietet, deren Output zwischenzuspeichern.

Dokumententitel sind üblicherweise im HTML-Kopf anzugeben, den in der Regel der oberste Autohandler generiert. Dies hätte zur Folge, dass alle Seiten denselben Titel haben. Damit derlei nicht passiert, haben Mason-Komponenten ein objektähnliches Interface und Verhalten, mit dem sie Methoden und Attribute für andere Komponenten bereitstellen können. Eine solche Methode wird in den Tags <%method>...</%method> deklariert und enthält gewöhnlichen Mason-Code:

<%method title>Titel dieser Seite</%method>

Sie wird direkt über <& component:title &> beziehungsweise $m->comp(‘component:title’) aufgerufen. Da man des öfteren eine Referenz auf die Top-Level-Komponente benötigt, kann man sie durch SELF ansprechen:

<head>  <title><&SELF:title&></title></head>

Alternativ kann man sich über $m zuerst einen Handle auf die betreffende Komponente verschaffen und auf diesen call_method() anwenden. Dieses Vorgehen ist für die Verwendung in der Navigationskomponente sinnvoll. Hier soll es jedoch nicht darum gehen, die Titel-Methoden aufrufen und so Text in das Dokument zu schreiben, sondern lediglich den Output der Methode zu erhalten. Hierzu dient scall_comp() (analog sprintf() zu printf()). In der Navigationskomponente navPath ändert sich dadurch die entsprechende Zeile in

my @titles =   map $_->scall_method("title") => $m->fetch_next_all();

Implementiert eine Komponente die Titel-Methode nicht selbst, wandert der Aufruf die Hierarchie der Autohandler nach oben, bis die Methode schließlich gefunden und aufgerufen wird. Existiert sie nicht, tritt ein Fehler auf.

Soll die Titelleiste des Browsers zusätzlich noch den Titel der Elternkomponente enthalten, bewirkt

<head> <title><&PARENT:title&> - <&SELF:title&></title> </head>

den Zugriff auf den Titel des PARENT und den des gegenwärtigen Handlers. Unabhängig von der Autohandler-Hierarchie kann eine Komponente mit dem inherit-Flag seine Eltern selbst bestimmen.

Neben den Autohandlern gibt es Defaulthandler. Die treten in Aktion, wenn eine angeforderte Top-Level-Komponente nicht auffindbar ist. In diesem Fall wird der URI-Pfad von unten nach oben nach einer Komponente mit dem Namen dhandler abgesucht und die erste gefundene aufgerufen. Über $m->dhandler_arg() gelangt ein Defaulthandler an die nicht gefundenen Teile des URI-Pfads und kann diese für die Suche in einer Datenbank verwenden. Um dem Client die berühmte Fehlermeldung ‘404 - Document not found’ zu ersparen, sollte sich daher zumindest in der comp_root ein Defaulthandler befinden. Diese können aber auch gezielt zum Erstellen mehrerer gleichartiger Seiten eingesetzt werden. Wie bei den Autohandlern ist der Name der Defaulthandler einstellbar.

Soll in einer Shop-Anwendung der Einkaufskorb des Benutzers persistent sein, kommt das Apache::Session-Modul ins Spiel. Dessen Einsatz ist nicht auf den Apache-Webserver beschränkt, da es keinerlei Interaktion mit dem Webserver enthält.

Apache::Session ist ein Interface, das ein Session-Framework bereitstellt. Es wird von Klassen implementiert, die die jeweiligen Details pro Backend realisieren, in denen die Datenhaltung geschieht. Insbesondere stehen hierzu normale Dateien, DBM-Dateien und Datenbanken zur Verfügung, sodass eine Anwendung über verschiedene Server verteilt werden kann.

Um Apache::Session mit Mason zu nutzen, ist der Apache-Handler anzupassen, wozu ein Beispiel-Handler in der Mason-Distribution enthalten ist. Dabei wird die Session-Variable als global deklariert, damit sie in allen Komponenten sichtbar ist. In einer Session, die sich für den Anwender als ‘tied hash’ darstellt, lassen sich alle Perl-Datentypen speichern, auch zu serialisierende Objekte.

Es gibt eine Vielzahl von Möglichkeiten zur Architektur von Webanwendungen. Das Spektrum reicht von Seiten, die den gesamten Quelltext inklusive SQL-Statements enthalten, bis hin zum MVC-Modell. All diese Varianten sind mit Mason realisierbar.

Für den Shop wurde jede Datenbanktabelle in eine Klasse gekapselt, deren Methoden Komponenten verwenden. Ferner ist es sinnvoll, statische (HTML-Dateien, Bilder et cetera) und dynamische Inhalte zu trennen und letztere danach zu unterschieden, ob sie für den Client zugängig sein sollen (öffentliche) oder nicht (private Komponenten).

Nach diesem kurzen Streifzug durch Mason nun zum angekündigten CD-Shop. Da dieser relativ wenige Komponenten hat, ist der Einsatz der erwähnten navPath-Komponente nicht sinnvoll (die Anzeige würde sich stets auf ein einziges Element beschränken, was nicht spannend ist).

Der Einkauf beginnt mit der CD-Suche, wahlweise nach Titel oder Künstler in einer der angebotenen Musikkategorien. Die Datenbanktabelle CATALOG, die alle CD-Daten enthält, ist dazu in der Klasse Catalog gekapselt, die Suchmethoden zur Verfügung stellt. Auf diese Weise sind Datenzugriffslogik und Präsentation voneinander getrennt.

Pro Musikkategorie soll eine eigene Suchmaske existieren. Die Masken unterscheiden sich lediglich im Titel sowie einem an die Suchkomponente zu sendenden Parameter. Alle Suchmasken erzeugt daher ein einziger Defaulthandler, den Listing 4 zeigt.

Mehr Infos

Listing 4: pub/search/dhandler - Defaulthandler zur Erzeugung der Suchseiten

<%method title>Suche <%$page->{title}%></%method>

<h2 align="center">Suche im Bereich <%$page->{title}%></h2>

<form action="search" method="post">
<input type="hidden" name="genre" value="<%$page->{genre}%>">
<hr size="1" width="90%">
Bitte w&auml;hlen Sie Ihr Suchkriterium
<blockquote>
<table border="0">
<tr>
<td><input type="radio" checked name="crit" value="title"></td>
<td>Titel</td>
</tr>
<tr>
<td><input type="radio" name="crit" value="artist"></td>
<td>K&uuml;nstler</td>
</tr>
</table>
</blockquote>
sowie den
<blockquote>
Suchtext: <input type="text" name="text" size="30">
</blockquote>
<hr size="1" width="90%">

<div align="center">
<input type="submit" value="Suchen">&nbsp;
<input type="reset" value="Zur&uuml;cksetzen">
</div>
</form>

<%once>
my %list = ( "searchjazz.html" => { title => "Jazz", genre => "jazz",
},
"searchrock.html" => { title => "Rock", genre => "rock",
},
"searchclassic.html" => { title => "Klassik", genre => "classic",
},
"searchcountry.html" => { title => "Country", genre => "country",
},
);
</%once>

<%shared>
my $page = %list->{$m->dhandler_arg()};
</%shared>

Die Angaben für die einzelnen Suchmasken sind in der Datenstruktur %list hinterlegt, die sich in einem <%once>...</%once>-Block befindet. Ein solcher wird beim Laden der Komponente einmalig ausgeführt. %list ist während des ganzen Lebenszyklus der Komponente in dieser sichtbar.

Im <%shared>...</%shared>-Block ist mit

my $page = %list->{$m->dhandler_arg()};

der richtige Teil von %list auszuwählen. Dazu dient $m->dhandler_arg(), das den nicht gefundenen URI-Pfadanteil - zum Beispiel searchclassic.html - enthält. Der Code in diesem Block ist in der gesamten Komponente inklusive ihrer Subkomponenten und Methoden sichtbar und wird pro Request einmal ausgeführt. Die Titel-Methode liefert für searchclassic.html daher ‘Suche Klassik’.

Mit dem versteckten Formularparameter genre wird ganz genauso verfahren. Die Anfrage wird weiter an die Komponente search geschickt, die Listing 5 zeigt (die ausgelassenen Teile dienen der Fehlerbehandlung bei nicht eingegebenem Suchtext oder keinen Treffern).

Mehr Infos

Listing 5: pub/search/search - Anzeige der Suchtreffer

<%method title>Suchergebnisse</%method>

<h2 align="center">Trefferliste</h2>
<hr size="1" width="90%">
... ... ...
<%perl>
my @ids;
if ($crit eq "title")
{ @ids = $catalog->searchTitle($text);
} elsif ($crit eq "artist")
{ @ids = $catalog->searchArtist($text);
}
</%perl>
... ... ...
Die folgenden CDs wurden zu Ihrer Suche gefunden
<form method="post" action="/mason/cart/add">
<p>
<table border="0" align="center" cellspacing="5" bgcolor="#eebb00">
<tr>
<th>Titel</th>
<th>K&uuml;nstler</th>
<th>Preis</th>
<th></th>

% for my $cd (@ids)
% { my ($type, $title, $artist, $price) = $catalog->getDetails($cd);
<tr>
<td><%$title%></td>
<td><%$artist%></td>
<td><%$price%></td>
<td><input type="checkbox" name="id" value="<%$cd%>"></td>
</tr>
% }

</table>
</p>
<p>
Bitte markieren Sie diejenigen CDs, die Sie in den Einkaufskorb legen
m&ouml;chten und best&auml;tigen Sie mit dem Knopf unten.
</p>

<hr size="1" width="90%">
<div align="center">
<input type="submit" value="In den Einkaufskorb">&nbsp;
<input type="reset" value="Zur&uuml;cksetzen">
</div>
</form>

<%init>
my $catalog = new Catalog();
$catalog->setType($genre);
</%init>

<%args>
$crit => "title"
$genre => "classic"
$text => ""
</%args>

Suchseite: Ein Defaulthandler erzeugt mehrere Suchmasken, die sich nur im Titel und einem Parameter unterscheiden (Abb. 2).

search nimmt die Parameter crit, genre und text von der jeweiligen Suchmaske per HTTP-POST entgegen, die zur Fehlervermeidung mit Defaultwerten vorbelegt sind. Der <%init>-Block, der bei einem Request stets zuerst ausgeführt wird, erzeugt ein Catalog-Objekt zur Suche in der Datenbank. Je nach Suchkriterium werden die Methoden searchArtist() beziehungsweise searchTitle() der Klasse Catalog aufgerufen. Diese liefern die IDs der gefundenen CDs (Primärschlüssel der Datenbanktabelle CATALOG) zurück. Eine for-Schleife füllt anschließend eine Tabelle mit den Suchergebnissen. Dies alles geschieht in einem Formular, mit dem man die gefundenen CDs dem Einkaufskorb hinzufügen kann.

Ergebnis: Die gefundenen CDs kann der Surfer gleich in den Einkaufskorb klicken (Abb. 3).

Die oben stehenden Abbildungen zeigen eine Suchseite sowie die zugehörigen Ergebnisse, von denen man getrost auf das Hobby des Autors schließen darf.

Nach Auswahl der gewünschten CDs werden deren IDs an die Komponente add gesendet, die Listing 6 zeigt. Beim Senden von Formulardaten mit mehreren Schlüsseln - hier die CD-ID -

<input type="checkbox" name="id" value="<%$cd%>">

erzeugt Mason in der Zielkomponente aus diesen automatisch ein Array. Läse man dort die Daten in einen Skalar ein, enthielte dieser eine Referenz auf das betreffende Array. Das macht das Verarbeiten von Formulardaten in Mason so einfach.

Mehr Infos

Listing 6: pub/cart/add

[Komponente zur Bestückung des Einkaufskorbs]
<%perl>
my $catalog = new Catalog();
for my $cd (@id)
{ $cart->setCount($cd, 1);
my ($type, $title, $artist, $price) =
$catalog->getDetails($cd);
$cart->setPrice($cd,$price);
my $descr = $m->scomp( "descr",
title => $title,
artist => $artist,
price => $price,
);
$cart->setDescr($cd, $descr);
}
</%perl>

<& showCart.html &>

<%def descr>
<i><%$title%></i> von <i><%$artist%></i>
<br>zum Einzelpreis von
<%$price%> DM

<%args>
$title => ""
$artist => ""
$price => 0
</%args>
</%def>

<%init>
my $cart = $session{cart} ||= new Cart();
</%init>

<%cleanup>
$session{cart} = $cart;
</%cleanup>

<%args>
@id => ()
</%args>

add nimmt die CD-IDs im Array @id entgegen und fügt sie dem Einkaufskorb hinzu, der durch die Klasse Cart modelliert ist. Das Cart-Objekt ist in der Benutzersession persistent gehalten. Dazu wird im <%init>-Block eine lokale Kopie des Objekts angelegt (beziehungsweise ein neues Objekt erzeugt), dem $cart->setCount($cd, 1) je ein Exemplar der gewählten CDs hinzufügt. Der Beschreibungstext für jede CD wird in einer eigenen Subkomponente descr erstellt. Schließlich zeigt <& showCart.html &> den Inhalt des Einkaufskorbs an. Am Ende der Komponente wird der <%cleanup>-Block, das Gegenstück zu <%init>, ausgeführt, der das Cart-Objekt explizit in die Session zurückschreibt.

showCart.html, das für die Anzeige des Inhalts des Einkaufskorbs verantwortlich ist, liest die Daten aus dem Cart-Objekt wieder aus und bringt sie in einer Tabelle zur Ansicht. Die einfach gehaltene Klasse Cart, die im wesentlichen get- und set-Methoden bereitstellt, zeigt Listing 7.

Mehr Infos

Listing 7: Cart.pm - Cart-Klasse

package Cart;

use strict;
use vars qw($VERSION);

$VERSION = "1.0";

# ----- Konstruktor -----
sub new { return bless {}, shift }

# ----- getItems() - gibt Liste aller Items im Einkaufskorb zurück
sub getItems { return keys %{$_[0]} }

# ----- delItem() - löscht ein Item aus dem Einkaufskorb
sub delItem { delete ${$_[0]}{$_[1]} }

# ----- reset() - löscht den Inhalt des Einkaufskorbs (nach erfolgter Bestellung)
sub reset
{ my $this = shift;
$this->delItem($_) for keys %$this;
}

# ----- getCount() - gibt die aktuelle Anzahl des übergebenen Items zurück
sub getCount { return $_[0]->{$_[1]}->{count} }

# ----- setCount() - setzt die Anzahl des übergebenen Items auf einen neuen Wert
sub setCount
{ my ($this, $item, $count) = @_;
$this->{$item}->{count} = $count;
$this->delItem($item) if 0 == $count;
}

# ----- getDescr() - gibt die Beschreibung des übergebenen Items zurück
sub getDescr { return $_[0]->{$_[1]}->{descr} }

# ----- setDescr() - setzt die Beschreibung des übergebenen Items
sub setDescr
{ my ($this, $item, $descr) = @_;
$this->{$item}->{descr} = $descr;
}

# ----- getPrice() - gibt den Einzelpreis des übergebenen Items zurück
sub getPrice { return $_[0]->{$_[1]}->{price} }

# ----- setPrice() -----
# * setzt den Einzelpreis des übergebenen Items
sub setPrice
{ my ($this, $item, $price) = @_;

$this->{$item}->{price} = $price;
}

# ----- getTotalPrice() -----
# * gibt den Gesamtwert des Einkaufskorbs zurück
sub getTotalPrice
{ my $this = shift;
my $price = 0;
$price += $this->{$_}->{price} * $this->{$_}->{count} for keys %$this;
return $price;
}

1;

Nach Beendigung des Einkaufs gehts zur Kasse. Hierzu muss sich der Benutzer zunächst anmelden. Alle Benutzerdaten sind in der Datenbanktabelle USER gespeichert, auf die User-Objekte zugreifen. Als Login und Primärschlüssel von USER dient die E-Mail-Adresse des Benutzers. Die Passwörter werden als MD5-Hashes gespeichert. Handelt es sich um einen Neukunden, so muss dieser zunächst die üblichen Daten wie Name, Anschrift und Bankverbindung eingeben.

Obwohl ein Artikel wie dieser viele Aspekte unberührt lassen muss (Debugging, Caching und Filterung), ist das Potenzial dieser komponentenbasierten Technik hoffentlich erkennbar geworden. Für kleine Webauftritte ist Mason mit PHP vergleichbar. Ein möglicher Vorteil liegt in der Vielfalt der verfügbaren Perl-Klassenbibliotheken. Bei größeren Webprojekten kann das Modul seine durch objektorientierte Struktur und Komponentenaufteilung bedingten Stärken klar ausspielen. Jedoch ist hierzu auf der Designseite, die vom Templating mit Auto- und Defaulthandlern profitiert, eine genaue Planung unerlässlich.

Dr. Peter Dintelmann
ist gelernter Mathematiker und für die Dresdner Bank im Bereich Transaction Banking, Market Information Services, tätig. iX

Mehr Infos

iX-TRACT

  • Das Perl-Modul Mason ermöglicht das komponentenbasierte Erzeugen dynamischer Webseiten.
  • Mason-Komponenten liefern entweder HTML-Code aus oder verhalten sich wie Perl-Subroutinen.
  • Auto- und Default-Handler erlauben die Gestaltung einheitlicher Seiten sowie die Ausgabe von Seiten trotz Fehlens einer Komponente.
Mehr Infos

Installation

Mason installiert man wie jedes Perl-Modul (erhältlich bei CPAN) mit den bekannten Schritten perl Makefile.PL; make; make test; make install. Voraussetzung sind das Params::Validate- sowie ein DBM-Modul. Der erste Schritt legt die Datei lib/HTML/Mason/Config.pm an, die unter anderem Angaben über die zu verwendenden DBM- und Serialisierungsmodule für das Caching enthält. Diese Daten, die bei einem Upgrade erhalten bleiben, kann man vor dem make-Schritt manuell anpassen, etwa indem man sich für einen bestimmtem Serializer entscheidet:

'mldbm_serializer' => 'Data::Dumper',

Nach der Installation ist Apache über httpd.conf zu konfigurieren:

Alias /mason /data/www/mason/htdocs
<Location /mason>
PerlRequire /data/www/etc/handler.pl
SetHandler perl-script
PerlHandler HTML::Mason
</Location>

Die PerlRequire-Direktive lädt das Handler-Skript. Die Mason-Installation bringt dazu zwei Muster (mit und ohne Sessionhandling) mit. Im Handlerskript sind das Verzeichnis für die temporären Dateien (data_dir), das Komponentenbasisverzeichnis (comp_root) sowie Benutzer und Gruppe, unter denen Apache läuft, einzutragen.

Wer den Client-Aufruf der Autohandler unterbinden möchte, kann dies durch

<Files "autohandler">
Order deny,allow
Deny from all
</Files>

erreichen. Für Win32-Systeme mit Apache und ActiveState-Perl erhält man eine mod_perl-Distribution, die direkt mit dem PPM installiert werden kann. Details dazu finden sich im zugehörigen Readme. Mason kann als PPM-Paket von ActiveState bezogen und mit dem Einzeiler

C:\>PPM install HTML-Mason

installiert werden (bei Verwendung eines HTTP-Proxies ist zuvor die Umgebungsvariable HTTP_proxy zu setzen).

(hb)