Splitting a Catalyst App and recombining it with Plack::Builder

I had a big Catalyst App serving HTML.

Some time later a RESTful interface was needed so I added RESTful controllers using Catalyst::Controller::REST

But that broke Plack::Middleware::CSRFBlock, because the REST calls don't request a form and thus cannot add the secure token to POST requests.

Thinking about a solution it dawned on my that having a single App serving HTML and RESTful requests is probably a bad design choice.

Thankfully most of my business logic is in my DBIx::Class schema so splitting up one Catalyst App into two Catalyst Apps under the same namespace shouldn't be much of a problem.

Let's call the old App 'MyApp'. I wanted the new Apps to be named 'MyApp::Web::HTML' and 'MyApp::Web::API'

First part was to create lib/MyApp/Web/API and lib/MyApp/Web/HTML folders and moving as much components (Controllers/Views/Forms) there as possible. This part meant quite some renaming of file and package names. Your IDE can be quite helpful with that.

The DBIC schema continued its existence in lib/MyApp/Schema and both apps used it in their respective model.

Both apps share the same config, creating symlinks to the old catalyst config with names corresponding to the namespace of the new apps helped so the new apps would load the correct config


myapp_local.pl
myapp.pl
myapp.psgi
myapp_testing.pl
myapp_web_api_local.pl -> myapp_local.pl
myapp_web_api.pl -> myapp.pl
myapp_web_api_testing.pl -> myapp_testing.pl
myapp_web_html_local.pl -> myapp_local.pl
myapp_web_html.pl -> myapp.pl
myapp_web_html_testing.pl -> myapp_testing.pl

lib/MyApp.pm is needed for(I think) Catalyst::Plugin::ConfigLoader to find the project root. Or maybe not, but without that file some things don't work. I used it to build together the psgi app which will be used for the testserver and deployment.


package MyApp;

use strict;
use warnings;
use MyApp::Web::API;
use MyApp::Web::HTML;
use Plack::Builder;

sub psgi_app {
    my $api_app =
      MyApp::Web::API->apply_default_middlewares(
        MyApp::Web::API->psgi_app );
    my $html_app =
      MyApp::Web::HTML->apply_default_middlewares(
        MyApp::Web::HTML->psgi_app );

    builder {
        mount '/'    => $html_app;
        mount '/api' => $api_app;
    };
}

1;

and in myapp.psgi I just call MyApp::psgi_app


use strict;
use warnings;
use MyApp;
MyApp::psgi_app;

running my testserver is now done with plackup

CATALYST_DEBUG=1 plackup -Ilib myapp.psgi --port 3000

and for apache2/fastcgi deployment I had to write a starter script using Plack::Handler::FCGI ( I use Daemon::Control to make it a system daemon)

# script/myapp_fcgi.pl

#!/usr/bin/env perl
use strict;
use warnings;
use FindBin qw/$Bin/;
use lib "$Bin/../lib";
use Plack::Handler::FCGI;
use MyApp;

my $server = Plack::Handler::FCGI->new(
    nproc  => 1,
    listen => ['/var/run/myapp/fcgi.socket'],
    detach => 0,
);
$server->run( MyApp::psgi_app() );
# apache config file

<VirtualHost *:80>
        ServerAdmin root@nio
        ServerName myapp.nio
        ErrorLog /var/log/apache2/myapp-error.log
        CustomLog /var/log/apache2/myapp-access.log common
        SetEnv no-gzip 1

        DocumentRoot /var/www/MyApp
        Alias /static /var/www/MyApp/root/static

        <Location />
                Order allow,deny
                Allow from all
        </Location>

        <Location /static>
                SetHandler default
        </Location>

        # Possible values include: debug, info, notice, warn, error, crit, alert, emerg.
        LogLevel warn

        FastCgiExternalServer /tmp/MyApp.fcgi -socket /var/run/myapp/fcgi.socket
        Alias / /tmp/MyApp.fcgi/
</VirtualHost>

Leave a comment

About davewood

user-pic I like Toast.