Grammar-based dispatching – a step further

You may remember the idea of dispatching HTTP requests with Perl 6 grammars. Turned out to be funny and interesting, but wait, what’s the big deal? What’s good about a webapp that runs in the CLI, and dispatches only the strings given in the source code? Yeah, it’s funny, but is it practical? Not quite. So here we go with something practical this time.

Someone asked me about a HTTP requests parser. Looks like there alredy was one, built-in in HTTP::Server::Simple by mberends++. After adapting it to Rakudo’s needs (specs change) and to my needs (REQUEST_URI wasn’t working in it), and commiting some hacks of mine, i ended up with a PSGI server suitable for serving webapps. Webapps like our last week’s dispatcher. So here we go:

use HTTP::Server::Simple::PSGI;

grammar Mysite {
    token TOP { <main> | <about> | <contact> | <api> | <e404> }
    token main    { ^ '/' $ }
    token about   { ^ '/about' $ }
    token contact { ^ '/about/contact' $ }
    token api     { ^ '/api/' $<type>=[ \S ]* $ }
    token e404    { }
}

class Mysite::Actions {
    method TOP($/)     { make $/.values[0].ast }
    method main($/)    { make "Hello from the main page!" }
    method about($/)   { make "About me" }
    method contact($/) { make "Reach me at 555 01 23" }
    method api($/)     {
        given $<type>.Str {
            when 'json' { make '{"foo":"bar"}' }
            when 'xml'  { make '<foo>bar</foo>' }
            default     { make 'Wrong serialization method passed' }
        }
    }
    method e404($/)    { make "404: Not found" }
}

my $app = sub ($env) {
    my $res = Mysite.parse(
        $env<REQUEST_URI>,
        :actions(Mysite::Actions.new)
    ).ast;
    return ['200', [ 'Content-Type' => 'text/plain' ], $res];
}

given HTTP::Server::Simple::PSGI.new {
    .host = 'localhost';
    .app($app);
    .run;
}

The code is pretty straightforward (methinks), so let’s just skip to the interesting part. Which is: it actually works. You can write a working PSGI app now, dispatching requests with a Perl 6 grammars. Cute, eh?


Perl 6 Grammars – not only for parsing

Yesterday on #perl6 edenc, (a Catalyst dev), together with ruoso started discussing an idea of dispatching http requests with Perl 6 grammars. My nose smelled some awesomeness being baked around, so I decided to work something out too.

And that’s it. A grammar with paths (being regexes), methods-controllers building responses, in this case only plaintext ones, but that could have been objects as well.

grammar Mysite {
    token TOP {
        <main>    |
        <about>   |
        <contact> |
        <cookie>  |
        <api>     |
        <e404>
    }
    token main    { ^ '/' $ }
    token about   { ^ '/about' $ }
    token contact { ^ '/about/contact' $ }
    token cookie  { ^ '/cookie' $ }
    token api     { ^ '/api/' $<type>=[ \S ]+ $ }
    token e404    { }
}

class Mysite::Actions {
    method TOP($/) {
        make $/.values[0].ast;
    }
    method main($/)    { make "Hello from the main page!" }
    method about($/)   { make "About me" }
    method contact($/) { make "Reach me at 555 01 23" }
    method cookie($/)  { make $*REQUEST.cookie }
    method api($/)     {
        given $<type>.Str {
            when 'json' { make '{"foo":"bar"}' }
            when 'xml'  { make '<foo>bar</foo>' }
            default     { make 'Wrong serialization method passed' }
        }
    }
    method e404($/)    { make "404: Not found" }
}

class Request is Cool {
    has $.path;
    has $.cookie;
    method Str {
        $.path
    }
}

my ($*REQUEST, $req, $res);
$*REQUEST = $req = Request.new(path => '/cookie', cookie => 'monster');
$res = Mysite.parse($req, :actions(Mysite::Actions.new));
say $res.ast; # monster

$*REQUEST = $req = Request.new(path => '/nope');
$res = Mysite.parse($req, :actions(Mysite::Actions.new));
say $res.ast; # 404: Not found

Few interesting things:

  • method TOP is just picking out the right result from the matched token
  • token e404 is empty so it matches everything that wasn’t alredy matched
  • Request is a class acting as a String, so it can store things like cookies, POST etc. aside from the query string
  • $*REQUEST is a contextual variable (no worries, it’s not global although it may look like one) so we can extract the Request fields from inside the action methods

And there it is, shiny and pretty. Look out for some future posts, once I get more $time I’ll probably code some reusable components and present them here as well.