Cheetah als CGI-Vorlagensprache fuer den Apachen

Cheetah ist eine einfache, aber trotzdem mächtige Vorlagensprache für Python. Mit Cheetah kann man jede Art von Textdokumenten generieren. Der Haupt-Zweck ist natürlich das Generieren von HTML-Dokumenten.

Cheetah ist ein reines Python-Programm. Deshalb kann es normalerweise auf jedem Webserver ausgeführt werden der Python unterstützt. Selbst diese Seite, die du gerade liest, wurde mit Cheetah zusammengesetzt.

Ich zeige hier nur meine Versuche mit dem Apache-Webserver auf. Dieser unterstützt so genannte .htaccess-Dateien. Damit kann man die Einstellungen des Webservers für die eigene Website verändern. Unter Anderem kann man auch eine Handler-Datei für gewisse Dateitypen oder Dateien mit gewissen Endungen angeben.

Man kann den Apachen also so einstellen, dass immer dann wenn eine Cheetah-Vorlage mit der Endung .tmpl (=Cheetah-Vorlage) angefordert wird, ein Python-Programm aufgerufen wird. So ein Programm nennt man Handler.

Ich beschränke mich bei meinen Versuchen auf CGI (Common Gateway Interface). Wenn jemand einen einfachen Webspace gemietet hat, dann ist, wenn überhaupt, Python oft nur über das CGI verwendbar.

Zum Probieren verwende ich den Apachen, den ich mir irgendwann mal auf meinem Arbeitscomputer unter WindowsXP installiert habe. Und da auf meinem Computer sowiso auch Python installiert ist, kann der Apache automatisch auch Python-Programme als CGI-Programme verwenden. Da muss nichts zusätzlich installiert werden.

Noch einen Hinweis möchte ich los werden. CGI eignet sich hervorragend, wenn du nur etwas Kleines machen möchtest. Ein Formular ausfüllen lassen und dann per Email verschicken -- kein Problem. Den Benutzer in ein Formular etwas eingeben lassen und darauf reagieren -- kein Problem. Daten aus einer Datenbank auslesen und sogar Daten in eine Datenbank schreiben -- kein Problem.

Aber sobald du mehr machen möchtest und du dich nicht nur um ein paar dynamische Seiten kümmern musst, ist CGI einfach zu "low level" dafür. Du müsstest dich sogar um Sessiondaten selber kümmern. Chaching ist so gut wie nicht möglich. Jeder Aufruf einer Seite startet Python als eigenständigen Prozess neu. Das funktioniert bei bis zu (geschätzten) 20 Aufrufen die Minute sicher noch schnell genug. Aber wehe es werden mehr. -- Durch die vielen laufenden Python-Prozesse, durch den Speicherverbrauch der einzelnen Python-Prozesse und sogar durch das Starten der Python-Prozesse selbst, wird der Server stark ausgelastet. Da eine private Website selten so viele Zugriffe pro Minute haben dürfte, sollte das für Viele kein Problem darstellen. Wer mehr erwartet, sei auf den Erfahrungsbericht mit Cherrypy und Cheetah hingewiesen. Darin erkläre ich auch Cheetah etwas besser als hier.

Installation von Cheetah auf dem Webserver

Zum Glück braucht man keinen SSH-Zugang um Cheetah auf dem Webserver zu installieren. Es genügt, wenn man die TAR-Datei (Cheetah-2.0.tar.gz) lokal entpackt und dann einen der entpackten Ordner per FTP zum Webserver überträgt.

Cheetah-2.0
+---bin
+---src
    +---Macros
    +---Templates
    +---Tests
    +---Tools
    ¦   +---turbocheetah
    ¦       +---tests
    +---Utils
        +---optik

Für mich ist nur der Ordner src wichtig. Dieser muss nach Cheetah umbenannt werden.

Unterhalb des Cheetah-Ordners befinden sich Dateien und Ordner, die nicht unbedingt gebraucht werden. Diese kann man getrost löschen.

Das sind z.B. die Dateien und Ordner:

  • Cheetah/_namemapper.c
  • Cheetah/Tests/
  • Cheetah/src/Tools/turbocheetah/tests/

