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’.

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)