summaryrefslogtreecommitdiff
path: root/tests/scripts/webserver.pl
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xtests/scripts/webserver.pl276
1 files changed, 276 insertions, 0 deletions
diff --git a/tests/scripts/webserver.pl b/tests/scripts/webserver.pl
new file mode 100755
index 0000000..a1d171d
--- /dev/null
+++ b/tests/scripts/webserver.pl
@@ -0,0 +1,276 @@
+#!/usr/bin/perl -w
+
+# Simple WEB server.
+#
+# Inspired by some examples from the perlipc and other perl manual pages.
+#
+# Copyright (C) 2009 Michael Adam
+#
+# License: GPL
+
+use strict;
+
+use IO::Socket;
+use IO::Select;
+use Carp;
+use POSIX qw(setsid :sys_wait_h);
+use Errno;
+use Getopt::Long;
+use Pod::Usage;
+use Fcntl ':flock'; # import LOCK_* constants
+
+my $EOL = "\015\012";
+
+my $port = 2345;
+my $proto = getprotobyname('tcp');
+my $pid_file = "./webserver.pid";
+my $log_dir = "./";
+my $access_log_file;
+my $error_log_file;
+my $document_root = "./";
+my $help = 0;
+
+sub create_child($$$);
+
+sub logmsg {
+ print STDERR "[", scalar localtime, ", $$] $0: @_\n";
+}
+
+sub start_server($$) {
+ my $proto = shift;
+ my $port = shift;
+ my $server;
+
+ $server = IO::Socket::INET->new(Proto => $proto,
+ LocalPort => $port,
+ Listen => SOMAXCONN,
+ Reuse => 1);
+
+ logmsg "server started listening on port $port";
+
+ return $server;
+}
+
+sub REAPER {
+ local $!; # don't let waitpid() overwrite current error
+
+ while ((my $pid = waitpid(-1,WNOHANG)) > 0 && WIFEXITED($?)) {
+ logmsg "reaped $pid" . ($? ? " with exit code $?" : '');
+ }
+
+ $SIG{CHLD} = \&REAPER;
+}
+
+sub child_action($) {
+ my $client = shift;
+ my $client_ip = shift;
+
+ logmsg "client_action: client $client_ip";
+
+ $client->autoflush();
+
+ my $fortune_bin = "/usr/games/fortune";
+ my $fortune = "";
+ if ( -x $fortune_bin) {
+ $fortune = qx(/usr/games/fortune);
+ $fortune =~ s/\n/$EOL/g;
+ }
+
+ my $request = "";
+ while (my $request_line = <$client>) {
+ $request .= $request_line;
+ last if ($request_line eq $EOL);
+ }
+
+ logmsg "request:\n" .
+ "------------------------------\n" .
+ "$request" .
+ "------------------------------";
+
+ print $client "HTTP/1.0 200 OK$EOL";
+ print $client "Server: Tinyproxy Test Web Server$EOL";
+ print $client "Content-Type: text/html$EOL";
+ print $client "$EOL";
+ print $client "<html>$EOL";
+ print $client "<h1>Tinyproxy test WEB server</h1>$EOL";
+ print $client "<h2>Fortune</h2>$EOL";
+ if ($fortune) {
+ print $client "<pre>$fortune</pre>$EOL";
+ } else {
+ print $client "Sorry, no $fortune_bin not found.$EOL";
+ }
+ print $client "<h2>Your request:</h2>$EOL";
+ print $client "<pre>$request<pre>$EOL";
+ print $client "</html>$EOL";
+
+ close $client;
+
+ return 0;
+}
+
+sub create_child($$$) {
+ my $client = shift;
+ my $action = shift;
+ my $client_ip = shift;
+
+ unless (@_ == 0 && $action && ref($action) eq 'CODE') {
+ confess "internal error. create_child needs code reference as argument";
+ }
+
+ my $pid = fork();
+ if (not defined($pid)) {
+ # error
+ logmsg "cannot fork: $!";
+ return;
+ } elsif ($pid) {
+ # parent
+ logmsg "child process created with pid $pid";
+ return;
+ } else {
+ # child
+ exit &$action($client, $client_ip);
+ }
+}
+
+sub process_options() {
+ my $result = GetOptions("help|?" => \$help,
+ "port=s" => \$port,
+ "pid-file=s" => \$pid_file,
+ "log-dir=s" => \$log_dir,
+ "root|document-root=s" => \$document_root);
+ die "Error reading cmdline options! $!" unless $result;
+
+ pod2usage(1) if $help;
+
+ # some post-processing:
+
+ ($port) = $port =~ /^(\d+)$/ or die "invalid port";
+ $access_log_file = "$log_dir/webserver.access_log";
+ $error_log_file = "$log_dir/webserver.error_log";
+}
+
+sub daemonize() {
+ umask 0;
+ chdir "/" or die "daemonize: can't chdir to /: $!";
+ open STDIN, "/dev/null" or
+ die "daemonize: Can't read from /dev/null: $!";
+ open STDOUT, ">> $access_log_file" or
+ die "daemonize: Can't write to '$access_log_file': $!";
+ open STDERR, ">> $error_log_file" or
+ die "daemonize: Can't write to '$error_log_file': $!";
+
+ my $pid = fork();
+ die "daemonize: can't fork: $!" if not defined($pid);
+ exit(0) if $pid != 0; # parent
+
+ # child (daemon)
+ setsid or die "damonize: Can't create a new session: $!";
+}
+
+sub get_pid_lock() {
+ open LOCKFILE, "> $pid_file" or
+ die "Error opening pid-file $pid_file: $!";
+ unless (flock(LOCKFILE, LOCK_EX|LOCK_NB)) {
+ my $other_pid = qx(cat $pid_file);
+ print "Webserver is already running (pid $other_pid)";
+ exit(0);
+ }
+
+ print LOCKFILE "$$";
+}
+
+sub release_pid_lock() {
+ flock(LOCKFILE, LOCK_UN);
+ close LOCKFILE;
+}
+
+# "main" ...
+
+$|=1; # autoflush
+
+process_options();
+
+daemonize();
+
+get_pid_lock();
+
+$SIG{CHLD} = \&REAPER;
+
+my $server = start_server($proto, $port);
+my $slct = IO::Select->new($server);
+
+while (1) {
+ my @ready_for_reading = $slct->can_read();
+ foreach my $fh (@ready_for_reading) {
+ if ($fh != $server) {
+ logmsg "select: fh ready for reading but not server",
+ "don't know what to do...";
+ }
+
+ # new connection:
+ my $client = $server->accept() or do {
+ # try again if accept() returned because
+ # a signal was received
+ if ($!{EINTR}) {
+ logmsg "accept: got signal EINTR ...";
+ next;
+ }
+ die "accept: $!";
+ };
+ my $client_ip = inet_ntoa($client->peeraddr);
+ logmsg "connection from $client_ip at port " . $client->peerport;
+ create_child($client, \&child_action, $client_ip);
+ close $client;
+ }
+}
+
+# never reached...
+logmsg "Server done - ooops!\n";
+
+release_pid_lock();
+
+exit(0);
+
+__END__
+
+=head1 webserver.pl
+
+A simple WEB server written in perl.
+
+=head1 SYNOPSIS
+
+webserver.pl [options]
+
+=head1 OPTIONS
+
+=over 8
+
+=item B<--help>
+
+Print a brief help message and exit.
+
+=item B<--port>
+
+Specify the port number for the server to listen on.
+
+=item B<--root|--document-root>
+
+Specify the document root directory from which to serve web content.
+
+=item B<--log-dir>
+
+Specify the directory where the log files should be stored.
+
+=item B<--pid-file>
+
+Specify the location of the pid lock file.
+
+=back
+
+=head1 DESCRIPTION
+
+This is a very simple web server. It currently does not deliver the specific web
+page requested, but constructs the same kind of answer for each request, citing
+a fortune if fortune is available, and printing the originating request.
+
+=cut