Catalyst ist das Ruby on Rails der Perl-Welt: Bei der Entwicklung von Webapplikationen – beispielsweise eines Quiz – bietet das MVC-Framework enormen Komfort und eine saubere Trennung der Komponenten.
Ob Spiegel-Online “Reicht ihr Latein zum Angeben?” fragt [2] oder Food.aol.com seine Benutzer durchgeschnittene Schokoriegel identifizieren lässt (Abbildung 1) – ein Quiz vertreibt dem Onliner erfolgreich die Zeit. Arbeitskollegen leiten die URLs gern weiter und beim abschließenden Vergleichen der Punktzahlen ergibt sich neben einer anregenden Diskussion auch eine Neuordnung der büroweiten Hackordnung.
![Abbildung 1: Welcher Schokoriegel mag das sein? Ein Schnittbilder-Quiz auf der Website Food.aol.com [3] verlangt Onlinern Wissen über Kalorienreiches ab.](https://www.linux-magazin.de/wp-content/uploads/2008/08/abb1_jpg-9-300x228.jpg)
Abbildung 1: Welcher Schokoriegel mag das sein? Ein Schnittbilder-Quiz auf der Website Food.aol.com [3] verlangt Onlinern Wissen über Kalorienreiches ab.
Was liegt näher, als selbst ein Quiz zusammenzuklopfen? Abbildung 2 zeigt den vorgestellten Rat-Dampfer in Aktion. Damit der Code wiederverwertbar ist, holt die Applikation die Quizfragen samt Multiple-Choice-Antworten aus einer Yaml-Datei (Abbildung 3). Das Beispiel benutzt eine Auswahl aus Fragen der Einbürgerungsprüfung der USA. Die Anwärter müssen zum Beispiel wissen, wie viele Sterne die US-Flagge aufweist und was sie symbolisieren [4].

Abbildung 2: Die mit Catalyst implementierte Quizapplikation für US-Neubürger in Aktion. Hier die Frage nach der Sterne-Symbolik in der Flagge.

Abbildung 3: Multiple Choice: Die Fragen mit drei Antwortvarianten liegen in einer Yaml-Datei. Die jeweils erste Antwort ist die richtige.
Die Webapplikation soll die Yaml-Datei einlesen und die Fragen einzeln auf einer neuen Seite darstellen. Die Yaml-Datei gibt die richtige Antwort stets als erste, doch die Applikation soll später die Antworten zufällig durcheinanderwürfeln, damit der Test spannend bleibt.
Die Implementierung ist nicht sonderlich trickreich, aber es kommt so einiges zusammen: Schön designtes HTML mit dynamisch aufbereiteten Feldern, Session-Management zwischen den einzelnen Fragen, damit die Applikation die Punktzahl des Users nicht vergisst, und zum Schluss eine Ergebnisseite, die dem Benutzer den Endstand mitteilt und zu einer weiteren Runde einlädt (Abbildung 4). Und schließlich darf der Server dem Client niemals über den Weg trauen, denn der könnte schummeln.

Abbildung 4: Am Ende des Quiz angelangt bekommt der Teilnehmer seine Punktzahl angezeigt. Die Applikation muss sich deshalb die Vorgeschichte merken.
Das Framework Catalyst [5] unterstützt Perl-Programmierer bei derartigen Projekten. Es erzeugt automatisch ein Rohgerüst des Programmcode, in das der Entwickler dann nur noch die applikationsspezifischen Teile einfügt. Die Aufteilung der Komponenten in Model (Datenmodell), View (HTML-Darstellung) und Controller (Kontrollfluss) hat sich bei der Entwicklung von Webapplikationen bewährt und ermöglicht eine saubere Codetrennung und folglich leichte Wartbarkeit.
Framework installieren
Die Catalyst-Module liegen auf dem CPAN vor. Wegen ihrer schieren Menge empfehlen sich aber vorgefertigte Pakete. Auf Debian-basierten Systemen sorgt
sudo apt-get install libcatalyst-perl libcatalyst-modules-perl
für die fachgerechte Installation aller Module samt einem Rattenschwanz abhängiger Pakete. Damit der Entwickler nicht bei Adam und Eva anfangen muss, legt das mitgelieferte Skript
catalyst.pl QuizShow
ein neues Verzeichnis »QuizShow« für die neu erzeugte Applikation an und stellt dort auch noch etwa 30 Dateien in verschiedene Unterverzeichnisse hinein, damit das Ganze sofort betriebsfähig ist. Es finden sich unter anderem ein »Makefile.PL«, um die Applikation CPAN-gerecht zu verpacken, vordefinierte Konfigurationsdateien, Modulskelette zum Ausfüllen und diverse Skripte, um neue Teile anzulegen und die Applikation auf unterschiedliche Weise zu starten.
Später lässt sie sich als CGI-Skript oder unter Mod_perl auf einem Apache-Server fahren. Aber während der Entwicklung bietet es sich an, einfach den mitgelieferten Webserver zu starten:
cd QuizShow script/quizshow_server.pl
Sofort fährt der Server wie in Abbildung 5 hoch und gibt akkurat formatierte Informationen über die Serverkonfiguration und die URL, unter der ein Browser ihn erreichen kann, bekannt. Die Standardeinstellung ist »http://localhost:3000«. Wer sie in einen Browser eingibt, bekommt die Catalyst-Startseite zu sehen. In Produktionssystemen kommt später freilich ein Apache-Server zum Einsatz.

Abbildung 5: Der im Catalyst-Paket mitgelieferte Testserver fährt hoch und gibt bereits implementierte Details der Anwendung bekannt.
HTTP mit Gedächtnis
Wenn ein Browser mit einem Webserver kommuniziert, weisen beide zwischen den einzelnen Requests keinen Zustand auf – falls Session-Cookies und Server-seitig gespeicherte Session-Daten diesen nicht explizit sichern. Ein Quiz, das nach jeder Frage den aktuellen Punktestand vergäße, wäre wenig hilfreich, und so muss der Entwickler wohl in den sauren Apfel beißen und diese nicht ganz triviale Logik implementieren. Catalyst bietet glücklicherweise schon ein vorgefertigtes Session-Management an, ebenfalls als Debian-Paket. Der Aufruf
sudo apt-get install libcatalyst-plugin-session-fastmmap-perl
installiert die notwendigen Perl-Module. Damit das Quiz dem Browser beim ersten Andocken automatisch ein Session-Cookie unterjubelt, mit diesem einen Server-seitigen Speicher indiziert und dort Userdaten speichert, muss man die Zeile
use Catalyst qw/-Debug ConfigLoader Static::Simple/;
der vorher automatisch erzeugten Datei »lib/QuizShow.pm« zu
use Catalyst qw/-Debug ConfigLoader Static::Simple Session Session::State::Cookie Session::Store::FastMmap/;
umbauen. Damit kann eine Applikation jederzeit bequem mit der Methode »session()« des Catalyst-Kontextobjekts auf den Session-Hash zugreifen. Der enthält die Session-Daten im Key-Value-Format; Catalyst sichert sie automatisch unter der im Browser-Cookie gesetzten Session-ID und verwaltet sie auf dem Server.
Das Verfahren funktioniert natürlich nur, wenn der Browser bei jedem neuen Request mit demselben Server spricht und nicht etwa mit einer zufälligen Instanz einer Serverfarm. Für anspruchsvollere Konfigurationen bietet Catalyst darum datenbankbasierte Sessions an.
Sehenswert
Als View, also als Anzeige-Komponente, kommt hier Perls Template Toolkit [6] zu Ehren. Es definiert eine bewusst simpel gehaltene Template-Sprache, mit der der Anwender dynamische Felder in statischem HTML definiert. Sie kennt zwar auch einfache Programmlogik wie Bedingungen oder Schleifen, distanziert sich aber bewusst von anderen Lösungen, die die volle Kapazität einer Skriptsprache anbieten. Der Grund: Allzu oft verführt dies unerfahrene Entwickler dazu, immer mehr Spaghetticode in die Darstellungsschicht einzuschleusen, statt die saubere Trennung von Kontrollfluss (Controller) und Darstellung (View) einzuhalten. Der Aufruf
script/quizshow_create.pl view TT TT
des von Catalyst im Projektskelett mitgelieferten Skripts »quizshow_create.pl« fügt zum vorher erzeugten Dateibaum das Perl-Modul »lib/QuizShow/View/TT.pm« hinzu. Das erste »TT« steht für den Namen des zu erzeugenden Moduls (»TT.pm«), das zweite dafür, dass es sich bei letzterem um eine vom Template-Toolkit-View abgeleitete Klasse handelt. Denn Catalyst unterstützt auch noch Mason und HTML::Template.
Das Modul »TT.pm« macht Catalyst auch damit bekannt, dass Dateien mit der Endung ».tt« mit dem Template-Toolkit-Prozessor zu bearbeiten sind, bevor der Webserver sie ausliefert. Abbildung 6 zeigt die Template-Datei »quiz.tt«, die im Verzeichnis »root« des neu erzeugten Catalyst-Projekts liegen muss.

Abbildung 6: Das Catalyst-Template »quiz.tt« bestimmt das Erscheinungsbild der Applikation. Für die Interpretation ist der Toolkit-Prozessor zuständig.
Am Anfang prüft die in Template-Toolkit-Syntax als »[% IF %]« geschriebene Bedingung, ob (weitere) Fragen vorliegen. Wenn nicht, zeigt der Browser den Endstand, andernfalls mit den Template-Variablen »score_ok« und »score_nok« den Spielstand und die Anzahl der noch ausstehenden Fragen an.
Dann gibt das Template die aktuelle Frage aus und iteriert mit einer »FOREACH«-Schleife über die in zufälliger Reihenfolge vorliegenden Antworten und präsentiert sie als Radiobuttons zum Anklicken. Ein Submit-Button schickt das Webformular ab und kontaktiert den Webserver wegen einer fehlenden URL einfach wieder unter der ursprünglichen URL.
Kontrolliere den Fluss
Um den Kontrollfluss der Applikation zu definieren, muss auch ein Controller »Quiz.pm« her:
script/quizshow_create.pl controller Quiz
Der Kommandozeilenaufruf erzeugt die Datei »lib/QuizShow/Controller/Quiz.pm«, die der Entwickler mit dem in Listing 1 namens »Ctrl-Quiz.pm« gezeigten Code erweitert.
|
Listing 1: |
|---|
01 ###########################################
02 package QuizShow::Controller::Quiz;
03 # Mike Schilli, 2008 (m@perlmeister.com)
04 ###########################################
05 use strict;
06 use warnings;
07 use base 'Catalyst::Controller';
08
09 ###########################################
10 sub quiz : Global {
11 ###########################################
12 my ( $self, $c, @args ) = @_;
13
14 if((@args and $args[0] eq "reset") or
15 !defined $c->session->{next_question} or
16 $c->session->{"next_question"} == -1
17 ) {
18 $c->session->{"next_question"} = 0;
19 $c->session->{"score_ok"} = 0;
20 $c->session->{"score_nok"} = 0;
21 $c->session->{"total"} =
22 $c->model('Questions')->total();
23 $c->response->redirect($c->uri_for());
24 $c->detach();
25 }
26
27 if(my $answer =
28 $c->req->param("answer")) {
29
30 if($answer ==
31 $c->session()->{"correct_answer"}) {
32
33 $c->session()->{"score_ok"}++;
34 } else {
35
36 $c->session()->{"score_nok"}++;
37 }
38 }
39
40 my $next_question =
41 $c->session()->{"next_question"} || 0;
42
43 $c->stash->{template} = 'quiz.tt';
44
45 my ($question, @answers) =
46 $c->model('Questions')->
47 get_question( $next_question );
48
49 if(defined $question) {
50 @answers = map { [$_, 'incorrect'] }
51 @answers;
52 $answers[0]->[1] = 'correct';
53
54 my $correct_answer;
55 my $i = 0;
56
57 while (@answers) {
58 my $pick = splice(@answers,
59 rand @answers, 1);
60 push @{ $c->stash->{answers} },
61 { text => $pick->[0],
62 num => ++$i};
63
64 $c->session()->{"correct_answer"}= $i
65 if $pick->[1] eq 'correct';
66 }
67 $c->session()->{"next_question"} =
68 $next_question + 1;
69 } else {
70 $c->session->{next_question} = -1;
71 }
72
73 $c->stash->{question} = $question;
74
75 for(qw( total score_ok score_nok
76 next_question)) {
77 $c->stash->{ $_ } =
78 $c->session()->{ $_ };
79 }
80 }
81
82 1;
|
»Quiz.pm« definiert die Methode »quiz()«, die mit dem Attribut »:Global« versehen ist. Damit fängt Catalyst alle Requests unter der URL »http://localhost:3000/quiz« ab und gibt etwaige weiterführende Pfade im Parameter »@args« an die Applikation weiter. Hängt der Benutzer zum Beispiel »/quiz/reset« an die URL an, leitet Catalyst das Gesamtkunstwerk an die Methode »quiz()« weiter und belegt das erste »@args«-Element mit »reset«.
In diesem Fall setzt »quiz()« die Session-Daten zurück auf null und lässt den Browser einen Redirect auf die Startseite der Applikation ausführen. So wandelt sich die im Browser angezeigte URL wieder von »/quiz/reset« auf »/quiz«, der Controller setzt die Zähler für falsche und richtige Antworten auf null zurück und ein neues Quiz darf beginnen.
Die Methode »uri_for()« des Catalyst-Objekts produziert aus relativ zur Applikationswurzel angegebenen URLs vollständige URLs, auf die ein Browser einen Redirekt ausführen kann. Die »redirect()«-Methode selbst setzt nur einen HTTP-Header, aber unterbricht den Kontrollfluss nicht, sodass es wichtig ist, in Zeile 24 ein »$c->detach()« nachfolgen zu lassen.
Das veranlasst Catalyst dazu, den gerade bearbeiteten Request abzubrechen. Übrigens eine sehr praktische Methode, den Kontrollfluss abzubrechen, selbst wenn man sich gerade in einem verschachtelten Schleifenkonstrukt befindet. Die Variable »$c« zeigt auf das Kontext-Objekt des Catalyst-Systems, sie wird den Controllermethoden beim Aufruf beigepackt und eignet sich dafür, so ziemlich alles in den Tiefen des Catalyst-Systems Verborgene hochzuholen.
Speichern mit Methode
Den durch Cookies und Server-seitige Speicherung persistent gemachten Session-Hash bringt die Methode »session()« des Catalyst-Objekts »$c« zum Vorschein. Er führt die Einträge »next_question« (Index der nächsten zu stellenden Frage im Yaml-Array), »score_ok« (Anzahl der richtig beantworteten Fragen), »score_nok« (Anzahl der falsch beantworteten Fragen), »total« (Gesamtzahl der Fragen) und »correct_answer«, damit der Server weiß, welche der durcheinandergewürfelten Antworten nun die richtige für die gerade gestellte Frage ist.
Mit »$c->req->param(“answer”)« in Zeile 28 besorgt Catalyst den vom Browser geschickten Formularparameter »answer« aus dem Request-Objekt. Diese Zahl entspricht der Nummer 1, 2 oder 3 des vom Benutzer aktivierten Radiobuttons, mit dem er eine Antwort ausgewählt hat. Stimmt der Wert mit dem vor der Auslieferung der Webseite im Session-Hash auf dem Server hinterlegten Wert überein, war die Antwort richtig und der Controller zählt die Session-Variable »score_ok« um eins hoch.
Spickzettel im Session-Hash
In Zeile 43 bestimmt der Controller »quiz.tt« als Template und legt anschließend die Werte der Variablen aus dem Template im so genannten Stash fest. Setzt der Controller zum Beispiel »$c->stash->{score _ok}«, substituiert der Template-Prozessor den Template-Eintrag »[% score_ok %]« mit dem Controller-Wert.
Stash-Variablen dürfen beliebig verschachtelte Datenstrukturen sein, so enthält der Stash-Eintrag »answers« zum Beispiel eine Referenz auf einen Array, dessen Elemente wiederum Referenzen auf Hashes sind, die unter den Keys »text« und »num« den Text und die Nummer einer Antwort enthalten.
Das Template »quiz.tt« iteriert zur Darstellung über diesen Array, weist dem jeweils bearbeiteten Element als Alias »answer« zu und greift anschließend mit »[% answer.text %]« und »[% answer.num %]« auf die dahinter versteckten Hasheinträge zu – eine sehr praktische Eigenschaft des Template-Toolkits, die viel Tipparbeit spart.
Die Zeilen 50 bis 52 bauen aus dem aus der Yaml-Datei extrahierten Antworten-Array eine Datenstruktur, die der ersten Antwort den Eintrag »correct« zuweist und allen weiteren den Wert »incorrect«. Um die Antworten in zufälliger Reihenfolge darzustellen, holt die »while«-Schleife ab Zeile 57 ein zufälliges Element aus diesem Array von Arrays. Ist es die vorher als korrekt markierte Antwort, merkt sich der Controller deren Nummer für später im Session-Hash.
Zeile 61 macht aus der Antwort einen Hash mit den Einträgen »text« und »num« und schiebt diesen ans Ende des Stash-Array »answers«. Von dort holt das Template »quiz.tt« die Daten ab und erzeugt dynamisch das rausgehende HTML.
Yaml als Model
Catalyst arbeitet normalerweise mit Datenbank-basierten Datenmodellen. Hier jedoch liegen die Daten in einer Yaml-Datei. Ein eigenes Modell zu definieren ist nicht weiter schwierig: Der Aufruf
script/quizshow_create.pl model Questions
legt die Datei »lib/QuizShow/Model/Questions.pm« an, die wiederum der Entwickler mit dem in Listing »Mod-Questions.pm« gezeigten Code auffüllt.
Die Yaml-Datei in Abbildung 3 definiert einen Array von Einträgen für jede Frage, die der Test stellt. Mit »#« beginnende Kommentarzeilen bleiben dabei außen vor. Die einzelnen Array-Einträge beginnen an einem Bindestrich ohne weiteren Zusatz und bestehen ihrerseits wiederum aus Arrays, die je vier Elemente enthalten: den Wortlaut der Frage, gefolgt von der richtigen Antwort und zwei passenden falschen Antworten.
In Listing 2 definiert die Variable »$FILE« den Pfad zur Yaml-Datei. Die Methode »total()« liest die Daten ein und gibt die Anzahl der Fragen zurück, damit die Webapplikation anzeigen kann wie viele Fragen noch ausstehen. »total()« liefert hierzu den Yaml-Array in einem skalaren Kontext zurück, was in Perl die Array-Länge angibt.
|
Listing 2: |
|---|
01 ###########################################
02 package QuizShow::Model::Questions;
03 # Mike Schilli, 2008 (m@perlmeister.com)
04 ###########################################
05 use strict;
06 use warnings;
07 use base 'Catalyst::Model';
08 use YAML qw(LoadFile);
09
10 my $FILE = "/home/mschilli/data/quiz.yml";
11
12 ###########################################
13 sub total {
14 ###########################################
15 my $yml = LoadFile $FILE;
16 return scalar @$yml;
17 }
18
19 ###########################################
20 sub get_question {
21 ###########################################
22 my($m, $index) = @_;
23
24 my $yml = LoadFile $FILE;
25 return undef if $index > $#$yml;
26 return @{ $yml->[$index] };
27 }
28
29 1;
|
Die Methode »get_question()« in Zeile 20 holt die zu einem Array-Index (0 bis n-1) gehörenden Fragen und deren Antworten hervor und gibt sie als Liste zurück. Falls der Index nicht auf einen gültigen Eintrag zeigt, gibt sie »undef« zurück. Dies ist für das Onlinequiz das Signal, dass keine Fragen mehr übrig sind und es den Endstand anzeigen muss.
Will der Controller auf das von ihm abgeschottete Datenmodell zugreifen, schnappt er sich das ihm vorliegende Catalyst-Objekt und ruft »$c->model(\’Questions\’)« auf. Das gibt ihm eine Instanz des »Questions«-Datenmodells, dessen Methoden »get_question()« und »total()« er anschließend starten kann.
Installations-Dreisprung
Um die fertige Catalyst-Applikation auf einem Produktionsserver zu installieren, führt man einfach den mit CPAN-Modulen üblichen Dreisprung aus:
cd QuizShow perl Makefile.PL make install
Catalyst pflanzt damit die für die Applikation notwendigen Module, Templates und Skripte in die auf der jeweiligen Plattform definierte Perl-Hierarchie. Statt des mitgelieferten Perl-Servers empfiehlt sich eine Mod_perl-Installation, damit Apache 2 die Programmlogik zügiger ausführt:
PerlModule QuizShow <Location /> SetHandler modperl PerlResponseHandler QuizShow </Location>
Für Apache 1.3 gibt es ebenfalls eine Konfiguration, die die ausführliche Catalyst-Dokumentation beschreibt. Kommt es nicht auf Geschwindigkeit an, geht auch ein CGI-Skript, das Catalyst als »quizshow_cgi.pl« gleich mitinstalliert. Wer es ins konfigurierte CGI-Verzeichnis des Webservers stellt und die URL »http://localhost/cgi/quizshow_cgi.pl/quiz« aufruft, startet auch das Quiz.
Wer vom Testserver auf den Webserver umstellt, muss beachten, dass die Sessions im Verzeichnis »/tmp/quizshow« liegen sollen und der Webserver normalerweise unter einem anderen User läuft. Passt man die Nutzerrechte entsprechend an, kann der neue Server den alten Session-Store übernehmen.
Ein ähnliches Problem stellt sich mit Apache 2 und Mod_perl 2: Das Perl-Modul ist auf Ubuntu mit den Paketen »libapache2-mod-perl2« und »libcatalyst-engine-apache-perl« ruck zuck installiert. Allerdings mault der Session-Store Session::Store::FastMmap anschließend, dass er nicht mit Threads zurechtkommt. Die Alternative Session::Store::File funktioniert aber prächtig – also »lib/QuizShow.pm« anpassen.
Jedoch legt Apache 2 die entsprechenden Verzeichnisse beim Hochfahren als Root an (!) und kann später nicht mehr darauf schreibend zugreifen, wenn er seine Kinder mit weniger Privilegien startet. Das Kommando »sudo chown -R www-data /tmp/quizshow« behebt das Problem, indem es dem Session-Store die Eigentumsrechte des Webserver-Nutzers zuweist.
Ausblick
Catalyst bietet viel mehr als die vorgestellten Funktionen. Es taugt ebenso für kleine wie für große Projekte, an denen Teammitglieder in Teilbereichen arbeiten. Es bietet ein ausgereiftes Test-Framework, das ebenfalls frei Haus beim Anlegen eines neuen Projekts entsteht. Auch sich dynamisch auffrischende Ajax-Webseiten sind machbar. Mit einem Erweiterungsmodul schickt die Webapplikation dann zum Beispiel Json-Daten auf im Browser wartendes Javascript, in dem sich das mollig-warme Web-2.0-Feeling einstellt, da unnötige Page-Reloads entfallen.
Neben den online verfügbaren Manualseiten und Tutorials [5] gibt das Buch “Catalyst” [7] einen recht guten Überblick. Letzteres ist jedoch weniger zum Nachschlagen geeignet, da ihm sowohl ein ordentlicher Index als auch die Referenz-typische Detailtiefe fehlt. (jk)
|
Infos |
|---|
|
[1] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2008/09/Perl] [2] “Reicht ihr Latein zum Angeben?”: [http://www1.spiegel.de/active/lateintest/fcgi/lateintest.fcgi] [3] “Candy Bar Identification”: [http://food.aol.com/play-with-your-food/candy-bar-id-quiz/?u] [4] US-Einbürgerungsprüfung: [http://www.washingtonpost.com/wp-srv/national/longterm/citizen/citizen.htm] [5] Catalyst: [http://www.catalystframework.org] [6] Template-Toolkit: [http://template-toolkit.org] [7] Jonathan Rockaway, “Catalyst”: Packt Publishing, 2007 |
|
Der Autor |
|---|
|
|






