Statt eingehende Requests nur in der Logdatei des Webservers zu verfolgen, macht ein Soundserver sie für den Systemadministrator hörbar. Nun kann man die Surfer nebenbei belauschen.
Immer wenn ich eine neue Ausgabe unseres USA-Familien-Rundbriefs [1] hochlade, eine Ankündigungs-E-Mail abschicke und das RSS-Feed auffrische, sehe ich danach gerne im Accesslog des Webservers zu, wie die ersten Infohungrigen die Berichte lesen und hochaufgelöste Versionen der Bilder herunterladen.
Allerdings ist das Verfolgen des Serverlogs recht monoton. Viel besser wäre, wenn ich die Abrufe im Hintergrund registrieren und mich derweil anderen Aufgaben widmen könnte. Etwa indem ich den Surfern akustisch über die Schulter schauen könnte. In dem Buch “Netscape Time” von Jim Clark [2] las ich vor vielen Jahren einmal, dass die frühen Netscapeler, nachdem sie eine neue Release fertiggestellt hatten, die eintrudelnden Hits für den Download akustisch über den PC-Lautsprecher ausgaben.
Ein Browser-Download für Windows führte zu einem Froschquaken, für Macs wurde das Geräusch zerbrechenden Glases gespielt und für die Unix-Plattform löste sich ein Kanonenschuss. So erfreuten sich die Internetpioniere in ihren Cubicles am hörbaren Erfolg.
Etwas Ähnliches lässt sich leicht in Perl implementieren. Allerdings ist die Lage in meinem Fall ein wenig verzwickter, denn der Webserver läuft bei einem Hosting-Provider, der zwar einen Shell-Zugang über SSH erlaubt, aber keine Töne an mich übermitteln kann.
Firewall-Tunnel
Abhilfe schafft das Skript »boom-sender« auf dem Mietserver, das die »access.log«-Datei des Webservers überwacht und bei ausgewählten URLs Nachrichten über einen SSH-Tunnel an einen Soundserver »boom-receiver« auf meinem heimischen Rechner schickt.
Abbildung 1 zeigt den Aufbau: Der Soundserver ist ein mit dem CPAN-Modul POE realisiertes Skript, das auf Port 8080 des lokalen Rechners auf Befehle wartet. Auf der Seite des Hosting-Providers läuft ein weiteres POE-Skript, das bei Änderungen im Accesslog anspringt und als TCP-Client Nachrichten abschickt. Da mein Heimrechner hinter einer Firewall steckt, kann ihn der Logchecker »boom-sender« nicht direkt kontaktieren. Stattdessen verbindet ein Tunnel, den das Kommando
ssh -R 8080:localhost:8080 host.xyz-hosting.com
vom Heimrechner aus anlegt, die beiden Gesprächspartner. Das Logskript auf dem gehosteten Rechner muss seine Nachrichten nur an seinen Port 8080 schicken, damit sie durch den Tunnel wie durch Zauberei auf Port 8080 des Heimrechners ankommen, ganz so, als gäbe es die Firewall gar nicht.

Abbildung 1: Das Skript, das die Accesslog-Datei des Webservers auf dem Hostingrechner überwacht, kommuniziert über den SSH-Tunnel mit dem Sound erzeugenden Desktop.
Auf Befehl Sound
Die lokale Soundmaschine empfängt auf diese Weise Namen von Tondateien und spielt sie anschließend mit dem Play-Utility aus dem Fundus des Programmpakets Sox unter Linux ab. Das Programm »play« kommt standardmäßig mit Ubuntu und kann nicht nur WAV-Dateien, sondern auch MP3s ausgeben, falls Ubuntu dafür konfiguriert ist.
Abbildung 2 illustriert die Interaktion eines Testclients mit dem laufenden Soundserver: Ein »telnet« auf den Verbindungs-Port von Localhost zeigt zunächst eine Grußnachricht des Servers und alle Sounddateien, die dieser anbietet. Schickt der Client den Namen eines dieser WAV-Files an den Server, spielt der es ab. Das Skript in Listing 1 legt in Zeile 9 in »$SOUND_DIR« das Verzeichnis fest, in dem die Sounddateien liegen müssen. Aus Sicherheitsgründen sind nur Dateinamen und keine Pfade erlaubt. Voreingestellt ist das aktuelle Verzeichnis (».«).

