Today we’ll write a simple Dancer clone in Perl 6. Simple also means Very Minimal — it will only recognize basic GET requests. Let’s look at how the simplest Dancer app possible looks like:
get '/' => sub { "Hello World!" }; dance;
So we need something to add routes to our app, and something to run it. Let’s take care of adding routes first. We’ll create an array to store all those, and thus get()
will just add them to it.
my @routes; sub get(Pair $x) is export { @routes.push: $x; }
In case you’re not familiar with the Pair
thing, in Perl 6 a fat comma (=>
) creates an actual data structure containing a key and a value. In this case, the key is just a string ‘/’, and the value is a subroutine.
Having @routes
being a simple array of keys and values we can now write a simple dispatcher:
sub dispatch($env) { for @routes -> $r { if $env<REQUEST_URI> ~~ $r.key { return $r.value.(); } } return "404"; }
dispatch()
takes a hash representing our environment, which contains the REQUEST_URI
string, basing on which we’ll try to find an appropriate candidate to dispatch to.
The above example is also cheating a bit: it just returns a ‘404’ string instead of creating a proper HTTP response. Making it respond properly is left as an exercise for the reader (not the last one in this short article, I assure you :)).
Since we got that far already, writing our own dance()
is a piece of cake. We’re going to call it baile()
though. Why do we write all this in Spanish? If you can guess on which classes I was bored and wrote this thing on a piece of paper, then on the next YAPC I’ll show you the fastest possible way to tie a shoe. No kidding! But let’s finish this thing first.
Luckily we don’t need to write our own web server now. We have HTTP::Server::Simple::PSGI
in Perl 6, which will do most of the hard work for us. The only thing we have to do is to create a PSGI app. In case you’ve never heard of it, a PSGI app is a mere subroutine, taking the environment as an argument, and returning an array of three things: an HTTP response code, an array of HTTP headers and a response body. Once we have our PSGI app ready, we just feed HTTP::Server::Simple::PSGI
with it, and we’re good to go.
sub baile is export { my $app = sub($env) { my $res = dispatch($env); return ['200', [ 'Content-Type' => 'text/plain' ], $res]; } given HTTP::Server::Simple.PSGI.new { .host = 'localhost'; .app($app); .run; } }
Yep, we’re cheating again and returning 200
no matter what. Remember the part about “an exercise for the reader?” You can think about it while celebrating a working Dancer clone.
But wait, there’s more!
Let’s look at our dispatch()
once again:
sub dispatch($env) { for @routes -> $r { if $env<REQUEST_URI> ~~ $r.key { return $r.value.(); } } return "404"; }
You probably noticed that we’ve used ~~
— a smartmatching operator. Thanks to that, we can match REQUEST_URI
against strings, but not only. Junctions
will work fine too:
get any('/h', '/help', '/halp') => sub { "A helpful help message" }
And regexes:
get /greet\/(.+)/ => sub ($x) { "Welcome $x" }
The last one will need a bit of tweaking in dispatch()
. Yes, ~~
does the regex matching for us, but we have to take care of passing the match results to the callback function. Let’s modify the if
body then:
sub dispatch($env) { for @routes -> $r { if $env<REQUEST_URI> ~~ $r.key { if $/ { return $r.value.(|$/.list); } else { return $r.value.(); } } } return "404"; }
The if $/
part checks whether the match resulted in setting the Match
object in the $/
variable. If it did, we flatten the Match
to a list, and pass it to the callback function. We need a |
prefix, so it becomes expanded to a parameter list instead of being passed as a mere array. From now on, the above example with greet
will Just Work. Yay!
The Bailador code presented here is available in the Github repository. If you feel challenged by the “exercises for the reader”, or just want to make it a bit more proper Dancer port, you’re welcome to hack on it a bit and contribute. I hope I showed you how simple it is to write a simple, yet useful thing, and going with those simple steps we can hopefully get to something close to a full-blown Dancer port. Happy hacking, and have an appropriate amount of fun!