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 <: |
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< > |
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(&)" 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 |
|