mod_perl6 Registry Scripts

One of the more common uses of mod_perl is as a CGI accelerator. The dirty work is done by ModPerl::Registry (or Apache::Registry in mod_perl 1.0), which emulates a CGI environment, loading and executing CGI scripts while caching them for future requests. You can also use Apache::RequestRec and friends in your scripts to access more data about the request, set headers, and do many other interesting things.

Up to this point, mod_perl6 has been lacking support for registry-style scripts, and all my examples have been handlers for various Apache phases. My goal was to remedy that in time for YAPC, and this week I succeeded. Rakudo needed the following things to make it work:

  • basic eval support
  • nested subroutines
  • interpolated namespaces
  • the ability to capture stdout (via ties, redirection, or something else)

Eval support was easy -- it magically appeared in r26963 in Parrot. It doesn't yet support passing the lexical scope of the caller, but we don't need that. Nested subroutines also worked out of the box.

Interpolated namespaces required some work. They give us the ability to call a subroutine in a namespace defined at runtime, such as ::($mynamespace)::mysub(). Fortunately I did most of this work to support pure-perl mod_perl6, and had to tweak it for recent changes in Rakudo's calling conventions. Since it only supports subroutine calls right now, I am not committing this change to Rakudo just yet, as I'd like to investigate a more generic implementation first.

Capturing stdout required some deep thought, and that can be good or bad, depending on how much coffee I've had. Fortunately the coffee was plentiful the day I was working on this. mod_perl 2.x captures CGI output using filehandle ties and other magic. We don't have that yet in Parrot. I proposed writing an IO layer to do what I needed, but at some point both chromatic and Allison noted that IO layers were going away or being completely reworked, so I threw that idea out. I decided to use what we had already, the string IO layer. Yes, it's going away eventually, but I can very easily plug whatever replaces it down the road. The string IO layer works by capturing output to a file descriptor in a string. The upside is that the implementation is simple. The downside is that, at least in mod_perl6, you can't dump the output until the script is complete, meaning you have to store the entire output of the script in memory, possibly twice. But hey, this is a first pass and I really don't care, so I added some methods to capture stdout to mod_parrot, which any language can use.

With all the prerequisites satisfied, I was able to produce a very simple pure-perl ModPerl6::Registry, which loads each script into its own namespace and caches it for future requests. Here's a snippet of the juciest part of the module, the mod_perl6 handler:

module ModPerl6::Registry;

our %registry;

...

sub handler($r)
{
    my $script = $r.filename();
    unless (%registry{$script}) {
        my $data = load_script($script);
        my $mod = gen_module_name($script);
        my $code = "module $mod; sub _handler { $data }";
        eval $code;
        %registry{$script} = $mod;
        %registry{$mod} = $script;
    }

    my $interp = ModParrot::Interpreter.new();
    $interp.capture_stdout(1);
    my $mod = %registry{$script};
    ::($mod)::_handler();
    my $buf = $interp.dump_stdout();
    $interp.capture_stdout(0);

    $r.puts($buf);

    0;
}

The Apache configuration should look familiar:

Alias /perl6-bin/ /usr/local/apache2/perl6-bin/
<Directory /usr/local/apache2/perl6-bin>
    Options +ExecCGI
    SetHandler perl6-script
    ParrotHandler ModPerl6::Registry
</Directory>

ModPerl6::Registry is available in mod_parrot as of r345, but I have yet to commit the required patches for Rakudo, so they won't work for anyone yet. Expect the patches next week, and hopefully native support in Parrot sometime real soon now!