Tuesday, April 28, 2015

Dada Mail Eight Released

Dada Mail v8 was recently released  - Dada Mail is a mailing list manager that works with announcement-only and discussion lists, public and private lists and a ton of things in between. It's written in Perl and runs on practically anything you can throw at it (as long is it's Unix-like)

The big ta-do with v8 is that Dada Mail supports being run under FastCGI and Plack/PSGI, when before it was only able to run under CGI. That's a big level up. Thankfully, because of the Perl community,  porting a project that began in 1999  and has been growing quite organically since then wasn't terribly impossible. For the rest of this post, I'll be going into some of approaches I used, to make this happen, with the minimal amount of work maximum amount of laziness, impatience and hubris.

CGI::Application

Fortunately or unfortunately, one of the constraints of adding support for Plack/PSGI is that CGI support still has to be kept: meaning you can throw up the app via FTP, run an install script, give it some parameters like database creds. and be off to the races.

The vast majority of installs of Dada Mail are done on pretty simple, cPanel-based (also a Perl company) shared hosting accounts. Small business use Dada Mail to keep in contact with their customers. Churches and private groups (think doctors and lawyers that want to talk shop) run off of it. With Dada Mail's support for third party email services like Amazon SES, this is actually a pretty realistic situation. Shared host companies usually have pretty terrible shared host mail servers, especially if you're running a very big/busy list; Amazon SES (and friends) work much better.

This means using a framework like Catalyst or Mojolicious is currently not looking so hot. The minimum Perl version being targeted is v5.10.1 (and even that isn't available in many shared hosts - tragic!)

But, the venerable CGI::Application framework looks great! Since it already walks the line of easily supporting both CGI apps (which it was built to do!) and PSGI/Plack apps (which it has since been given support for!).

The main difference with writing a CGI app for CGI::Application, is that you return a string for the body of your page in each method, and set parameters for any headers you would also like to send. CGI::App does the actual printing to (say) STDOUT for you. This small amount of encapsulation helps keep your app a bit tidier than without it, since you're not print()ing whilly-nilly everywhere.

So, the first step, if you were to migrate to CGI::App, is to find all those print statements, and get rid of them, only returning the data you want to show.  That's an easy step, but can take a little while to find every instance of this happening.

Run Modes

 CGI::App has the idea of run modes - run modes can be thought of each screen of your app. You probably have a default run mode. For Dada Mail there's a list page run mode, and admin login run mode, a send out a mass mailing run mode, etc.

Thankfully, the previous version of Dada Mail had a similar idea, with code right out of the Perl Cookbook:


sub run {

    #external (mostly..) functions called from the web browser)
    # a few things this program  can do.... :)
    my %Mode = (
        'default'                                     => \&default,
        'subscribe'                                   => \&subscribe,
        # a whole bunch of other subs
    );

    if ($flavor) {
        if ( exists( $Mode{$flavor} ) ) {
            $Mode{$flavor}->();    #call the correct subroutine
        }
        else {
            &default;
        }
    }
    else {
        &default;
    }
}


 Porting this to CGI was simple enough, in its setup() method. In App.pm:


sub setup {

    my $self = shift;

    $self->start_mode('default');
    $self->mode_param('flavor');
    $self->error_mode('yikes');

    $self->run_modes(
        'default'                                     => \&default,
        'subscribe'                                   => \&subscribe,
        # Many, many more, 
        );

}



CGI::App does have CGI::App::Dispatch to help with mapping PATH_INFO information to various resources served up by your app. I wasn't keen on porting Dada Mail's dispatch-ish stuff to this - yet. It seemed like a great way to break current ways we're doing things, AND the current way of doing things is 15 years of messiness.

Instead, I modified the parameters you pass to CGI::App, before it's given the param()-compatible object (in our case: CGI.pm). in the harness script (in CGI::App parlance: app.cgi):


 
use CGI;

# DADA::App is our CGI::App
use DADA::App;
 
# DADA::App::Dispatch changes around paramaters, and puts things
# form the PATH_INFO env. var. into param()s:  
use DADA::App::Dispatch;

my $d = DADA::App::Dispatch->new;
my $q = new CGI;
   $q = $d->prepare_cgi_obj($q);

my $dadamail = new DADA::App( QUERY => $q, );
   $dadamail->run();



Here, prepare_cgi_obj() just takes the CGI.pm object, reads the PATH_INFO and stuffs values into various parameters, much like CGI::APP::Dispatch, but without the need for me to rewrite everything - we just encapsulate it into its own method.  When I'm ready, I could move over to CGI::App::Dispatch (or not! Thus the beauty of CGI::App).

Once that's all figured out, I'm still left with a CGI script, rather than a Plack/PSGI script. My solution is to ship with multiple version of this harness script, and run a different harness script, depending on the environment I want to run under. The PSGI/Plack harness script looks like this:


use CGI::PSGI;

use DADA::App;
use DADA::App::Dispatch;
 
my $handler  = sub {
    my $env    = shift; 
    require Data::Dumper; 
    my $d      = DADA::App::Dispatch->new;
    my $q      = CGI::PSGI->new($env); 
    my $q      = $d->prepare_cgi_obj($q);
    my $webapp = DADA::App->new({ QUERY => $q });
       $webapp->run_as_psgi;
};


Dada Mail already comes with a web-based installer, which works great for installing Dada Mail as a CGI script, but would need a little more work to work under Plack/PSGI. It didn't seem really worth it to add support for that, so I instead gave Dada Mail's current installer support for being run via the command line,  so you can install/configure Dada Mail for Plack/PSGI, right before you run  the, plackup command, which is a much nicer workflow.

In a nutshell, that's all it took to take a very mature codebase and have it run under Plack/PSGI, as well as still keep support for CGI. There's some details I've skipped, like making sure database connections are persistent (I used a singleton module to keep the connection cached and persistent)

Here's some more meta on this dramatic change in the back end of things:

Dada Mail is on Github, so you can see the difference between the pre and post CGI::App-based Dada Mail. There's not too many new features, but we're hoping all this new organization helps with keeping the app growing well into the future:
Showing 281 changed files, with 38,717 additions and 31,998 deletions.
Here's how the installer works for web-based (for CGI installs), as well as on the command line (for PSGI/Plack)

More on PSGI/Plack support with Dada Mail

Please give Dada Mail a spin, the next time you have the need to set up a mailing list.

2 comments:

Ron Savage said...

Interesting. I have re-written CGI::Application, cleaning it up in the process. See CGI::Snapp.

The Perl Hacker Painter said...

One of the reasons I picked CGI::App over CGI::Snapp is some of the XS modules dependencies required. Dada Mail is offered by many app services in hosting environments as a one-click install. I'm afraid these services are pretty primitive, and boil down to FTP'ing a bunch of files to the user's hosting account, and running a configuration script. Having an XS dependency may be a little over and above what these services will deal with, especially when they're used to just throwing up the files and running the app ala Wordpress. It's def. a difficult environment to write for!