Übrig bleibt dieser Baum:

Cheetah
¦   CacheRegion.py
¦   CacheStore.py
¦   CheetahWrapper.py
¦   Compiler.py
¦   convertTmplPathToModuleName.py
¦   DummyTransaction.py
¦   ErrorCatchers.py
¦   FileUtils.py
¦   Filters.py
¦   ImportHooks.py
¦   ImportManager.py
¦   NameMapper.py
¦   Parser.py
¦   Servlet.py
¦   SettingsManager.py
¦   SourceReader.py
¦   Template.py
¦   TemplateCmdLineIface.py
¦   Unspecified.py
¦   Version.py
¦   __init__.py
¦
+---Macros
¦       I18n.py
¦       __init__.py
¦
+---Templates
¦       SkeletonPage.py
¦       SkeletonPage.tmpl
¦       _SkeletonPage.py
¦       __init__.py
¦
+---Tools
¦   ¦   CGITemplate.py
¦   ¦   MondoReport.py
¦   ¦   MondoReportDoc.txt
¦   ¦   RecursiveNull.py
¦   ¦   SiteHierarchy.py
¦   ¦   __init__.py
¦   ¦
¦   +---turbocheetah
¦           cheetahsupport.py
¦           __init__.py
¦
+---Utils
    ¦   htmlDecode.py
    ¦   htmlEncode.py
    ¦   Indenter.py
    ¦   memcache.py
    ¦   Misc.py
    ¦   VerifyType.py
    ¦   WebInputMixin.py
    ¦   __init__.py
    ¦
    +---optik
            errors.py
            option.py
            option_parser.py
            __init__.py

Am Besten, man kopiert den Ordner Cheetah direkt in den Ordner in dem man die Web-Anwendung entwickeln möchte. Um den direkten Zugriff auf den Cheetah-Ordner zu verweigern, sollte man eine .htaccess-Datei mit diesem Inhalt in den Cheetah-Ordner legen.

.htaccess:

Order allow,deny
Deny from all

Infos über diese Apache-Anweisungen bekommt man hier:

Damit kann jetzt niemand mehr von außen auf diesen Ordner zugreifen. Man kann intern aber weiterhin damit arbeiten.

Aufbau der CGI-Webanwendung

Ich habe mir das so vorgestellt:

  • Ein Browser ruft die URL einer Cheetah-Vorlage (.tmpl) auf
  • Der Apache erkennt, dass es sich um die Endung (".tmpl") eines CGI-Programmes handelt und sieht in den Einstellungen nach, welches Programm für diese Dateiendung zuständig ist.
  • Der Apache schreibt die bekannten Parameter wie z.B. aufgerufene URL, übergebene GET-Parameter, übergebene POST-Parameter und noch ein paar andere Daten in die dafür vorgesehenen Umgebungsvariablen.
  • Das aus den Einstellungen ermittelte Python-Programm wird aufgerufen.
    • Dieses Python-Programm kann jetzt alle benötigten Daten aus den Umgebungsvariablen auslesen, damit arbeiten und einen Text über STDOUT an den Apachen zurück schicken.
    • Dieses Python-Programm bekommt vom Apachen über eine Umgebungsvariable (PATH_TRANSLATED) übermittelt, welche Datei (=die Cheetah-Vorlage) ursprünglich aufgerufen wurde. Diese Cheetah-Vorlage wird jetzt vom Python-Programm gerendert und mit print, also per STDOUT, an den Apachen zurück gegeben.
  • Der Apache sendet die Daten, die er vom Python-Programm zurück bekommen hat, an den Browser zurück.
  • Der Browser zeigt die Daten (z.B. die HTML-Seite) an.

Ordnerstruktur auf dem Server

Da ich mit meinem lokal installierten Apachen (unter Windows XP) teste, arbeite ich direkt unterhalb des Ordners htdocs. Der htdocs-Ordner ist (in der Standardeinstellung des Apachen) der öffentliche Ordner, dessen Dateien auf Aufrage ausgeliefert werden (z.b. an einen Browser). In diesem htdocs-Ordner erstelle ich den Ordner cheetah_test. Und unterhalb dieses Ordners sollte auch der Cheetah-Ordner liegen.

