#!/usr/bin/perl -w # testserver.pl - a bare-bones test server for Net::Gopher. use strict; use Cwd; use Errno 'EINTR'; use Getopt::Std; use IO::Socket qw(SOCK_STREAM SOMAXCONN); use IO::Select; use constant BUFFER_SIZE => 4096; use constant TIMEOUT => 60; use vars qw($CRLF); $CRLF = "\015\012"; BEGIN { # this hack allows us to "use bytes" or fake it for older (pre-5.6.1) # versions of Perl (thanks to Liz from PerlMonks): eval { require bytes }; if ($@) { # couldn't find it, but pretend we did anyway: $INC{'bytes.pm'} = 1; # 5.005_03 doesn't inherit UNIVERSAL::unimport: eval "sub bytes::unimport { return 1 }"; } } # the script arguments: my %opts; # a string containing the error message for the last error to occur: my $error; # the blessed file handle of the IO::Socket server: my $server; # -e tells us to echo out what we're sent: getopts('e', \%opts); # now, create a TCP socket and listen() on what ever port the OS assigns to us: $server = new IO::Socket::INET ( Type => SOCK_STREAM, Proto => 'tcp', Timeout => TIMEOUT, Listen => SOMAXCONN, Reuse => 1, ) or die "(Test server) Couldn't make TCP server: $@"; # this process is meant to be pipe opened for reading, so we write something # meaningful back to the parent process for them to read... printf("# Listening on port %d...\n", $server->sockport); # ...and then redirect standard output to tell the kernel we're done writing. # This makes sure that we get to the while loop below, and once we start # blocking, waiting for incoming connections, control will be returned to the # parent so it can start making requests of us: if ($^O !~ /MSWin/i) { open(STDOUT, '>/dev/null') || die "Can't redirect STDOUT: $!"; } else { close STDOUT; } while (my $client = $server->accept) { # we do non-blocking IO on the socket: $client->blocking(0); my $select = new IO::Select ($client); my $request = ''; my $buffer; while (read_from_socket($client, $select, \$buffer)) { $request .= $buffer; } die $error if ($error); my ($selector) = split(/(?:\t|$CRLF)/, $request); if ($opts{'e'}) { # echo: send back the request we were sent: write_to_socket($client, $select, $request); die $error if ($error); } else { $selector =~ s{\\}{/}g; $selector = "/$selector" unless ($selector =~ m|^/|); my $path = (getcwd() =~ m|/t$|) ? './items' : './t/items'; open(FILE, "< $path$selector") || do { error("Couldn't return file (.$path$selector): $!"); die $error; }; binmode FILE; my $item = join('', ); close FILE; write_to_socket($client, $select, $item); die $error if ($error); } close $client; } sub read_from_socket { my ($socket, $select, $buffer) = @_; # make sure we can read from the socket; that there's stuff waiting to # be read: return error('timeout while reading.') unless ($select->can_read(TIMEOUT)); while (1) { # read part of the request from the socket into the buffer: my $num_bytes_read = sysread($socket, $$buffer, BUFFER_SIZE); # make sure something was received: unless (defined $num_bytes_read) { redo if ($! == EINTR); return error("read error: $!."); } return $num_bytes_read; } } sub write_to_socket { my ($socket, $select, $data) = @_; # make sure we can write to the socket; that the socket's ready for # writing: return error('timeout while writing.') unless ($select->can_write(TIMEOUT)); # now send the response to the client: while (1) { my $num_bytes_written = syswrite($socket, $data, size_in_bytes($data)); # make sure *something* was sent: unless (defined $num_bytes_written) { redo if ($! == EINTR); return error("write error: $!"); } # make sure the entire response was sent: return error("short write.") unless (size_in_bytes($data) == $num_bytes_written); return $num_bytes_written; } } sub error { if (@_) { $error = '(Test Server) ' . shift; return; } else { return $error; } } sub size_in_bytes ($) { use bytes; return length shift; }