!init OPT_STYLE="paper" !define DOC_NAME "PApp - Perl-Anwendungen für die Zukunft des WWW" !define DOC_AUTHOR "Marc Lehmann " !build_title H1: PApp - Wie beschreibt man ein Chamäleon? PApp ist schwer zu beschreiben, da es sich nicht auf ein spezielles Problem spezialisiert, sondern möglichst eine offene Universallösung sein will. Unter anderem ist PApp: - ein Embedded-Perl-Dialekt. Es gibt verschiedene Formen: Zum einen den "literal programming style", der nicht XML-konform ist, sondern beliebige Daten ausgeben kann und viele Möglichkeiten des Einbettens von Perl in XML. Zum anderen einen Standard-XML-Dialekt, der eine Mischung aus beidem darstellt. - eine Bibliothek (aus vielen Perl-Modulen), die das Arbeiten in einer CGI-artigen Umgebung erlauben. Die Umgebung hält sich mehr an Apache, ist jedoch unabhängig davon, ob die PApp-Anwendung in einer CGI-Umgebung, unter mod_perl oder (z.B.) unter CGI::SpeedyCGI ausgeführt wird. - eine Bibliothek, die das Arbeiten mit HTML, XML, SQL und anderen, häufig benutzten Umständlichkeiten vereinfacht. - eine grosse "State-Maschine", die technisch getrennte Programmteile (z.B. CGI) logisch zu {{einer}} Applikation zusammenfasst. - ein XSLT-Stylesheet-Prozessor. Damit kann man Layout und Inhalt voneinander trennen. Oder auch Ausgabeformat (PDF/HTML/XML etc.) vom Layout. Oder vom Inhalt... Oder der Benutzersprache... H2: Warum PApp geschrieben wurde Grosse und kleine Applikationen für das WWW zu erstellen ist sehr arbeitsaufwendig. Session-Variablen kennt Perl (zum Glück ;) nicht, Session- und Usertracking natürlich auch nicht. Es gibt sehr gute Module, die einem einen Teil der Arbeit abnehmen, aber es ist immer sehr aufwendig und umständlich. Dies führt zu unübersichtlichen Programmen, die noch dazu auf viele Dateien aufgeteilt sind, die nicht unbedingt der inneren Logik des Programmes entsprechen. Auch Sicherheitsfehler (wie das Übergeben von sicherheitsrelevanten Daten über {{hidden-fields}} oder ähnliches) passieren schnell, schliesslich muss man sich bei jeder Seite überlegen, wie man seine Daten weiterreicht. Datenbankabfragen, das Einbauen des Layouts usw. sind sehr umständlich. Internationalisierung existiert praktisch nicht. PApp vereinfacht alle diese Probleme, indem es sie weitestgehend automatisiert. H1: Grundlegende PApp-Features oder "Nie wieder CGI" Zunächst möchte ich einige der vielen Features von PApp vorstellen (natürlich die wichtigsten ;). Wem das zu langweilig ist und erstmal eine richtige PApp-Applikation sehen will, sollte zum nächsten Abschnitt gehen, "Eine einfache Anwendung mit PApp". H2: Applikationen statt Einzelseitenverwaltungsmonstren Bei CGI oder anderen Schnittstellen ist die Sicht auf die Anwendung protokollbedingt seitenbasiert - jede "logische" Seite ist eine Datei oder eine Fallunterscheidung innerhalb der Datei - gemeinsame Komponenten müssen in Bibliotheken (oder auch einfachen "Include"-Dateien; das ist im Prinzip das gleiche) abgelegt werden, die auf jeder Seite erneut geladen, konfiguriert und eingebaut werden müssen. Perl-Programme funktionieren jedoch anders, da gibt es keine Zustände, die beim Anklicken wechseln, sondern ein lineares Programm. Dies läßt sich zwar auch mit PApp nicht verwirklichen, aber es tut sein Bestes, diese Einschränkungen aufzuheben, indem es ganze Anwendungen bearbeitet: Ob mehrere Seiten in einer Datei oder eine Seite auf mehrere Dateien verteilt wird ist in PApp egal. H2: Development/Maintainance: Aktivposten statt Bremsklötzen H3: Entwicklungshilfe Programme müssen erst entwickelt werden. Dabei werden Fehler gemacht. Daran ändert PApp nichts. Da bei PApp sehr viele Komponenten zusammenwirken, können die Fehler entsprechend komplex sein. Eine gute Unterstützung bei der Fehlersuche ist also wichtig. Dies fängt damit an, dass die Zeilennummern des Quelltextes auch nach Kompilieren von Perl/XML zu reinem Perl oder z.B. in ein XSLT erhalten bleiben. Sollte ein Fehler auftreten, so hat man sofort Zugriff auf die Quelldateien, einen kompletten Backtrace und natürlich die State-Daten. Das Exception-System von PApp ist einfach zu benutzen (C genügt, will man es schöner haben benutzt man C ;) und beliebig erweiterbar. Im Betrieb will man natürlich keine ausführlichen Fehlermeldungen, womöglich komplett mit Passwort der Datenbank usf... Deshalb protokolliert PApp alle Fehler in eine SQL-Datenbank zusammen mit der State-ID, so dass nur die Fehlerkategorie erscheint (plus ein nettes Textfeld, in dem der Benutzer zusätzliche Informationen eingeben kann). Solange die Seite nur wiederholbare Aktionen enthält, kann der Entwickler den Fehler jederzeit reproduzieren. H3: Datenbanken Wie soll man ohne auskommen? Natürlich garnicht. In Perl gibt es ein ganz wunderbares System (DBI!), um mehr oder weniger genormt auf SQL-Datenbanken zugreifen zu können. Jetzt ist "nacktes" DBI recht umständlich, zumindest für mich, für den vier Aufrufe pro SQL-Befehl entschieden zu viel sind. Vor allem, wenn man diese aus effizienzgründen über das Programm verstreuen muss (C und C). In PApp erledigt man die meisten Aufgaben mit der C-Funktion (den Rest erledigt man mit C). Der folgende Aufruf beispielsweise ersetzt C, C, C und C: !block perl my $st = sql_exec \my($p, $w), "select purzel, wurzel from table where name like ?", $S{search}; while ($st->fetch) { echo "$p, $w\n"; } !endblock Der besondere Clou: Die SQL-Statements werden gecached, d.h. im Allgemeinen nur ein einziges mal Cd. Bei Datenbanken, die einiges an Aufwand in diesem Schritt investieren (Stichwort Query-Optimizer) ist das ein grosses Plus. Sogar bei Datenbanken, die dies nicht tun (z.B. MySQL) steigt die Geschwindigkeit merklich. Zudem stehen C und das C nicht mehr weit auseinander (Initialisierung vs. Benutzung!). Bei Datenbanken, die eine Art "insertid" unterstützen (zum Glück fast alle), kann man diese nach einem C gleich mit abfragen: !block perl my $id = sql_insertid sql_exec "insert into tiere (name) values (?)", "hase"; !endblock Den Database-Handle habe ich übrigens nicht vergessen: Wenn man keinen angibt, wird ein Default-Handle benutzt. Geschickterweise wird dieser von PApp selbst gesetzt, so dass man sich im Normalfall noch nicht einmal um die Datenbankverbindung kümmern muss (weil diese auf dem Produktionssystem meistens andere Passwörter benötigt als auf dem Entwicklungssystem, stellt man diese geschickterweise bei der Konfiguration der Applikation ein). <<>> H2: Trennung von Quelltext und Sprache H3: XML XML wird in mehreren Teilen von PApp unterstützt bzw. gefordert: Der Standard-Dialekt von PApp ist in XML geschrieben, d.h. um "normale" PApp-Applikationen zu schreiben muss man sich XML bedienen. Die Ausgabe von PApp (meistens Text) ist beliebig, sofern man keine Stylesheets benutzt. Tut man das, muss die Ausgabe natürlich in XML-Syntax sein. Und last-not-least gibt es ein PApp-Modul, mit dem man beliebige XML-Fragmente mit eingebettetem Perl-Quelltext bearbeiten/ausführen/anzeigen kann. Die Entscheidung, XML so zentral einzusetzen, fiel mir nicht leicht: Meiner Meinung nach ist XML wunderbar dafür geeignet, Daten innerhalb von Programmen zu verarbeiten und vor allem auszutauschen. Toll ist, dass Menschen XML notfalls lesen und auch schreiben können. Ansonsten ist doch die Mächtigkeit von SGML (oder etwas anderem) vorzuziehen. XML jedoch hat bestechende Vorteile: HTML ist eine XML-Applikation (und HTML ist wichtig ;); es existiert eine breite Unterstützung für XML; es gibt bestehende Standards für Stylesheets, Layout, Metadaten und mehr. Schliesslich ist XML auch noch sehr schnell. Nun ja. Das hat mich trotzdem nicht davon abgehalten, noch etwas draufzusetzen: Fast alle Dokumente können in einem "Meta-Format" mit eingebettetem Perl-Quelltext geschrieben werden. Die Standard-Syntax dafür bedient sich vier sog. "Modus-Umschalter", die jeweils vom "Verbatim" in den "Perl"-Modus bzw. zurück schalten: !block verbatim <: schalte von HTML in Perl um schalte in den HTML-Modus ?> schalte in den interpolierten HMTL-Modus !endblock Eine HTML-Seite kann man z.B. so schreiben: !block perl

