Schnell, nicht schmutzig

Gerade im Web haben Scriptsprachen sich durchgesetzt; immer dann, wenn es um interaktive Webseiten geht. Wie man in den einzelnen Sprachen Aufgaben löst, zeigt das ‘Gästebuch’.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 7 Min.
Von
  • Tobias Himstedt
  • Kristian Köhntopp
  • Frank Pilhofer
  • Dr. Holger Schwichtenberg
  • Henning Behme
  • Christian Kirsch
Inhaltsverzeichnis

Vor dem Browser sind alle Scriptsprachen gleich, und einige sind gleicher. Miteinander verglichen stellen sich schnell Unterschiede auch in Details heraus. Am fast schon klassischen Beispiel eines Gästebuchs sollen diese Unterschiede in diesem Artikel deutlich werden; nur der meist obligatorische Kommentar, den Gäste abgeben können, fehlt hier.

Für ein solches Gästebuch ist zunächst eine - durchweg bereits durch ein Script generierte - HTML-Seite erforderlich, die ein Formular enthält; hier mit drei Feldern: Vorname, Nachname und E-Mail. Nach dem Ausfüllen landen die Daten in der Standardeingabe, wenn die CGI-Methode post verwendet wird beziehungsweise als Parameter der URL bei Verwendung von get. Von stdin oder über eine Evaluation der Parameter müssen die einzelnen Sprachen sie sich holen und verarbeiten. Was wiederum heißt, dass alles Wesentliche auf dem Server geschieht - und nicht beim Client.

Einige Sprachen (Perl, Tcl sowie VBScript und JavaScript in ASP) erlauben in HTML-Seiten eingebetteten Code, manche sind auch oder meistens dort vorzufinden: JavaScript, PHP, VBScript. JavaScript stellt insofern eine Ausnahme dar, als sie überwiegend im Browser verarbeitet wird, also clientseitig. Alle Sprachen erlauben den Zugriff auf Datenbanksysteme. Für ihn benötigt man Code in Perl, PHP, Python, Tcl oder VBScript, der auf dem Server ausgeführt wird. Zumindest sollen diese fünf im Folgenden berücksichtigt werden.

Der Vorteil von JavaScript ist, dass ohne Serverbelastung eine Überprüfung der Eingaben möglich ist. Innerhalb einer HTML-Seite lassen sich JavaScript-Funktionen unterbringen (im head des Dokuments oder in einer ladbaren Datei), die dafür sorgen, dass der Server nur dann kontaktiert wird, wenn die Daten stimmen. Eine solche Funktion kann beispielsweise prüfen, ob eine E-Mail-Adresse überhaupt korrekt sein kann, indem sie Einträge darauf durchsucht, ob ‘@’ darin vorkommt. Ist das nicht der Fall, kann die Adresse nur falsch sein. Listing 1 zeigt ein Formular, das eine Funktion aufruft, wenn jemand ‘O. K.’ gedrückt hat.

Mehr Infos

Listing 1: Formular

<form name="guestbook" onSubmit="return check_mailaddr()" 
method="post"

action="/cgi-bin/showform">
Vorname: <input type="text" size="25"
name="vorname">
<br>Nachname: <input type="text" size="25"
name="nachname">
<br>E-Mail: <input type="text" size="25"
name="email">
<input type="submit" value="O.K.">

</form>
onSubmit=:"return check_mailaddr():"
Mehr Infos

Listing 2: JavaSript-Funktion

<script language="JavaScript">
function check_mailaddr () {
var is_it =true
var Email = document.guestbook.email.value


if (Email.indexOf("@") < 1) {
alert("Hey Joe, das geht aber nicht s@...")
is_it = false
}
return is_it
}
</script>

Im Start-Tag von form löst onSubmit=... die in Listing 2 wiedergegebene Funktion aus, die eine Variante der von Stefan Mintert in seinem JavaScript-Buch verwendeten ist. Sie weist der Variablen Email den Wert zu, den ein Anwender in das entsprechende Feld eingetragen hat; er ist über das Objektmodell eruierbar: document.guestbook.email.value. Fehlt in Email das ‘@’, was

if (Email.indexOf("@") << 1)

abfragt, ergibt sich als Ergebnis von indexOf() ein Wert von -1, und als Konsequenz kommt es nicht zur im Formular vorgesehenen Aktion (/cgi-bin/showform), sondern ein kleines Fenster mahnt zur Eingabe richtiger Daten.