Das sieht bei mir so aus:

J:\Apache\htdocs\
   |
   +---cheetah_test\
   |   |
   |   +---Cheetah\
   |   |   |
   |   |   + ...
   |   |

Der Ordner cheetah_test ist der Basisordner der neuen CGI-Anwendung und ist über die URL http://localhost/cheetah_test erreichbar.

Apache-Einstellungen

Damit der Apache weiß, dass py-Dateien und tmpl-Dateien über CGI angesprochen werden sollen, muss man dies dem Apachen mitteilen. Dafür erstelle ich im Basisordner (cheetah_test) eine .htaccess-Datei.

.htaccess:

Options +ExecCGI
AddHandler cgi-script .py
AddHandler cheetah-template .tmpl
Action cheetah-template /cheetah_test/cheetah_handler.py

DirectoryIndex index.html index.htm index.tmpl

<FilesMatch "\.ini$">
    Order allow,deny
    Deny from all
</FilesMatch>

Die Einstellung Options +ExecCGI gibt an, dass in diesem Ordner und in dessen Unterordner CGI erlaubt ist.

Die Einstellung AddHandler cgi-script .py, weist Dateien mit der Dateiendung .py als CGI-Programme aus.

Die Einstellung AddHandler cheetah-template .tmpl, weist Dateien mit der Dateiendung .tmpl als CGI-Programme aus.

Die Einstellung Action cheetah-template /cheetah_test/cheetah_handler.py gibt an, dass immer dann wenn eine Datei vom Typ cheetah-template, also eine Datei mit der Endung .tmpl, angefordert wird, stattdessen das Programm /cheetah_test/cheetah_handler.py ausgeführt werden soll. Man achte auf den Slash (/) am Anfang des Pfades. Der Pfad beginnt für den Apachen, in meinem Fall, beim htdocs-Ordner.

Die Einstellung DirectoryIndex index.html index.htm default.htm index.tmpl gibt an, dass immer dann, wenn kein Dateiname, sondern nur der Ordnername, übergeben wurde, im Ordner nach den Dateien index.html, index.htm, default.htm und index.tmpl gesucht werden soll. Die erste Datei die gefunden wird, wird statt dem Ordnerinhalt zurück gegeben. Damit kann man eine Standard-Datei für einen Ordner und dessen Unterordner festlegen.

Die Einstellungen in den FilesMatch-Tags geben an, dass von Außen niemand auf irgendwelche INI-Dateien zugreifen und diese auslesen kann. Das ist eine reine Sicherheitsvorkehrung die im Moment noch nicht wichtig ist.

Handler-Programm

Alles was ich dafür brauche, ist ein kleines Python-Programm, welches zuerst den CGI-Kopf (Header) erstellt und dann die übergebene Cheetah-Vorlage rendert und an STDOUT zurück gibt.

Nichts einfacher als das. :-)

cheetah_handler.py:

#!J:/Python25/python.exe
# -*- coding: iso-8859-1 -*-

import os
import sys
import cgi
import cgitb; cgitb.enable()

APPDIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, APPDIR)

from Cheetah.Template import Template


def main():
    # Header
    print "Content-Type: text/html;charset=iso-8859-1"
    print

    # Cheetah-Vorlage Rendern und zurück geben
    tmpl_filename = os.environ.get("PATH_TRANSLATED", None)
    assert tmpl_filename.endswith(".tmpl")
    tmpl = Template(file = tmpl_filename)
    print tmpl


if __name__ == "__main__":
    main()

Im Kopf steht der genaue Pfad zu Python und das Encoding, in dem die Datei geschrieben wurde. iso-8859-1 ist unter Windows keine schlechte Wahl.