Ein bisschen HTML

<: echo "mit eingebautem perl" :> <: print "so gehts auch, ist aber unendlich wenig langsamer ;)" :> <:for my $text (qw(Noch eine komische Methode)) { ?>$text <: }:> !endblock Die Modus-Umschalter verhalten sich dabei wie eine Statement-Grenze in Perl (also C<;>). H3: I18n I18n steht kurz für Internationalisierung: Das englische Wort {{Internationalization}} hat 18 Buchstaben zwischen dem 'I' und dem 'n', und da das Wort für den Durchschnittsamerikaner viel zu lang und kompliziert ist, hat man diese 18 Buchstaben einfach durch "18" ersetzt. In Kontext von PApp bedeutet dies, dass beinahe überall Textmeldungen übersetzt werden können (wer das C-Paket kennt, mit dem üblicherweise C-Programme internationalisiert werden, wird sich fast sofort Zuhause fühlen). Hier ist ein Beispiel: !block verbatim

__"Contents"

<:for (1..10) {:> $i:> <:}:> !endblock Wie man sieht, kann man C<__"text"> sowohl im HTML/XML/wasauchimmer-Quelltext benutzen, als auch auf Perl-Ebene aufrufen. Die C<__>-Funktion erledigt dabei zwei Aufgaben: Zunächst werden damit Textkonstanten zum übersetzen {{markiert}}. Zur Laufzeit wird dann in der Übersetzungstabelle nachgesehen und der (evt.) übersetzte String zurückgeliefert. Wenn man Daten z.B. aus einer Datenbank in eine Variable holt, darf man diese natürlich {{nicht}} markieren, da sie ja nur zur Laufzeit einen String {{enthält}}, selbst aber keine Textkonstante ist. In solchen Fällen verwendet man C: !block perl my $st = sql_exec \my($id, $name), "select id, name from table"; while ($st->fetch) { echo "
  • ", gettext$name, "

  • "; } !endblock In welche Sprache jeweils übersetzt wird, entscheidet bei HTTP die Einstellung des Browsers, wobei man zweckmässigerweise ein Menü anbietet, in dem der Benutzer dies überschreiben kann: !block perl "en":> "de":> !endblock Auch die Spracheinstellung ist eine Preferences-Variable. Im Gegensatz zum C-Paket müssen die Quelltexte nicht alle in einer Sprache sein. Bei C ist das auch weniger ein Problem, da der Quellcode eines Programms meistens in einer (natürlichen) Sprache geschrieben wurde, in PApp kann man aber Seiten dynamisch einfügen oder Datenbanken übersetzen. Da diese meist nicht vom selben Team geschrieben werden, ist es nützlich, dort unterschiedliche Sprachen verwenden zu können. <<>> <<>> H3: Unicode / Zeichensatzunabhängigkeit Intern unterstützt PApp genau zwei Datentypen (genau wie Perl selbst): {{Binärdaten}} und {{Text}}. Binärdaten verwendet man üblicherweise für Bilder, Datei-Downloads und ähnliches. Diese Daten werden von PApp nicht angerührt. Ganz anders Text: Perl arbeitet intern mit Unicode, das z.Zt. entweder in ISO-8859-1 oder UTF-8 gespeichert wird. Dieser interne Zeichensatz ist völlig unabhängig von der Ausgabe, d.h. der Text wird bei der Ausgabe automatisch in den gewünschten Zeichensatz kodiert (das geschieht wieder Browser/Benutzer/Programmabhängig wobei von den gebräuchlichen Browsern nur Netscape und Lynx die entsprechenden Header liefern). Umgekehrt werden Formulardaten u.ä. automatisch in Unicode gewandelt, d.h. in C<%P> steht Unicode auch wenn der Browser ein Formular in ISO-2022-JP zurückgeschickt hat. Ein JPEG-Bild kann man z.B. so ausgeben: !block perl content_type "image/jpeg", undef; $gd->jpeg(80); !endblock Während man eine japanische Benutzerin vielleicht mit ISO-2022-JP zufriedenstellen kann: !block perl content_type "text/html", "iso-2022-jp"; echo __"Hoffentlich war der Übersetzer fleissig"; !endblock H2: Trennung von Daten und Layout/Protokoll Sprache und Quelltext trennt PApp ja schon. Jetzt muss man noch das Layout vom eigentliche Programm bzw. das Protokoll von den eigentlichen Daten trennen. Mit XSLT-Stylesheets geht das. Und mehr: "Browser XYZ hat ein Problem? Ich fix' es im Stylesheet und vergesse es dann einfach." (z.B. sollte man leere HTML-Tabellenzellen für Netscape lieber mit einem C< > füllen). "Es soll WML (oder CHTML) sein? Ich weiss zwar nicht, wozu das ganze WML-Zeugs sinnvoll sein soll, aber dann mache ich halt' ein Stylesheet dafür." Und eine der wichtigsten Anwendungen: "Wir brauchen eine Version zum drucken. Dafür wandeln wir unser XML-Dokument in XSL" (und dann nach Latex, PDF...). Das bedeutet, dass man - Planung natürlich vorausgesetzt - einen hohen Grad an Protokollunabhängigkeit erreichen kann, ohne den Quelltext zu sehr mit Layout-Entscheidungen o.ä. belasten zu müssen. Leider gibt es noch keine Web-Designer, die XSLT statt HTML/CSS liefern, aber (meine) Vision des Webs der Zukunft sieht so aus: Der Server liefert XML zusammen mit einem XSLT (vom Designer), welches XSL erzeugt. Dieses wird dann angezeigt/gedruckt etc.a.. Metadaten (mit dem etwas besseren Nachfolger von RDF) gestatten dann Fragen wie: "Wer hat dieses Dokument geschrieben?", "Wozu dient es?" aber auch: "Wieso ist das Ding so bunt?". H2: Trennung von Funktionalität und Ausführungsumgebung Plattform-Unabhängigkeit: ein toller Begriff. Was bedeutet er? Nicht viel. Bei PApp bedeutet es, dass auf Unix-Abhängigkeiten möglichst verzichtet wurde und sich PApp nicht an einen bestimmten Server (z.B. Apache) bindet. In der Praxis kann man als Platform mod_perl oder CGI verwenden und für Unfälle wie Windoze schere ich mich einen Dreck (d.h. ich habe momentan nicht vor, für proprietäre Schnittstellen Module zu schreiben bzw. habe PApp nur mit den beschriebenen Plattformen getetst). Aber auch andere Umgebungen wie Text-Interfaces sind denkbar: Da PApp der Applikation immer eine gleichbleibende Umgebung anbietet, muss man lediglich ein Schnittstellenmodul schreiben. H2: Persistente Variablen für Sessions und User PApp kümmert sich automatisch um persistente Variablen. So kann man automatisch Variablen erzeugen, die über eine gesamte Sitzung (die mit dem Aufruf der ersten URL beginnt und dann einen Baum bildet) persistent sind. Das geht so einfach, dass man spezielle Hilfsmittel braucht, um nicht an jeder Stelle der Sitzung Zugriff auf beinahe alles zu besitzen. Persistente Variablen gibt es in verschiedenen Geschmäckern. Es gibt: - Session-Variablen (sogenannte {{state keys}}), die über eine gesamte Sitzung erhalten bleiben. Diese werden im Hash C<%S> gespeichert. Man kann beliebige Werte darin ablegen (solange C diese serialisieren kann). Meistens geschieht dies durch Anklicken eines Links, weniger häufig direkt. - Benutzerabhängige Variablen (die auch über Sitzungsgrenzen hinaus erhalten bleiben und deshalb {{preferences items}}, Voreinstellungen, heissen). Auch diese befinden sich in C<%S>, von wo sie automatisch in die Benutzerdatenbank wandern bzw, von dort gelesen werden. - Lokale Variablen ({{local keys}}) zu nur innerhalb einer Seite oder einer Gruppe von Seiten Bedeutung haben. Diese befinden sich ebenfalls in C<%S> und werden automatisch daraus entfernt. - oder beliebige Kombinationen davon. Ein Beispiel für eine Sitzungsvariable ist die Information, ob der Benutzer sich schon angemeldet/eingeloggt hat oder ob er bestimmte Zugriffsrechte besitzt. Eine benutzerabhängige Variable ist z.B. die Sprache, die der Benutzer zuletzt ausgewählt hat (global) oder die Anzahl der Tabellenzeilen, die der Benutzer gerne auf einer bestimmten Seite angezeigt haben möchte. Eine lokale Variable ist z.B. ein Datenbank-Objekt, das über mehrere Seiten (z.B. einer Transaktion) gebraucht wird und danach seine Gültigkeit verliert. Dadurch ergeben sich mehr Möglichkeiten, als man erwarten würde: Unabhängige Komponenten (z.B. Werbebanner oder Foren) sind auf normale Weise nur sehr schwer zu programmieren, da sie immer Hilfe vom "Hauptprogramm" erfordern, wenn es um die Parameterübergabe geht. In PApp benutzt man einfach persistente Variablen. Ein Beispiel für eine etwas ungewöhnlichere Komponente ist die {{editform}}-Bibliothek. Mit ihr kann man HTML-Formulare erstellen, die sich direkt an eine bestimmte Variable binden: !block perl ef_begin; ef_string \$S{search}; ef_end; !endblock Dieses Beispiel stammt von einer Seite, die Objekte aus einer Datenbank anzeigt und sich dabei auf solche beschränkt, die ein Suchwort enthalten. Das Formular bindet die State-Variable C an ein HTML-Textfeld. Bei der Ausgabe wird der aktuelle Wert von C<$S{search}> benutzt. Ändert der Benutzer den Text und schickt das Formular ab wird die Seite neu aufgebaut - mit einem anderen Wert in C<$S{search}>. Da C beliebige Perl-Referenzen akzeptiert und man in PApp auch Referenzen auf Dateien, SQL-Spalten etc... erzeugen kann, werden viele Formulare zum Kinderspiel. Zusätzlich zu den Sitzungsvariablen gibt es noch sogenannte {{Argumente}}, die nur eine Seite lang "persistent" sind, d.h. bei einem Link auf eine andere Seite übergeben werden: !block perl echo slink "Klicken Sie hier", -argument1 => "wert1", var2 => "wert2"; !endblock Wenn man diesem Link folgt, steht der "wert1" im Hash C<%A> bzw. "wert2" im Hash C<%S>: !block perl printf "Das Argument argument1 ist %s, die State-Variable var2 ist %s", $A{argument1}, $S{var2}; !endblock Werte, die von "Aussen" (z.B. GET-Request in CGI) kommen, stehen, um die Verwirrung komplett zu machen, in C<%P>. Als Faustregel gilt: was in C<%S> und C<%A> steht ist sicher (bzw. man hat es selbst dorthin gepackt), was in C<%P> steht ist unsicher und muss erst gefiltert/geprüft werden. H2: Sicherheit In vielen CGI-Programmen (aber nicht nur dort) wird der Fehler begangen, sensitive Daten in {{hidden}}-Feldern in einem Formular zu "verstecken" oder sie in die URL zu kodieren um die Parameterübergabe zu realisieren. Dies ist in PApp nicht nötig, da der persistente 'State' in einer Datenbank gespeichert wird und nur der (mit 256 Bit verschlüsselte) Zeiger darauf zum Client übertragen wird. Dadurch werden sensitive Daten gar nicht erst zum Client übertragen, was natürlich auch Bandbreite spart. Sollte der Schlüssel einmal bekannt werden kann man zwar auf andere Sessions zugreifen, die Daten jedoch immer noch nicht verändern (dieser Fall tritt beispielsweise auch auf, wenn ein kaputtes Proxy zwischen Server und Client sitzt). Eine typische PApp-URL sieht übrigens so aus (Applikation "kis", Modul "abteilungswahl"): !block verbatim /kis/abteilungswahl/NIlRSJNDIfP3AHfVjxLF5F !endblock Da eine "Seite" in PApp aus einem Modulbaum besteht, geht es auch komplizierter: !block verbatim /exec/admin/+admin=poedit+poedit=view--?papp=eTDgL.I-lj9O9lTO3wznTk !endblock Zusammen mit einem sicheren Transportprotokoll (z.B. SSL, wobei die meisten Browser nur ungenügend oder garnicht gegenüber Attacken schützen, nur so am Rande ;) schützt man sich damit gegen alle Seiten. Benutzerauthentifizierung über Zertifikate ist auch im Einsatz: Mit dem C-Modul von Stefan Traby gibt es seit neuestem eine komplette, transparente SSL-Integration in das PApp User-Management: Nach einem "<:ssl_user_needed:>" hat man beispielsweise eine garantierte SSL-Session mit User-Zertifikat und einem eindeutigen PApp-user. Das CA-Management-Tool ist in Vorbereitung. <<>> H2: Session/User-tracking Persistente Variablen erfordern Session-Tracking. Benutzerabhängige Einstellungen (Preferences) darüber hinaus auch User-Tracking. Ersteres geschieht mit einer Session-ID, die (auf verschiedene Arten) in die URL-kodiert wird und die alle notwendigen Daten enthält, um die gewünschte Seite komplett zu erzeugen. Darüber hinaus wird (optional) ein Cookie benutzt, das aber z.Zt. nur zum identifizieren des Benutzers dient, da ich Session-Definitionen über Cookie als Unsinn erachte. Das Cookie wird auch nur einmal am Tag gesetzt, so dass man auch mit der Browser-Einstellung "vor Cookies warnen" arbeiten kann. Eine Anwendung wird darüber informiert, ob eine neue Session begonnen wurde oder ob sich ein bisher unbekannter Benutzer angemeldet hat, so dass man Benutzereinstellungen o.ä. initialisieren kann. Dies nützt PApp selbst, um sich z.B. die gewünschte Sprache des Benutzers zu merken oder um das User-Cookie nur einmal täglich zu setzen. Eine "Sitzung" ist übrigens ein Baum (nicht nur in PApp), der mit dem ersten "Hit" als Wurzel beginnt. Dadurch ist es unter anderem möglich, die Anzahl der "Reloads" einer Seite zu bestimmen. Dies ist nützlich, um potentiell gefährliche Aktionen (z.B. Löschen von Datenbanken, abschicken von E-mails) nur einmal auszuführen. H2: Benutzerverwaltung Da PApp Benutzer (mehr oder weniger) eindeutig identifizieren muss, implementiert es intern eine eigene Benutzerverwaltung inklusive einer Unix-artigen Rechtevergabe (es gibt allerdings keinen Super-User im Unix-Sinne). In vielen Anwendungen kann man diese gleich mitbenutzen, da eine eindeutige Benutzer-ID automatisch vergeben wird. !block verbatim Sie sind wahrscheinlich Benutzer Nummer
    #if auth_p Und ausserdem haben sie sich authentifiziert.
    # if access_p "admin" Oh, und "admin"-Rechte besitzen Sie auch noch! Meine Güte!!
    # endif #endif !endblock H2: Geschwindigkeit und Skalierbarkeit Geschwindigkeit war bei der Implementierung von PApp das zweitwichtigste Ziel (Korrektheit ist das wichtigste ;). Als Marke dient mir ein Pentium-II 266Mhz-Rechner, auf dem auch komplexe Seiten mit mindestens 15 Hits/Sekunde dargestellt werden, bzw. der Vergleich mit einem ähnlichen C-Skript. Das State-Management von PApp verschlingt pro Seite 1-3 Datenbankzugriffe, die die Zeit bei weitem dominieren (andere Features wie I18n sind im Prinzip kostenlos). Da komplexe Seiten im Allgemeinen wesentlich mehr und kompliziertere Zugriffe enthalten bzw. man ja irgendwie seine Daten speichern muss, ist dies in der Praxis selten eine Einschränkung (Ausnahmen gab und gibt es immer). Andere PApp-Features (z.B. im SQL-Bereich) bringen häufig sogar eine Steigerung der Geschwindigkeit. Da ich bisher keine Seite fabrizieren konnte, die die 15 Zugriffe/s-Marke unterschritten hätte, glaube ich noch etwas Spielraum für noch mehr Features zu haben. Wer sich übrigens fragt, woher diese magische Grenze von 15 Hits/s kommen: 15 Hits ist die Marke, die einen wirklich grossen Server von den 99.99% der restlichen Welt unterscheidet. Hat man auf seinem Server 15 oder mehr Zugriffe pro Sekunde kann man sich meistens auch eine schnellere Datenbank oder mehrere Rechner leisten: PApp skaliert problemlos auf mehrere Maschinen oder SMP-Rechner. H2: Ideen, Ideen: z.B. "tied forms" Ich habe es schon angesprochen: Persistenz von fast allem, zusammen mit den Möglichkeiten von Perl können einen schon auf Ideen bringen, z.B. auf eine Bibliothek (editform), die HTML-Formularfelder an Perl-Refrenzen bindet. Nun ist es an der Zeit, einmal eine Referenz auf eine SQL-Tabelle zu erzeugen: !block perl <: my $row = new PApp::DataRef 'DB_row', table => "user", where => [id => $userid], delay => 1, autocommit => 1; # pre-set name $row->{name} ||= ""; ef_begin; :>
    __"ID:" {id} , 5:><: :>
    __"Name:" {name}, 20:><: ef_submit __"Update"; ef_end; :> !endblock Die Referenz auf die Zeile steht in C<$row> und verhält sich wie eine Referenz auf einen herkömmlichen Perl-Hash. Das C sorgt dafür, dass die geänderten Daten automatisch zurückgeschrieben werden sobald das C<$row>-Objekt gelöscht wird (ausser Scope geht, Ced wird). Sie ist lokal (C!) gespeichert, da aber die C-Funktionen die Referenz persistent speichern, wird das Objekt erst auf der nächsten Seite (also nachdem die Ergebnisse hineingeschrieben wurden!) zerstört, bzw. die Daten geschrieben. Etwas kompliziert, aber zum benutzen muss man die Details ja auch nicht kennen. Nun macht das Beispiel keine Fehlerüberprüfung, meistens das schwierigste. Mit PApp kein Problem. Zuerst schalten wir das C ab, damit die Daten nicht "aus Versehen" geschrieben werden. Ausserdem übergeben wir die Referenz als Argument an die "nächste" Seite. !block perl <: my $row = $A{row} || new PApp::DataRef 'DB_row', table => "user", where => [id => $userid], delay => 1, autocommit => 0; ef_begin -row => $row; # Daten als Argument übergeben !endblock Nun brauchen wir ein Flag, das uns sagt, ob der Datensatz fehlerhaft ist und schon können wir loslegen: !block perl my $err = 0; # Daten fehlerhaft? :>
    __"ID:" {id} , 5:><: #if $row->{id} !~ /^(\d+)$/ __"The ID must be an integer!" <:$err++:> #endif :>
    __"Name:" {name}, 20:><: #if 2 > length $row->{name} __"The Name must contain at least two characters!" <:$err++:> #endif ef_submit __"Update"; ef_end; !endblock Die Daten schreibt man dann bei Bedarf in die Datenbank: !block perl #if $row->dirty # if $err __"The entered Data is invalid, please surrender or die (or correct it ;) # else <:$row->flush:> __"The record has been updated." # endif #endif :> !endblock H2: "Web-Widgets" Eine relativ neue "Neuerung" in PApp ist die Einführung von Web-Widgets. Wie so vieles sind das keine speziellen Objekte, sondern eine Art der Programmierung, die zwar schon immer möglich war, aber an die man nicht sofort denkt. Statt einer ganzen Applikation, die "ganze Seiten" (z.B. ganze HTML-Seiten) ausgibt, schreibt man eine Applikation, die nur noch Teilseiten ausgibt, die man in größere Applikationenn einbaut. Dies ist nicht das gleiche wie ein "include": Der Namensraum (globale Variablen, state-keys, also Session-Variablen und Preferences) einer solchen eingebetteten Applikation ist getrennt von der einbettenden Anwendung und bildet eine Art "Unternamensraum". Derlei eingebettete Anwendungen sind vollkommen autark, haben ihren eigenen Zustand ("aktuelle Seite") und können ihre eigenen Formulare, Links etc. erzeugen die mit anderen Applikationen nicht kollidieren. In einer Anwendung verwende ich dasselbe Forum-Element, um "Web Chat", "Kleinanzeigen" und eine "News!"-Seite zu implementieren - jedesmal eine leicht andere Konfiguration aber derselbe Code. Da PApp-Applikationen im Prinzip nur grosse Statemaschinen sind (das ist eine Einschränkung der verwendeten Protokolle, d.h. bis Perl effektive und schnelle continuations bekommt ;), kann man sie auch einfach in andere einbauen. Sie können sich gegenseitig beeinflussen, treten sich aber nicht auf die Füsse. Also im Prinzip genauso wie ein "Widget" in X11 oder gtk+. H2: Logging/Protokollierung PApp protokolliert wesentlich mehr, als man benötigt, legal wäre, und speicherbar wäre. Da PApp jederzeit in der Lage sein muss, eine ältere Seite zu regenerieren ("Back & Reload"), speichert es pro Seite die persistenten Variablen (ca. 300-900 Byte, je nach Applikation, das ist, wie gesagt, auch eine tolle Sache zum debuggen). Aber irgendwann müssen diese Daten wieder weg bzw. statistische Daten her - nichts ist interessanter, als Zugriffsmuster, Voreinstellungen oder ähnliches, an dem man ablesen kann, welche Dinge beliebt sind und welche nicht (klar, man kann auch ganz andere Sachen auswerten, aber das ist nicht mein Problem). Beim Aufräumen spielt PApp die einzelnen Zugriffe noch einmal durch. Statt jedoch Seiten zu erzeugen gibt PApp den einzelnen Applikationen die Möglichkeit, statistische Daten zu sammeln. Etwas undokumentierter (wenn es da Abstufungen gibt) ist die Möglichkeit, globale Daten zu sammeln, aber es geht... H1: Eine einfache Anwendung mit PApp Jetzt kommen wir zur eigentlichen Frage: Wie sieht so ein PApp-Programm aus? Nun, meistens so: H2: Das Hauptprogramm !block verbatim !endblock Hmm... das ist also erstmal XML mit furchtbar vielen neuen Elementen. Normalerweise kopiert man sich einfach ein anderes Programm und schreibt nicht alles neu. Gut: zuersteinmal das C-Element: damit wird - genau wie in Perl - ein neuer Namensraum erzeugt, der sowohl normale Package-Variablen als auch State-Variablen enthält. Nicht jedes Programm muss einen eigenen Namensraum öffnen. Das nächste Element, C, aktiviert die Übersetzungen: Alle markierten Texte werden unter einem Namen, der C gebündelt. Beispielsweise befinden sich alle Texte des PApp-Systems selbst in der "papp"-Domain. Da im Beispiel kein Domainname angegeben wird, nimmt PApp den Namen des umschliessenden C-Elements, d.h. wir definieren hier eine Übersetzungsdomain "demo". Das nächste Element deklariert die Standarddatenbank: Wird die Datenbankhandle bei SQL-Abfragen weggelassen, wird diese Datenbank benutzt. Normalerweise deklariert man Datenbanken aber nicht im Quellcode, sondern beim Einrichten der Anwendung im Konfigurationsmenü. Zu den nächsten beiden Elementen muss ich etwas über das Programm erklären, das ich hier entwickeln möchte: Das Demo-Programm sollte kurz sein und die wichtigsten Features von PApp zeigen. Es wird aus drei (Web-) Seiten bestehen, eine Art Hauptseite mit einem Menü und zwei Unterseiten, auf denen man ein Projekt auswählen kann (ein Projekt ist ein einfacher Datensatz in der Datenbank, der den Projektnamen, den Ort und die Beschreibung enthält). Auf der dritten Seite kann man die einzelnen Felder eines Projektes ansehen und ändern. Als besonders überflüssiger Schnickschnack sollen die Projektdaten übersetzbar sein, d.h. in der Sprache des Benutzers angezeigt werden. Das ist nicht sehr wirklichkeitsnah, gibt dafür aber ein sehr simples Beispiel ab. Da die Projektdaten in der Datenbank gespeichert sind, kann man sie nicht einfach mit C<__"xxx"> markieren, sondern braucht ein C-Element. Wo PApp die Übersetzungen herbekommt, ist relativ egal, man muss PApp nur sagen, wie es an die Texte herankommt und in welcher Sprache sie sind. Das erste C-Element sagt, dass die Spalten C in der Tabelle und die Spalte C in der Tabelle C in Deutsch sind und dementsprechend in andere Sprachen zu übersetzen sind. Das zweite C-Element ist schon komplizierter: Nicht alle Projektbeschreibungen sind in derselben Sprache (behaupte ich einfach mal), weshalb als Sprache C<*> angegeben wird. Das bedeutet lediglich, dass {{alle}} Übersetzer diese Texte übersetzen müssen. Wenn der Englisch-Deutsch-Übersetzer also auf einen Deutschen Text stößt, muss er ihn einfach überspringen. Wäre die Spalte als "de" (oder "deu") markiert, würde er sie garnicht erst zu Gesicht bekommen. Da Beschreibungen ausserdem sehr lang sein können, erhält man mit C die Möglichkeit, die C<__"xxx">-Syntax auch in den Beschreibungen zu verwenden. Dies kann man auch für einfache Textbausteine mißbrauchen... Das darauffolgende C-Element ist wieder etwas einfacher: es entspricht mehr oder weniger der C-Anweisung in Perl (in genau die wird es auch übersetzt), bezieht sich aber auf PApp-Dateien. Damit kann man Funktionen aus anderen (PApp-) Namensräumen importieren. Im vorliegenden Fall importiere ich einfach mal das C-Paket, das sich immer wieder grosser Beliebtheit erfreut. Das letzte Element tut zur Abwechslung genau das, was es sagt: es fügt (logisch gesehen) eine andere Datei an dieser Stelle ein. Die Entscheidung, die folgenden Seiten in eine andere Datei auszulagern, lohnt sich normalerweise nur für grössere Programme, aber so bleibt das Beispiel klein. Bis jetzt tut sich noch nichts. Ändern wir das: H2: Das erste PApp-Modul Einzelne Zustände (z.B. Webseiten) werden bei PApp "Module" genannt, wohl einzig um die Anwender zu verwirren. Diese Module werden meist in andere Elemente (C, C, C !endblock perl Das C bezieht sich auf den Zeitpunkt, zu dem das Stylesheet angewendet werden soll. C bestimmt, dass das Stylesheet kurz vor der Ausgabe angewendet werden soll. Normalerweise würde man nun den Dateinamen des Stylesheets mit C angeben, da wir aber zwischen zwei Stylesheets hin- und herschalten wollen, geben wir mit C einen Perl-Ausdruck an, der ein Stylesheet-Objekt als Ergebnis haben (sollte). Das muss {{natürlich}} auch kein XSLT-Stylesheet sein, aber wem sage ich das... Das Modul ist übrigens (bis auf die durch "..." angedeuteten Lücken) vollständig, d.h. der Kopf mit Titel und Sprachumschalter (sowie Stylesheet-Umschalter) wird durch das Stylesheet hinzugefügt. H1: Tinychat - ein kleiner Chat in 20 Zeilen Zum Schluss noch ein sehr einfaches Beispiel: C ist ein sehr einfaches Chat-Fenster: Es zeigt die letzten fünf Eingabezeilen an, gefolgt von einer Eingabebox. Der Chat-Inhalt ist systemweit, d.h. auf allen Servern in einem PApp-System ist immer derselbe Text, man kann diese "Chatbox" also in beliebige Programme einbauen. !block perl 5; push @$r, escape_html sprintf "%s: %s", username || "", $P{input}; setenv "TINYCHAT", $r; }; :> #endif <: my $r = getenv "TINYCHAT"; echo map "$_
    ", @$r; :>
    1:>__"Chat: " ]]>
    !endblock Zuallererst ist C nur eine Funktion, teilt sich also den Namensraum mit dem Aufrufer. Tinychat ist so winzig und so schlecht konfigurierbar, dass das Sinn macht... Sehen wir uns mal den Teil an, der die Ausgabe erledigt: !block perl <: my $r = getenv "TINYCHAT"; echo map "$_
    ", @$r; :> !endblock Die Texte sind in einer "Environment"-Variable gespeichert. Diese Variablen sind global für das gesamte PApp-System und könenn auch ausserhalb von PApp abgefragt bzw. verändert werden. Das ganze ist also eher ein System zur asynchronen Kommunikation. Neben normalen Strings kann man alles darin ablegen, was irgendwie serialisierbar ist, insbesondere eine Array-Referenz, in der die einzelnen Zeilen sind. Zur Ausgabe wird also lediglich die Variable C ausgelesen und die einzelnen Zeilen in ein "zeile
    " gepackt. Fehlt noch das Eingabefeld: !block perl 1:> __"Chat: " !endblock Hier wird kein C benutzt: Der Overhead ist im Vergleich zum Gewinn (es gibt keinen) zu gross. Die Funktion C (die auch von C benutzt wird) gibt das einleitende C
    -Tag aus. Dann folgt der Text C und ein ganz normales HTML-C-Element. C schleisslich gibt ein "" aus und existiert eigentlich nur aus Symmetrie. Das Eingabefeld heisst C. Was passiert, wenn wir sonst noch ein C-Feld haben und dieses andere Feld submittet wird? Kein Problem{{}}: Wir übergeben C einfach ein Argument, das nur dazu dient, die Information "Tinychat-Formular ist gemeint" zu übertragen. Ganz nebenbei: C ist - wie sehr viele PApp-Funktionen - sehr einfach definiert: !block perl PApp::HTML::_tag "form", { method => 'GET', action => &surl }; !endblock Nun zum Teil, der die Eingabezeile nach dem Abschicken hinzufügt: !block perl #if $A{tinychat_submit} && $P{input} && !reload_p <: lockenv { my $r = getenv "TINYCHAT"; shift @$r while @$r > 5; push @$r, escape_html sprintf "%s (%s): %s", username || "", $P{input}; setenv "TINYCHAT", $r; }; :> #endif !endblock Die erste Zeile testet drei Dinge: ^ Es muss "unser" Formular sein; das erkennt man daran, dass der Parameter C logisch wahr ist. & Die Eingabe sollte nicht leer sein (o.k. "0" ist auch nicht erlaubt). & Die Seite sollte nicht das Ergebnis eines "Reloads" sein. Der letzte Punkt bedarf einer Erklärung: PApp weiss, wie oft eine Seite angefordert wurde und teilt dies über die Funktion C mit, die die Anzahl der Seitenaufrufe für {{dieselbe}} Seite minus eins zurückliefert. Ist diese Zahl ungleich null, wurde der Code schon ausgeführt. Das eigentliche hinzufügen ist Standard: Variable holen, alte Zeilen rauslöschen, neue Zeile hinzufügen, Variable auf neuen Wert setzen. Die Funktion C, in die die Manipulation eingeschlossen ist, schützt das Programm gegen gleichzeitige Modifikationen anderer Webserver, d.h. die Operation wird atomar. C quoted das Argument. Die Funktion C (aus dem C-Paket) liefert den Namen des Benutzers, falls dieser einen besitzt. Ansonsten nimmt Tinychat C + die numerische User-ID, die jedem Benutzer zugeteilt wird. H1: Die Nachteile Bei allen Vorteilen, es gibt auch Nachteile... die packe ich ans Ende und fasse mich auch gerne sehr kurz: H2: Die Lizenz (Oder doch ein Vorteil?) Tja, PApp war doch tatsächlich mal GPL, und zwar zu einer Zeit, zu der wir es praktisch nur als CGI-Krücke verwendet haben. Inzwischen hat sich PApp gemausert und wurde zu einem unserer Standbeine. Als eine andere Firma versuchte, uns mit unserem Produkt Konkurrenz bei unseren Kunden zu machen ("wir können da einfach ein paar Dutzend Programmierer dransetzen"), mussten wir leider handeln. Die "PApp Public License" ist so ähnlich wie die MySQL Public License, d.h. wer sie privat einsetzt (bzw. für die Forschung und Lehre oder für eine not-for-profit-Organisation), darf PApp weiterhin kostenlos nutzen. Wer kräftig Kohle damit macht, muss uns einen Teil davon abgeben (die eigentliche Lizenz ist etwas länger ;). Langfristig ist geplant, PApp wieder in GPL oder besser zu überführen. Mittelfristig muss man damit Leben ;) H2: Die Abhängigkeiten Zur Zeit gibt es keine Perl-Release, die annähernd mit UTF8 zurechtkommt. Z.Zt. (d.h. buchstäblich in dieser Minute) benötigt PApp perl-5.7.0-DEVEL7952 oder ein paar hundert Patches davor oder danach. Demnächst wird auf DEVEL8xxx umgestellt (z.Zt. gibt es einige Bugs, die dies verhindern). PApp funktioniert zwar wunderbar, aber eben nur, wenn die restlichen Komponenten aufeinander abgestimmt sind. H2: MySQL Das Grundsystem von PApp benötigt zwar kein MySQL, einige Module (z.B. C) dagegen (aus Geschwindigkeitsgründen) schon. Möchte man also eine einfache Installation und alle Features empfiehlt sich MySQL, zumindest für die PApp-interne Datenbank selbst. Ansonsten arbeitet PApp mit allen Datenbanken, die ein DBI-Interface aufweisen, ohne Probleme. H2: Geschmackssache PApp ist etwas sehr persönliches - ich habe meine eigenen Vorstellungen davon, wie Web/CGI etc. funktionieren sollte, darin verwirklicht. Es hat meine Motivation (die sich mit "nie wieder CGI" zusammenfassen liess) gewaltig gesteigert - Ein einfaches aber dennoch komplettes Content-Management-System kann man in weniger als 500 Zeilen hinlegen - für mich ein wichtiger Faktor, denn ich hasse nichts mehr, als das Rad jedesmal neu erfinden zu müssen. Da ich bekannt bin für meinen etwas merkwürdigen Geschmack (sagt man mir) muss das {{wie}} nicht unbedingt jedem gefallen. A1: Referenzen * {{http://papp.plan9.de/}}. Die PApp-Homepage. A1: XSLT-Quelltext "Text-Only-Layout" Dies und das folgende XSLT-Stylesheet kratzen nur an der Oberfläche dessen, was mit XSLT möglich ist. !block verbatim <xsl:value-of select="@module"/> 'en':> "de":>
    1:>

    <:debugbox:>
    !endblock A1: XSLT-Quelltext "Superbunt und Superhässlich-Layout" !block verbatim <xsl:value-of select="@module"/> 'en':> "de":>
    0:> #if $PApp::module ne "" #endif #if $PApp::module ne "editor" or $S{projectid} #endif
    undef:>

    __"Demo"

    __"Welcome to our pages..."

    Copyright whatever, whenever etc...
    !endblock