# HTTP/Server/Simple.pm6
role HTTP::Server::Simple {
has $.port;
has $.host is rw;
has IO::Socket::INET $!listener;
has $.connection; # returned by accept()
has Str $!request;
has Str @!headers;
class Output-Interceptor {
has $.socket is rw;
multi method print(*@a) {
# $*ERR.say: "Intercepting print " ~ @a;
$.socket.send(@a);
}
multi method say(*@a) {
# $*ERR.say: "Intercepting say " ~ @a;
$.socket.send(@a ~ "\x0D\x0A");
}
}
method new ( $port=8080 ) {
my %methods = self.^methods Z 1..*; # convert list to hash pairs
self.bless( self.CREATE(), # self might also be a subclass
port => $port,
host => self.lookup_localhost,
);
}
method lookup_localhost () {
# should return this computer's "127.0.0.1" or somesuch
return 'localhost';
}
method run ( *@arguments ) { self.net_server(); }
method net_server () {
# an overrideable, minimal implementation called by run()
self.print_banner;
self.setup_listener;
self.after_setup_listener;
while $!connection = $!listener.accept {
self.accept_hook;
# receive only one request per session - no keepalive yet
my $received = $!connection.recv();
@!headers = split("\x0D\x0A", $received);
$!request = @!headers.shift;
my ($method, $uri, $protocol) = self.parse_request;
unless self.valid_http_method($method) { self.bad_request; }
my ( $path, $query-string ) = $uri.split('?',2);
$query-string //= ''; # // confuses P5 syntax highlighters
self.setup(
:method($method), # rakudobug RT
protocol => $protocol || 'HTTP/0.9',
query_string => $query-string,
request_uri => $uri,
path => $path,
localname => $!host,
localport => $!port,
peername => 'NYI',
peeraddr => 'NYI',
);
self.headers( self.parse_headers() );
self.post_setup_hook;
my $res = self.handler;
$!connection.close();
}
}
# Methods that a sub-class may want to override
method handler () {
# Called from net_server()
# $*ERR.say: "in handler";
my $stash-stdout = $*OUT;
my Output-Interceptor $myIO .= new( socket => $!connection );
$*OUT = $myIO;
self.handle_request();
$*OUT = $stash-stdout;
# $*ERR.say: "end handler";
}
method handle_request () {
# Called from handler()
# generate a default reply to show that it works
# $*ERR.say: "in handle_request";
print "HTTP/1.0 200 OK\x0D\x0A\x0D\x0A";
say "\n
";
say self.WHAT, " at {$!host}:{$!port}
";
say "{hhmm} {$!request}
";
say "\n";
# $*ERR.say: "end handle_request";
}
method setup ( :$method, :$protocol, :$request_uri, :$path,
:$query_string, :$localport, :$peername, :$peeraddr, :$localname ) {
}
method headers (@headers) {
for @headers -> $key, $value {
self.header( $key, $value );
}
}
method header ( $key, $value ) {
# $*ERR.say: "header $key => $value";
}
method accept_hook () {
# $*ERR.say: "accepted";
}
method post_setup_hook {
# my $seconds = floor(time()) % 86400; # 24*60*60
# my $hhmm = floor($seconds/3600).fmt('%02d')
# ~ floor(($seconds/60) % 60).fmt(':%02d');
$*ERR.say: "{hhmm} {$!request}";
}
method print_banner {
say "{hhmm} ", self.WHAT, " started at {$!host}:{$!port}";
}
sub hhmm {
my $seconds = floor(time) % 86400; # 24*60*60
my $hhmm = floor($seconds/3600).fmt('%02d')
~ floor(($seconds/60) % 60).fmt(':%02d');
$hhmm;
}
# Methods below are probably not useful to override
method parse_request () {
$!request.split( /\s/ );
}
method parse_headers () {
return gather for @!headers -> $line {
my ( $key, $value ) = $line.split( ': ' );
if defined($key) and defined($value) {
# $*ERR.say: "parse_headers $key => $value";
take $key, $value;
}
}
}
method setup_listener () {
# say "setup listener on port $!port";
# PF_INET=2, SOCK_STREAM=1, TCP=6
$!host //= '0.0.0.0'; # // confuses P5 syntax highlighters
$!listener = IO::Socket::INET.new(
:localhost($!host),
:localport($!port),
:listen,
);
}
method valid_http_method (Str $candidate_method) {
$candidate_method eq any( );
}
# Not Yet Implemented
method background ( *@arguments ) { <...> }
method restart () { <...> }
method stdio_handle () { <...> }
method stdin_handle () { <...> }
method stdout_handle () { <...> }
method after_setup_listener () { }
method bad_request () { <...> }
}
=begin pod
=head1 NAME
HTTP::Server::Simple - small embedded HTTP server
=head1 SYNOPSIS
use HTTP::Server::Simple;
HTTP::Server::Simple $server.new;
$server.run; # says "alive" on port 8080
Normally one would use a class that wraps this server with a familiar
web API, such as CGI, FastCGI or PSGI. HTTP::Server::Simple is a role
that classes can import with a 'does'. For example:
class HTTP::Server::Simple::Example does HTTP::Server::Simple;
=head1 DESCRIPTION
This is a Perl 6 re-implementation of the Perl 5 HTTP::Server::Simple.
Web applications generally do use this directly, but use a subclass such
as HTTP::Server::Simple::CGI, or similar ones based on FastCGI or PSGI.
=head1 ATTRIBUTES
=head2 host
The server's IP address (rw)
=head2 port
The port server is to run on (ro)
=head1 METHODS
=head2 new
Construct and return a server object. The optional argument is a port
number (default 8080). The server begins to listen and accept incoming
connections on the port when the run method is executed.
=head2 run
Start the server as foreground process in an infinite loop. The server
is either a Net::Server, a subclass of that, or (default) a minimal
emulation of it.
=head2 background
Fork and run the child process as a server daemon. Not Yet Implemented.
=head2 handler
Called from C. Sends a default response to the client.
=head2 setup
Called with named parameters: method, protocol, request_uri, path,
query_string, port, peername, peeraddr, localname.
As in the Perl 5 version, the default setup handler takes each
tries to call
=head2 headers
=head2 print_banner
Announces on the console that the server is running.
=head2 process_request
Called from C<_default_run>. Calls C, C,
C, C, C, C,
C.
=head2 parse_request
=head2 parse_headers
=head2 setup_listener
Prepares the server TCP socket up to the bind and listen operations.
Called from C.
=head2 after_setup_listener
Called by C as an event hook, the default handler does nothing.
=head1 TODO
Refactor and re-structure if necessary to be more compatible with the
Perl 5 version. This requires testing with webserver applications that
have been ported.
=head1 SEE ALSO
Most of the code was inspired by the following Perl 5 modules:
L L L
=end pod