The PSGI/Mac OS X Dance

In this blog post I'll show you how to get a PSGI web application to start up and listen on a port when you boot your Mac OS X machine. To do this, I'll be quickly covering the plackup command line utility and then delving into the basics of OS X's launchd plist configuration files.

Our example PSGI application

The first step is to create a PSGI compatible application. For the purpose of this blog post, let's just use the example application from the Dancer framework's documentation:
#!/usr/bin/perl

use strict;
use warnings;

use Dancer;

get '/hello/:name' => sub {
   return "Why, hello there " . params->{name};
};

dance;
We should probably check from the command line that this works as we expect before we go any further.
bash$ perl hellow.pl 
>> Dancer server 5758 listening on http://0.0.0.0:3000
== Entering the development dance floor ...
And then in another terminal:
bash$ lwp-request http://127.0.0.1:3000/hello/world
Why, hello there world
Of course, we could have just as easily used a Mojolicious or Catalyst application here! But that's not the point....in just a few lines of code we've got a PSGI compatible web application written and ready to host.

Running this with a PSGI webserver and plackup

The PSGI standard is essentially a compatibility layer between Perl web framework and Perl webservers; Without changing a line of code you can switch one webserver to another, and likewise our webservers can be written to support any web framework without needing to add further code for each framework. In this example I'm going to use a server called Twiggy as my PGSI compliant webserver, which is a module that can be installed from the CPAN in the normal manner. I've chosen it because it's fast and has a low memory footprint (the latter being quite important if I don't want to use up too much of my desktop's RAM.) The only drawback with Twiggy is that my application can't use too much CPU or block on IO in a non-any-event compatible way without holding up the next request. For me this doesn't matter to me because I'm the only one going to be using my machine! Of course, it's a simple configuration variable change to switch to another PSGI compatible server like Starman that handles preforking for us. To start our Dancer application with Twiggy we just need to use the plackup command:
bash$ plackup -s Twiggy -p 4077 -a dancer.pl 
Twiggy: Accepting connections at http://0.0.0.0:4077/
And then again, in another terminal:
bash$ lwp-request http://127.0.0.1:4077/hello/world
Why, hello there world

Configuring plackup to run on boot on a Mac

Mac OS X uses a process called launchd to manage services, replacing the more traditional init.d system you'd find on a typical linux box. To define a new service we need to create a plist file (a correctly formatted xml file.) The standard place for plists for deamons launched on boot is /System/Library/LaunchDaemons; Mac OS X will load all the plist files in this directory when the machine starts up. Each of these files need a unique name, one that will be guaranteed not clash with any other service that Apple or third parties will create in the future. To ensure this they use the same "reverse-domain-name" scheme that Java uses for its class names: You start the file name with your domain name in reverse. Today I'm going to create a file called com.twoshortplanks.hellow.plist, which I know is unique because I control the twoshortplanks.com domain name:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Disabled</key>
        <false/>
        <key>Label</key>
        <string>com.twoshortplanks.hellow</string>
        <key>ProgramArguments</key>
        <array>
                <string>/usr/local/bin/plackup</string>
                <string>-a</string>
                <string>Twiggy</string>
                <string>-p</string>
                <string>4077</string>
                <string>-a</string>
                <string>/usr/local/hellow/hellow.pl</string>
        </array>
        <key>OnDemand</key>
        <false/>
</dict>
</plist>
So, this is fairly straight forward, with the plist containing alternating keys and data structures. Since we want the server to start up straight away on boot both the Disabled and OnDemand keys need to be set to false. Label needs to be set to the same name we used in the filename. Finally the slightly confusingly named ProgramArguments needs to be set to contain both the name of the executable and its arguments. This is exactly as we would have passed it to the shell, but with each part that was separated by spaces in its own <string> tag. You'll note that we've also used absolute paths here because, obviously, when this is run by launchd it won't have either our current PATH or current working directory. (It's also worth noting at this point, just incase you're using this example to write something to run a daemon other than plackup, that the command should run in the foreground and not fork itself off into a daemon. We're not passing the options to plackup to do this, so that's all good.) The first thing we should probably do after writing the plist check we got the plist syntax right and there are no typos (especially as launchd gives the world's most unhelpful error messages.) The system-supplied plutil utility comes with a lint mode that can help here:
bash$ plutil -lint com.twoshortplanks.hellow.plist
com.twoshortplanks.hellow.plist: OK
Once we've done that we can force Mac OS X to load the daemon settings right away (without having to reboot the computer):
bash$ sudo launchctl load /System/Library/LaunchDaemons/com.twoshortplanks.hellow.plist
And now we can check it's loaded:
bash$ sudo launchctl list | grep hellow
2074    -    com.twoshortplanks.hellow
And we can use it as a webserver!
bash$ lwp-request http://127.0.0.1:4077/hello/world
Why, hello there world
Great! It's running! Now what? Well, assuming we're not going to be using plackup's --reload option (which is a little too complicated to go into now) we need to know how to restart the server whenever we make changes. The simpliest thing is to unload and reload it again:
bash$ sudo launchctl unload /System/Library/LaunchDaemons/com.twoshortplanks.hellow.plist
bash$ sudo launchctl load /System/Library/LaunchDaemons/com.twoshortplanks.hellow.plist

Conclusion

With PGSI it's possible to have a low impact custom webserver running on your local Mac without much work at all.

- to blog -

blog built using the cayman-theme by Jason Long. LICENSE