Abbildung 2: Der Testclient Telnet verbindet sich mit dem Empfänger-Server, der auf Kommando eine Sounddatei abspielt.
POE bietet sich als Technologie sowohl für die Server- als auch für die Client-Seite an, weil es hier ausreicht, seine Komponenten nach dem Baukastenprinzip zusammenzustecken.
Eine recht gute Einführung in POE geben die Website [7] oder das Kapitel über POE in [3]. Der Server in Listing 1 definiert Callbacks für die Zustände »ClientConnected« (Client hat angedockt), »ClientInput« (Client hat eine Textzeile übermittelt) sowie einen speziellen Parameter »InlineStates« mit etlichen Zuständen, der für die weiter unten erklärten Abräumarbeiten nach dem Abspielen eines Geräuschs verantwortlich ist.
Der Server bearbeitet viele Clientverbindungen quasi gleichzeitig. Die in der POE-Komponente versteckte Logik sorgt für den reibungslosen Ablauf hinter den Kulissen. Wie bei jedem POE-Skript legt der Programmcode zunächst das Verhalten bei allen möglichen Events fest, um dann den POE-Kernel mit »POE::Kernel->run()« zu starten. Der läuft dann bis zum Programmende.
|
Listing 1: |
|---|
01 #!/usr/local/bin/perl -w
02 use strict;
03 use POE;
04 use POE::Component::Server::TCP;
05 use POE::Wheel::Run;
06 use File::Basename;
07 use Log::Log4perl qw(:easy);
08
09 my $SOUND_DIR = ".";
10 my @SOUND_FILES = map { basename $_ }
11 <$SOUND_DIR/*.wav>;
12
13 Log::Log4perl->easy_init($DEBUG);
14
15 POE::Component::Server::TCP->new(
16 Port => 8080,
17
18 ClientConnected => sub {
19 $_[HEAP]{client}->put("Soundfiles: [".
20 join(", ", @SOUND_FILES) . "]" );
21
22 $_[HEAP]{client}->put(
23 "Ready when you are.");
24 },
25
26 ClientInput => sub {
27 my $client_input = $_[ARG0];
28
29 if( $client_input !~ /^[w.-]+$/ ) {
30 $_[HEAP]{client}->put(
31 "Illegal input.");
32 return;
33 }
34
35 if( $client_input eq "q" ) {
36 POE::Kernel->yield("shutdown");
37 return;
38 }
39
40 my $msg = sound_play(
41 $_[HEAP],
42 basename($client_input));
43
44 $_[HEAP]{client}->put( $msg );
45 },
46
47 InlineStates => {
48 sound_ended => sub {
49 my ($heap, $wid) = @_[HEAP, ARG0];
50 DEBUG "Deleting wheel $wid";
51 delete $heap->{players}->{$wid};
52 },
53 },
54 );
55
56 POE::Kernel->run();
57 exit;
58
59 ###########################################
60 sub sound_play {
61 ###########################################
62 my($heap, $file) = @_;
63
64 if(! -f "$SOUND_DIR/$file") {
65 return "$file doesn't exist";
66 }
67
68 POE::Kernel->sig(CHLD => "reaped");
69
70 my $wheel =
71 POE::Wheel::Run->new(
72 Program => "/usr/bin/play",
73 ProgramArgs => ["$SOUND_DIR/$file"],
74 StderrEvent => 'ignore',
75 CloseEvent => 'sound_ended',
76 );
77
78 DEBUG "Creating wheel ", $wheel->ID;
79 $heap->{players}->{ $wheel->ID } = $wheel;
80
81 return "Played $file";
82 }
|
Nicht trödeln
Die Funktion »sound_play()« ab Zeile 60 Listing 1 spielt eine ihr überreichte Sounddatei ab. Dafür erzeugt sie ein POE::Wheel, ein Rädchen im Getriebe des POE-Systems, mit dem der POE-Kernel mit der Außenwelt kommuniziert.
Damit das System keine Zeit verplempert, darf Perl-Code in POE nur so lange ohne Unterbrechung vor sich hin werkeln, wie er mit voller Geschwindigkeit läuft. Bei der Interaktion mit Dateien, Sockets oder anderen Prozessen kommt es naturgemäß zu Verzögerungen. Weil der Zugriff auf die Festplatte oder das Netzwerk um Größenordnungen langsamer ist als das Abarbeiten von CPU-Instruktionen oder der Zugriff auf den Hauptspeicher, wäre es äußerst ineffizient, würde POE blockierend auf die Erledigung warten. Stattdessen sorgen Wheels dafür, dass der POE-Kernel die Tasks Event-basiert und asynchron abarbeitet.
Einen neuen Prozess durch das Programm »play« zu starten, ihm eine kurze Sounddatei zu übergeben und auf das Abspielen zu warten, kostet gut und gerne eine Sekunde. Wäre das Skript in dieser Zeit blockiert, würde es die Antwort an den Client so lange verzögern und könnte auch keine neuen Anfragen entgegennehmen. Das Wheel hingegen sorgt dafür, dass der Callback sofort zurückkehrt, der POE-Kernel wieder die Kontrolle übernimmt und der Rest einfach im Hintergrund abläuft.
Das Wheel des Moduls POE::Wheel::Run verlangt außer dem zu startenden externen Programm und dessen Argumenten auch noch die Definition eines »Stderr-Event«. Dahinter verbirgt sich eine Funktion, zu der das Wheel verzweigt, falls Daten an dessen Standard-Fehlerausgabe ankommen.
Für das Programm »play« jedoch, das normalerweise keine Fehlermeldungen ausgibt und sich nach jeder abgespielten Sounddatei wieder beendet, ist das nicht relevant. Aus diesem Grund legt »boom-receiver« einfach einen speziellen Zustand fest, den der POE-Kernel ignoriert. Stellt das Wheel fest, dass der gestartete »play«-Prozess sich beendet hat, löst es den in der Zeile 75 gesetzten und bereits in der Zeile 48 definierten »Close-Event« aus.
Idealerweise würde das Wheel einmalig einen dauerhaft laufenden Prozess wie »xmms« starten und ihm anschließend gelegentlich Sounddateien unterschieben. Die für diesen Ablauf vorgesehene POE-Komponente auf dem CPAN ist jedoch leider schon etwas in die Jahre gekommen und lässt sich nicht mehr ohne etliche Klimmzüge mit dem aktuellen XMMS kompilieren. Schade!
Holzauge sei wachsam!
Die gezeigte Implementierung geht zwar verschwenderisch mit den Ressourcen des lokalen Rechners um, ist aber durchaus in der Lage, quasi-parallele Requests in Geräusche umzuwandeln. Das Skript muss hierzu eine Referenz auf das Sound-erzeugende Wheel-Objekt behalten, da POE es sonst sofort abräumt, sobald sich niemand mehr darum kümmert.
Am Ende von »sound_play()« ist allerdings die Aufgabe des Wheel noch nicht erledigt, da der POE-Kernel es scheibchenweise nach dem Beenden der Funktion abarbeitet. Um einen verfrühten Tod zu vermeiden und gleichzeitig die Wheels nicht unnötig herumlungern zu lassen, legt die Zeile 79 das jeweils erzeugte Wheel-Objekt mit seiner ID im POE-Session-Heap unter dem Schlüssel »players« ab.
Da das Wheel einen »CloseEvent« mit dem Sprungziel »sound_ended« definiert, ruft POE beim Abschluss des Soundprozesses die ab Zeile 48 definierte Funktion auf, die die Wheel-Referenz wieder löscht, damit POE den Sensenmann vorbeischickt. Als weiteres Gotcha räumt »POE::Wheel::Run« terminierte Kindsprozesse nicht automatisch auf, sodass sie als Zombies im Unix-System herumlungern. Zeile 68 definiert deswegen einen »SIGCHLD«-Handler, der den Elternprozess dazu veranlasst, ein »wait()« auf das terminierte Kind abzusetzen und damit dessen Verwandlung in einen Zombie zu verhindern.
Sobald ein Client sich mit dem Port 8080 der Serverkomponente POE::Component::Server::TCP verbindet, springt deren Zustandsautomat den Zustand »ClientConnected« an. Dort liegt in »$_[HEAP]{client}« ein Clientobjekt vor, über dessen »put()«-Methode der Server die Nachrichten an den Client schicken kann. In »ClientConnected« teilt der Server dem andockenden Client noch die verfügbaren Sounddateien mit und begrüßt ihn mit einem freundlichen “Ready when you are”.
Wann immer der Client etwas an den Server schickt, springt dieser die Subroutine an, die dem Zustand »ClientInput« zugeordnet ist. Die eingegangene Nachricht liegt dort in »$_[ARG0]«, einem der POE-typischen Felder des Argument-Arrays »@_«. Damit der Client keinen Schabernack mit dem Server treibt und bösartige Unix-Kommandos statt der erwarteten Sounddatei schickt, prüft Zeile 29, ob außer den in Dateien üblichen Zeichen noch andere vorliegen, und verwirft in diesem Fall den Request sofort mit einer Fehlermeldung.
Schickt der Client hingegen das Zeichen »q«, möchte er die Session beenden und der Server wechselt in den Zustand »shutdown«, der die aktuelle Clientverbindung löst, aber den Server weiterlaufen lässt. Sendet der Client dagegen tatsächlich den Namen einer existierenden Sounddatei, spielt ihn die Funktion »sound_play« ab und gibt einen Statusstring zurück, den der Server dem Client zur Bestätigung mit »put()« schickt.
Am Ende des Tunnels
Auf der anderen Seite des Tunnels überwacht das POE-Skript aus Listing 2 das Access-Logfile des Webservers. Es läuft auf dem Rechner des Hosting-Service und nutzt die TCP-Client-Komponente des POE-Framework, um mit dem Server Kontakt zu halten. Diese bietet von Haus aus unter anderem die Events »ServerInput« und »ConnectError« an, deren Callbacks sie angespringt, falls der Server Text sendet oder die Verbindung fehlschlägt. Zum Senden definiert »boom-sender« über »InlineStates« den Zustand »send«, der eine ihm übergebene Nachricht mit »put()« an den Server schickt.
Die ab Zeile 29 definierte Logfile-Überwacher-Session bekommt mittels eines »FollowTail«-Wheels aus der POE-Werkzeugkiste mit, wenn der Webserver eine neue Zeile an die in Zeile 35 definierte Logdatei anhängt. Auch hierbei ist es wichtig, dass sie eine Referenz auf das Wheel beibehält, denn sonst würde POE Letzteres rücksichtslos abräumen, sobald der »_start«-Callback beendet wäre. Die Referenz bleibt im so genannten Heap der POE-Session unter dem Eintrag »tail« bestehen, so lange die Session läuft, das heißt also bis zum Ende des Skripts »boom-sender«.
Produktionssysteme rotieren ihre Logdateien oft täglich, »FollowTail« ist darauf vorbereitet und springt in diesem Fall den »ResetEvent« an, der »got_log_rollover()«-Callback zugeordneten ist. Der macht damit nichts Großartiges, sondern schreibt nur eine Debug-Meldung, damit der User Bescheid weiß.
Jedes Mal, wenn das Wheel eine neu angehängte Logzeile findet, wechselt es in den Status »got_log_line« und führt den entsprechenden Callback aus. Dort analysiert es neue Zeilen, die im Format
67.195.37.108 - - [01/Sep/2008:17:25:20-0700] "GET /1/p3.html HTTP/1.0" 200 8678 "-" "Mozilla/5.0 (X11; U; Linux i686 (x86_64); en-US; rv:1.8.1.4) Gecko/20080721 BonEcho/2.0.0.4"
vorliegen, mit dem Modul ApacheLog:: Parser vom CPAN. Dessen exportierte Funktion »parse_line_to_hash()« liefert einen Hash zurück, der unter dem Eintrag »file« die per HTTP angeforderte Datei enthält. Da der TCP-Client in Zeile 12 den Aliasnamen »boom« bekommt, kann die in Zeile 29 definierte Session mit dem »FollowTail-Wheel« dem TCP-Server die zu sendende Sounddatei über den Aufruf
POE::Kernel->post("boom", "send", file2sound($file));
mitteilen. Hier kommunizieren zwei verschiedene Sessions miteinander, weswegen nicht »yield()«, sondern »post()« mit dem Namen der empfangenden Session das Ereignis einleitet. Der Event »send« der Session »boom« bekommt dann in »ARG0« den Namen der WAV-Datei übermittelt, die es in Zeile 21 mit »put()« an den TCP-Client übergibt, der diese wiederum als Kommando an den Soundserver schickt. Nicht direkt zwar, aber an Port 8080 des lokalen Rechners, von wo aus sie über den Tunnel Port 8080 des Soundservers erreicht.
|
Listing 2: |
|---|
01 #!/home/mschilli/PERL/bin/perl -w
02 use strict;
03 use POE;
04 use POE::Wheel::FollowTail;
05 use POE::Component::Client::TCP;
06 use ApacheLog::Parser
07 qw(parse_line_to_hash);
08 use Log::Log4perl qw(:easy);
09 Log::Log4perl->easy_init($DEBUG);
10
11 POE::Component::Client::TCP->new(
12 Alias => 'boom',
13 RemoteAddress => 'localhost',
14 RemotePort => 8080,
15 ServerInput => sub {
16 DEBUG "Server says: $_[ARG0]";
17 },
18 InlineStates => {
19 send => sub {
20 DEBUG "Sending [$_[ARG0]] to server";
21 $_[HEAP]->{server}->put($_[ARG0]);
22 },
23 },
24 ConnectError => sub {
25 LOGDIE "Cannot connect to server";
26 }
27 );
28
29 POE::Session->create(
30 inline_states => {
31 _start => sub {
32 $_[HEAP]->{tail} =
33 POE::Wheel::FollowTail->new(
34 Filename =>
35 "/var/log/apache2/access.log",
36 InputEvent => "got_log_line",
37 ResetEvent => "got_log_rollover",
38 );
39 },
40 got_log_line => sub {
41 my %fields =
42 parse_line_to_hash $_[ARG0];
43 my $file = $fields{ file };
44 if(my $sound = file2sound($file)) {
45 POE::Kernel->post("boom", "send",
46 $sound);
47 }
48 },
49 got_log_rollover => sub {
50 DEBUG "Log rolled over.";
51 },
52 }
53 );
54
55 POE::Kernel->run();
56 exit;
57
58 ###########################################
59 sub file2sound {
60 ###########################################
61 $_ = $_[0];
62
63 DEBUG "Got $_";
64
65 s#/$#/index.html#;
66
67 m#/index.html$# and
68 return "article-page.wav";
69
70 m#/posting.php# and
71 return "forum-post.wav";
72
73 m#/viewforum.php# and
74 return "forum-page.wav";
75
76 m#/images/.*html# and
77 return "image.wav";
78
79 return "";
80 }
81 @KE;
|
Keine Kakophonie
Löste jeder neue Eintrag im Accesslog einen Ton aus, entstünde bei einer Webseite mit 20 Bildern, die der Browser in kürzesten Abständen einholt, eine nervige Kakophonie von überlagerten Geräuschen. Aus diesem Grund filtert »boom-sender« die Ausgabe des Accesslogs und übermittelt nur bei aufgerufenen Hauptseiten, vergrößerten Bildern und Forumsaktivität Anfragen an den Soundserver. Die ab Zeile 59 definierte Funktion »file2sound()« nimmt den vom Browser angeforderten Dateipfad (zum Beispiel »/index.html«) entgegen und gibt den Namen der abzuspielenden Sounddatei zurück. Hierzu macht sie ein paar Annahmen, die eine Installation eventuell anpassen muss wie zum Beispiel, dass ein auf »/« endender Pfad eine »index.html«-Datei herausgibt.
Installation
Das Skript »boom-sender« ist auf dem Hosting-Rechner zu installieren, die notwendigen Perl-Module gibt\’s alle auf dem CPAN. AccessLog::Parser erfordert noch die Module Getopt::Helpful, Date::Piece, File::Fu und Class::Accessor::Classy. Sträubt sich der Hosting-Provider, die Installation mit dem angebotenen Perl durchzuführen, geht dies auch mit Hilfe eines zusätzlichen Modulverzeichnisses in einem vom Mieter beschreibbaren Bereich auf dem gehosteten Rechner und der Direktive
use lib "/home/name/perl-modules";
im Perl-Skript. Alternativ funktioniert auch eine eigene Perl-Installation, ebenfalls in einem für den Hosting-Kunden beschreibbaren Bereich. Details hierzu finden sich in [4]. Als weitere Alternative kommt das PAR-Toolkit in Betracht, mit dem sich ähnlich wie mit Javas Jar-Dateien Modularchive und sogar ausführbare Executables ohne Installationssorgen packen lassen [5].
Die in der Funktion »file2sound()« in »boom-sender« vorgenommenen Zuweisungen von URL-Dateien zu Soundfiles sind noch an die lokalen Verhältnisse anzupassen. Die Funktion sollte nur Klangdateien verwenden, die der Soundserver tatsächlich anbietet.
Auf dem Soundserver werden die Sounddateien im Verzeichnis »$SOUND_DIR« installiert. Eine gute Auswahl an kurzen Geräuschen bietet das Verzeichnis »/usr/share/sounds/purple«. Dort legt der IM-Client Pidgin (ehemals Gaim) die Daten von Geräuschen ab, die das Program von sich gibt, wenn sich Buddies an- oder abmelden oder Instant Messages eintrudeln oder abgehen.
Nach dem Starten des Soundservers »boom-receiver«, einem kurzen Test mit dem »telnet«-Client und dem Einrichten des oben beschriebenen SSH-Tunnels lässt sich der Logdatei-Überwacher auf dem Hosting-Rechner starten, und das Konzert beginnt.
Erweiterungen
Zusätzlich zu den angeforderten URL-Pfaden könnte der Soundserver auch noch bei fehlschlagenden Requests Töne abspielen. Der Webserver legt auch den Returncode jedes Requests in »access.log« ab, der Logparser stellt ihn mit dem Hasheintrag »$fields{code}« zur Verfügung. Flatulenzgeräusche oder eine Explosion wären etwa die geeignete Hintergrundmusik für solche Fehlleistungen und zögen die Aufmerksamkeit des Systemadministrators auf sich. (jcb)
|
Infos |
|---|
|
[1] USA-Rundbrief: [http://usarundbrief.com] [2] Jim Clark, “Netscape Time”:[http://www.amazon.com/gp/reader/0312263619/ref=sib_dp_ptu#] [3] Simon Cozens, “Advanced Perl Programming (2nd edition)”: O\’Reilly, 2005 [4] Michael Schilli, “Da hab ich dann was Eigenes”: Linux-Magazin,[http://perlmeister.com/snapshots/199911/index.html] [5] Michael Schilli, “Gepackte Koffer”: Linux-Magazin, [https://www.linux-magazin.de/heft_abo/ausgaben/2004/09/gepackte_koffer] [6] Listings zu diesem Artikel: [ftp://www.linux-magazin.de/pub/listings/magazin/2008/11/Perl] [7] POE: [http://poe.perl.org] |
|
Der Autor |
|---|
|
|