In allen Fällen muss zunächst ein Formular existieren, das die jeweilige Scriptsprache auswerten kann. PHP und Sprachen in den ASP (Active Server Pages) unter NT sind hier vielleicht am einfachsten, weil es in ihnen möglich ist, Script- und HTML-Code zu mischen. Anders als beispielsweise Perl, Python und Tcl ist PHP immer ins Webdokument integriert (für Perl und Tcl existieren Plugins, die das ermöglichen). Listing 3 enthält einen Ausschnitt, der aus einer Processing Instruction (<?php ... ?>) mit dem Code besteht. HTML und PHP lassen sich dadurch beliebig mischen.

Mehr Infos

Listing 3: PHP

<?php

# Wenn der Submit-Knopf gedrückt war, Daten anzeigen...
if ($submit) {
show_data();
# Wenn die Daten nicht "stimmen", Formular anzeigen und anhalten.
if (!validate_form()) {
show_form();
die;
}

# Verbindung zur Datenbank herstellen, DB auswählen
$link = mysql_pconnect($host, $user, $pass);
if (!$link)
die("$user@$host kann die Datenbank nicht erreichen!");
if (!mysql_select_db($db))
die("$user@$host hat keinen Zugriff auf Datenbank $db!");

# Daten holen und bereitstellen...
$query = "select id, email from $table where ".
"nachname = '$nachname' and vorname = '$vorname'";
$res = mysql_query($query, $link);
if (!$res)
die("Query failed: $query ". mysql_error());

$r = mysql_fetch_array($res, MYSQL_ASSOC);
$oldmail = $r["email"];
$id = $r["id"];
# Die drei Möglichkeiten
# 1. Mailadresse existiert nicht -> Datensatz einfügen
# 2. Mailadresse existiert, aber anders -> Datensatz updaten
# 3. Mailadresse existiert und ist gleich -> melden und halten.

if (!$oldmail) {
# Fall 1
$query = "insert into $table (vorname, nachname, email) ".
"values ('$vorname','$nachname','$email')";
mysql_query($query, $link);
print "Wir haben Sie ins Gästebuch aufgenommen.<br>";

} elseif ($oldmail != $email) {
# Fall 2
$query = "update $table set email = '$email' where id = '$id'";
mysql_query($query, $link);
print "Wir haben Ihre E-Mail-Adresse geändert: " .
"Vorher '$oldmail', jetzt '$email'.<br>";

} else {
# Fall 3
print "Sie sind bereits im Gästebuch eingetragen.<br>";
}
} else {
# Kein Submit gedrückt -> Formular anzeigen.
show_form();
}
?>

show_data(), show_form() und validate_form() sind Funktionen, die im selben Dokument, aber an anderer Stelle definiert sind (fehlen hier). Sie erledigen die Ausgabe des Formulars und der Daten beziehungsweise validieren, ob die Eingaben vorhanden sind. Falls nicht, gibt das Script eine Nachricht aus und den Wert ‘falsch’ zurück, beispielsweise:

if (!$email)  {
print "E-Mail-Adresse angeben!<br>\n";
$ok = false;
}

Die Rückgabe von $ok führt dazu, dass Daten nur dann ausgegeben werden, wenn

if (!validate_form())

nicht true liefert (durch die Negation in Gestalt des Ausrufezeichens). Sind alle Angaben vorhanden, öffnet das Script eine Verbindung zu dem Datenbanksystem, das die Information speichert.

PHP benötigt zwei Funktionen dafür, das Ergebnis der Suche darzustellen, show_data() für das Ergebnis und show_form(), die anfangs und im Fehlerfall erneut das Formular ausgibt. In letzterer sorgt der in HTML eingebettete PHP-Schnipsel

<td>Nachname:</td>
<td><input type="text" maxlen="25"
size="25" name="nachname"
value="<?php print $nachname ?>"></td>

dafür, dass value, so gesetzt, den Nachnamen enthält. Das bedeutet, dass nach dem Validieren show_form() auch benutzt werden kann, wenn kein Ergebnis vorhanden ist: value ist dann leer.

Listing 3 bis Listing 7 enthalten in etwa parallelen Code zu dem Zweck, für das Gästebuch ein Datenbanksystem anzusprechen: zweimal MySQL, je einmal Postgres, Gadfly und Access. Der Kasten ‘Datenbankverbindung herstellen’ zeigt als Übersicht, wie der konkrete Verbindungsaufruf jeweils aussieht.

