use strict; use warnings;
use IO::Socket;
my $DEBUG = 1;
print "STARTING($0)\n" if $DEBUG;
# first we define our story:
our %I;
sub get {$I{'GET'}{$_[0]} = $_[1]}
my $DEFAULTS = '*';
get $DEFAULTS => {
'title' => "The Foo of the Bar Baz",
'label' => 'story',
'paragraph' => 'story',
'link' => '/',
'content-page' => qq(
),
# Before and after parsing
'(' => sub {my($page,$HAVE,$msg)=@_; return $msg },
')' => sub {my($page,$HAVE,$msg)=@_;
print "msg=$msg\nDmsg=$$msg\n" if $DEBUG;
$$msg=~s{
};
},
};
get '/' => {
'title' => 'The story of the Thirsty Dragon',
'paragraph' => "In a little cave, there once lived a dragon. It was a scary dragon. Somewhat temperamental. And around it's abodes, there were small piles of skeletons. It was afternoon. And the dragon was thirsty.",
'&' => sub {
my($page,$HAVE,$msg)=@_;
$I{'HAVE'}{'STARTED'}++;
$msg;
},
'button' => [
{'label' => 'Go out and drink',
'link' => '/outside'},
{'label' => 'Stay inside',
'link' => '/sleep'},
],
};
get '/sleep' => {
'title' => 'sleepy...',
'paragraph' => "And night fell, and the dragon got sleepy. He rolled up and fell asleep. Kind of a boring story, don't you think? How about trying again?",
'&' => sub {
my($page,$HAVE,$msg)=@_;
if($I{'HAVE'}{'STARTED'} > 1){
$I{$HAVE}{$page}->{'button'}[0]->{'label'} = "Not AGAIN! ($I{HAVE}{STARTED})";
}
$msg;
},
'button' => [
{'label' => 'Try again',
'link' => '/'},
],
};
get '/outside' =>{
'title' => 'Where to?',
'paragraph' => "Yes, well... I don't mind going to the pub, but I could also go to the lake.",
'button' => [
{'label' => 'Go to the Pub',
'link' => '/pub'},
{'label' => 'Go to the Lake',
'link' => '/lake'},
],
};
get '/pub' =>{
'title' => 'Dragons not welcome',
'paragraph' => "I will have to be careful though. Those humans do not like dragons. And the pub is full of humans. Now, how do I disguise myself?",
'button' => [
{'label' => 'Put on a wig and walk like a chicken',
'link' => '/pubfrontchicken'},
{'label' => 'put on a hat and walk on back legs only',
'link' => '/pubfrontlegs'},
],
};
get '/pubfrontlegs' =>{
'title' => 'A gentleman with a hat',
'paragraph' => "The dragon produces a gentleman's hat and puts it on his head. he stands on his back legs, and starts walking towards the village. On the way, he picks up a stick, which he uses as a cane. Suddenly he bumps into a drunkard.
- Hic... pardon kind sir - And the drunkard walks off, a bottle in his hand. You are now in a back alley, next to the pub. You can hear laughter and loud noises. Want to try walking in from the main entrance, or use the back door?",
'button' => [
{'label' => 'Make a pompous entrance through the main gate...door...whatever',
'link' => '/grandentrance'},
{'label' => 'sneak in through the side window',
'link' => '/sneak'},
],
};
# http://grammarist.com/spelling/toward-towards/
get '/pubfrontchicken' =>{
'title' => 'Walk like an Egy^H^H^Hchicken',
'paragraph' => "This wig is too small, it barely covers the top of the head of the Dragon. But that was all the props he had. He took a deep breath, flapped his arms and walked towards the village. Near the village the dragon also started bobbing his head, like chickens do. A drunkard gazed with open mouth and dropped his bottle. But nobody else noticed. You are now in a back alley, next to the pub. You can hear laughter and loud noises. Want to try walking in from the main entrance, or use the back-door?",
'button' => [
{'label' => 'Make a pompous entrance through the main gate...door...whatever',
'link' => '/grandentrance'},
{'label' => 'sneak in through the side window',
'link' => '/sneak'},
],
};
get '/grandentrance' =>{
'title' => 'The grand entrance',
'paragraph' => "The dragon takes a deep breath, and walks into the pub bobbing his head and flapping his arms. The bartender looks up and points to a sign. The sign says 'No chickens allowed'. Patrons are standing up towards you and shooing you out. Now you are standing in the back alley. There is this other option: Use the window!",
'button' => [
{'label' => 'sneak in through the side window',
'link' => '/sneak'},
],
};
get '/sneak' =>{
'title' => 'sneaking in',
'paragraph' => "This window is too small for dragons. You try to twist and shove. The window cracks and bends... you are stuck",
'button' => [
{'label' => 'look around',
'link' => '/lookaround'},
],
};
get '/lake' =>{
'title' => 'Where to?',
'paragraph' => "Ah well... water is refreshing, although a bit boring. The dragon drank, then returned to the cave to sleep",
'button' => [
{'label' => 'Go back to the cave',
'link' => '/sleep'},
],
};
get '/404' => sub {
'Sorry. This page has not been created yet. (press back on your browser)'
};
# Some basic types we can serve. You can add your own of course.
my %TYPES = (
'PL' => "text/html",
'HTM' => "text/html",
'HTML' => "text/html",
'JS' => "text/javascript",
'CSS' => "text/css",
'ICO' => "image/vnd.microsoft.icon",
"GIF" => "image/gif",
"JPEG" => "image/jpeg",
"JPG" => "image/jpeg",
"BMP" => "image/bmp",
"PNG" => "image/png",
"SVG" => "image/svg+xml",
"SVGZ" => "image/svg+xml",
'TXT' => "text/plain",
);
my %CFG = (
'MYPORT' => 1337,
'MAXCONNECT' => 10
);
# read the html template at the end of this file (after __DATA__) in one gulp
{
local $/; $CFG{'TEMPLATE'} = ;
close DATA;
};
my %ESC_LIST = ('&'=>'&', '>'=>'>', '<'=>'<');
# open a listening port on your computer. Note: ports under 1000 require Administrator/superuser rights.
my $server = IO::Socket::INET->new(
LocalPort => $CFG{'MYPORT'},
Type => SOCK_STREAM,
Reuse => 1,
Listen => $CFG{'MAXCONNECT'},
Timeout => 2
) or die "Can not open port $CFG{'MYPORT'}: $!\n";
# Automatically start a webbrowser to http://localhost:$CFG{'MYPORT'}
if( ($0 =~/.exe$/i) && ($^O eq 'MSWin32') ){
system("start","http://localhost:$CFG{'MYPORT'}");
}
# This is an infinite loop. (press control C on the commandline to stop the webserver)
while(1){
my ($client,$client_adress,%request,$type);
while ( ($client,$client_adress) = $server->accept() ) {
my $DATA = '';
my ($client_port, $client_iaddr) = sockaddr_in($client_adress);
my $client_dot_ip = inet_ntoa($client_iaddr);
my $client_name = gethostbyaddr($client_iaddr, AF_INET);
print "\nAccepting $client_dot_ip:$client_port [$client_name] (".(scalar localtime).")\n" if $DEBUG;
eval {
local $SIG{ALRM} = sub { die 'TIMEOUT'; };
alarm 1; # does not work, default is 5 seconds... hardcoded somewhere
$client->recv($_,2048);
alarm 0;
};
alarm 0;
print "$$ Read ".length($_)." bytes:$_\n" if $DEBUG;
# Normally, here we would fork and let the child process (new process)
# do all the work, while the program is free to keep listening to new connections
# As webbrowsers make several connections at once, this is almost a requirement
# but makes the program harder to understand.
if($_){
m{^\s*(\w+)\s+(.+)\s+HTTP/(\d.\d)};
$request{METHOD} = uc $1; # GET
$request{URL} = $2; # /
$request{KEEPALIVE} = 1 if m{Keep-Alive}i;
$_ = $request{URL};
$_ = '/404' if m/\.\./; # not so waterproof way of not accessing any file on disk
$_ = '/' if m/^index.html$/i;
if(m/\./){
$DATA = slurp('./'.$_) || '';
s/.*\.//; # leave extension only
$type = uc($_); # the type of file is the extension name
}else{
$DATA = jap($request{METHOD},$_);
$type = "HTML";
}
}
my $HEADER = http_header("OK",length($DATA)||0,$TYPES{$type}, $request{KEEPALIVE});
print "$HEADER\n" if $DEBUG;
print $client $HEADER . $DATA;
if($request{KEEPALIVE}){
print "KEEPALIVE\n" if $DEBUG;
}else{
close($client);
print "CONNECTION CLOSED\n" if $DEBUG;
}
}
}
# setup an http header string
sub http_header {
my ($returnvalue, $size, $ctype, $keepalive) = @_;
my $HEADER = "HTTP/1.0 " . $returnvalue . Socket::CRLF
. ($ctype ? "Content-Type: ". $ctype ."; charset=utf-8". Socket::CRLF : '')
. (defined($size)? "Content-Length: ". $size . Socket::CRLF : '')
. "Connection: ". ($keepalive?'Keep-Alive':'close') . Socket::CRLF
. Socket::CRLF;
return $HEADER;
}
sub slurp{
return "" unless -r $_[0];
open(IN,'<:raw',$_[0]) or return ""; # Windows requires binmode
local $/;
my $X = ;
close IN;
$X
}
sub jap{
my($HAVE,$page) = @_;
my $msg;
$page = '/404' unless $I{$HAVE}{$page};
my $me = $I{$HAVE}{$page};
my $default = $I{$HAVE}{$DEFAULTS};
print "page=$page;me=$me;\n" if $DEBUG;
# Run the default before
if(ref($default) eq 'HASH'){
&{$_}($page,$HAVE,\$msg) if $_=$default->{'('};
}
# First load template, then use sub code
if(ref($me) eq 'HASH'){
&{$_}($page,$HAVE,\$msg) if $_=$me->{'('};
if(($_=$me->{_}) && -f $_){
$msg = slurp $_
}elsif($_=$me->{'#'}){
$msg = $_;
}
if($_=$me->{'&'}){
$msg = &{$_}($page,$HAVE,$msg);
}
&{$_}($page,$HAVE,\$msg) if $_=$me->{')'};
}
# If there is no msg, use the appended template
$msg = $CFG{'TEMPLATE'} unless $msg;
# use the function as a post processing or independant $msg generator
if(ref($_=$me) eq 'CODE'){
$msg = &{$_}($page,$HAVE,$msg);
}
# Moustache
$msg = TinyMoustache($msg, $me, $default);
# Run the default after
if(ref($default) eq 'HASH'){
&{$_}($page,$HAVE,\$msg) if $_=$default->{')'};
}
return $msg;
}
# $escaped_string = esc("string>withhtml")
sub esc {
$_ = $_[0];
s/([&<>])/$ESC_LIST{$1}/gex;
$_
}
# template language; a subset of Moustache
# Escaped: {{var}}
# Non-Escaped: {{{var}}}
# Loops: {{#loop}} here add {{{var}}} or {{var}} then {{/loop}}
sub TinyMoustache {
my($TXT, $ME, $DEFAULTS) = @_;
my $i = 1;
do{
$TXT =~ s/\{\{([\#\^])\s*([\w\-]+)\s*\}\}([\s\S]*?)\{\{\/\s*\2\s*\}\}/loop($1,$2,$3,($ME->{$2}||$DEFAULTS->{$2}||""))/gem;
$TXT =~ s/\{\{\{\s*([\w\-]+)\s*\}\}\}/$ME->{$1}||$DEFAULTS->{$1}||""/gme;
$TXT =~ s/\{\{\s*([\w\-]+)\s*\}\}/esc($ME->{$1}||$DEFAULTS->{$1}||"")/gme unless $TXT=~/\{\{#/;
}while($TXT=~/\{\{/ && $i--);
$TXT;
}
# handle moustache {{^tag}} or {{#tag}} ... {{/tag}}
sub loop {
my($n,$a,$b,$t,$r)=@_;
print "loop($n,$a,$b,$t,$r)\n" if $DEBUG;
return ($t ? '' : $b) if $n eq '^';
$t = &$t($a,$b) if ref($t) eq 'CODE';
if(ref($t) eq 'ARRAY'){
print " loop ARRAY @$t\n" if $DEBUG;
$DEBUG && print " >> $_\n" for (@$t);
for my $x (@$t){
$r .= TinyMoustache($b,$x);
}
#$r .= TinyMoustache($b,$_) for @$t;
}elsif(ref($t) eq 'SCALAR'){
$r = $t;
}elsif(ref($t) eq 'SUB'){
$r = &$t($a,$b,$t);
}
$r;
}
__DATA__
{{title}}