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.

Posted in Uncategorized

Permalink 1 Comment

One response to “The PSGI/Mac OS X Dance

  1. Assuming that you configured your launchd plist to restart the app if it exits, you can add a special URL that will just call exit(2).

    Instant reload, just a GET away.

    I use this for some developer environments with great success. My Catalyst action looks like this:

    sub exit : Local {
    my ($self, $c) = @_;

    return $c->res->redirect(‘/’) unless $c->debug;
    print STDERR “Exiting server, per user request to /exit\n”;
    exit(5);
    }

    I use 5 as signal for the wrapper script (in this case) that this is a expected exit, not an abnormal one.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.