Nach dem Import der Python-Module füge ich den Anwendungspfad zum Pythonpfad hinzu. Damit haben die Cheetah-Vorlagen direkten Zugriff auf den Anwendungsordner -- egal in welchem Unterordner sich diese später befinden. Da ich den Anwendungsordner ganz an den Anfang des Pythonpfades setze, kann ich so auch sicher gehen, dass Cheetah von diesem Ordner importiert wird. Und sich nicht irgendein älteres, bereits installiertes Cheetah einschleicht.

Danach beginnt das Hauptprogramm. Zuerst wird der Header übermittelt -- ganz nach CGI-Vorgabe, mit einer Leerzeile vom Inhalt getrennt. Dann wird die Umgebungsvariable PATH_TRANSLATED ausgelesen. In dieser befindet sich der Pfad zur aufgerufenen Cheetah-Vorlage. Man könnte die Prüfung, ob es sich auch wirklich um eine Datei mit der Endung .tmpl handelt, übergehen oder zumindest eine Korrekte Meldung an den Browser zurück geben. Aber mir genügt es im Moment, einfach nur ein assert darauf los zu lassen.

Danach wird die Cheetah-Vorlage gerendert und mit print an den Apachen zurück gegeben.

Jede Cheetah-Vorlage, die angefordert wird, wird also nicht direkt vom Apachen ausgeliefert. Es wird stattdessen dieses Programm aufgerufen, welches die angeforderte Cheetah-Vorlage rendert und das Ergebnis an den Apachen zum Ausliefern zurück gibt.

Jetzt fehlt uns nur noch eine Cheetah-Vorlage um die Anwendung zu testen.

Die erste Cheetah-Vorlage

hallo_welt.tmpl:

#attr $sentences = (
    "Hier kommt der Wolf!",
    "Wir sind gekommen um zu bleiben.",
    "Das ist die perfekte Welle.",
    "Aber jetzt geht's auf!",
)
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
  <title>Ein geparstes Cheetah-Template</title>
  <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
</head>
<body>

<h1>Ich bin eine einfache Cheetah-Vorlage</h1>

<p><strong>Und hier - ein paar Sätze:</strong></p>
<ul>
  #for $sentence in $sentences
    <li>$sentence</li>
  #end for
</ul>

</body>
</html>

Diese kann über die URL http://localhost/cheetah_test/hallo_welt.tmpl aufgerufen werden. Zum Demonstrieren von Cheetah habe ich eine kleine For-Schleife eingebaut. :-)

Mit Formulardaten arbeiten

Dafür kann man in einer Cheetah-Vorlage direkt auf die CGI-Mechanismen zugreifen. Aber zuerst erstelle ich mir unterhalb des Ordners cheetah_test den neuen Ordner form_test.

J:\Apache\htdocs\
   |
   +---cheetah_test\
   |   |
   |   +---Cheetah\
   |   |   |
   |   |   + ...
   |   |
   |   +---form_test\
   |   |   index.tmpl
   |   |   datenempfang.tmpl

Ich arbeite im ersten Beispiel mit zwei Cheetah-Vorlagen. Eine Cheetah-Vorlage enthält das Formular. Die zweite Cheetah-Vorlage wird über den Action-Parameter des Formulars aufgerufen und zeigt die Formulardaten an.

Das Formular

index.tmpl:

#import cgi
#attr $form = cgi.FieldStorage()
#attr $title = "Formulartest - Startseite"
<?xml version="1.0"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
  <title>$title</title>
  <meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
</head>
<body>

<h1>$title</h1>


## Fehlermeldung - BEGIN
## Wird nur angezeigt, wenn der Parameter *error* an diese Seite übergeben wurde
#if $form.has_key("error")
  <div style="background-color: yellow; padding: 0.5em; margin: 1em;">
    $form.getfirst("error")
  </div>
#end if
## Fehlermeldung - END


## Eingabeformular - BEGIN
<form action="datenempfang.tmpl" method="GET">
  <p>
    Gib ein Wort ein und klicke danach auf die "Absenden"-Schaltfläche:
    <input type="text" name="wort" value="$form.getfirst('wort', '')" />
    <input type="submit" value="Absenden" />
  </p>
</form>
## Eingabeformular - END


</body>
</html>

.

.

An dieser Seite wird noch geschrieben...

.

.