Mehr Infos

Listing 4: Perl

if (!$q->param()) {
# Script ohne Parameter aufgerufen: Formular ausgeben.
show_form($q);
} elsif (! $q->param("cancel")) {
# Script mit Parametern aufgerufen: Parameter rausholen
my ($oldmail,$id);
my $vorname = $q->param("vorname");
my $nachname = $q->param("nachname");
my $email = $q->param("email");
$q->dump();
# Keine EMail-Adresse: Fehlermeldung. Dito für fehlenden Nachnamen
if (! $email) {
print "E-Mail-Adresse muss angegeben werden!\n";
show_form($q);
print $q->end_html;
exit(0);
}
if (! $nachname) {
print "Nachname muss angegeben werden!\n";
show_form($q);
print $q->end_html;
exit(0);
}
# Verbindung zur DB aufbauen (siehe Parameter am Anfang des Scripts)
my $dbh = DBI->connect("DBI:$RDBMS:$DB",$user,$password) ||
die "Konnte Datenbank $RDBMS\/$DB nicht öffnen";
# Nachgucken, ob Eintrag für Kombination in DB vorhanden
my $query = "select id, email from $table where " .
"nachname = '$nachname' and vorname = '$vorname'";
my $sth = $dbh->prepare($query) ||
die "Fehler in SQL-Statement '$query': " . $dbh->errstr;
# Query ausführen, 1. Ergebnis an $id und 2. an $oldmail binden
$sth->execute() ||
die "Fehler beim Ausführen von '$query': " . $dbh->errstr;
$sth->bind_col(1,\$id) ||
die "Fehler bei \"bind\" für '$query': " . $dbh->errstr;
$sth->bind_col(2,\$oldmail) ||
die "Fehler bei \"bind\" für '$query': " . $dbh->errstr;

# Ergebnis aus der DB holen.
$sth->fetch();
#
# Drei Möglichkeiten: Eintrag erzeugen/ändern/nichts tun
#
if (!$oldmail) {
# Neuer Eintrag
$dbh->do("insert into $table (vorname, nachname, email) " .
"values ('$vorname','$nachname','$email')");
print "Wir haben Sie ins Gästebuch aufgenommen."
} elsif ($oldmail ne $email) {
# Alten Eintrag ändern
$dbh->do("update $table set email='$email' where id = $id");
print "Wir haben Ihre E-Mail-Adresse geändert: " .
"Vorher '$oldmail', jetzt '$email'."
} else {
# Nichts tun
print "Sie sind bereits im Gästebuch eingetragen.";
}
$dbh->disconnect;
}
Mehr Infos

Listing 5: Tcl

# Ohne Parameter aufgerufen?
if {![array exists form]} {
show_form
exit 0
}

# Fehlerprüfung
if {![info exists form(vorname)] || $form(vorname)==""} {
puts "<b>Vorname muß angegeben werden!</b><p>"
show_form
exit 0
} elseif {![info exists form(nachname)] || $form(nachname)==""} {
puts "<b>Nachname muß angegeben werden!</b><p>"
show_for
exit 0
} elseif {![info exists form(email)] || $form(email)==""} {
puts "<b>Nachname muß angegeben werden!</b><p>"
show_form
exit 0
}

# Verbindung zur Datenbank aufbauen
if {[catch {pg_connect fp} conn]} {
puts "<b>Konnte Datenbank nicht öffnen!</b></body></html>"
exit 0
}
# Nachsehen, ob schon ein Eintrag für Vorname, Nachname enthalten ist
set handle [pg_exec $conn "select id, email from gaestebuch\
where vorname='$form(vorname)'\
and nachname='$form(nachname)'"]
pg_result $handle -assign db
pg_result $handle -clear

# Die drei Möglichkeiten (siehe PHP-Listing)
if {![info exists db(0,email)]} {
set handle [pg_exec $conn "select max(id) from gaestebuch"]
set maxid [pg_result $handle -getTuple 0]
pg_result $handle -clear
incr maxid
set handle [pg_exec $conn \
"insert into gaestebuch values\
($maxid,'$form(vorname)','$form(nachname)','$form(email)')"]
puts "Wir haben Sie ins Gästebuch aufgenommen."
pg_result $handle -clear
} elseif {[info exists db(0,email)] && $db(0,email) != $form(email)} {
set handle [pg_exec $conn \
"update gaestebuch set email='$form(email)' where id=$db(0,id)"]
pg_result $handle -clear
puts "Emailadresse geändert. Vorher '$db(0,email)', jetzt '$form(email)."
} else {
puts "Sie sind bereits im Gästebuch eingetragen."
}
Mehr Infos

