#!/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('', <FILE>);
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;
}
syntax highlighted by Code2HTML, v. 0.9.1