ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/docs/papp.sdf
Revision: 1.8
Committed: Sun Feb 25 21:28:42 2001 UTC (23 years, 3 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.7: +238 -147 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 !init OPT_STYLE="paper"
2
3 !define DOC_NAME "PApp - Perl-Anwendungen für die Zukunft des WWW"
4 !define DOC_AUTHOR "Marc Lehmann <pcg@goof.com>"
5 !build_title
6
7 H1: PApp - Wie beschreibt man ein Chamäleon?
8
9 PApp ist schwer zu beschreiben, da es sich nicht auf ein spezielles
10 Problem spezialisiert, sondern möglichst eine offene Universallösung sein
11 will. Unter anderem ist PApp:
12
13 - ein Embedded-Perl-Dialekt. Es gibt verschiedene Formen: Zum einen den
14 "literal programming style", der nicht XML-konform ist, sondern beliebige
15 Daten ausgeben kann und viele Möglichkeiten des Einbettens von Perl in
16 XML. Zum anderen einen Standard-XML-Dialekt, der eine Mischung aus beidem
17 darstellt.
18
19 - eine Bibliothek (aus vielen Perl-Modulen), die das Arbeiten in einer
20 CGI-artigen Umgebung erlauben. Die Umgebung hält sich mehr an Apache, ist
21 jedoch unabhängig davon, ob die PApp-Anwendung in einer CGI-Umgebung,
22 unter mod_perl oder (z.B.) unter CGI::SpeedyCGI ausgeführt wird.
23
24 - eine Bibliothek, die das Arbeiten mit HTML, XML, SQL und anderen,
25 häufig benutzten Umständlichkeiten vereinfacht.
26
27 - eine grosse "State-Maschine", die technisch getrennte Programmteile
28 (z.B. CGI) logisch zu {{einer}} Applikation zusammenfasst.
29
30 - ein XSLT-Stylesheet-Prozessor. Damit kann man Layout und Inhalt
31 voneinander trennen. Oder auch Ausgabeformat (PDF/HTML/XML etc.) vom
32 Layout. Oder vom Inhalt... Oder der Benutzersprache...
33
34 H2: Warum PApp geschrieben wurde
35
36 Grosse und kleine Applikationen für das WWW zu erstellen ist sehr
37 arbeitsaufwendig. Session-Variablen kennt Perl (zum Glück ;) nicht,
38 Session- und Usertracking natürlich auch nicht. Es gibt sehr gute
39 Module, die einem einen Teil der Arbeit abnehmen, aber es ist immer sehr
40 aufwendig und umständlich. Dies führt zu unübersichtlichen Programmen,
41 die noch dazu auf viele Dateien aufgeteilt sind, die nicht unbedingt der
42 inneren Logik des Programmes entsprechen. Auch Sicherheitsfehler (wie das
43 Übergeben von sicherheitsrelevanten Daten über {{hidden-fields}} oder
44 ähnliches) passieren schnell, schliesslich muss man sich bei jeder Seite
45 überlegen, wie man seine Daten weiterreicht.
46
47 Datenbankabfragen, das Einbauen des Layouts usw. sind sehr
48 umständlich. Internationalisierung existiert praktisch nicht.
49
50 PApp vereinfacht alle diese Probleme, indem es sie weitestgehend
51 automatisiert.
52
53 H1: Grundlegende PApp-Features oder "Nie wieder CGI"
54
55 Zunächst möchte ich einige der vielen Features von PApp vorstellen
56 (natürlich die wichtigsten ;). Wem das zu langweilig ist und erstmal
57 eine richtige PApp-Applikation sehen will, sollte zum nächsten Abschnitt
58 gehen, "Eine einfache Anwendung mit PApp".
59
60 H2: Applikationen statt Einzelseitenverwaltungsmonstren
61
62 Bei CGI oder anderen Schnittstellen ist die Sicht auf die Anwendung
63 protokollbedingt seitenbasiert - jede "logische" Seite ist eine Datei
64 oder eine Fallunterscheidung innerhalb der Datei - gemeinsame Komponenten
65 müssen in Bibliotheken (oder auch einfachen "Include"-Dateien; das ist im
66 Prinzip das gleiche) abgelegt werden, die auf jeder Seite erneut geladen,
67 konfiguriert und eingebaut werden müssen. Perl-Programme funktionieren
68 jedoch anders, da gibt es keine Zustände, die beim Anklicken wechseln,
69 sondern ein lineares Programm. Dies läßt sich zwar auch mit PApp nicht
70 verwirklichen, aber es tut sein Bestes, diese Einschränkungen aufzuheben,
71 indem es ganze Anwendungen bearbeitet: Ob mehrere Seiten in einer Datei
72 oder eine Seite auf mehrere Dateien verteilt wird ist in PApp egal.
73
74 H2: Development/Maintainance: Aktivposten statt Bremsklötzen
75
76 H3: Entwicklungshilfe
77
78 Programme müssen erst entwickelt werden. Dabei werden Fehler
79 gemacht. Daran ändert PApp nichts. Da bei PApp sehr viele Komponenten
80 zusammenwirken, können die Fehler entsprechend komplex sein. Eine gute
81 Unterstützung bei der Fehlersuche ist also wichtig.
82
83 Dies fängt damit an, dass die Zeilennummern des Quelltextes auch
84 nach Kompilieren von Perl/XML zu reinem Perl oder z.B. in ein XSLT
85 erhalten bleiben. Sollte ein Fehler auftreten, so hat man sofort Zugriff
86 auf die Quelldateien, einen kompletten Backtrace und natürlich die
87 State-Daten. Das Exception-System von PApp ist einfach zu benutzen (C<die>
88 genügt, will man es schöner haben benutzt man C<fancydie> ;) und
89 beliebig erweiterbar.
90
91 Im Betrieb will man natürlich keine ausführlichen Fehlermeldungen,
92 womöglich komplett mit Passwort der Datenbank usf... Deshalb
93 protokolliert PApp alle Fehler in eine SQL-Datenbank zusammen mit der
94 State-ID, so dass nur die Fehlerkategorie erscheint (plus ein nettes
95 Textfeld, in dem der Benutzer zusätzliche Informationen eingeben
96 kann). Solange die Seite nur wiederholbare Aktionen enthält, kann der
97 Entwickler den Fehler jederzeit reproduzieren.
98
99 H3: Datenbanken
100
101 Wie soll man ohne auskommen? Natürlich garnicht. In Perl gibt es ein ganz
102 wunderbares System (DBI!), um mehr oder weniger genormt auf SQL-Datenbanken
103 zugreifen zu können. Jetzt ist "nacktes" DBI recht umständlich,
104 zumindest für mich, für den vier Aufrufe pro SQL-Befehl entschieden
105 zu viel sind. Vor allem, wenn man diese aus effizienzgründen über das
106 Programm verstreuen muss (C<prepare> und C<execute>).
107
108 In PApp erledigt man die meisten Aufgaben mit der C<sql_exec>-Funktion
109 (den Rest erledigt man mit C<sql_fetch/fetchall/exists/insertid>). Der
110 folgende Aufruf beispielsweise ersetzt C<prepare>, C<bind_params>,
111 C<execute> und C<bind_columns>:
112
113 !block perl
114 my $st = sql_exec \my($p, $w),
115 "select purzel, wurzel from table
116 where name like ?",
117 $S{search};
118
119 while ($st->fetch) {
120 echo "$p, $w\n";
121 }
122 !endblock
123
124 Der besondere Clou: Die SQL-Statements werden gecached, d.h. im
125 Allgemeinen nur ein einziges mal C<prepare>d. Bei Datenbanken, die einiges
126 an Aufwand in diesem Schritt investieren (Stichwort Query-Optimizer)
127 ist das ein grosses Plus. Sogar bei Datenbanken, die dies nicht tun
128 (z.B. MySQL) steigt die Geschwindigkeit merklich. Zudem stehen C<prepare>
129 und das C<execute> nicht mehr weit auseinander (Initialisierung
130 vs. Benutzung!).
131
132 Bei Datenbanken, die eine Art "insertid" unterstützen (zum Glück fast
133 alle), kann man diese nach einem C<INSERT> gleich mit abfragen:
134
135 !block perl
136 my $id = sql_insertid
137 sql_exec "insert into tiere (name) values (?)",
138 "hase";
139 !endblock
140
141 Den Database-Handle habe ich übrigens nicht vergessen: Wenn man
142 keinen angibt, wird ein Default-Handle benutzt. Geschickterweise wird
143 dieser von PApp selbst gesetzt, so dass man sich im Normalfall noch
144 nicht einmal um die Datenbankverbindung kümmern muss (weil diese auf
145 dem Produktionssystem meistens andere Passwörter benötigt als auf
146 dem Entwicklungssystem, stellt man diese geschickterweise bei der
147 Konfiguration der Applikation ein).
148
149 <<<admin.png>>>
150
151 H2: Trennung von Quelltext und Sprache
152
153 H3: XML
154
155 XML wird in mehreren Teilen von PApp unterstützt bzw. gefordert: Der
156 Standard-Dialekt von PApp ist in XML geschrieben, d.h. um "normale"
157 PApp-Applikationen zu schreiben muss man sich XML bedienen. Die Ausgabe
158 von PApp (meistens Text) ist beliebig, sofern man keine Stylesheets
159 benutzt. Tut man das, muss die Ausgabe natürlich in XML-Syntax sein. Und
160 last-not-least gibt es ein PApp-Modul, mit dem man beliebige XML-Fragmente
161 mit eingebettetem Perl-Quelltext bearbeiten/ausführen/anzeigen kann.
162
163 Die Entscheidung, XML so zentral einzusetzen, fiel mir nicht
164 leicht: Meiner Meinung nach ist XML wunderbar dafür geeignet,
165 Daten innerhalb von Programmen zu verarbeiten und vor allem
166 auszutauschen. Toll ist, dass Menschen XML notfalls lesen und auch
167 schreiben können. Ansonsten ist doch die Mächtigkeit von SGML (oder
168 etwas anderem) vorzuziehen.
169
170 XML jedoch hat bestechende Vorteile: HTML ist eine XML-Applikation (und
171 HTML ist wichtig ;); es existiert eine breite Unterstützung für XML;
172 es gibt bestehende Standards für Stylesheets, Layout, Metadaten und
173 mehr. Schliesslich ist XML auch noch sehr schnell. Nun ja.
174
175 Das hat mich trotzdem nicht davon abgehalten, noch etwas
176 draufzusetzen: Fast alle Dokumente können in einem "Meta-Format" mit
177 eingebettetem Perl-Quelltext geschrieben werden. Die Standard-Syntax
178 dafür bedient sich vier sog. "Modus-Umschalter", die jeweils vom
179 "Verbatim" in den "Perl"-Modus bzw. zurück schalten:
180
181 !block verbatim
182 <: schalte von HTML in Perl um
183 <? wie <:, füge jedoch das Ergebnis in die Ausgabe ein
184
185 :> schalte in den HTML-Modus
186 ?> schalte in den interpolierten HMTL-Modus
187 !endblock
188
189 Eine HTML-Seite kann man z.B. so schreiben:
190
191 !block perl
192 <h1>Ein bisschen HTML</h1>
193 <: echo "mit eingebautem perl" :>
194 <: print "so gehts auch, ist aber unendlich wenig langsamer ;)" :>
195 <?"So gehts am schnellsten":>
196 <:for my $text (qw(Noch eine komische Methode)) {
197 ?>$text&#160;<:
198 }:>
199 !endblock
200
201 Die Modus-Umschalter verhalten sich dabei wie eine Statement-Grenze in Perl (also C<;>).
202
203 H3: I18n
204
205 I18n steht kurz für Internationalisierung: Das englische Wort
206 {{Internationalization}} hat 18 Buchstaben zwischen dem 'I' und dem
207 'n', und da das Wort für den Durchschnittsamerikaner viel zu lang und
208 kompliziert ist, hat man diese 18 Buchstaben einfach durch "18" ersetzt.
209
210 In Kontext von PApp bedeutet dies, dass beinahe überall Textmeldungen
211 übersetzt werden können (wer das C<gettext>-Paket kennt, mit dem
212 üblicherweise C-Programme internationalisiert werden, wird sich fast
213 sofort Zuhause fühlen). Hier ist ein Beispiel:
214
215 !block verbatim
216 <h1>__"Contents"</h1>
217 <:for (1..10) {:>
218 <?slink sprintf(__"Chapter %d", $i++),
219 "view_chapter", chapterid => $i:>
220 <:}:>
221 !endblock
222
223 Wie man sieht, kann man C<__"text"> sowohl im
224 HTML/XML/wasauchimmer-Quelltext benutzen, als auch auf Perl-Ebene
225 aufrufen. Die C<__>-Funktion erledigt dabei zwei Aufgaben: Zunächst
226 werden damit Textkonstanten zum übersetzen {{markiert}}. Zur Laufzeit
227 wird dann in der Übersetzungstabelle nachgesehen und der (evt.)
228 übersetzte String zurückgeliefert.
229
230 Wenn man Daten z.B. aus einer Datenbank in eine Variable holt, darf man
231 diese natürlich {{nicht}} markieren, da sie ja nur zur Laufzeit einen
232 String {{enthält}}, selbst aber keine Textkonstante ist. In solchen
233 Fällen verwendet man C<gettext>:
234
235 !block perl
236 my $st = sql_exec \my($id, $name), "select id, name from table";
237
238 while ($st->fetch) {
239 echo "<li>", gettext$name, "</li><br/>";
240 }
241 !endblock
242
243 In welche Sprache jeweils übersetzt wird, entscheidet bei HTTP die
244 Einstellung des Browsers, wobei man zweckmässigerweise ein Menü
245 anbietet, in dem der Benutzer dies überschreiben kann:
246
247 !block perl
248 <?slink "I want English", SURL_SET_LANG => "en":>
249 <?slink "Deutsch willich!", SURL_SET_LANG => "de":>
250 !endblock
251
252 Auch die Spracheinstellung ist eine Preferences-Variable. Im Gegensatz
253 zum C<gettext>-Paket müssen die Quelltexte nicht alle in einer Sprache
254 sein. Bei C<gettext> ist das auch weniger ein Problem, da der Quellcode
255 eines Programms meistens in einer (natürlichen) Sprache geschrieben
256 wurde, in PApp kann man aber Seiten dynamisch einfügen oder Datenbanken
257 übersetzen. Da diese meist nicht vom selben Team geschrieben werden, ist
258 es nützlich, dort unterschiedliche Sprachen verwenden zu können.
259
260 <<<poedit1.png>>>
261 <<<poedit2.png>>>
262
263 H3: Unicode / Zeichensatzunabhängigkeit
264
265 Intern unterstützt PApp genau zwei Datentypen (genau wie Perl
266 selbst): {{Binärdaten}} und {{Text}}. Binärdaten verwendet man
267 üblicherweise für Bilder, Datei-Downloads und ähnliches. Diese Daten
268 werden von PApp nicht angerührt.
269
270 Ganz anders Text: Perl arbeitet intern mit Unicode, das z.Zt. entweder in
271 ISO-8859-1 oder UTF-8 gespeichert wird. Dieser interne Zeichensatz ist
272 völlig unabhängig von der Ausgabe, d.h. der Text wird bei der Ausgabe
273 automatisch in den gewünschten Zeichensatz kodiert (das geschieht wieder
274 Browser/Benutzer/Programmabhängig wobei von den gebräuchlichen Browsern
275 nur Netscape und Lynx die entsprechenden Header liefern). Umgekehrt werden
276 Formulardaten u.ä. automatisch in Unicode gewandelt, d.h. in C<%P> steht
277 Unicode auch wenn der Browser ein Formular in ISO-2022-JP zurückgeschickt
278 hat.
279
280 Ein JPEG-Bild kann man z.B. so ausgeben:
281
282 !block perl
283 content_type "image/jpeg", undef;
284 $gd->jpeg(80);
285 !endblock
286
287 Während man eine japanische Benutzerin vielleicht mit ISO-2022-JP
288 zufriedenstellen kann:
289
290 !block perl
291 content_type "text/html", "iso-2022-jp";
292 echo __"Hoffentlich war der Übersetzer fleissig";
293 !endblock
294
295 H2: Trennung von Daten und Layout/Protokoll
296
297 Sprache und Quelltext trennt PApp ja schon. Jetzt muss man noch das Layout
298 vom eigentliche Programm bzw. das Protokoll von den eigentlichen Daten
299 trennen.
300
301 Mit XSLT-Stylesheets geht das. Und mehr: "Browser XYZ hat ein Problem? Ich
302 fix' es im Stylesheet und vergesse es dann einfach." (z.B. sollte man
303 leere HTML-Tabellenzellen für Netscape lieber mit einem C<&nbsp;>
304 füllen). "Es soll WML (oder CHTML) sein? Ich weiss zwar nicht, wozu
305 das ganze WML-Zeugs sinnvoll sein soll, aber dann mache ich halt' ein
306 Stylesheet dafür." Und eine der wichtigsten Anwendungen: "Wir brauchen
307 eine Version zum drucken. Dafür wandeln wir unser XML-Dokument in XSL" (und dann
308 nach Latex, PDF...).
309
310 Das bedeutet, dass man - Planung natürlich vorausgesetzt - einen hohen
311 Grad an Protokollunabhängigkeit erreichen kann, ohne den Quelltext zu
312 sehr mit Layout-Entscheidungen o.ä. belasten zu müssen.
313
314 Leider gibt es noch keine Web-Designer, die XSLT statt HTML/CSS liefern,
315 aber (meine) Vision des Webs der Zukunft sieht so aus: Der Server liefert
316 XML zusammen mit einem XSLT (vom Designer), welches XSL erzeugt. Dieses
317 wird dann angezeigt/gedruckt etc.a.. Metadaten (mit dem etwas besseren
318 Nachfolger von RDF) gestatten dann Fragen wie: "Wer hat dieses Dokument
319 geschrieben?", "Wozu dient es?" aber auch: "Wieso ist das Ding so bunt?".
320
321 H2: Trennung von Funktionalität und Ausführungsumgebung
322
323 Plattform-Unabhängigkeit: ein toller Begriff. Was bedeutet er? Nicht
324 viel. Bei PApp bedeutet es, dass auf Unix-Abhängigkeiten möglichst
325 verzichtet wurde und sich PApp nicht an einen bestimmten Server
326 (z.B. Apache) bindet. In der Praxis kann man als Platform mod_perl oder
327 CGI verwenden und für Unfälle wie Windoze schere ich mich einen Dreck
328 (d.h. ich habe momentan nicht vor, für proprietäre Schnittstellen
329 Module zu schreiben bzw. habe PApp nur mit den beschriebenen Plattformen
330 getetst). Aber auch andere Umgebungen wie Text-Interfaces sind denkbar: Da
331 PApp der Applikation immer eine gleichbleibende Umgebung anbietet, muss
332 man lediglich ein Schnittstellenmodul schreiben.
333
334 H2: Persistente Variablen für Sessions und User
335
336 PApp kümmert sich automatisch um persistente Variablen. So kann man
337 automatisch Variablen erzeugen, die über eine gesamte Sitzung (die mit
338 dem Aufruf der ersten URL beginnt und dann einen Baum bildet) persistent
339 sind. Das geht so einfach, dass man spezielle Hilfsmittel braucht, um
340 nicht an jeder Stelle der Sitzung Zugriff auf beinahe alles zu besitzen.
341
342 Persistente Variablen gibt es in verschiedenen Geschmäckern. Es gibt:
343
344 - Session-Variablen (sogenannte {{state keys}}), die über eine gesamte
345 Sitzung erhalten bleiben. Diese werden im Hash C<%S> gespeichert. Man kann
346 beliebige Werte darin ablegen (solange C<Storable> diese serialisieren
347 kann). Meistens geschieht dies durch Anklicken eines Links, weniger
348 häufig direkt.
349
350 - Benutzerabhängige Variablen (die auch über Sitzungsgrenzen hinaus
351 erhalten bleiben und deshalb {{preferences items}}, Voreinstellungen,
352 heissen). Auch diese befinden sich in C<%S>, von wo sie automatisch in die
353 Benutzerdatenbank wandern bzw, von dort gelesen werden.
354
355 - Lokale Variablen ({{local keys}}) zu nur innerhalb einer Seite oder
356 einer Gruppe von Seiten Bedeutung haben. Diese befinden sich ebenfalls in
357 C<%S> und werden automatisch daraus entfernt.
358
359 - oder beliebige Kombinationen davon.
360
361 Ein Beispiel für eine Sitzungsvariable ist die Information, ob der
362 Benutzer sich schon angemeldet/eingeloggt hat oder ob er bestimmte
363 Zugriffsrechte besitzt. Eine benutzerabhängige Variable ist z.B. die
364 Sprache, die der Benutzer zuletzt ausgewählt hat (global) oder die
365 Anzahl der Tabellenzeilen, die der Benutzer gerne auf einer bestimmten
366 Seite angezeigt haben möchte. Eine lokale Variable ist z.B. ein
367 Datenbank-Objekt, das über mehrere Seiten (z.B. einer Transaktion)
368 gebraucht wird und danach seine Gültigkeit verliert.
369
370 Dadurch ergeben sich mehr Möglichkeiten, als man erwarten
371 würde: Unabhängige Komponenten (z.B. Werbebanner oder Foren) sind auf
372 normale Weise nur sehr schwer zu programmieren, da sie immer Hilfe vom
373 "Hauptprogramm" erfordern, wenn es um die Parameterübergabe geht. In PApp
374 benutzt man einfach persistente Variablen.
375
376 Ein Beispiel für eine etwas ungewöhnlichere Komponente ist die
377 {{editform}}-Bibliothek. Mit ihr kann man HTML-Formulare erstellen, die
378 sich direkt an eine bestimmte Variable binden:
379
380 !block perl
381 ef_begin;
382 ef_string \$S{search};
383 ef_end;
384 !endblock
385
386 Dieses Beispiel stammt von einer Seite, die Objekte aus einer Datenbank
387 anzeigt und sich dabei auf solche beschränkt, die ein Suchwort
388 enthalten. Das Formular bindet die State-Variable C<search> an ein
389 HTML-Textfeld. Bei der Ausgabe wird der aktuelle Wert von C<$S{search}>
390 benutzt. Ändert der Benutzer den Text und schickt das Formular ab wird
391 die Seite neu aufgebaut - mit einem anderen Wert in C<$S{search}>. Da
392 C<editform> beliebige Perl-Referenzen akzeptiert und man in PApp auch
393 Referenzen auf Dateien, SQL-Spalten etc... erzeugen kann, werden viele
394 Formulare zum Kinderspiel.
395
396 Zusätzlich zu den Sitzungsvariablen gibt es noch sogenannte
397 {{Argumente}}, die nur eine Seite lang "persistent" sind, d.h. bei einem
398 Link auf eine andere Seite übergeben werden:
399
400 !block perl
401 echo slink "Klicken Sie hier", -argument1 => "wert1", var2 => "wert2";
402 !endblock
403
404 Wenn man diesem Link folgt, steht der "wert1" im Hash C<%A> bzw. "wert2"
405 im Hash C<%S>:
406
407 !block perl
408 printf "Das Argument argument1 ist %s, die State-Variable var2 ist %s",
409 $A{argument1}, $S{var2};
410 !endblock
411
412 Werte, die von "Aussen" (z.B. GET-Request in CGI) kommen, stehen, um die
413 Verwirrung komplett zu machen, in C<%P>. Als Faustregel gilt: was in C<%S>
414 und C<%A> steht ist sicher (bzw. man hat es selbst dorthin gepackt), was
415 in C<%P> steht ist unsicher und muss erst gefiltert/geprüft werden.
416
417 H2: Sicherheit
418
419 In vielen CGI-Programmen (aber nicht nur dort) wird der Fehler
420 begangen, sensitive Daten in {{hidden}}-Feldern in einem Formular zu
421 "verstecken" oder sie in die URL zu kodieren um die Parameterübergabe zu
422 realisieren. Dies ist in PApp nicht nötig, da der persistente 'State' in
423 einer Datenbank gespeichert wird und nur der (mit 256 Bit verschlüsselte)
424 Zeiger darauf zum Client übertragen wird. Dadurch werden sensitive Daten
425 gar nicht erst zum Client übertragen, was natürlich auch Bandbreite
426 spart. Sollte der Schlüssel einmal bekannt werden kann man zwar auf
427 andere Sessions zugreifen, die Daten jedoch immer noch nicht verändern
428 (dieser Fall tritt beispielsweise auch auf, wenn ein kaputtes Proxy
429 zwischen Server und Client sitzt).
430
431 Eine typische PApp-URL sieht übrigens so aus (Applikation "kis", Modul
432 "abteilungswahl"):
433
434 !block verbatim
435 /kis/abteilungswahl/NIlRSJNDIfP3AHfVjxLF5F
436 !endblock
437
438 Da eine "Seite" in PApp aus einem Modulbaum besteht, geht es auch
439 komplizierter:
440
441 !block verbatim
442 /exec/admin/+admin=poedit+poedit=view--?papp=eTDgL.I-lj9O9lTO3wznTk
443 !endblock
444
445 Zusammen mit einem sicheren Transportprotokoll (z.B. SSL, wobei die
446 meisten Browser nur ungenügend oder garnicht gegenüber Attacken
447 schützen, nur so am Rande ;) schützt man sich damit gegen alle Seiten.
448
449 Benutzerauthentifizierung über Zertifikate ist auch im Einsatz: Mit
450 dem C<ssl>-Modul von Stefan Traby gibt es seit neuestem eine komplette,
451 transparente SSL-Integration in das PApp User-Management: Nach
452 einem "<:ssl_user_needed:>" hat man beispielsweise eine garantierte
453 SSL-Session mit User-Zertifikat und einem eindeutigen PApp-user. Das
454 CA-Management-Tool ist in Vorbereitung.
455
456 <<<janman.png>>>
457
458 H2: Session/User-tracking
459
460 Persistente Variablen erfordern Session-Tracking. Benutzerabhängige
461 Einstellungen (Preferences) darüber hinaus auch User-Tracking. Ersteres
462 geschieht mit einer Session-ID, die (auf verschiedene Arten) in die
463 URL-kodiert wird und die alle notwendigen Daten enthält, um die
464 gewünschte Seite komplett zu erzeugen. Darüber hinaus wird (optional)
465 ein Cookie benutzt, das aber z.Zt. nur zum identifizieren des Benutzers
466 dient, da ich Session-Definitionen über Cookie als Unsinn erachte. Das
467 Cookie wird auch nur einmal am Tag gesetzt, so dass man auch mit der
468 Browser-Einstellung "vor Cookies warnen" arbeiten kann.
469
470 Eine Anwendung wird darüber informiert, ob eine neue Session begonnen
471 wurde oder ob sich ein bisher unbekannter Benutzer angemeldet hat, so dass
472 man Benutzereinstellungen o.ä. initialisieren kann. Dies nützt PApp
473 selbst, um sich z.B. die gewünschte Sprache des Benutzers zu merken oder
474 um das User-Cookie nur einmal täglich zu setzen.
475
476 Eine "Sitzung" ist übrigens ein Baum (nicht nur in PApp), der mit dem
477 ersten "Hit" als Wurzel beginnt. Dadurch ist es unter anderem möglich,
478 die Anzahl der "Reloads" einer Seite zu bestimmen. Dies ist nützlich,
479 um potentiell gefährliche Aktionen (z.B. Löschen von Datenbanken,
480 abschicken von E-mails) nur einmal auszuführen.
481
482 H2: Benutzerverwaltung
483
484 Da PApp Benutzer (mehr oder weniger) eindeutig identifizieren muss,
485 implementiert es intern eine eigene Benutzerverwaltung inklusive einer
486 Unix-artigen Rechtevergabe (es gibt allerdings keinen Super-User im
487 Unix-Sinne). In vielen Anwendungen kann man diese gleich mitbenutzen, da
488 eine eindeutige Benutzer-ID automatisch vergeben wird.
489
490 !block verbatim
491 Sie sind wahrscheinlich Benutzer Nummer <?$userid:><br/>
492 #if auth_p
493 Und ausserdem haben sie sich authentifiziert.<br/>
494 # if access_p "admin"
495 Oh, und "admin"-Rechte besitzen Sie auch noch! Meine Güte!!<br/>
496 # endif
497 #endif
498 !endblock
499
500 H2: Geschwindigkeit und Skalierbarkeit
501
502 Geschwindigkeit war bei der Implementierung von PApp das zweitwichtigste
503 Ziel (Korrektheit ist das wichtigste ;). Als Marke dient mir ein
504 Pentium-II 266Mhz-Rechner, auf dem auch komplexe Seiten mit mindestens 15
505 Hits/Sekunde dargestellt werden, bzw. der Vergleich mit einem ähnlichen
506 C<Apache::Registry>-Skript.
507
508 Das State-Management von PApp verschlingt pro Seite 1-3 Datenbankzugriffe,
509 die die Zeit bei weitem dominieren (andere Features wie I18n sind im
510 Prinzip kostenlos). Da komplexe Seiten im Allgemeinen wesentlich mehr und
511 kompliziertere Zugriffe enthalten bzw. man ja irgendwie seine Daten
512 speichern muss, ist dies in der Praxis selten eine Einschränkung
513 (Ausnahmen gab und gibt es immer). Andere PApp-Features (z.B. im
514 SQL-Bereich) bringen häufig sogar eine Steigerung der Geschwindigkeit.
515
516 Da ich bisher keine Seite fabrizieren konnte, die die 15 Zugriffe/s-Marke
517 unterschritten hätte, glaube ich noch etwas Spielraum für noch mehr
518 Features zu haben. Wer sich übrigens fragt, woher diese magische Grenze
519 von 15 Hits/s kommen: 15 Hits ist die Marke, die einen wirklich grossen
520 Server von den 99.99% der restlichen Welt unterscheidet. Hat man auf
521 seinem Server 15 oder mehr Zugriffe pro Sekunde kann man sich meistens
522 auch eine schnellere Datenbank oder mehrere Rechner leisten: PApp skaliert
523 problemlos auf mehrere Maschinen oder SMP-Rechner.
524
525 H2: Ideen, Ideen: z.B. "tied forms"
526
527 Ich habe es schon angesprochen: Persistenz von fast allem, zusammen mit
528 den Möglichkeiten von Perl können einen schon auf Ideen bringen, z.B.
529 auf eine Bibliothek (editform), die HTML-Formularfelder an Perl-Refrenzen
530 bindet.
531
532 Nun ist es an der Zeit, einmal eine Referenz auf eine SQL-Tabelle zu
533 erzeugen:
534
535 !block perl
536 <:
537 my $row = new PApp::DataRef 'DB_row',
538 table => "user",
539 where => [id => $userid],
540 delay => 1,
541 autocommit => 1;
542
543 # pre-set name
544 $row->{name} ||= "<username>";
545
546 ef_begin;
547 :><br>__"ID:" <?ef_string \$row->{id} , 5:><:
548 :><br>__"Name:" <?ef_string \$row->{name}, 20:><:
549 ef_submit __"Update";
550 ef_end;
551 :>
552 !endblock
553
554 Die Referenz auf die Zeile steht in C<$row> und verhält sich wie eine
555 Referenz auf einen herkömmlichen Perl-Hash. Das C<autocommit> sorgt
556 dafür, dass die geänderten Daten automatisch zurückgeschrieben werden
557 sobald das C<$row>-Objekt gelöscht wird (ausser Scope geht, C<DESTROY>ed
558 wird). Sie ist lokal (C<my>!) gespeichert, da aber die C<ef_>-Funktionen
559 die Referenz persistent speichern, wird das Objekt erst auf der nächsten
560 Seite (also nachdem die Ergebnisse hineingeschrieben wurden!) zerstört,
561 bzw. die Daten geschrieben. Etwas kompliziert, aber zum benutzen muss man
562 die Details ja auch nicht kennen.
563
564 Nun macht das Beispiel keine Fehlerüberprüfung, meistens das
565 schwierigste. Mit PApp kein Problem. Zuerst schalten wir das C<autocommit>
566 ab, damit die Daten nicht "aus Versehen" geschrieben werden. Ausserdem
567 übergeben wir die Referenz als Argument an die "nächste" Seite.
568
569 !block perl
570 <:
571 my $row = $A{row}
572 || new PApp::DataRef 'DB_row',
573 table => "user",
574 where => [id => $userid],
575 delay => 1,
576 autocommit => 0;
577
578 ef_begin -row => $row; # Daten als Argument übergeben
579 !endblock
580
581 Nun brauchen wir ein Flag, das uns sagt, ob der Datensatz fehlerhaft ist
582 und schon können wir loslegen:
583
584 !block perl
585 my $err = 0; # Daten fehlerhaft?
586
587 :><br/>__"ID:" <?ef_string \$row->{id} , 5:><:
588 #if $row->{id} !~ /^(\d+)$/
589 <error>__"The ID must be an integer!"</error>
590 <:$err++:>
591 #endif
592 :><br/>__"Name:" <?ef_string \$row->{name}, 20:><:
593 #if 2 > length $row->{name}
594 <error>__"The Name must contain at least two characters!"</error>
595 <:$err++:>
596 #endif
597 ef_submit __"Update";
598 ef_end;
599 !endblock
600
601 Die Daten schreibt man dann bei Bedarf in die Datenbank:
602
603 !block perl
604 #if $row->dirty
605 # if $err
606 <error>__"The entered Data is invalid, please surrender or die (or correct it ;)</error>
607 # else
608 <:$row->flush:>
609 __"The record has been updated."
610 # endif
611 #endif
612 :>
613 !endblock
614
615 H2: "Web-Widgets"
616
617 Eine relativ neue "Neuerung" in PApp ist die Einführung von
618 Web-Widgets. Wie so vieles sind das keine speziellen Objekte, sondern eine
619 Art der Programmierung, die zwar schon immer möglich war, aber an die man
620 nicht sofort denkt.
621
622 Statt einer ganzen Applikation, die "ganze Seiten" (z.B. ganze
623 HTML-Seiten) ausgibt, schreibt man eine Applikation, die nur noch
624 Teilseiten ausgibt, die man in größere Applikationenn einbaut. Dies ist
625 nicht das gleiche wie ein "include": Der Namensraum (globale Variablen,
626 state-keys, also Session-Variablen und Preferences) einer solchen
627 eingebetteten Applikation ist getrennt von der einbettenden Anwendung und
628 bildet eine Art "Unternamensraum".
629
630 Derlei eingebettete Anwendungen sind vollkommen autark, haben ihren
631 eigenen Zustand ("aktuelle Seite") und können ihre eigenen Formulare,
632 Links etc. erzeugen die mit anderen Applikationen nicht kollidieren. In
633 einer Anwendung verwende ich dasselbe Forum-Element, um "Web Chat",
634 "Kleinanzeigen" und eine "News!"-Seite zu implementieren - jedesmal eine
635 leicht andere Konfiguration aber derselbe Code.
636
637 Da PApp-Applikationen im Prinzip nur grosse Statemaschinen sind (das ist
638 eine Einschränkung der verwendeten Protokolle, d.h. bis Perl effektive
639 und schnelle continuations bekommt ;), kann man sie auch einfach in andere
640 einbauen. Sie können sich gegenseitig beeinflussen, treten sich aber
641 nicht auf die Füsse.
642
643 Also im Prinzip genauso wie ein "Widget" in X11 oder gtk+.
644
645 H2: Logging/Protokollierung
646
647 PApp protokolliert wesentlich mehr, als man benötigt, legal wäre, und
648 speicherbar wäre. Da PApp jederzeit in der Lage sein muss, eine ältere
649 Seite zu regenerieren ("Back & Reload"), speichert es pro Seite die
650 persistenten Variablen (ca. 300-900 Byte, je nach Applikation, das ist,
651 wie gesagt, auch eine tolle Sache zum debuggen).
652
653 Aber irgendwann müssen diese Daten wieder weg bzw. statistische Daten
654 her - nichts ist interessanter, als Zugriffsmuster, Voreinstellungen oder
655 ähnliches, an dem man ablesen kann, welche Dinge beliebt sind und welche
656 nicht (klar, man kann auch ganz andere Sachen auswerten, aber das ist
657 nicht mein Problem).
658
659 Beim Aufräumen spielt PApp die einzelnen Zugriffe noch einmal
660 durch. Statt jedoch Seiten zu erzeugen gibt PApp den einzelnen
661 Applikationen die Möglichkeit, statistische Daten zu sammeln. Etwas
662 undokumentierter (wenn es da Abstufungen gibt) ist die Möglichkeit,
663 globale Daten zu sammeln, aber es geht...
664
665 H1: Eine einfache Anwendung mit PApp
666
667 Jetzt kommen wir zur eigentlichen Frage: Wie sieht so ein PApp-Programm
668 aus? Nun, meistens so:
669
670 H2: Das Hauptprogramm
671
672 !block verbatim
673 <package name="demo"> <domain lang="en">
674
675 <database dsn="DBI:mysql:demodb"/>
676
677 <translate fields="project.name place.name" lang="de"/>
678 <translate fields="project.description" lang="*" style="auto"/>
679
680 <import src="macro/util"/>
681
682 <include src="demo/somepages"/>
683
684 </domain> </package>
685 !endblock
686
687 Hmm... das ist also erstmal XML mit furchtbar vielen neuen
688 Elementen. Normalerweise kopiert man sich einfach ein anderes Programm
689 und schreibt nicht alles neu. Gut: zuersteinmal das C<package>-Element:
690 damit wird - genau wie in Perl - ein neuer Namensraum erzeugt, der sowohl
691 normale Package-Variablen als auch State-Variablen enthält. Nicht jedes
692 Programm muss einen eigenen Namensraum öffnen.
693
694 Das nächste Element, C<domain>, aktiviert die Übersetzungen: Alle
695 markierten Texte werden unter einem Namen, der C<domain>
696 gebündelt. Beispielsweise befinden sich alle Texte des PApp-Systems
697 selbst in der "papp"-Domain. Da im Beispiel kein Domainname angegeben
698 wird, nimmt PApp den Namen des umschliessenden C<package>-Elements, d.h.
699 wir definieren hier eine Übersetzungsdomain "demo".
700
701 Das nächste Element deklariert die Standarddatenbank: Wird die
702 Datenbankhandle bei SQL-Abfragen weggelassen, wird diese Datenbank
703 benutzt. Normalerweise deklariert man Datenbanken aber nicht im Quellcode,
704 sondern beim Einrichten der Anwendung im Konfigurationsmenü.
705
706 Zu den nächsten beiden Elementen muss ich etwas über das Programm
707 erklären, das ich hier entwickeln möchte: Das Demo-Programm sollte
708 kurz sein und die wichtigsten Features von PApp zeigen. Es wird aus drei
709 (Web-) Seiten bestehen, eine Art Hauptseite mit einem Menü und zwei
710 Unterseiten, auf denen man ein Projekt auswählen kann (ein Projekt ist
711 ein einfacher Datensatz in der Datenbank, der den Projektnamen, den
712 Ort und die Beschreibung enthält). Auf der dritten Seite kann man die
713 einzelnen Felder eines Projektes ansehen und ändern.
714
715 Als besonders überflüssiger Schnickschnack sollen die Projektdaten
716 übersetzbar sein, d.h. in der Sprache des Benutzers angezeigt werden. Das
717 ist nicht sehr wirklichkeitsnah, gibt dafür aber ein sehr simples
718 Beispiel ab.
719
720 Da die Projektdaten in der Datenbank gespeichert sind, kann man
721 sie nicht einfach mit C<__"xxx"> markieren, sondern braucht ein
722 C<translate>-Element. Wo PApp die Übersetzungen herbekommt, ist relativ
723 egal, man muss PApp nur sagen, wie es an die Texte herankommt und in
724 welcher Sprache sie sind.
725
726 Das erste C<translate>-Element sagt, dass die Spalten C<name> in der
727 Tabelle <project> und die Spalte C<name> in der Tabelle C<place> in
728 Deutsch sind und dementsprechend in andere Sprachen zu übersetzen sind.
729
730 Das zweite C<translate>-Element ist schon komplizierter: Nicht alle
731 Projektbeschreibungen sind in derselben Sprache (behaupte ich einfach
732 mal), weshalb als Sprache C<*> angegeben wird. Das bedeutet lediglich,
733 dass {{alle}} Übersetzer diese Texte übersetzen müssen. Wenn der
734 Englisch-Deutsch-Übersetzer also auf einen Deutschen Text stößt, muss
735 er ihn einfach überspringen. Wäre die Spalte als "de" (oder "deu")
736 markiert, würde er sie garnicht erst zu Gesicht bekommen.
737
738 Da Beschreibungen ausserdem sehr lang sein können, erhält man mit
739 C<style="auto"> die Möglichkeit, die C<__"xxx">-Syntax auch in
740 den Beschreibungen zu verwenden. Dies kann man auch für einfache
741 Textbausteine mißbrauchen...
742
743 Das darauffolgende C<import>-Element ist wieder etwas einfacher: es
744 entspricht mehr oder weniger der C<use>-Anweisung in Perl (in genau die
745 wird es auch übersetzt), bezieht sich aber auf PApp-Dateien. Damit
746 kann man Funktionen aus anderen (PApp-) Namensräumen importieren. Im
747 vorliegenden Fall importiere ich einfach mal das C<macro/util>-Paket, das
748 sich immer wieder grosser Beliebtheit erfreut.
749
750 Das letzte Element tut zur Abwechslung genau das, was es sagt: es
751 fügt (logisch gesehen) eine andere Datei an dieser Stelle ein. Die
752 Entscheidung, die folgenden Seiten in eine andere Datei auszulagern, lohnt
753 sich normalerweise nur für grössere Programme, aber so bleibt das
754 Beispiel klein.
755
756 Bis jetzt tut sich noch nichts. Ändern wir das:
757
758 H2: Das erste PApp-Modul
759
760 Einzelne Zustände (z.B. Webseiten) werden bei PApp "Module" genannt, wohl
761 einzig um die Anwender zu verwirren. Diese Module werden meist in andere
762 Elemente (C<domain>, C<package>, C<style> etc..) verpackt, die auf die
763 Sete auf verschiedenste Weise einwirken.
764
765 Ausführbaren Code kann man grundsätzlich in zwei Formen
766 angeben: Normaler Perl-Code und Perl-Code gemischt mit Text (also wie bei
767 anderen embedded-Dialekten):
768
769 !block verbatim
770 <module name=""><phtml><![CDATA[
771 <html> <title>
772 __"PApp - demo", <?localtime:>
773 </title>
774
775 <?slink "Deutsch", "/lang" => "de":>
776 <?slink "English", SURL_SET_LANG, 'en':>
777
778 <p><?slink __"To the editor", "editor":>
779 <p><?slink __"Edit project 1", "editor", projectid => 1:>
780
781 </html>
782 ]]></phtml></module>
783 !endblock
784
785 Zuerst zum C<module>-Element: Jedes Modul besitzt einen eindeutigen
786 Namen. Das Standardmodul (das angezeigt wird, wenn nichts spezielles
787 ausgewählt ist, also sozusagen die Startseite) ist das Modul mit dem
788 leeren Namen: C<"">.
789
790 Das Ergebnis eines Moduls sind die Ausgaben, die darin gemacht werden
791 (z.B. mit C<printf> oder dem PApp-C<echo>). Die Ausgabe des obersten
792 Moduls (im Baum) wird an den Browser geschickt. Für Ausgabe braucht man
793 Perl und das habe ich in ein C<phtml>-Element gepackt (für verbatimen
794 Perl-code würde man ein C<perl>- oder C<xperl>-Element verwenden, für
795 Perl gemischt mit XML gibt es noch C<pxml>).
796
797 Innerhalb des C<phtml>-Elementes darf nur eine Zeichenkette stehen, die
798 ausgegeben wird. Da wir innerhalb der Zeichenkette Perl einbetten wollen
799 und die Modus-Umschalter {{kein}} gültiges XML sind, muss der Inhalt
800 geschützt werden, in diesem Fall mit einem C<CDATA> (die Verwendung von
801 Abkürzungen in VI o.ä. empfiehlt sich ;).
802
803 Da dies das oberste Modul ist und wir (noch) kein Stylesheet verwenden,
804 müssen wir eine ganz HTML-Seite ausgeben. Als Titel nehmen wir den Text
805 C<PApp-demo>, der übersetzt werden muss sowie, weil es so schön ist, die
806 aktuelle Uhrzeit. Dies geschieht mit einem {{C:<?}}: Der Ausdruck (hier
807 C<localtime>) wird in einem skalaren Kontext ausgewertet und das Ergebnis
808 ausgegeben.
809
810 Die nächste Code-Zeile ist interessanter:
811
812 !block verbatim
813 <?slink "Deutsch", "/lang" => "de":>
814 !endblock
815
816 C<slink> ist PApps Art, eine Hypertext-Referenz (C<A>-Element) zu
817 erzeugen. C<slink> erwartet als erstes Argument den Inhalt des Verweises
818 (der Text, den der Benutzer "anklicken" muss) und daraufhin die
819 sogenannten C<surl>-Argumente, die bei vielen PApp-Funktionen angegeben
820 werden können. C<surl>-Argumente sind im wesentlich "Name => Wert"-Paare,
821 die dem Zielmodul als Argumente übergeben werden können. Im Beispiel
822 ist das die Variable C</lang> (der Slash am Anfang markiert eine globale
823 Variable), die auf den Wert "de" gesetzt wird. Das Ergebnis ist, dass
824 Textmeldungen nun auf Deutsch übersetzt werden.
825
826 !block verbatim
827 <?slink "English", SURL_SET_LANG, 'en':>
828 !endblock
829
830 Neben Argumenten kann man auch bestimmte "Cookies" in die
831 C<surl>-Argumente einbauen. C<SURL_SET_LANG> z.B. setzt die Sprache auf
832 den folgenden Wert und speichert ausserdem die Voreinstellungen ab,
833 d.h. die Sprachwahl ist nun permanent. Neben C<SURL_SET_LANG> gibt es
834 noch eine ganze Reihe weitere "Cookies" wie z.B. C<SURL_EXEC>, mit dem
835 man bei Auswahl des Links eine Unterroutine aufrufen lassen kann oder
836 C<SURL_STYLE_GET>, mit dem man den Stil der URL einmalig überschreiben
837 kann. Die nächste Zeile bringt eine weitere Erweiterung:
838
839 !block verbatim
840 <p><?slink __"To the editor", "editor":>
841 !endblock
842
843 Bisher wurde kein {{Ziel}} für den Link angegeben. In solchen Fällen
844 nimmt PApp die aktuelle Seite (das aktuelle Modul) als Ziel, d.h. sie wird
845 einfach neu geladen (bei Sprachänderungen etc. ja gewünscht). Möchte
846 man ein anderes Modul ansteuern, gibt man einfach den Namen des Moduls an,
847 z.B. C<"editor">. Ein "Klick" auf den Link (oder das Laden der URL, wie
848 auch immer das geschieht) ruft dann das C<editor>-Modul auf.
849
850 Das "Ziel" muss auch nicht immer ein Modulname sein, es geht auch
851 komplizierter: C<"admin,user/edit,group/"> z.B. verzweigt auf das
852 C<admin>-Modul und gleichzeitig in einem eingebetteten Widget C<user> auf
853 das Modul C<edit>, während es im Widget C<group> auf das Hauptmodul (das
854 mit dem leeren String als Namen) schaltet. Aber das braucht man wirklich
855 nur in grossen Projekten ;).
856
857 Ziel und Argumente kann man natürlich kombinieren:
858
859 !block verbatim
860 <p><?slink __"Edit project 1", "editor", projectid => 1:>
861 !endblock
862
863 Hier wird dieselbe Seite (C<editor>) angesteuert, dieses mal wird jedoch
864 ein Argument übergeben. Beim Aufruf wird dieses Argument in den State
865 geschrieben, steht dem C<editor>-Modul also als C<$S{projectid}> zur
866 Verfügung. Manchmal möchte man einfach nur ein Argument übergeben,
867 dass nicht im State endet, d.h. nicht persistent ist. In diesem Fall
868 kann man vor den Namen ein '-' stellen, der Wert landet dann nicht in
869 C<%S> sondern in C<%A> und wird nach dem Aufruf weggeworfen. Eine dritte
870 Möglichkeit ist es, eine Referenz auf einen Skalar anzugeben (der
871 natürlich persistent sein muss, sonst existiert er beim Aufruf nicht
872 mehr).
873
874 Die Werte dürfen beliebige Perl-Referenzen sein, solange sie
875 serialisierbar sind. In PApp gehören Code-Referenzen und (PApp-)
876 Datenbankhandles übrigens zu den serialisierbaren Datentypen, wenn man
877 etwas Vorsicht walten lässt.
878
879 Der erste Link auf C<editor> soll, da er kein Projekt auswählt, eine
880 Liste aller Projekte zeigen während der zweite ein spezielles Projekt
881 (das hoffentlich existiert) anzeigen soll. Damit wäre die erste Seite
882 erklärt, schreiten wir zum C<editor>-Modul:
883
884 H2: Das C<editor>-Modul
885
886 Ich gebe zu, ich habe etwas gemogelt. Natürlich macht es normalerweise
887 mehr Sinn, zwei getrennte Seiten (z.B. C<project_list> und
888 C<project_edit>) für das Listen und Edieren zu benutzen. Aber dann hätte
889 ich keinen Grund, ein weiteres Stückchen "syntactic sugar" zu zeigen,
890 Präprozessorkommandos:
891
892 !block perl
893 <module name="editor">
894 <state keys="projectid" local="yes"/>
895 <phtml><![CDATA[
896 <:header:>
897
898 #if defined $S{projectid}
899 ... edit the specific project
900 #else
901 ... show a list of projects
902 #endif
903
904 <:footer:>
905 ]]></phtml></module>
906 !endblock
907
908 Das C<module>-Element kennen wir ja schon. Diesmal deklariert es das
909 C<editor>-Modul. Das nächste Element C<state> ist schon interessanter:
910 hier markiert es bestimmte State-Keys (hier: C<projectid>) als C<local>,
911 d.h. lokal zu allen Seiten, die diese Variable so markieren. Da das
912 Hauptmodul die C<projectid> {{nicht}} als lokal markiert, wird sie beim
913 "Klick" auf das Hauptmodul automatisch gelöscht. Hätte man stattdessen
914 geschrieben
915
916 !block perl
917 <state keys="projectid bgcolour" preferences="yes"/>
918 !endblock
919
920 hätte man die beiden State-Keys als Voreinstellungswerte markiert, d.h.
921 beim nächsten Aufruf würde der Benutzer das gleiche Projekt sehen (und
922 eventuell die gleiche Hintergrundfarbe, je nachdem, was C<bgcolour>
923 bedeutet).
924
925 Die beiden "Tags", {{C:<:header:>}} und {{C:<:footer:>}}, sind eigentlich
926 nur getarnte Funktionsaufrufe: In den Tagen vor XSLT habe ich meistens auf
927 diese Weise ein Standard-Layout erstellt. C<header> könnte man z.B. so definieren:
928
929 !block perl
930 <macro name="header"><phtml><![CDATA[
931 <html>
932 <head><title>__"Hallole"</title></head>
933 <body>
934 ]]></phtml></macro>
935 !endblock
936
937 C<macro> definiert eine ganz normale Perl-Funktion, sie kann Argumente
938 annehmen und Werte zurückliefern. Der einzige Unterschied ist, dass man
939 Perl-Funktionen auf diese Weise in "phtml"-Syntax schreiben kann. Um
940 einzelne Seiten gegen unberechtigen Zugriff zu schützen (und um noch mehr
941 abzuschweifen) habe ich früher folgendes gemacht:
942
943 !block perl
944 <macro name="page(&amp;)" args="$body"><phtml><![CDATA[
945 <html> <body>
946 #if access_p "project_editor"
947 <:&$body:> <!-- eigentliche seite anzeigen -->
948 #else
949 __"You need to login yourself first"<p>
950 <:loginbox:> <!-- loginbox anzeigen -->
951 #endif
952 </body> </html>
953 ]]></phtml></macro>
954
955 <module name="secure_page"><phtml><![CDATA[
956 <:page {:>
957 <h1>__"Hallo"</h1>
958 <:}:>
959 ]]></phtml></module>
960 !endblock
961
962 Gut, zurück zum Thema: Die Aufgabe ist klar: ist eine C<projectid>
963 gegeben, soll das entsprechende Projekt ediert werden, sonst soll eine
964 Liste von Projekten zur Auswahl angeboten werden.
965
966 Das könnte man zwar mit einem C<if> lösen, es geht aber auch anders:
967
968 !block perl
969 #if <perl-ausdruck>
970 ...
971 #elif <perl-ausdruck>
972 ...
973 #else
974 ...
975 #endif
976 !endblock
977
978 Das ist fast so schön wie C... *hüstel*. Jedenfalls wird es in ein
979 hundsnormales Perl-if/elsif/else/endif umgesetzt. Die beiden Teile "edit
980 the specific project" und "show a list of projects" werden sofort gefüllt:
981
982 H3: "show a list of projects"
983
984 Zuerst die Liste der Projekte. Ah, wir benötigen SQL. Nun, das ist
985 einfach, wir brauchen nur "jede Menge" HTML auf das Problem zu werfen:
986
987 !block perl
988 <table><tr><th>__"Project"<th>__"Budget"
989 <:
990 my $st =
991 sql_exec \my($id, $name, $budget),
992 "select id, name, budget
993 from project";
994
995 while ($st->fetch) {
996 :><tr><td>
997 <?slink gettext$name,
998 projectid => $id?>
999 <td>$budget
1000 <:
1001 }
1002 :>
1003 </table>
1004 !endblock
1005
1006 Dies erzeugt eine einfache HTML-Tabelle mit den beiden
1007 Spaltenüberschriften "Project" und "Budget", natürlich übersetzbar. Der
1008 Aufruf von C<sql_exec> tut drei Dinge:
1009
1010 ^ C<prepare>, falls notwendig (C<prepare>te Statements werden gecached).
1011 & C<bind_columns>, auf die drei Referenzen C<\$id>, C<\$name> und C<\$budget>.
1012 & C<execute>, um die Abfrage zu starten.
1013
1014 Nun müssen wir nur noch in einer Schleife über die Zeilen iterieren
1015 (mit dem alten DBI-Bekannten C<fetch>) und TR/TD-Zeilen ausgeben. In
1016 der Schleife kann man sehr schön sehen, wie man zwischen Perl und HTML
1017 umschalten kann. Keine Angst, daran gewöhnt man sich sehr schnell, vor
1018 allem mit Syntax-Hilighting (ein weiterer Grund, auf vim umzusteigen).
1019
1020 Das einzig Komplizierte ist der Aufruf von C<slink>, der einen C<A
1021 HREF>-Link auf das C<editor>-Modul (also auf die aktuelle Seite) erzeugt
1022 und diesem gleich noch die aktuelle Projekt-ID mitgibt. C<gettext> ist
1023 der Runtime-Teil der C<__>-Funktion, d.h. die Funktion, die von C<__> zur
1024 Laufzeit aufgerufen wird. Sie {{übersetzt}} das Argument, {{markiert}} es
1025 aber nicht.
1026
1027 Das Ergebnis ist ein Link, der beim Aufruf die Seite neu lädt, nur mit
1028 dem Unterschied, dass diesmal eine Projekt-ID verfügbar ist also nicht
1029 mehr die Liste angezeigt wird.
1030
1031 H3: "edit the specific project"
1032
1033 Der Rest ist nun relativ einfach: Mit C<DataRef> eine Referenz erzeugen,
1034 mit C<editform> ein Formular, der Rest geht von alleine:
1035
1036 !block perl
1037 <:ef_begin:>
1038
1039 <:my $row = new PApp::DataRef 'DB_row',
1040 table => "project",
1041 where => [id => $S{projectid}]:>
1042
1043 <p>__"Name": <:ef_string \$row->{name}, 40:>
1044 <p>__"Place:" <:ef_relation \$row->{place},
1045 "id, name from place order by 2",
1046 0 => __"unknown":>
1047 <p>__"Budget": <:ef_string \$row->{budget}, 8:>
1048 <p>__"Description": <:ef_text \$row->{description}, 60, 10:>
1049
1050 <:ef_constant \$row->{user}, $userid:>
1051
1052 <:ef_submit __"Update":>
1053 <:ef_end:>
1054 !endblock
1055
1056 C<new PApp::DataRef> erzeugt eine Zeilenreferenz auf die Zeile mit
1057 dem gewünschten Projekt, C<ef_begin> und C<ef_end> umschliessen ein
1058 Formular, C<ef_string> erzeugt ein Textfeld in der gewünschten Breite
1059 während C<ef_text> ein C<textarea>-Element erzeugt. C<ef_submit> sollte
1060 selbsterklärend sein.
1061
1062 Habe ich was vergessen? Oh ja, der Ort ist ja in einer separaten Tabelle,
1063 in C<project> wird ja nur die ID des Ortes gespeichert. Für solche
1064 Relationen gibt es bei C<editform> das C<ef_relation>-"Element". Man
1065 übergibt einen SQL-Ausdruck, der zwei Spalten (ID und Name) liefert
1066 und optional ein paar weitere Paare (z.B. kann man auch "unbekannt"
1067 angeben). Als Ergebnis erhält man ein C<select>-Element in HTML.
1068
1069 Als letztes wird ein Formularfeld noch mit einer Konstanten (die der
1070 Benutzer nicht ändern kann) gefüllt, in diesem Fall wird das Feld
1071 C<user> auf die aktuelle C<userid> gesetzt, so weiss man immer, wer den
1072 Datensatz zuletzt verändert hat.
1073
1074 Wer Formulare in anderen Sprachen (WML...) benötigt, kann sich seien
1075 eigenen Elemente basteln: C<editform> ist es grundsätzlich egal, wie die
1076 Syntax ist, solange das Schnittstellenmodul von PApp die Daten dekodieren
1077 kann.
1078
1079 H2: Aufgemotzt hält besser
1080
1081 Bis jetzt war alles noch langweilige Grundlagen. Vor allem ist
1082 Standard-HTML doch soo langweilig: die Tabellen sind nicht farbig
1083 hinterlegt, um das Lesen zu erleichtern. Und die C<header>- und
1084 C<footer>-Methode ist auch nicht gerade ein flexibles Layout-Werkzeug.
1085
1086 Deshalb{{}}: XSLT muss her ("eXtensible StyLesheet Transformations"). Und weil
1087 ich so anspruchsvoll bin, gleich zwei Stylesheets: eins ohne aufwendige
1088 Grafik zum benutzen und eins mit vielen farbigen Elementen zum verkaufen
1089 ("bunt haben wollen").
1090
1091 Zuerst müssen wir das Stylesheet laden:
1092
1093 !block perl
1094 <perl><![CDATA[
1095 $stylesheet[0] = $papp->load_stylesheet("demo/demo1");
1096 $stylesheet[1] = $papp->load_stylesheet("demo/demo2");
1097 ]]></perl>
1098 !endblock perl
1099
1100 Naja, zuerst müsste man es schreiben oder besser klauen. Ausserdem muss
1101 man es nicht zuerst laden, aber darüber gehe ich einfach mal hinweg...
1102
1103 Das C<perl>-Element ist übrigens {{nicht}} in einem Modul: Perl-Code,
1104 der nicht in ein Modul gesteckt wird, wird beim Laden der Applikation
1105 ausgeführt, d.h. ganz ähnlich wie ein Perl-Modul. Zu diesem Zeitpunkt
1106 kann man aufwendige Initialisierungen machen bzw. Dateien nachladen, die
1107 man später braucht.
1108
1109 Wie wendet man diese "Stylesheets" nun an? Ganz einfach, man packt alle
1110 Module, die "gestyled" werden sollen, in ein C<style>-Element:
1111
1112 !block perl
1113 <style apply="output" expr="$stylesheet[ $S{style} ]">
1114
1115 <module name=""><phtml><![CDATA[
1116 <p/><?slink __"To the edito...
1117 <p/><?slink __"Edit projec...
1118 ]]></phtml></module>
1119
1120 ...
1121 </style>
1122 !endblock perl
1123
1124 Das C<apply> bezieht sich auf den Zeitpunkt, zu dem das Stylesheet
1125 angewendet werden soll. C<apply="output"> bestimmt, dass das Stylesheet
1126 kurz vor der Ausgabe angewendet werden soll. Normalerweise würde man nun
1127 den Dateinamen des Stylesheets mit C<src="pfad"> angeben, da wir aber
1128 zwischen zwei Stylesheets hin- und herschalten wollen, geben wir mit
1129 C<expr> einen Perl-Ausdruck an, der ein Stylesheet-Objekt als Ergebnis
1130 haben (sollte). Das muss {{natürlich}} auch kein XSLT-Stylesheet sein,
1131 aber wem sage ich das...
1132
1133 Das Modul ist übrigens (bis auf die durch "..." angedeuteten Lücken)
1134 vollständig, d.h. der Kopf mit Titel und Sprachumschalter (sowie
1135 Stylesheet-Umschalter) wird durch das Stylesheet hinzugefügt.
1136
1137 H1: Tinychat - ein kleiner Chat in 20 Zeilen
1138
1139 Zum Schluss noch ein sehr einfaches Beispiel: C<macro/tinychat.papp> ist
1140 ein sehr einfaches Chat-Fenster: Es zeigt die letzten fünf Eingabezeilen
1141 an, gefolgt von einer Eingabebox. Der Chat-Inhalt ist systemweit, d.h. auf
1142 allen Servern in einem PApp-System ist immer derselbe Text, man kann diese
1143 "Chatbox" also in beliebige Programme einbauen.
1144
1145 !block perl
1146 <macro name="tinychat*()"><pxml><![CDATA[
1147 #if $A{tinychat_submit} && $P{input} && !reload_p
1148 <:
1149 lockenv {
1150 my $r = getenv "TINYCHAT";
1151 shift @$r while @$r > 5;
1152 push @$r, escape_html sprintf "%s: %s",
1153 username || "<ANON$userid>", $P{input};
1154 setenv "TINYCHAT", $r;
1155 };
1156 :>
1157 #endif
1158 <:
1159 my $r = getenv "TINYCHAT";
1160 echo map "<tt>$_</tt><br />", @$r;
1161 :>
1162 <br />
1163 <?sform -tinychat_submit => 1:>__"Chat: "<?textfield "input":><?endform:>
1164 ]]></pxml></macro>
1165 !endblock
1166
1167 Zuallererst ist C<tinychat> nur eine Funktion, teilt sich also den
1168 Namensraum mit dem Aufrufer. Tinychat ist so winzig und so schlecht
1169 konfigurierbar, dass das Sinn macht... Sehen wir uns mal den Teil an, der
1170 die Ausgabe erledigt:
1171
1172 !block perl
1173 <:
1174 my $r = getenv "TINYCHAT";
1175 echo map "<tt>$_</tt><br />", @$r;
1176 :>
1177 !endblock
1178
1179 Die Texte sind in einer "Environment"-Variable gespeichert. Diese
1180 Variablen sind global für das gesamte PApp-System und könenn auch
1181 ausserhalb von PApp abgefragt bzw. verändert werden. Das ganze ist also
1182 eher ein System zur asynchronen Kommunikation. Neben normalen Strings kann
1183 man alles darin ablegen, was irgendwie serialisierbar ist, insbesondere
1184 eine Array-Referenz, in der die einzelnen Zeilen sind.
1185
1186 Zur Ausgabe wird also lediglich die Variable C<PAPP_TINYCHAT> ausgelesen
1187 und die einzelnen Zeilen in ein "<tt>zeile</tt><br />" gepackt.
1188
1189 Fehlt noch das Eingabefeld:
1190
1191 !block perl
1192 <?sform -tinychat_submit => 1:>
1193 __"Chat: "
1194 <?textfield "input":>
1195 <?endform:>
1196 !endblock
1197
1198 Hier wird kein C<editform> benutzt: Der Overhead ist im Vergleich
1199 zum Gewinn (es gibt keinen) zu gross. Die Funktion C<sform>
1200 (die auch von C<ef_begin> benutzt wird) gibt das einleitende
1201 C<FORM>-Tag aus. Dann folgt der Text C<Chat:> und ein ganz normales
1202 HTML-C<INPUT>-Element. C<endform> schleisslich gibt ein "</FORM>" aus und
1203 existiert eigentlich nur aus Symmetrie.
1204
1205 Das Eingabefeld heisst C<input>. Was passiert, wenn wir sonst noch
1206 ein C<input>-Feld haben und dieses andere Feld submittet wird? Kein
1207 Problem{{}}: Wir übergeben C<sform> einfach ein Argument, das nur dazu
1208 dient, die Information "Tinychat-Formular ist gemeint" zu übertragen.
1209
1210 Ganz nebenbei: C<sform> ist - wie sehr viele PApp-Funktionen - sehr
1211 einfach definiert:
1212
1213 !block perl
1214 PApp::HTML::_tag "form", { method => 'GET', action => &surl };
1215 !endblock
1216
1217 Nun zum Teil, der die Eingabezeile nach dem Abschicken hinzufügt:
1218
1219 !block perl
1220 #if $A{tinychat_submit} && $P{input} && !reload_p
1221 <:
1222 lockenv {
1223 my $r = getenv "TINYCHAT";
1224 shift @$r while @$r > 5;
1225 push @$r, escape_html sprintf "%s (%s): %s",
1226 username || "<ANON$userid>", $P{input};
1227 setenv "TINYCHAT", $r;
1228 };
1229 :>
1230 #endif
1231 !endblock
1232
1233 Die erste Zeile testet drei Dinge:
1234
1235 ^ Es muss "unser" Formular sein; das erkennt man daran, dass der Parameter
1236 C<tinychat_submit> logisch wahr ist.
1237 & Die Eingabe sollte nicht leer sein (o.k. "0" ist auch nicht erlaubt).
1238 & Die Seite sollte nicht das Ergebnis eines "Reloads" sein.
1239
1240 Der letzte Punkt bedarf einer Erklärung: PApp weiss, wie oft eine
1241 Seite angefordert wurde und teilt dies über die Funktion C<reload_p>
1242 mit, die die Anzahl der Seitenaufrufe für {{dieselbe}} Seite minus
1243 eins zurückliefert. Ist diese Zahl ungleich null, wurde der Code schon
1244 ausgeführt.
1245
1246 Das eigentliche hinzufügen ist Standard: Variable holen, alte Zeilen
1247 rauslöschen, neue Zeile hinzufügen, Variable auf neuen Wert setzen.
1248
1249 Die Funktion C<lockenv>, in die die Manipulation eingeschlossen ist,
1250 schützt das Programm gegen gleichzeitige Modifikationen anderer
1251 Webserver, d.h. die Operation wird atomar. C<escape_html> quoted das
1252 Argument. Die Funktion C<username> (aus dem C<macro/admin>-Paket) liefert
1253 den Namen des Benutzers, falls dieser einen besitzt. Ansonsten nimmt
1254 Tinychat C<ANON> + die numerische User-ID, die jedem Benutzer zugeteilt
1255 wird.
1256
1257 H1: Die Nachteile
1258
1259 Bei allen Vorteilen, es gibt auch Nachteile... die packe ich ans Ende und
1260 fasse mich auch gerne sehr kurz:
1261
1262 H2: Die Lizenz (Oder doch ein Vorteil?)
1263
1264 Tja, PApp war doch tatsächlich mal GPL, und zwar zu einer Zeit, zu der
1265 wir es praktisch nur als CGI-Krücke verwendet haben. Inzwischen hat
1266 sich PApp gemausert und wurde zu einem unserer Standbeine. Als eine
1267 andere Firma versuchte, uns mit unserem Produkt Konkurrenz bei unseren
1268 Kunden zu machen ("wir können da einfach ein paar Dutzend Programmierer
1269 dransetzen"), mussten wir leider handeln.
1270
1271 Die "PApp Public License" ist so ähnlich wie die MySQL Public License,
1272 d.h. wer sie privat einsetzt (bzw. für die Forschung und Lehre oder
1273 für eine not-for-profit-Organisation), darf PApp weiterhin kostenlos
1274 nutzen. Wer kräftig Kohle damit macht, muss uns einen Teil davon abgeben
1275 (die eigentliche Lizenz ist etwas länger ;).
1276
1277 Langfristig ist geplant, PApp wieder in GPL oder besser zu
1278 überführen. Mittelfristig muss man damit Leben ;)
1279
1280 H2: Die Abhängigkeiten
1281
1282 Zur Zeit gibt es keine Perl-Release, die annähernd mit UTF8
1283 zurechtkommt. Z.Zt. (d.h. buchstäblich in dieser Minute) benötigt
1284 PApp perl-5.7.0-DEVEL7952 oder ein paar hundert Patches davor oder
1285 danach. Demnächst wird auf DEVEL8xxx umgestellt (z.Zt. gibt es einige
1286 Bugs, die dies verhindern). PApp funktioniert zwar wunderbar, aber eben
1287 nur, wenn die restlichen Komponenten aufeinander abgestimmt sind.
1288
1289 H2: MySQL
1290
1291 Das Grundsystem von PApp benötigt zwar kein MySQL, einige Module
1292 (z.B. C<PApp::Env>) dagegen (aus Geschwindigkeitsgründen) schon.
1293 Möchte man also eine einfache Installation und alle Features empfiehlt
1294 sich MySQL, zumindest für die PApp-interne Datenbank selbst. Ansonsten
1295 arbeitet PApp mit allen Datenbanken, die ein DBI-Interface aufweisen, ohne
1296 Probleme.
1297
1298 H2: Geschmackssache
1299
1300 PApp ist etwas sehr persönliches - ich habe meine eigenen Vorstellungen
1301 davon, wie Web/CGI etc. funktionieren sollte, darin verwirklicht. Es
1302 hat meine Motivation (die sich mit "nie wieder CGI" zusammenfassen
1303 liess) gewaltig gesteigert - Ein einfaches aber dennoch komplettes
1304 Content-Management-System kann man in weniger als 500 Zeilen hinlegen -
1305 für mich ein wichtiger Faktor, denn ich hasse nichts mehr, als das Rad
1306 jedesmal neu erfinden zu müssen.
1307
1308 Da ich bekannt bin für meinen etwas merkwürdigen Geschmack (sagt man
1309 mir) muss das {{wie}} nicht unbedingt jedem gefallen.
1310
1311 A1: Referenzen
1312
1313 * {{http://papp.plan9.de/}}. Die PApp-Homepage.
1314
1315 A1: XSLT-Quelltext "Text-Only-Layout"
1316
1317 Dies und das folgende XSLT-Stylesheet kratzen nur an der Oberfläche
1318 dessen, was mit XSLT möglich ist.
1319
1320 !block verbatim
1321 <xsl:stylesheet version="1.0"
1322 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
1323 xmlns:papp="http://www.plan9.de/xmlns/papp"
1324 >
1325
1326 <xsl:output method="html" omit-xml-declaration='yes' media-type="text/html" encoding="utf-8"/>
1327
1328 <xsl:template match="papp:module">
1329 <html>
1330 <title><xsl:value-of select="@module"/></title>
1331 <body>
1332 <?slink "English", "/lang" => 'en':>
1333 <xsl:text> </xsl:text>
1334 <?slink "Deutsch", "/lang" => "de":>
1335 <br/>
1336 <?slink __"Fancy", style => 1:>
1337
1338 <hr/>
1339 <xsl:apply-templates/>
1340 <hr/>
1341 <:debugbox:>
1342 </body>
1343 </html>
1344 </xsl:template>
1345
1346 <xsl:template match="node()|@*">
1347 <xsl:copy>
1348 <xsl:apply-templates/>
1349 </xsl:copy>
1350 </xsl:template>
1351
1352 </xsl:stylesheet>
1353 !endblock
1354
1355 A1: XSLT-Quelltext "Superbunt und Superhässlich-Layout"
1356
1357 !block verbatim
1358 <xsl:stylesheet version="1.0"
1359 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
1360 xmlns:papp="http://www.plan9.de/xmlns/papp"
1361 >
1362
1363 <xsl:output method="xhtml" omit-xml-declaration='yes' media-type="text/html" encoding="utf-8"/>
1364
1365 <xsl:template match="papp:module">
1366 <html>
1367 <title><xsl:value-of select="@module"/></title>
1368 <body link="#a000000" alink="#a00000" vlink="#a00000" text="#000000" bgcolor="#ffffb4">
1369 <?slink "English", "/lang" => 'en':>
1370 <xsl:text> </xsl:text>
1371 <?slink "Deutsch", "/lang" => "de":>
1372 <br/>
1373 <?slink __"Plain", style => 0:>
1374 <table bgcolor='#ffff00' border="1"><tr>
1375 #if $PApp::module ne ""
1376 <td><?slink __"[MAIN PAGE]", "":></td>
1377 #endif
1378 #if $PApp::module ne "editor" or $S{projectid}
1379 <td><?slink __"[PROJECTS]", "editor", projectid => undef:></td>
1380 #endif
1381 </tr></table>
1382 <xsl:if test="@module=''">
1383 <h1>__"Demo"</h1>
1384 __"Welcome to our pages..."
1385 </xsl:if>
1386 <table bgcolor="#ffffff" border="5" cellpadding="20"><tr><td>
1387 <xsl:apply-templates/>
1388 </td></tr></table>
1389 <hr/>
1390 <font size="1">Copyright whatever, whenever etc...</font>
1391 </body>
1392 </html>
1393 </xsl:template>
1394
1395 <xsl:template match="node()|@*">
1396 <xsl:copy>
1397 <xsl:apply-templates/>
1398 </xsl:copy>
1399 </xsl:template>
1400
1401 </xsl:stylesheet>
1402 !endblock
1403
1404