When I feel there are too many steps to get to what I want to acheive — a feeling that occurs often enough within the Web.pm effort — I like to get down to basics. What's the simplest thing that could possibly work?
Usually, there's a frustrating buildup until I hit that point, and click over to simplest-thing mode. Usually, once I do, I thank myself for it, and marvel at the time I spent trying to cautiously nose around the problem in other ways, as if looking for some weak point.
A live coding session from first principles
Like what I'm doing now, for example. (Today I'm writing the blog post and wrapping up this week of development at the same time.) I'm writing a Handler
for mberends++' HTTP::Daemon
. This is a step towards making Web.pm webserver-agnostic by putting all the webserver-specific code in Handler
classes.
So, in order to understand what makes the HTTP::Daemon
module tick, I nosed around aimlessly, reading the module code over and over again. Then — click — I decided to do what make run
does, since it obviously sets up a functioning local web server.
Turns out make run
simply calls a bin/httpd
script. I scavenged that script, and ended up with a "server one-liner" for Perl 6:
$ perl6 -e 'use HTTP::Daemon; my HTTP::Daemon $d .= new; while my $c = $d.accept and my HTTP::Request $r = $c.get_request { say $r.method; $c.send_response("OH HAI") }'
After starting this script, I can hit http://127.0.0.1:8888/
in my web browser, and the browser will say "OH HAI", and the one-liner will say "GET". It doesn't get much simpler than that.
Next step: something like this (perhaps familiar for those who saw my talk at YAPC) should be made to work:
Web::Handler::HTTPDaemon.run( sub ($env) { [200, { 'Content-Type' => 'text/plain' }, ['Hello World!']] } )
Turns out this code (mostly a modularization of the one-liner above), did the trick:
use v6;
use HTTP::Daemon;
class Web::Handler::HTTPDaemon {
method run(Callable &app, :$port = 8888) {
my HTTP::Daemon $d .= new(LocalPort => $port);
while my $c = $d.accept and my HTTP::Request $r = $c.get_request {
my %env = {};
$c.send_response(&app(%env)[2].Str)
}
}
}
(Update 2009-08-19: flip++ informs me on IRC that the &app(%env)[2].Str
would insert a space between each element. It should really be [~] &app(%env)[2].list
— thanks!)
And it works! The browser says "Hello world!" when I hit http://localhost:8888/
.
Now, a toy example such as this is nice and all, but the prototypical toy example — the Nibbler — should be made to work, or it somehow doesn't count.
So, I rewrite bin/run-nibbler
to this:
#!/usr/local/bin/perl6
use Web::Nibbler;
use Web::Handler::HTTPDaemon;
my $port = 8888;
say "Try out the Nibbler on http://127.0.0.1:$port/";
Web::Handler::HTTPDaemon.run( Web::Nibbler.new );
(That's much less code than before, and webserver-independent at that. Yay orthogonality!)
I'd like to be able to say that this worked on the first try, but actually I had to tweak two things in the Web::Nibbler
module itself for it to be compatible with this new scheme. First, due to some excellent on-demand hacking by jnthn++ during YAPC::EU, we can now start renaming our call
methods into postcircumfix:<( )>
— and in this case, we kinda have to. Second, run
expects a Callable
, so we'll have to declare that Web::Nibbler does Callable
.
After that, it works! Except nothing happens when I click the "flip" link on the Nibbler, because I'm a cheating bastard who doesn't send it a proper %env
argument. Time to fix that.
...and it turns out that this little detail was easy too. I just salvage two lines from the old bin/run-nibbler
, and it works:
my $qs = $r.url.path ~~ / '?' (.*) $/ ?? $0 !! '';
my %env = { "QUERY_STRING" => $qs };
Only this time, these two lines end up in Web::Handler::HTTPDaemon
, where they should be.
Yay! That sure felt good.
One thing I want to start habitually doing in the Web.pm posts is having a "Try it out yourself" section, where people can get interactive if they want. I've come to realize that this is something people want out of blog posts, and I certainly wouldn't mind having more hands and eyes on Web.pm. So here goes:
Try it out yourself!
See if you can get the latest Web.pm to run this piece of code:
Web::Handler::HTTPDaemon.run( sub ($env) { [200, { 'Content-Type' => 'text/plain' }, ['Hello World!']] } )
Then expand it into something cool, and go brag on #perl6 about it.
(Update 2009-08-19: Recall that Web.pm nowadays has HTTP::Daemon
as an external dependency, so make sure you have it and that it's in your PERL6LIB
path, along with Web.pm. PerlJam++ for pointing out that this wasn't obvious.)
Other interesting goings-on
HTTP::Daemon
stack crashes when the browser is trying to load the pre-packaged favicon. Why? Essentially because &slurp
doesn't have a :bin
parameter yet, and it won't realistically have that until we have a more mature Buf
type. I got some work done on Buf
during the Rakudo post-YAPC::EU hackathon, but more such work is needed to get there.I wish to thank The Perl Foundation for sponsoring the Web.pm effort.