Erstellen einer Webanwendung

(Auszug aus "Python & XML" von Christopher A. Jones & Fred L. Drake, Jr.)

Nun können Sie Ihr Wissen über DOM dazu benutzen, eine einfache Webanwendung zu erstellen. Entwerfen wir also eine, mit der Artikel veröffentlicht und gelesen werden können. Die Artikel werden über einen Webbrowser eingereicht und gelesen, aber vom Webserver als XML gespeichert, wodurch sie in verschiedenen Informationssystemen verarbeitet werden können, die XML verstehen. HTML-Artikel hingegen sind außerhalb eines Webbrowsers unbrauchbar.

Vorbereiten des Webservers

Um die Beispiele in diesem Abschnitt auszuführen, müssen Sie über einen Webserver verfügen, mit dem Sie CGI-Skripten ausführen können. Diese Beispiele wurden mit Apache entworfen, daher enthalten die CGI-Skripten eine sh-bang-Zeile, die den Pfad zum ausführbaren Python-Programm enthält (der Ausdruck #!/usr/bin/python zu Beginn der Datei), damit Apache sie wie jedes andere CGI-Skript ausführen kann. (Um den Begriff »sh-bang« zu verstehen, muß man ein wenig um die Geschichte von Unix wissen. Die traditionelle Kommandozeilenumgebung für Unix wurde ursprünglich mit dem Programm sh implementiert. Das Ausrufezeichen wurde »bang«-Zeichen genannt, weil es immer nach Wörtern wie »bang« und »pow« in englischen Comic-Büchern und Cartoons benutzt wurde. Da die Zeilen am Anfang eines Skripts, die mit #! anfingen, vom sh-Programm interpretiert wurden, wurden sie zunehmend als sh-bang-Zeilen bekannt.)

Die Ausführbarkeit des Skripts sicherstellen

Auf Ihrem Webbrowser müssen Sie die Ausführbarkeit Ihrer Python-Skripten garantieren. Bei Apache bedeutet das, CGI innerhalb des Webverzeichnisses zuzulassen und dabei zu gewährleisten, daß die CGI-Skripten den Verweis auf den Python-Interpreter enthalten, damit sie korrekt funktionieren, und die Ausführungsrechte des Skripts zu setzen. Letzteres kann man mit dem chmod-Programm bewerkstelligen:

 $> chmod +x start.cgi 

Bei anderen Webservern und unter Windows müssen Sie Ihren CGI-Skripten einen Handler zuweisen, damit sie vom Python-Interpreter ausgeführt werden. Dazu müssen Sie Ihre Skripten vielleicht mit der Erweiterung .py statt mit .cgi versehen, falls .cgi bereits an einen anderen Handler vergeben ist.

Setzen der Schreibrechte

Über das Ausführen von Skripten in einem Webverzeichnis hinaus muß der Webbenutzer im Verzeichnis auch Schreibrechte haben, damit die Beispiele funktionieren. Diese Beispiele sollen die Bearbeitung von XML sowie die Fähigkeit, XML für verschiedene Anwendungen zugänglich zu machen, illustrieren.

Um in diesem Abschnitt eine Abhängigkeit von einer bestimmten Datenbank zu vermeiden und um den Zugang zu XML sehr einfach zu machen, benutzen diese Beispiele das Dateisystem direkt als Speicher. Artikel werden als .xml-Dateien auf die Festplatte gespeichert.

Bei Apache müssen Sie dem Benutzer nobody-Schreibrechte für das spezielle Webverzeichnis geben. Wenn Ihre Seiten aus dem Verzeichnis /home/httpd/myXMLApplication kommen, müssen Sie etwa folgendes einrichten:

$> mkdir /home/httpd/myXMLApplication
$> chown nobody /home/httpd/myXMLApplication
$> chmod 755 /home/httpd/myXMLApplication

Das gibt dem Benutzer nobody (die Benutzer-ID, unter der Apache läuft) Schreibrechte in dem Verzeichnis. Es gibt viele andere Wege, das sicher einzurichten; dies ist nur eine Möglichkeit. Im allgemeinen ist es eine gute Idee, Webbenutzern keine Schreibrechte zu erteilen.

Struktur der Webanwendung

Die Webanwendung wird vorwiegend von einem Skript, nämlich start.cgi, betrieben. Das Skript erledigt den Großteil der Verarbeitung, liefert die Content-Templates und stößt jene Objekte an, die Ihre XML-Artikel speichern und laden. Die Hauptkomponenten bestehen aus dem Artikelobjekt, dem Speicherobjekt, dem Artikelmanager, dem SAX-basierten Artikel-Handler und dem start.cgi-Skript, das den ganzen Vorgang koordiniert. Die folgende Abbildung zeigt ein Diagramm dieser Hauptkomponenten.

Die Site-Architektur

Abbildung: Die Site-Architektur

In den nächsten Abschnitten untersuchen wir den Code und das Verhalten der CGI-Komponenten im Detail.

Die Article-Klasse

Die Article-Klasse stellt einen Artikel als XML-Information dar. Es ist eine »dünne« Klasse mit Methoden nur für die Erzeugung eines Artikels aus existierendem XML bzw. zum Laden des XML-Codes, der den Artikel als String darstellt. Weiterhin hat sie veränderliche Attribute, die es Ihnen erlauben, den Artikelinhalt zu bearbeiten:

def __init__(self):    
    """Setze initiale Datenattribute."""
    self.reset( )

def reset(self):
    self.title = ""
    self.size = 0
    self.time = ""        # Pretty-Print des Zeit-Strings
    self.author = ""
    self.contributor = ""
    self.contents = ""

Um es Ihnen zu ersparen, XML in Ihrem Programm erzeugen zu müssen, können die Attribute während ihrer gesamten Lebensdauer modifiziert werden. Zum Beispiel:

>>> from article import Article
>>> art = Article( )
>>> art.title = "ErsteNachricht"
>>> art.contents = "Dies ist der erste Artikel."
>>> print art.getXML( )
<?xml version="1.0"?>
<article title="ErsteNachricht">
  <contents>Dies ist der erste Artikel.</contents>
</article>

Der Aufruf der Methode getXML bewirkt, daß der XML-Code erneut erzeugt wird, falls nötig. Sie können Artikel mit einem wohlgeformten XML-String erzeugen oder indem Sie einen XML-String aus einer Datei laden. Die getXML-Methode dient dazu, den XML-Code wieder aus dem Objekt herauszuziehen. Beachten Sie die escape-Funktion, die wir aus dem Modul xml.sax.saxutils importiert haben; sie garantiert, daß Zeichen, die in XML von syntaktischer Bedeutung sind, im Ergebnis korrekt codiert werden.

def getXML(self):    
    """Liefert XML nach erneutem Zusammensetzen von
    Datenattributen, die verändert sein können."""

    attr = ''
    if self.title:
      attr = ' title="%s"' % escape(self.title)
    s = '<?xml version="1.0"?>\n<article%s>\n' % attr
    if self.author:
      s = '%s <author name="%s" />\n' % (s, escape(self.author))
    if self.contributor:
      s = '%s <contributor name="%s" />\n' % (s, escape(self.contributor))
    if self.contents:
      s = ('%s <contents>\n%s\n </contents>\n' % (s, escape(self.contents)))
    return s + "</article>\n"

Die fromXML-Methode der Artikel klasse füllt das aktuelle XML-Artikelobjekt mit den Werten des angegebenen Strings. Diese Methode benutzt die Hilfsfunktion parseString aus xml.dom.minidom, um die XML-Daten in ein Dokumentobjekt zu laden, und sie benutzt dann DOM-Methoden, um auf den Inhalt zuzugreifen und die benötigten Informationen zu sammeln:

def fromXML(self, data):   
   """Initialisiere mit einem als String übergebenen XML-Dokument."""
   self.reset()
   dom = xml.dom.minidom.parseString(data)
   self.title = get_attribute(dom, "article", "title")
   self.size = int(get_attribute(dom, "size", "bytes") or 0)
   self.time = get_attribute(dom, "time", "stime")
   self.author = get_attribute(dom, "author", "name")
   self.contributor = get_attribute(dom, "contributor", "name")
   nodelist = dom.getElementsByTagName("contents")
   if nodelist:
      assert len(nodelist) == 1
      contents = nodelist[0]
      contents.normalize()
      if contents.childNodes:
        self.contents = contents.firstChild.data.strip()

Diese Methode benutzt eine an anderer Stelle im Modul definierte Hilfsfunktion. Die Funktion get_attribute sucht im Dokument nach einem Attribut und gibt den gefundenen Wert zurück; wenn das gesuchte Attribut nicht existiert (oder das erwartete Element dazu nicht existiert), gibt sie statt dessen einen leeren String zurück. Falls sie mehr als ein Element findet, das dem gesuchten Elementtyp entspricht, beschwert sie sich lautstark mit der assert-Anweisung. (In einer echten Anwendung würden Sie assert nicht auf diese Art verwenden, aber für unsere Beispiele ist dies ausreichend, da wir hauptsächlich am XML-Aspekt interessiert sind.)

Beim Arbeiten mit der Website-Logik erfolgt die meiste Manipulation von Artikelobjekten entweder in der Storage-Klasse, wenn ein Artikel von der Festplatte geladen wird, oder nach dem Parsen eines eingereichten Formulars, mit dem ein Artikel für einen Benutzer erzeugt wird, wenn anschließend die Storage -Klasse zum Speichern der XML-Datei auf die Festplatte benutzt wird. Das Beispiel zeigt das komplette Listing der Article-Klasse.

Beispiel: Article-Klasse aus article.py

import xml.dom.minidom
from xml.sax.saxutils import escape

class Article:   
   """Stellt einen aus XML erzeugten Block von Text und Metadaten dar."""

   def __init__(self):
     """Setze initiale Datenattribute."""
     self.reset( )

   def reset(self):
     """Re-initialisiere Datenattribute."""
     self.title = ""
     self.size = 0
     self.time = ""          # Pretty-Print des Zeit-Strings
     self.author = ""
     self.contributor = ""
     self.contents = ""

def getXML(self):
     """Liefert XML nach erneutem Zusammensetzen von
     Datenattributen, die verändert sein können."""

   attr = ''
   if self.title:
      attr = ' title="%s"' % escape(self.title)
   s = '<?xml version="1.0"?>\n<article%s>\n' % attr
   if self.author:
   s = ('<?xml version="1.0"?>\n' '<article%s>\n' % attr)
   if self.author:
      s = '%s <author name="%s" />\n' % (s, escape(self.author))
   if self.contributor:
      s = '%s <contributor name="%s" />\n' % (s, escape(self.contributor))
   if self.contents:
      s = ('%s <contents>\n%s\n </contents>\n' % (s, escape(self.contents)))
   return s + "</article>\n"

   def fromXML(self, data):   
      """Initialisiere mit einem als String übergebenen XML-Dokument."""
      self.reset( )
      dom = xml.dom.minidom.parseString(data)
      self.title = get_attribute(dom, "article", "title")
      self.size = int(get_attribute(dom, "size", "bytes") or 0)
      self.time = get_attribute(dom, "time", "stime")
      self.author = get_attribute(dom, "author", "name")
      self.contributor = get_attribute(dom, "contributor", "name")
      nodelist = dom.getElementsByTagName("contents")
      if nodelist:
         assert len(nodelist) == 1
         contents = nodelist[0]
         contents.normalize( )
         if contents.childNodes:
            self.contents = contents.firstChild.data.strip( )

   # Hilfsfunktion:

   def get_attribute(dom, tagname, attrname):
      """Liefere den Wert eines einzelnen Elements & Attributs, falls vorhanden."""
      nodelist = dom.getElementsByTagName(tagname)
      if nodelist:
         assert len(nodelist) == 1
         node = nodelist[0]
         return node.getAttribute(attrname).strip( )
      else:
         return ""

Die Storage-Klasse

Mit der Storage-Klasse werden Artikel als XML-Datei gespeichert und Artikelobjekte aus XML-Dateien erzeugt, die bereits auf der Festplatte gespeichert sind:

>>> from article import Article
>>> from storage import Storage
>>> a = Article( )
>>> a.title = "ErsterArtikel"
>>> a.contents = "Dies ist ein ErsterArtikel."
>>> a.author = "Fred L. Drake, Jr."
>>> s = Storage( )
>>> s.save(a)
>>>
>>> b = s.load("FirstPost.xml")
>>> print b.getXML( )
<?xml version="1.0"?>
<article title="ErsterArtikel">
   <author name="Fred L. Drake, Jr." />
   <contents>Dies ist ein ErsterArtikel.</contents>
</article>

Hier erzeugen Sie einen neuen Artikel a, speichern ihn mit dem Storage-Objekt auf der Platte und erstellen dann eine Reinkarnation des Artikels als b, indem Sie die load-Methode von Storage benutzen. Beachten Sie, daß die load-Methode den tatsächlichen Dateinamen erwartet, der eine Verkettung von article.title und der Erweiterung .xml ist.

Die Methode Storage.save erwartet eine Artikelinstanz als einzigen Parameter und speichert diesen Artikel in der Form article.title.xml als XML-Datei auf die Platte:

sFilename = article.title + ".xml"
fd = open(sFilename, "w")

# Schreibe Datei auf Platte mit Daten aus getXML()-Aufruf
fd.write(article.getXML( ))
fd.close( )

Mit der Methode getXML bekommt man einen XML-String mit einer XML-Version des Artikels; dieser String wird dann in der Datei abgespeichert. Die Methode Storage.load nimmt eine XML-Datei von der Festplatte, liest ihre Daten aus und erzeugt daraus mit der Methode Article.fromXML einen Artikel:

fd = open(sName, "r")
sxml = fd.read( )
fd.close( )

# Erzeuge eine Artikelinstanz
a = Article( )
a.fromXML(sxml)

# Gib Artikelobjekt an Aufrufer zurück
return a

Zurückgegeben wird eine Article-Instanz. Das folgende Beispiel zeigt das komplette Listing von storage.py.

Beispiel: storage.py

# storage.py
from article import Article   

class Storage:
   """Speichert und lädt Artikelobjekte als XML-Datei
   --- sollte einfach zu einer Datenbank zu migrieren sein."""

def save(self, article):
   """Speichern als <article.title>.xml."""
   sFilename = article.title + ".xml"
   fd = open(sFilename, "w")

# Schreibe Datei auf Festplatte mit Daten aus getXML()-Aufruf
   fd.write(article.getXML( ))
   fd.close( )

def load(self, sName):
   """Name muß filename.xml sein -- Liefert ein Artikelobjekt."""
   fd = open(sName, "r")
   sxml = fd.read( )

   # Erzeuge eine Artikelinstanz
   a = Article( )

   # Benutze fromXML zur Initialisierung eines Artikelobjekts aus dem XML der Datei
   a.fromXML(sxml)
   fd.close( )

   # Gib Artikelobjekt an Aufrufer zurück
   return a

Implementieren der Site-Logik

Die Klassen Article und Storage sind nicht weborientiert. Sie könnten in jeder Art von Anwendung benutzt werden, da die Artikel in XML dargestellt werden und die Storage-Klasse sich lediglich um deren Eingabe bzw. Ausgabe zur Festplatte kümmert. Zumindest prinzipiell könnten Sie diese Klassen überall dazu benutzen, eine XML-basierte Datenhaltung aufzubauen.

Auf der anderen Seite könnten Sie ein einziges CGI-Skript schreiben, das die gesamte Logik enthält, um Artikel auf Platte zu speichern, sie von dort wieder zu lesen und XML zu parsen, aber dann wären Ihre Artikel und deren Verwendbarkeit auf dieses CGI-Skript beschränkt. Dadurch, daß Kernfunktionalität in diskrete Komponenten aufgebrochen wird, haben Sie die Freiheit, die Klassen Article und Storage aus jeder Art von Anwendung zu benutzen, die Ihnen vorschwebt.

Um die Interaktion der Artikelklassen mit dem Web zu managen, erzeugen wir eine weitere Klasse (ArticleManager) und ein weiteres Skript (start.cgi). Die Klasse ArticleManager baut eine Webschnittstelle zur Bearbeitung von Artikeln. Sie kann Artikel als HTML darstellen, auf einem Webformular eingereichte Artikel annehmen und die Interaktion des Benutzers mit der Site steuern. Das Skript start.cgi kümmert sich um die Eingabe/Ausgabe vom Webserver und steuert den ArticleManager.

Die ArticleManager-Klasse

Die ArticleManager-Klasse verfügt über vier Methoden für den Umgang mit Artikeln. Der Manager agiert als Verbindung zwischen den Artikelobjekten und dem CGI-Skript, das die Schnittstelle zum Webserver darstellt (und indirekt zum Browser des Benutzers).

Die Methode viewAll holt alle XML-Artikel von der Platte und erzeugt einen Abschnitt von HTML-Hyperlinks zu den Artikeln. Diese Methode wird vom CGI-Skript aufgerufen, um eine Seite mit allen Titeln und Artikel-Links darzustellen:

def viewAll(self):   
   """Zeigt alle XML-Dateien im aktuellen Arbeitsverzeichnis an."""
   print "<p>Alle ansehen<br><br>"

   # Hole Liste aller Dateien im aktuellen Verzeichnis
   fl = os.listdir(".")
   for xmlFile in fl:
      # Sortiere XML-Dateien aus
      tname, ext = os.path.splitext(xmlFile)
      if ext == ".xml":
         # Erzeuge HTML-Link um Artikelnamen herum
         print '<br><a href="start.cgi?cmd=v1a&af=%s">%s</a><br>' % (quote(xmlFile), tname)

Diese Methode ist nicht gerade sehr elegant. Sie liest einfach den Inhalt des aktuellen Verzeichnisses, pickt sich die XML-Dateien heraus und entfernt die .xml-Erweiterung aus den Namen, bevor sie als Link dargestellt werden. Die Links verbinden zurück zur selben Seite (start.cgi), diesmal jedoch mit Parametern für den Abfrage-String, die start.cgi anweisen, die Methode viewOne aufzurufen, um den Inhalt eines einzelnen Artikels anzuzeigen. Die aus urllib importierte quote-Funktion wird benutzt, um spezielle Zeichen im Dateinamen zu codieren, die dem Browser sonst Probleme bereiten könnten. Die Konstruktion von URLs und die Verwendung von Anführungszeichen darin wird detaillierter in Die Python-Internet-APIs erläutert.

Die Methode viewOne benutzt das Speicherobjekt, um einen auf Platte gespeicherten Artikel zu reanimieren. Nachdem die Artikelinstanz erzeugt ist, werden ihre Daten Schritt für Schritt abgefragt und für die Anzeige im Browser mit HTML-Code »garniert«:

def viewOne(self, articleFile):   
   """Nimmt einen Artikeldateinamen als Parameter,
   erzeugt ein Artikelobjekt dafür und zeigt es an.
   """
   # Erzeuge Speicher- und Artikelobjekte
   store = Storage( )
   art = store.load(articleFile)

   # Schreibe HTML zum Browser (Standardausgabe)
   print "<p>Title: " + art.title + "<br>"
   print "Author: " + art.author + "<br>"
   print "Date: " + art.time + "<br>"
   print "<table width=500><tr><td>" + art.contents
   print "</td></tr></table></p>"

Hierbei ist die Bemerkung angebracht, daß der an viewOne übergebene Parameter ein echter Dateiname und nicht nur der Titel des XML-Dokuments ist.

Die Methode postArticle ist vermutlich die einfachste bisher besprochene Methode, da ihre Aufgabe einfach darin besteht, HTML zu erzeugen. Das HTML besteht aus einem Formular, mit dem Benutzer neue Artikel schreiben und sie dem Server zur Speicherung in XML übergeben können. Da sich das HTML-Formular nicht verändert, kann diese Methode einfach den Wert einer String-Konstante ausgeben, die das Formular enthält.

Die Methode postArticleData ist etwas komplizierter. Ihre Aufgabe ist es, Schlüssel/Wert-Paare aus einem ausgefüllten HTTP-Formular zu extrahieren und einen XML-Artikel auf Basis der erhaltenen Werte zu erzeugen. Wenn das XML einmal erzeugt ist, muß es auf die Festplatte gespeichert werden. Dies geschieht, indem ein Artikelobjekt erzeugt wird und seine Instanzvariablen auf die Werte gesetzt werden, die dem Formular entnommen wurden, bevor der Artikel dann mit der Storage-Klasse abgespeichert wird.

def postArticleData(self, form):     
     """Erwartet übermittelte Formulardaten, erzeugt und
     speichert ein Artikelobjekt."""
     # Fülle Artikel mit Informationen aus dem Formular
     art = Article()
     art.title = form["title"].value
     art.author = form["author"].value
     art.contributor = form["contrib"].value
     art.contents = form["contents"].value

     # Speichere den Artikel
     store = Storage()
     store.save(art)

Das folgende Beispiel zeigt die komplette Datei ArticleManager.py.

Beispiel: ArticleManager.py

   # ArticleManager.py
   import os    
   from urllib import quote
   from article import Article
   from storage import Storage
      
   class ArticleManager:   
   """Verwaltet Artikel für die Webseite.
         
   Verantwortlich für das Erzeugen, Laden, Speichern und Anzeigen von Artikeln.
   """

   def viewAll(self):      
      """Zeigt alle XML-Dateien im aktuellen Arbeitsverzeichnis an."""
      print "<p>Alle ansehen<br><br>"
            
      # Hole Liste aller Dateien im aktuellen Verzeichnis
      fl = os.listdir(".")
      for xmlFile in fl:
        # Sortiere XML-Dateien aus
        tname, ext = os.path.splitext(xmlFile)
        if ext == ".xml":
          # Erzeuge HTML-Link um Artikelnamen herum
          print '<br><a href="start.cgi?cmd=v1a&af=%s">%s</a><br>' \ % (quote(xmlFile), tname)
      
   def viewOne(self, articleFile):
      """Nimmt einen Artikeldateinamen als Parameter,
      erzeugt ein Artikelobjekt dafür und zeigt es an.
      """
      # Erzeuge Speicher- und Artikelobjekte
      store = Storage( )
      art = store.load(articleFile)
      
      # Schreibe HTML zum Browser (Standardausgabe)
      print "<p>Title: " + art.title + "<br>"
      print "Author: " + art.author + "<br>"
      print "Date: " + art.time + "<br>"
      print "<table width=500><tr><td>" + art.contents
      print "</td></tr></table></p>"
      
   def postArticle(self):
      """Zeigt das Artikeleinsendeformular an."""
      print POSTING_FORM
      
   def postArticleData(self,form):
      """Erwartet übermittelte Formulardaten, erzeugt und
      speichert ein Artikelobjekt."""
      # Fülle Artikel mit Informationen aus dem Formular
      art = Article()
      art.title = form["title"].value
      art.author = form["author"].value
      art.contributor = form["contrib"].value
      art.contents = form["contents"].value
      # Speichere den Artikel
      store = Storage()
      store.save(art)
      
POSTING_FORM = '''\
<form method="POST" action="start.cgi?cmd=pd">
 <p>
 Title:      <br><input type="text" length="40" name="title"><br>
 Contributor:<br><input type="text" length="40" name="contrib"><br>
 Author:     <br><input type="text" length="40" name="author"><br>
 Contents:   <br><textarea rows="15" cols="80" name="contents"></textarea><br>
 <input type="submit">
</form>
...

Steuern der Anwendung

Das CGI-Skript ist das Hauptprogramm der Webanwendung. Es ist auch die einzige »Seite«, die jemals im Browser geladen sein wird. Wenn der Benutzer start.cgi ins Adressfeld schreibt, führt Apache das Skript auf dem Server aus.

Das Skript beginnt damit, die Module cgi und os zu importieren:

import cgi
import os

Dann gibt das Skript den Content-Header und das öffnende HTML aus. Dieses HTML ist immer gleich, egal welche Operation start.cgi durchführt; daher ist es als Konstante namens HEADER definiert (nicht gezeigt) und wird bei jeder Anfrage ausgegeben:

# Content-type-Header
print "Content-type: text/html"
print
print HEADER

Nachdem der gemeinsame Teil der Ergebnisseite ausgegeben wurde, wird der Abfrage-String auf den cmd-Parameter geprüft, der angibt, welche Aktionen start.cgi ausführen soll. Die Hyperlinks, die von start.cgi produziert und dem Browser übergeben werden, sind alle mit diesem gleichen Parameter ausgestattet, der eine spezielle Anweisung wie view oder post enthält. Der Abfrage-String wird mit dem cgi-Modul überprüft. Dabei wird geprüft, ob er den cmd-Parameter enthält. Wenn ja, geht die Bearbeitung weiter, ansonsten wird dem Benutzer eine Fehlermeldung angezeigt.

query = cgi.FieldStorage( )
if query.has_key("cmd"):
 cmd = query["cmd"][0].value

  # Instanziiere einen ArticleManager
  am = ArticleManager( )

Der ArticleManager wird als am instanziiert, und die Kommandobearbeitung geht weiter, indem cmd auf seine vier möglichen Werte geprüft wird. Um Artikeltitel zu sehen, wird die Kommandosequenz va benutzt:

# Kommando: viewAll - liste alle Artikel auf
if cmd == "va":
  am.viewAll( )

Um einen bestimmten Artikel zu sehen, wird die Kommandosequenz v1a benutzt:

# Kommando: viewOne - zeige einen Artikel an
if cmd == "v1a":
  aname = query["af"].value
  am.viewOne(aname)

Um Artikel abzuschicken, wird ein Formular angezeigt. Das CGI-Skript sucht dazu nach der Sequenz pa:

# Kommando: postArticle - zeige Seite zum Artikel-Abschicken
  if cmd == "pa":
    am.postArticle( )

Schickt der Benutzer das Artikelformular ab, werden die Daten an den Webserver gesendet. Das CGI-Skript sucht nach der Kommandosequenz pd, die angibt, daß die Artikeldaten gesendet wurden. Dann übergibt es das CGI-Formular an die Methode ArticleManager.postArticleData:

# Kommando: postData - nimm einen abgeschickten Artikel an
if cmd == "pd":
  print "<p>Danke f&uuml;r Ihren Artikel!</p>"
  am.postArticleData(query)

Wenn cmd im Abfrage-String nicht vorhanden ist oder falls der Wert von cmd nicht einer der vier beschriebenen ist, wird eine Fehlermeldung in der else-Klausel der ersten if-Anweisung angezeigt:

else:
  # ungueltige Auswahl
  print "<p>Ihre Auswahl wurde nicht erkannt.</p>"

Schließlich wird das HTML mit einer letzten print-Anweisung beendet:

# Beende HTML
print "</body></html>"

Das komplette Listing von start.cgi wird im folgenden Beispiel gezeigt.

Beispiel: start.cgi

#!/usr/local/bin/python
#
# start.cgi - ein Python-CGI-Skript

import cgi
import os
from ArticleManager import ArticleManager

HEADER = """\   
<html>
<body>
<p>
<table cellspacing="0" cellpadding="1">
  <tr>
    <td>
      <h1>XML-Artikel</h1>
    </td>
  </tr>
  <tr>
    <td>
      <h3><a href="start.cgi?cmd=va">Alle ansehen</a>&nbsp;|&nbsp;<a href="start.cgi?cmd=pa">Artikel einreichen</a></h3>
    </td>
  </tr>
</table>
"""

#
# HAUPTPROGRAMM
#

# Content-type-Header
print "Content-type: text/html"
print

print HEADER
# Hole Abfrage-String
query = cgi.FieldStorage( )
if query.has_key("cmd"):
   cmd = query["cmd"].value

   # Instanziiere einen ArticleManager
   am = ArticleManager( )

   # Mache etwas mit jedem Kommando

   # Kommando: viewAll - liste alle Artikel auf
   if cmd == "va":
      am.viewAll( )

   # Kommando: viewOne - zeige einen Artikel an
   if cmd == "v1a":
      aname = query["af"].value
      am.viewOne(aname)

   # Kommando: postArticle - zeige Seite zum Artikel-Abschicken
   if cmd == "pa":
      am.postArticle( )

   # Kommando: postData - nimm einen abgeschickten Artikel an
   if cmd == "pd":
      print "<p>Danke f&uuml;r Ihren Artikel!</p>"
      am.postArticleData(query)

else:
   # ungültige Auswahl
   print "<p>Ihre Auswahl wurde nicht erkannt.</p>"

# Beende HTML
print "</body></html>"

Beachten Sie den ersten Ausdruck, #!/usr/local/bin/python. Da dies ein CGI-Skript ist, braucht das Betriebssystem einen Hinweis darauf, wie es zu starten ist. Wenn es übersetzter C-Code ist, könnte er vom Webserver ausgeführt werden; wenn es jedoch ein Skript ist, muß es sehr wahrscheinlich an einen Skript-Interpreter weitergegeben werden. Dies ist der Fall bei Python. Man beachte, daß wir nicht die sh-bang-Zeile #!/usr/bin/env python benutzt haben; das könnte bei CGI-Skripten ein Sicherheitsloch aufreißen. Siehe die Dokumentation von Pythons cgi-Modul für weitere Informationen über CGI-Sicherheitsaspekte und wie man sie korrekt behandelt, wenn man Python einsetzt.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Python & XML bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2002 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Python & XML" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de