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