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(

{{title}}

{{paragraph}}
), # 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{
([\s\S]*?)
}{PUTITBACKLATER}; $_ = $1; if($_){ s{^\s*(\w)}{   $1}; s{\b(drunkard)\b}{$1}gi; s{\b(pub)\b}{$1}gi; s{\b(cave)\b}{$1}gi; s{\b(dragon)\b}{$1}gi; s{\n\n}{

}gi; } $$msg =~ s{PUTITBACKLATER}{
$_
}; }, }; 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}}
{{{content-page}}}
Story by Foo Bar