Listing 6: Python

#!/usr/bin/python

import gadfly
import cgi

# Hierhin gehoeren PrintHeader(), printFooter() und formString

def main():
# Die Werte aus dem Form auslesen
form = cgi.FieldStorage()
if (not form.has_key("vorname") or form["vorname"].value == "") or \
(not form.has_key("nachname") or form["nachname"].value == "") or \
(not form.has_key("email") or form["email"].value == ""):

# Nicht alles ins Formular eingetragen
printHeader()
print "<H1>Bitte fuellen Sie alle Felder aus</h1>"
# formString (fehlt hier) = leeres Formular
print formString
printFooter()
return
else:
# Daten im Formular -> in die Datenbank
# Verbindung aufmachen
connection = gadfly.gadfly("ixdata", "ixdatadir")
cursor = connection.cursor()
# Werte auslesen
vorname = form["vorname"].value
nachname = form["nachname"].value
email = form["email"].value
# Jemanden mit diesem Vornamen/Nachnamen?
cursor.execute("select id, vorname, nachname, email from "
"gaestebuch where vorname = ? and nachname = ?",
(vorname, nachname))
rows = cursor.fetchall()
# Eintrag gefunden?
if rows == []:
# Nichts gefunden -> neuen Eintrag in DB
# Maximalen Wert fuer ID ermitteln
cursor.execute("select max(id) from gaestebuch")
id = cursor.fetchone()[0]
# Neuen eintrag in DB
cursor.execute("insert into gaestebuch(id, vorname, nachname, "
"email) values (?, ?, ?, ?)",
(id+1, vorname, nachname, email))
printHeader()
print "<h1>Sie sind nun in unserem Gaestebuch verewigt</h1>"
printFooter()
else:
# Ein Eintrag wurde gefunden, Update der E-Mail-Adresse
id = rows[0][0]
cursor.execute("update gaestebuch set email = ? where id = ?",
(email, id))
printHeader()
print "<h1>Ihre Email-Adresse wurde aktualisiert</h1>"
printFooter()
# Abschliessend noch ein Datenbank-commit
connection.commit()

main()
Mehr Infos

Listing 7: VBScript

<%
'---Lokales Speichern der Formularfelder aus Performancegründen!
Vorname = Request("Vorname")
Name = Request("Name")
email = Request("eMail")

if vorname = "" and name = "" and email = "" then
'---Script ohne Parameter aufgerufen: Formular ausgeben.
show_form
else
'---Script mit Parametern aufgerufen
fehler = ""
'--- Name und E-Mail fehlen -> Fehlermeldung generieren.
if email = "" then fehler = fehler & "<li>Ungültige eMail-Adresse</li>"
if Name = "" then fehler = fehler & "<li>Name bitte nicht
leerlassen</li>"
if fehler <> "" then
Response.Write "<h3>Fehler:<hr><ul>" & Fehler & "</ul></h3>"
show_form
Response.Write "</BODY></HTML>
Response.End
end if

'--- ADO-Objekt instanziieren
Set objRS = Server.CreateObject("ADODB.RecordSet")
' Verweis auf Datenbank
conn = "Provider=MSDASQL;Driver={Microsoft Access Driver
(*.mdb)};Dbq=f:\kom_data\gaeste.mdb;Uid=Admin;Pwd=;"
' SQL-Statement
sql = "SELECT * FROM Gaestebuch WHERE
Vorname = '" & Vorname & "' and Name ='" & Name & "'"
'---Verbindung zur Datenbank aufbauen und Query ausführen
on error resume next
objRS.open sql, Conn,1,3
if err.number <> 0 then
Response.Write "<h3>Fehler beim Öffnen der
Datenbank: " & err.number & ":" & err.description
Response.Write "</h3></BODY></HTML>"
Response.End
end if
on error goto 0

'---Die drei Möglichkeiten: siehe PHP-Listing
'---Neuer Eintrag
if objRS.EOF Then
objRS.AddNew
objRS("Name") = Name
objRS("eMail") = email
objRS("Vorname") = Vorname
objRS.Update
objRS.close
Response.Write "Ihre Daten wurden gespeichert."
Else
'---Alten Eintrag ändern
if not objRS.fields("email") = email then
objRS.fields("email") = email
objRS.Update
Response.Write "Ihre eMail-Adresse wurde geändert."
else
'---Nichts tun
Response.Write "Sie sind bereits im Gästebuch eingetragen."
End if
End if
End if

