Farid Hajji: Perl - Einführung, Anwendungen, Referenz
2., aktualisierte und erweiterte Auflage
Addison-Wesley Longman, ISBN 3-8273-1535-2
Web-Counter kennen wir alle. Das sind die kleinen Bildchen, die eine mehr oder weniger große Zahl anzeigen, die bei jedem Zugriff erhöht wird. Auf dieser Seite werden wir uns die Implementation eines solchen Zählers genauer anschauen. Sie ist aufgrund vieler Design-Entscheidungen nicht immer so naheliegend wie es zunächst den Anschein hat!
Programm counter1.pl zeigt einen primitiven Zähler, der auf dem CPAN-Modul File::CounterFile beruht.
Was geschieht nun hier genau?
File::CounterFile wird ein persistenter Zähler mit Hilfe einer Datei realisiert. Die Verwendung dieses Moduls ist sehr einfach:
use File::CounterFile; # Wo die Zaehler hinsollen. $File::CounterFile::DEFAULT_DIR = "/tmp"; # Der Zaehler soll mit der Datei namens $sessionid # realisiert werden. # Der Initialwert des Zaehlers sei 00000000 my $c = new File::CounterFile $sessionid, '00000000'; # Hier wird der Zaehler atomar erhoeht. my $newvalue = ++$c;
flock() synchronisiert. Somit brauchen wir uns keine Gedanken um die gegenseitige Beeinflussung mehrerer httpd-Prozesse beim Zugriff auf diese Zählerdatei zu machen. Hier ist der Zugriff ++$c als solcher atomar. Sie können jedoch auch eine ganze Klammer atomarer Zugriffe realisieren, indem Sie die Methoden lock() und unlock() von File::CounterFile verwenden. Darauf haben wir in diesem einfachen Beispiel aus naheliegenden Gründen verzichtet. Mehr Informationen finden Sie wie gewohnt in man File::CounterFile.IMG-Tag, der wie folgt aussehen kann ein:
<IMG SRC="http://sun-1.meta.net/cgi-bin/counter1.pl/12dbcxeg1"
ALT="Counter-Image">
counter1.pl eine zusätzliche Information angeheftet wird (hier: /12dbcxeg1). Diese Information wird, wie bereits bekannt, in die Umgebungsvariable PATH_INFO eingetragen und vom Zählerskript weiterverwendet.
PATH_INFO übergebene Information kennzeichnet einen speziellen Zähler. In counter1.pl werden zunächst alle Slashes durch Unterstriche erstzt, damit daraus ein einfacher Dateiname wird. So wird aus /12dbcxeg1 nach der Transformation _12dbcxeg1. Das wird der Name der Datei werden, die den Stand dieses Zählers speichern wird.File::CounterFile-Zähler immer nur genau eine Information gespeichert wird, müssen wir pro Seite, die wir zählen wollen, einen eigenen Zähler verwenden. Wenn Sie also mehrere verschiedene Seiten zählen wollen, müssen Sie verschiedene Dateinamen verwenden, wie etwa hier:
<IMG SRC="http://sun-1.meta.net/cgi-bin/counter1.pl/12dbcxeg1"> <IMG SRC="http://sun-1.meta.net/cgi-bin/counter1.pl/73dbc21d5"> <IMG SRC="http://sun-1.meta.net/cgi-bin/counter1.pl/bwc23fstl">
IMG) unter der SRC-URL anzufordern. Diese Anforderung triggert aber unser CGI-Programm und löst somit eine Erhöhung des jeweiligen Zählers aus. Mit dem neuen Zählerwert ausgestattet, liefert unser CGI-Programm counter1.pl ein on-the-fly generiertes GIF-Bild, das den neuen Zählerstand repräsentiert.GD-Moduls erzeugt. Hierfür verwenden wir die Subroutine output_as_gif(), die ein Bild mit den passenden Ausmaßen erzeugt und mit einem beliebigen Text füllt:
use GD;
sub output_as_gif {
my $message = shift; # Die auszugebende Nachricht
my ($x,$y) = (length($message) * gdLargeFont->width() + 10,
gdLargeFont->height() + 10);
my $im = new GD::Image($x, $y);
$im->transparent();
$im->interlaced('true');
$im->rectangle(0,0,$x,$y,$im->colorAllocate(0,0,0));
$im->string(gdLargeFont,5,5,
$message,$im->colorAllocate(255,255,255));
my $gif = $im->gif();
# GIF-Bild zum Browser senden:
print
$query->header(-type => 'image/gif', -expires => '-1d'),
$gif;
}
header() Methode des CGI-Moduls interessant. Beachten Sie, wie hier der MIME-Typ auf image/gif gesetzt wurde. Somit weiß der Browser, daß es sich um ein IMG des GIF-Typs handelt. Beachten Sie auch, wie mit Hilfe des Expires:-Header, die Gültigkeitsdauer dieses Bildes negativ gesetzt wurde. Damit wird dem Browser angezeigt, daß er dieses Bild nicht aus seinem lokalen Cache holen soll, sondern stets die neueste Version beim Server abholen soll. Nun so funktioniert auch der Zähler richtig!
<IMG SRC="http://sun-1.meta.net/cgi-bin/counter1.pl/mycounterxyz">
counter1.pl würde auch die Seiten dieser nicht befugten User brav mitzählen. Sie wollen aber bestimmt nicht Ihre Rechnerkapazitäten, Ihre Bandbreite und Ihr Plattenplatz völlig Unbekannten ohne besonderen Grund schenken. Auch wenn es Sie auf den ersten Blick nicht stören mag, einen Zähler für andere User bereitszustellen, wird sich dies im Laufe der Zeit rächen: Immer mehr User im Internet werden gegen Ihren Zähler linken. Das ist aber nicht gut, denn es müssen somit auch immer mehr Zählerdateien angelegt werden! Wie viele solcher Dateien faßt ein Inhaltsverzeichnis? Wie viele Dateien kann ein Betriebssystem verwalten? Auch wenn jede einzelne Datei winzig klein ist, ist die Verwaltung der Metainformationen dieser Dateien doch insgesamt kostspielig. Stellen Sie sich einfach ein Verzeichnis mit mehreren Millionen Zählerdateien vor! Dazu kommt die Tatsache, daß diese Millionen von Zählern von vielen hundert Millionen Hits getroffen werden, was jede einzelne Maschine schnell an den Rand des Zusammenbruchs führen wird. Außerdem müßte diese Maschine mit einer sehr hohen Bandbreite ans Internet angeschlossen sein.
http-User (z.B. nobody) schreibbar ist. Dadurch wird verhindert, daß File::CounterFile bei Bedarf neue Zählerdateien anlegt. Durch eine spezielle Userverwaltung sollten dann die Zählerdateien in diesem geschützten Verzeichnis vor ihrer ersten Verwendung angelegt werden. Somit dürfen nur noch berechtigte User ihre Zähler verwenden. Unberechtigte User können dann nicht mehr neue Zählerdateien bei uns anlegen.This site was hit <IMG SRC="http://sun-1.meta.net/cgi-bin/counter1.pl/sitecounter732"> times.
counter1.pl gezählt. Das ist aber nicht immer das gewünschte Zählverhalten, denn es zeigt nicht die wahren Verhältnisse: Wenn ich innerhalb von 5 Minuten beispielsweise dieselbe Seite mehrfach anschaue, sollten dann mehrere Hits gezählt werden, oder ist das nur ein einziger Treffer (auf einer höheren Ebene betrachtet)? Sollte nicht z.B. pro Browser, pro IP-Adresse oder IP-Adreßbereich, pro Domaine, pro Tag, pro Stunde etc. gezählt werden? Hmmm... schwer zu sagen!counter1.pl nicht möglich Fragen nach der Anzahl der Treffer am Sonntag oder der Treffer aus der .edu-Domain zu beantworten.DBI-basierter ZählerIn counter2.pl wird ein DBI-basierter Zähler vorgestellt, der mit Hilfe von Tie::RDBM realisiert wird. Dieses Persistenz-Modul wurde in Kapitel 18 vorgestellt, wobei eine komplette Anwendung im Beispielprogramm db-tierdbm.pl zu finden war.
counter2.pl ist lediglich eine Modifikation von counter1.pl. Anstatt File::CounterFile zu verwenden, wird hier als Persistenz-Backend Tie::RDBM verwendet.counter2.pl im gleichen Maße. Ein weiterer Nachteil dieses Programms ist der Aufwand, der notwendig ist, erst eine Verbindung zum Datenbankserver herzustellen und sich dort einzuloggen. Somit wird dieser Zähler sehr langsam. Ein Ausweg aus diesem Dilemma ist die Verwendung von mod_perl zusammen mit dem Modul Apache::DBI.Im folgenden Beispiel werden wir mehr Statistiken als nur einen einfachen Zähler sammeln und werden dann in der Lage sein, einen komplexeren Zähler zu implementieren.
Wir wollen also mehr Daten sammeln und einen komplexeren Zähler realisieren. Überlegen wir uns daher erst, was dieser Zähler leisten soll. Eine mögliche Anforderungsliste lautet:
Hier sind mehrere Design-Entscheidungen notwendig. Eine naheliegende Implementation würde eine DBI-Datenbank als Backend verwenden. Darauf werden auch wir uns einlassen. Die wesentliche Frage ist aber nun, welche Daten bei jedem Treffer zu sammeln sind. Zwei mögliche Richtungen sind:
Welche Strategie wollen wir nun verfolgen? Beide haben jeweils Vor- und Nachteile:
Welche Strategie wollen wir nun verfolgen? Das hängt natürlich von den Umständen ab. Vom Gefühl her, würde ich mehr zur zweiten Lösung tendieren und diese werden wir im folgenden auch implementieren.
Wir entscheiden uns also für eine Datenbanktabelle, die aus folgenden Feldern besteht:
counter1.pl und counter2.pl war dies die $sessionid. Unsere Tabelle sollte nach diesem Feld leicht durchsucht werden können. Wir können ruhigen Gewissens dieses Feld als Schlüsselfeld definieren, ja sogar als Primary Key.Weitere Felder sind ebenfalls denkbar. Sie können nach Wunsch diese Tabelle erweitern, sollten dann aber das entsprechende Programm ebenfalls aktualisieren.
Nun haben wir die Tabelle auf einer logischen Art beschrieben. Wir werden im folgenden aber diese Tabelle mit Hilfe von Tie::RDBM implementieren. Das ist dank Storable möglich, wie es in Kapitel 18 gezeigt wurde.
Schauen wir uns erst an, wie counter3.pl aufgerufen werden kann:
<IMG SRC="http://sun-1.meta.net/cgi-bin/counter3.pl/mycounter1"
ALT=["Counter"]>
http://sun-1.meta.net/cgi-bin/counter3.pl/mycounter1?getstats=1 http://sun-1.meta.net/cgi-bin/counter3.pl/mycounter1?getstats=2
Data::Dumper ausgegeben. Im zweiten Fall wird eine richtige HTML-Seite mit verschiedenen Tabelle ausgedruckt.
<html>
<head><title>Counter-Example</title></head>
<body>
<h1>Counter-Example</h1>
This page was accessed
<a href="http://sun-1.meta.net/cgi-bin/counter3.pl/mycounter1?getstats=2">
<img src="http://sun-1.meta.net/cgi-bin/counter3.pl/mycounter1"
alt="[Counter]">
</a>
times.
</body>
</html>
Schauen wir uns nun die Implementierung von counter3.pl näher an!
counter3.pl ist eine Kreuzung aus counter2.pl und aus dem Beispiel db-tierdbm.pl aus Kapitel 18.%counter persistent in eine Tie::RDBM-Tabelle abgelegt. Diesmal wird aber pro Zähler nicht mehr nur die maximale Trefferzahl als Wert vermerkt, sondern gleich eine ganze komplexe Datenstrutur, die aus einem Hash von Hashes besteht.
farid@sun-1:~> nc sun-1.meta.net 80
GET /cgi-bin/counter3.pl/mycounter1?getstats=1
Hier noch einmal Enter druecken!
$VAR1 = {
'MaxHits' => 30,
'Domain' => {
'net' => 30
},
'Week' => {
44 => 30
},
'Browser' => {
'' => 1,
'HotJava/1.0.1/JRE1.1.3' => 6,
'Mozilla/4.5 [en] (X11; I; SunOS 5.6 i86pc)' => 16,
'Lynx/2.8rel.2 libwww-FM/2.14' => 2,
'Lynx/2.8.1rel.1 libwww-FM/2.14' => 1,
'lwp-download/0.1 libwww-perl/5.41' => 1,
'Mozilla/4.08 [en] (X11; I; FreeBSD 3.1-RELEASE i386)' => 3
},
'Day' => {
'mon' => 30
},
'Month' => {
'nov' => 30
}
};
$counter{'_mycounter1'}.
counter3.pl lautet:
# Wir holen die Zaehlerdaten in eine lokale Variable:
tied(%counter)->{'dbh'}->do("LOCK TABLES " . DB_TABLE . " WRITE");
die $DBI::errstr if $DBI::err;
my $tmpcounter = $counter{$sessionid};
my $newvalue = ++$tmpcounter->{'MaxHits'};
++$tmpcounter->{'Domain'}->{which_domain()};
++$tmpcounter->{'Day'}->{which_day()};
++$tmpcounter->{'Week'}->{which_week()};
++$tmpcounter->{'Month'}->{which_month()};
++$tmpcounter->{'Browser'}->{which_browser()};
$counter{$sessionid} = $tmpcounter;
tied(%counter)->{'dbh'}->do("UNLOCK TABLES");
die $DBI::errstr if $DBI::err;
$tmpcounter kopiert wird. Anschließend wird diese lokale Kopie aktualisiert und schließlig zurück in die Datenbank übertragen, indem sie wieder an %counter zugewiesen wird. Diese Technik wurde in Kapitel 18 ausführlich erklärt.
db-tierdbm.pl gezeigt.++ inkrementiert. Beachten Sie hierbei die implizite Auswahl der Subkategorie (z.B. bei Kategorie Domain die Subkategorien net, com, de, edu etc.)! Dies wurde durch die Angabe:
++$tmpcounter->{'Domain'}->{which_domain()};
which_domain() eine Subroutine ist, die die Top-level Domain des Aufrufers ermittelt und als String zurückgibt. Dieses Beispiel ist auch ähnlich den Wortfrequenzbeispielen wfreq.pl, wfreq2.pl und wfreq3.pl aus Kapitel 8. Außerdem wurde heftig von Autovivikation Gebrauch gemacht (siehe Kapitel 13).
which_*()-Funktionen sind nicht besonders interessant. Sie finden diese in counter3.pl.text/plain- oder text/html-Format erkennt:
# Abhaengig von der Aufruf-Art, senden wir das Bildchen mit dem
# Zaehlerstand zurueck, oder aber eine Seite mit Statistiken.
if (defined ($query->param('getstats'))) {
output_statistics($tmpcounter, $query->param('getstats'));
} else {
output_as_gif($newvalue);
}
<img src="http://sun-1.meta.net/cgi-bin/counter3.pl/mycounter1">
getstats vorhanden. Dann wird die Funktion output_as_gif() aufgerufen, die ein GIF-Bild erzeugt. Wenn aber das Programm durch folgende URLs aufgerufen wird:
<img src="http://sun-1.meta.net/cgi-bin/counter3.pl/mycounter1?getstats=1"> <img src="http://sun-1.meta.net/cgi-bin/counter3.pl/mycounter1?getstats=2">
output_statistics() verzweigt, die eine andere Art der Ausgabe erzeugt, nänlich eine text/plain-Ausgabe bei getstats=1, oder eine HTML-Seite mit Tabellen bei getstats=2.
Unser counter3.pl könnte noch verbessert werden. Insbesondere leidet dieses Programm noch unter einer längeren Startzeit, da erst eine Verbindung zur Datenbank hergestellt werden muß. Mit Hilfe von mod_perl und Apache::DBI läßt sich dieses Problem jedoch leicht beheben.
[Prev] [Up] [Next]
[Alte Quelle]
| Last modified: $Date: 2006/05/18 12:55:46 $ FH. Search :: Sitemap :: Disclaimer :: Copyright :: Privacy |
|