A Perl script implements a singing, musical Internet

Beady-eyed!

Admittedly, the implementation shown here wastes resources on the local machine, but it can indeed convert quasi-parallel requests into sounds. To allow this to happen, the script keeps a reference to the wheel object that generates the sound because POE cleans the object up immediately if nobody takes care of it. The wheel's task of playing the sound does not end at sound_play(), because the POE kernel processes it slice by slice after the function terminates. To avoid an untimely demise, while at the same time avoiding keeping wheels for longer than necessary, line 79 saves a reference to the wheel object in the POE session heap with the key players and the wheel's ID.

Because the wheel defines a CloseEvent with a callback sound_ended, POE calls the function defined in line 48 when the sound process terminates; in turn, the function deletes the wheel reference to let POE move in for the kill.

Another issue is that POE::Wheel::Run does not automatically clean up terminated child processes, instead leaving them lying around as zombies on the Unix system. Therefore, line 68 defines a SIGCHLD handler that tells the parent process to issue a wait() for the terminated child process and prevent it from turning into a zombie.

As soon as a client connects to port 8080 on the POE::Component::Server::TCP server component, its state machine changes state to ClientConnected. In the callback, $_[HEAP]{client} contains a client object whose put() method is used by the server to send messages to the client. The server uses ClientConnected to inform the connecting client of the available sound files before announcing Ready when you are.

Whenever the client sends a line to the server, the server jumps to the subroutine mapped to the ClientInput state. The received message is available in $_[ARG0], one of the @_ argument array fields typical of POE.

To prevent the client from attacking the server with nasty shell commands, instead of sending a sound file as expected, line 29 checks the file name to see whether it contains anything apart from the normal characters and immediately issues an error message and rejects the request in this case.

The client sends the q character to indicate that it wants to quit the session; the server then switches to the shutdown state, terminating the current client connection but leaving the server running. If the client really does send the name of an existing sound file, the sound_play function plays the file and returns a status string, which the server sends via put() to the client to confirm successful execution.

End of the Tunnel

At the other end of the tunnel, the POE script (boom-sender) in Listing 2 monitors the web server's access logfile. It runs on the hosted machine and uses the POE framework's TCP client component to keep in touch with the server.

Listing 2

boom-sender

01 #!/usr/local/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 }

Among other things, the Client::TCP POE component defines the ServerInput and ConnectError events; the script jumps to the callbacks for these events if the server sends text back or a connection fails.

Boom-sender uses InlineStates to define the send state, which uses put() to send a message to the server that was passed in.

Thanks to the FollowTail wheel from the POE toolbox, the logfile monitoring session defined in line 29 notices when the web server appends a line to the logfile defined in line 35. Again, it is important to have a reference to the wheel to prevent POE cleaning it up after the _start callback terminates.

The reference is kept in the POE session heap under the tail key while the session is active – that is, until boom-sender terminates.

Production systems will tend to rotate their logfiles daily; FollowTail is prepared for this and jumps to the got_log_rollover callback mapped to ResetEvent in this case. All this does is write a debug message to let the user know what is going on. Whenever the wheel finds a newly appended line in the log, it changes state to got_log_line and executes the matching callback. It uses the CPAN ApacheLog::Parser module to analyze the new lines, which have the following 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"

The parse_line_to_hash() function exported by this module returns a hash containing the file requested by the http request under the file key.

In line 12, the TCP client component defines an alias (boom) for its session. The FollowTail wheel, running in another session defined in line 29, can use the following lines to tell the TCP server which sound file it needs to play:

POE::Kernel->post("boom", "send",file2sound($file));

Because two different sessions are communicating here, I can't use yield() to send the event; instead, I must use post() with the alias of the receiving session. Then the name of the WAV file is sent by the POE kernel to the send callback in the boom session as argument ARG0. The callback then uses put() to send the name to the TCP client in line 21, which in turn passes it on to the sound server – not directly, but to port 8080 on the local machine, and thus through the tunnel to port 8080 on the sound server.

Avoiding Cacophony

If every entry in the access log were to trigger a sound, a web page with 20 images, which the browser retrieves in short succession, would trigger an annoying cluster of superimposed noises. For this reason, boom-sender filters the access log output and only transmits to the sound server in case of index pages, high-res images, and discussion forum activity.

The file2sound() function defined in line 59 expects the file path requested by the browser (for example, /index.html) and returns the name of the sound file to play.

To allow this to happen, it makes a few assumptions – for example, that a path that ends with a / should output an index.html file – that you might need to modify when installing.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Admin Workshop: inetd & xinetd

    If you have many daemons running on your server, it can be quite difficult to keep track of them all. inetd and xinetd manage these services centrally and also take care of exchanges with your clients, allowing programs without network code to operate as Internet servers.

  • Free Software Projects

    DJs don’t need expensive decks now that Mixxx offers a competitive computer-based alternative. The Liquidsoap programming language provides a fully automated approach to generating flexible streams.

  • Ask Klaus!
  • LTSP

    The Linux Terminal Server Project offers a comprehensive approach to terminal services in Linux, including easy access to local sound cards, printers, and USB sticks.

  • MIDI with Linux

    A MIDI keyboard is a useful extension to any audio workstation. Learn how to connect a MIDI instrument to your Linux sound studio through a MIDI interface device.

comments powered by Disqus

Direct Download

Read full article as PDF:

072-076_perl.pdf (427.10 kB)

News