'---Subroutine zum Ausgeben des Formulars.
sub show_form
%>
<form method="get" action="gaestebuch.asp">
'---Inhalt des Formulars (HTML)
</form>
<%
End sub
%>

In den Listings vorgesehen sind ein Neueintrag, eine Änderung sowie ‘nichts tun’, wenn ein bestehender Datensatz neu eingegeben wurde. Die Listings sind nicht darauf ausgerichtet, Zeile für Zeile nebeneinander zu stellen, weil sie von unterschiedlichen Autoren stammen und teilweise gleichzeitig entstanden. So ist validate_form() (siehe das PHP-Listing) eine elegantere Lösung als der vergleichbare Code etwa in Perl. Eine solche Kapselung wäre dort aber ebenfalls denkbar und sinnvoll.

Der Zugriff auf die Datenbank und das Abfangen eventueller Fehler sieht in mehreren Sprachen fast identisch aus (die in PHP und Perl). Aber auch in den anderen können diejenigen, die die eine Sprache kennen, sehen, wie dasselbe in den anderen gelöst ist.

Perl, Python und Tcl haben eins gemeinsam: es gibt für sie CGI-Pakete, die das Eintippen einzelner HTML-Elemente überflüssig machen, oder das Einlesen von in Formulare eingegebenen Daten erleichtern. Andererseits ist es in VBScript oder PHP möglich, in HTML-Schnipsel ein bisschen Code einzubauen.

Perl: use CGI::Pretty ':standard',':html3';
TcL: source /path/to/CGI/ProcCGIInput.tcl
Python import cgi

Perl bindet das CGI-Modul ein, Tcl das zur Formularauswertung und Python das CGI-Modul. Allgemein erweiterbar sind Scriptsprachen entweder durch Module in der Interpretersprache selbst - oder durch Module in C/C++, die in das Binary einzubinden sind.

Die Einbettung der genannten Sprachen in Webserver beschleunigt die Ausführung von Scripts deutlich. Eine Scripting Engine für VBScript ist sowohl im Internet Information Server als auch im Internet Expolorer implementiert. Perl- und PHP-Interpreter lassen sich als Module in den Apache integrieren, und für Tcl gibt es ein FastCGI-Tool, das Ähnliches leisten kann.

Erst die kompletten Listings dürften die Entscheidung für eine Sprache erleichtern, wobei die individuellen Vorkenntnisse oft ausschlaggebender sein dürften als Design oder Elegenz des Codes. Auf dem FTP-Server der iX sind sie als scripting.tgz und scripting.zip im Verzeichnis dieses Heftes zu finden.

Die Scripts im Einzelnen stammen von Tobias Himstedt (Python), Kristian Köhntopp (PHP), Frank Pilhofer (Tcl), Holger Schwichtenberg (VBScript) und Christian Kirsch (Perl).

Mehr Infos

iX-TRACT

  • Die Erstellung interaktiver Webseiten lässt sich mit diversen Scriptsprachen schnell lösen.
  • Datenbankanbindungen sind für die meisten Scriptsprachen vorhanden und leicht zu kodieren.
  • JavaScript wird auch auf dem Clientrechner ausgeführt, alle andere Sprachen auf dem Server.
  • Die Entscheidung für eine Sprache fällt oft eher auf Grund individueller Vorkenntnisse als wegen des Designs.
Datenbankverbindung herstellen
In der folgenden Tabelle erklären sich ein paar Unterschiede schon dadurch, dass nicht alle Scripts dieselbe Datenbank verwenden. Bei genauem Hinsehen fällt allerdings auf, dass die Ähnlichkeiten, vor allem zwischen Perl, PHP und Python, überwiegen.
Perl my $dbh = DBI->connect('DBI:$RDBMS:$DB',$user,$password);
PHP $link = mysql_pconnect($host, $user, $pass);
Python connection = gadfly.gadfly($db, $table)
Tcl catch {pg_connect fp} conn
VBScript conn = 'Provider=MSDASQL;Driver={Microsoft Access Driver (*.mdb)};Dbq=f:\kom_data\gaeste.mdb;Uid=Admin;Pwd=;'

(hb)