| Zurück |
![]() |
Erstellung dynamischer HTML-Seiten unter Verwendung einer Datenbank (z.B. SQL-Server wie MySQL, PostgreSQL, ...) |
Nun aber genug mit dem einleitenden Geplänkel. Legen wir einach los.
Sicherheitshalber wird in den nachfolgenden Skripten das Perl-Compiler-Pragma strict verwendet.
Dies ist insbesondere bei Servern absolut notwendig die einen mittels "mod_perl"
eincompilierten Perl-Interpreter haben. Bei diesen Servern wird das Perl-Skript
nur einmal eingelesen und anschließend im Speicher gehalten. Alle Variablen
deren Lebensdauer nicht beschränkt wird, behalten dabei ihren Inhalt zwischen
verschiedenen Aufrufen des Skripts. Dies gilt sogar für verschiedene Threads!
Im ungünstigsten Fall können damit Daten von einem Skriptaufruf in einen anderen
übertragen werden. Das obige Compiler-Pragma erzwingt die Definition aller
Variablen mittels my. Damit ist aber die Lebensdauer der Variablen
spätestens am Ende des Skripts erreicht und diese Probleme können nicht auftreten.
Wie bereits weiter oben bemerkt, wird für die Datenbankanbindung das Perl-Modul DBI.pm verwendet.
connect(). Damit wird eine Verbindung
zwischen dem Skript und einer Datenbank auf dem SQL-Server aufgebaut. Der
Befehl verwendet als minimale Argumente die Datenquelle, den User-Namen auf
dem SQL-Server und das zughörige
Passwort.
$dbh = DBI->connect($data_source, $username, $password)wobei für die Datenquelle gilt:
dbi:DriverName:database=database_name;host=hostname;port=port
DriverName gibt den Namen des DBD-Treibers an, database ist
der Name der Datenbank (hier dbi_demo), host ermöglicht
die Benutzung eines anderen Rechners als Datenbankserver (nachfolgend ist
der DB-Server und WWW-Server auf
demselben Rechner - localhost) und port ermöglicht die Angabe einer von der
Standardinstallation abweichenden Portnummer. In diesem Beispiel lautet
$data_source also: dbi:mysql:dbi_demodisconnect()-Methode
die Verbindung zur
Datenquelle
beendet
werden. Obwohl es auch ohne diesen expliziten Verbindungsabbau geht,
erspart man sich damit wenigstens die unschöne Fehlermeldung "Database
handle destroyed without explicit disconnect".Damit ergibt sich als erstes Beispiel der nachfolgende Code (nicht abtippen, sie können weiter unten alle Beispiele herunterladen):
#!/usr/bin/perl -w
#
# Dateiname: script1.pl
# Autor: A. Grupp
# Compilerpragma strict um keine Fehler zu machen :-)
use strict;
# Benutzung der Perl-Module CGI und DBI
use CGI;
use DBI;
# Neues CGI-Objekt erzeugen
my $cgi_obj = new CGI;
# Datenbank-Verbindung aufbauen
my $dbh = DBI->connect( 'dbi:mysql:dbi_demo', 'andi', 'geheim') ||
die "Kann keine Verbindung zum MySQL-Server aufbauen: $DBI::errstr\n";
# Datenbank-Verbindung beenden
$dbh->disconnect; |
Dieses erste Beispiel erzeugt natürlich noch keinen HTML-Output. Es zeigt nur den Datenbank-Verbindungsaufbau.
Nachdem die Verbindug zu einer Datenbank aufgebaut wurde, können Abfragen ausgeführt werden. Die einzelnen Schritte hierfür sind:
pepare()-Methode. Damit
wird der eigentliche Befehl noch nicht ausgeführt sondern vom Datenbanktreiber
zur Ausführung vorbereitet. Obwohl MySQL einen Prepare nicht unterstützt
muss dieser Methodenaufruf erfolgen da er vom DBI-Modul für alle Folgeoperationen
vorgeschrieben ist. Das Ergebnis des prepare()-Aufrufs ist ein
"Statement-Handle". Wie bereits der Database-Handle ist auch der Statement-Handle
eine Referenz über die nachfolgend alle weiteren Zugriffe auf diese Abfrage
bzw. das Abfrageergebnis erfolgen.
Syntax des prepare()-Statements:
$sth = $dbh->prepare($statement)Dabei muss das Argument
$statement ein gültiger SQL-Befehl sein.
Falls sie mit SQL (Structured Query Language) noch nicht so vertraut sind
(auswendig
schaffe
ich es bei schwierigen Abfragen auch nicht immer so ohne weiteres), können
sie die Abfrage auch erst mal mit der grafischen Entwurfsoberfläche
eines anderen Datenbanksystems erstellen. Gerade Microsoft Access mit seinem
"Query by Entry" hilft da oft weiter. Dort haben sie dann auch
die Möglichkeit in eine SQL-Ansicht umzuschalten und sich das
SQL-Statement anzuschauen. Nach und nach schafft man die einfacheren
Statements dann auch aus eigener Kraft.
Ergebnis dieser Prozedur ist wie schon erwähnt ein sogenannter Statement-Handle mit dessen Hilfe sie nachfolgend die Abfrage auch tatsächlich ausführen und auf das Ergebnis zugreifen können. Der Variablenname für den Handle kann natürlich frei gewählt werden.
$sth->execute();Sie können sich bei Bedarf die Anzahl der gefundenen Datensätze auch gleich in einer Variablen merken. Selbiges ist nämlich eines der Ergebnisse der execute-Methode.
$dsnum = $sth->execute();
$sth->finish();
# Befehl fuer Ausfuehrung vorbereiten. Referenz auf Statement
# Handle Objekt wird zurueckgeliefert
my $sth = $dbh->prepare( 'SELECT * FROM tbl_art' ) ||
die "Kann Statement nicht vorbereiten: $DBI::errstr\n";
# Vorbereitetes Statement (Abfrage) ausfuehren
$sth->execute() ||
die "Kann Abfrage nicht ausfuehren: $DBI::errstr\n";
# Statement-Handle Resources freigeben
$sth->finish();
|
Wie bereits das vorige Beispiel wird immer noch kein HTML-Output erzeugt. Wir sind jetzt aber bereits im Besitz eines Abfrageergebnisses und können bald daran gehen mit den hier verfügbaren Daten auch eine HTML-Seite zu erzeugen.
Der Abfragehandle ist nach geglückter Ausführung der Abfrage das Tor zum Abfrageergebnis.
Z.B. durch Anwendung der fetchrow_array()-Methode auf den Statement-Handle
wird nämlich
ein Datensatz aus dem Abfrageergebnis geholt, und in einem Array als Rückgabewert
geliefert. Die einzelnen Datenfelder sind die Arrayelemente welche nun für
für
eine weitere Be/Verarbeitung zur Verfügung stehen. Außerdem wird auf den nächsten
Datensatz im Ergebnis positioniert. Ein weiteres fetchrow_array() liefert
dann
den nächsten Datensatz und so fort. Wird nach dem letzten Datensatz erneut
fetchrow_array() angewendet, ist das Ergebnis 'undef',
was in Perl ja aber geprüft werden kann.
Nachfolgend ein Codebeispiel zur Anwendung von "fetchrow_array()":
# Bearbeitung des Abfrageergenisses
while ( my @ergebnis = $sth->fetchrow_array() ){
# Im Array @ergebnis steht nun ein Datensatz
print $ergebnis[0] . " " . $ergebnis[1] . "\n";
}
Wird das Skript nun von "Hand" ausgeführt erhält man das folgende
Ergebnis:
user@server:~> script3.pl 1 Zweibeiner 2 Vierbeiner 3 Keinbeiner 4 Mehrbeiner user@server:~> |
Damit das ganze jetzt noch ein CGI-Skript mit HMTL-Output wird, müssen noch die entsprechenden Ausgabeteile um das vorige Beispiel gruppiert werden. Das ist jetzt aber bereits die Kür und hat mit dem eigentlichen Datenbank-Kontakt nichts mehr zu tun (siehe dazu die Erläterung zum Perl5-Modul CGI.pm bzw. benutzen sie den Befehl "perldoc CGI").
Und hier das Ergebnis unserer Bemühungen.
#==================================================================
# Bearbeitung des Abfrageergenisses und Ausgabe als HTML-CGI
#==================================================================
# HTTP-Header ausgeben (Perl 5 CGI Library)
print $cgi_obj->header( -type=>'text/html',
-expires=>'+1h');
# HTML-Header ausgeben (Perl 5 CGI Library)
print $cgi_obj->start_html(-title=>'Wieviele Beine hast den du?',
-author=>'grupp@elektronikschule.de',
);
print $cgi_obj->h2('Zu welcher Sorte gehörst denn du?');
print "<ul>\n";
while ( my @ergebnis = $sth->fetchrow_array() ){
# Im Array @ergebnis steht nun ein Datensatz
print '<li>' . $ergebnis[1] . "</li>\n";
}
print "</ul>\n";
# HTML-Dokument beenden
print $cgi_obj->end_html();
|
#!/usr/bin/perl -w
#
# Dateiname: script4.pl
# Autor: A. Grupp
# Compilerpragma strict um keine Fehler zu machen :-)
use strict;
# Benutzung der Perl-Module CGI und DBI
use CGI;
use DBI;
# Neues CGI-Objekt erzeugen
my $cgi_obj = new CGI;
# Datenbank-Verbindung aufbauen
my $dbh = DBI->connect( 'dbi:mysql:dbi_demo', 'root', '') ||
die "Kann keine Verbindung zum MySQL-Server aufbauen: $DBI::errstr\n";
# Befehl fuer Ausfuehrung vorbereiten. Referenz auf Statement
# Handle Objekt wird zurueckgeliefert
my $sth = $dbh->prepare( 'SELECT * FROM tbl_art' ) ||
die "Kann Statement nicht vorbereiten: $DBI::errstr\n";
# Vorbereitetes Statement (Abfrage) ausfuehren
$sth->execute ||
die "Kann Abfrage nicht ausfuehren: $DBI::errstr\n";
# HTTP-Header ausgeben (Perl 5 CGI Library)
print $cgi_obj->header( -type=>'text/html',
-expires=>'+1h');
# HTML-Header ausgeben (Perl 5 CGI Library)
print $cgi_obj->start_html(-title=>'Wieviele Beine hast den du?',
-author=>'grupp@elektronikschule.de',
);
print $cgi_obj->h2('Zu welcher Sorte gehörst denn du?');
print "<ul>\n";
while ( my @ergebnis = $sth->fetchrow_array() ){
# Im Array @ergebnis steht nun ein Datensatz
print '<li>' . $ergebnis[1] . "</li>\n";
}
print "</ul>\n";
# HTML-Dokument beenden
print $cgi_obj->end_html();
# Statement-Handle Resources freigeben
$sth->finish;
# Datenbank-Verbindung beenden
$dbh->disconnect;
|
Die Beispiel-Datenbank besteht aus zwei Tabellen. Bisher haben wir nur die erste Tabelle abgefragt. Das Ergebnis dieser Bemühungen kann man nun aber gleich als Hyperlink gestalten. Über den Hyperlink wird dann ein weiteres Skript aufgerufen das zu einer Art zugehörigen Detaildatensätze aus der zweiten Tabelle extrahiert und anzeigt. Hierzu sind im ersten Skript geringfügige Änderungen bei der Ausgabe nötig.
while ( my @ergebnis = $sth->fetchrow_array() ){
# Im Array @ergebnis steht nun ein Datensatz
print '<li><a href="script6.pl?art=' . $ergebnis[0]
. '">' . $ergebnis[1] . "</a></li>\n";
}
|
Das erste Datenfeld $ergebnis[0] enthät den Index um in
der zweiten
Tabelle die verwandten Datensätze aufzufinden. In die Ausgabe wird nun ein
Hyperlink integriert der auf das Perlskript "script6.pl" verweist. Diesem
Skript wird aber noch ein Parameter mit auf die Reise gegeben. Dies geschieht
durch Anhängen von z.B. "?art=1". Den "?art="-Teil findet man ja noch ganz
einfach. Die Nummer soll aber flexibel von Hyperlink zu Hyperlink wechseln und
die richtige Kennziffer beinhalten. Durch Stringverkettung gelingt dies mit
Hilfe des Datenfelds $ergebnis[0]. Innerhalb des Hyperlinks wird der
Obergriff aus Datenfeld $ergebnis[1] angezeigt.
Das Perlskript "script6.pl" muß nun auf die übergebene Kennziffer "art=x"
zugreifen. Hierfür gibt es ja die elegante Variante via CGI.pm.
my $art;
if ( defined($cgi_obj->param('art')) ){
$art = $cgi_obj->param('art');
}
else{
die "Dieses Skript muß anders aufgerufen werden!";
}
|
Nun haben wir es schon fast. Mit Hilfe der Variablen "$art" können wir
unsere ursprüngliche Abfrage etwas modifizieren. Es wird natürlich die zweite
Tabelle abgefragt und es wird eine kleine WHERE-Klausel an die Abfrage
angehängt.
# Befehl fuer Ausfuehrung vorbereiten. Referenz auf Statement
# Handle Objekt wird zurueckgeliefert
my $sth = $dbh->prepare( "SELECT * FROM tbl_bsp WHERE bsp_artID=$art" ) ||
die "Kann Statement nicht vorbereiten: $DBI::errstr\n";
|
Das restliche Folgeskript "script6.pl" müssten Sie eigentlich schon selbst erstellen können.
Auch das Zusammenspiel dieser beiden Skripte lässt sich natürlich wie immer ausprobieren.
ACHTUNG: In diesem Beispiel werden erstmals Daten die über das Internet gesendet wurden in einen SQL-Befehl eingebaut. Normalerweise stammen diese Daten aus den von uns selbst erzeugten Hyperlinks und sollten deshalb stimmen und gefahrlos sein. Nun gibt es aber böse Menschen die versuchen derartige Codestellen auszunutzen und "selbstgeschnitzte" Daten einschleusen. Damit stürzt im besten Fall das Skript ab - im schlechtesten Fall tut es Dinge die Sie nicht haben wollen (z.B. Löschen von Datensätzen, Dateien, ..., Einschleusen weiteren Codes, ...) und ist dann eine riesige Gefahr!!! So wie im obigen Beispiel dürfen Sie deshalb von Außen stammende Daten nie weiterverwenden. Hier sind vorher Tests durchzuführen und problematische Zeichen zu entfernen. Hierzu finden Sie weiter unten einen kurzen Abschnitt den Sie unbedingt lesen sollten bevor Sie eigene Anwendungen auf einem Internet-Server platzieren.
In eine Datenbank lassen sich auf diese Art und Weise auch neue Datensätze einfügen. So könnte es im obigen Beispiel z.B. den Wunsch geben zusätzliche "Füssler" in die Datenbank zu schreiben. Dazu muß natürlich der Wert für den neuen Eintrag beim Benutzer mit Hilfe eines Formulars abgefragt werden. Das Formular wird dabei 2 Dinge abfragen:
sorte mit einem
Wertebereich von 1-5).bezeichnung
als freies Textfeld).Gegenüber den bisherigen Skripten gibt es aber auch hier nicht allzu viel Neues da das eigentlich Prinzip gleich bleibt. Die vom Formular übergebenen Werte werden mit Hilfe des CGI.pm-Moduls in eine Variable des Skripts eingelesen (beachten Sie auch hier wieder, dass die Daten von Außen kommen und vor der Verwendung eigentlich erst "unschädlich" gemacht werden müssen!!!)
my $sorte;
if ( defined($cgi_obj->param('sorte')) ){
$sorte = $cgi_obj->param('sorte');
}
my $bezeichnung;
if ( defined($cgi_obj->param('bezeichnung')) ){
$bezeichnung = $cgi_obj->param('bezeichnung');
}
|
Die Hauptänderung betrifft den SQL-String der Abfrage. Bislang hatten wir hier immer Abragen im eigentlichen Sinn. Der nun verwendete SQL-String ist eher als höfliche Anfrage an den Datenbankserver zu verstehen. Würde man sie in Worte fassen, so könnte man dies so versuchen:
Lieber Datenbankserver, könntest du mir in die Tabelle tbl_bsp einen neuen Datensatz mit den Werten ... eintragen?.
In SQL-Syntax sieht das etwas nüchterner aus. Soll beispielsweise für die Sorte 2 (Vierbeiner) eine "Kuh" eingetragen werden, so ist das in SQL folgendermaßen zu formulieren:
INSERT INTO tbl_bsp SET bsp_artID='2', bsp_text='Kuh';
Anstelle der beiden festen Werte (2 und Kuh) werden die beiden
obigen Variablen $sorte und $bezeichnung eingesetzt.
Das entsprechende Statement hat damit dieses Aussehen:
my $sql = "INSERT INTO tbl_bsp SET bsp_artID='$sorte', bsp_text='$bezeichnung'";
my $sth = $dbh->prepare( $sql ) ||
die "Kann Statement nicht vorbereiten: $DBI::errstr\n";
|
Das Ganze wird noch mit ein wenig Ausgabekosmetik versehen und verhält sich dann so.
Bei Verwendung des obigen SQL-Statements kann es noch gravierende
Probleme geben. Stellen Sie sich einfach vor jemand versucht über das Formular
den Begriff "Has'" einzugeben. Beachten Sie hierbei das einzelne
Hochkomma an der schwäbischen Bezeichnung für einen "Hase" :-). Wenn Sie nun
diesen als CGI-Parameter übergebenen Datenwert einfach ungeprüft in Ihren SQL-Befehl
einfügen,
dann
sieht
das Resultat
so
aus:
INSERT INTO tbl_bsp SET bsp_artID='2', bsp_text='Has''; |
Am Ende des Befehls sehen sie die zwei aufeinander folgenden Hochkommas.
Das eine Hochkomma gehört zum Begriff und soll in die Datenbank übernommen
werden. Das zweite Hochkomma schließt den Sollinhalt des Datenfelds ab. Leider
ist das aber ein SQL-Syntaxfehler und das bisherige Erfassungsskript stürzt
hier mit einem Fehler ab. Je nach Datenbank und SQL-Know-How des "Benutzers"
ist ein böswilliger Angreifer auf diese Art und Weise sogar in der Lage Ihren
SQL-Befehl
so
abzuändern, dass er ganz andere Dinge tut als Sie ursprünglich vorgesehen hatten!
In
diesem
Fall
wird
der "Benutzer"
ganz einfach zum "Angreifer" :-(! Deshalb sollten sie
DRINGEND alle Parameter die Ihnen von Außen in Ihr Skript
gereicht werden nach Zeichen "abklopfen" die solche Effekte erzielen könnten
(' " % * ...). Diesen Zeichen ist dann z.B. in MySQL ein
\ (Backslash) voranzustellen! Beispiel: \'.
Netterweise liefert das DBI-Modul bereits eine entsprechende Funktion! Sie
heißt $dbh->quote() und
Sie sollten prinzipiell jeden von Außen gelieferten Parameter auf diese Weise quoten. Diese Aufforderung betrifft auch Parameter die Sie in Ihrem Formular fest vorgegeben haben oder auch Hidden-Felder. Es gibt nälich auch die Möglichkeit Ihrem Skript die Daten unter Umgehung Ihres Formulars "unterzuschieben". Dabei lassen sich dann beliebige Daten an das Skript schicken. Achtung: Das gilt auch wenn Sie nur die POST-Methode zulassen und zusätzlich den Referer auswerten (alles nur eine Frage der Angriffsenergie)!
Das Quoten könnte beispielsweise folgendermaßen in unser bisheriges Skript eingefügt werden:
my $bezeichnung = $dbh->quote($bezeichnung);
my $sorte = $dbh->quote($sorte);
my $sql = "INSERT INTO tbl_bsp SET bsp_artID=$sorte, bsp_text=$bezeichnung";
my $sth = $dbh->prepare( $sql ) ||
die "Kann Statement nicht vorbereiten: $DBI::errstr\n";
|
Beachten Sie, dass im SQL-Befehl die Hochkommas um die beiden Variablen
$sorte und $bezeichnung entfernt wurden. Die
Hochkommas befinden sich ja, wie bereits erwähnt, im Variableninhalt! Dieses
korrigierte Skript kann dann auch mit Hochkommas umgehen und verhält sich dann so.
Falls sie das alles in Ruhe zuhause ausprobieren wollen, können sie hier alle Beispielskripte dieses kleinen Kurses herunterzuladen.
Falls sie es bis hierher durchgehalten habe, wünsche ich ihnen viel Erfolg und würde mich freuen von ihnen zu hören (Kritik, Anregungen, Lob, ...)
Lokal bzw. Remote-Zugriffe seit
dem 20. Dezember 1998