=head1 NAME Catalyst::Manual::Deployment - Deploying Catalyst =head1 =head1 mod_perl L =head1 FastCGI =head2 Apache L =head2 mod_perl Deployment mod_perl is not the best solution for many applications, but we'll list some pros and cons so you can decide for yourself. The other (recommended) deployment option is FastCGI, for which see below. =head3 Pros =head4 Speed mod_perl is fast and your app will be loaded in memory within each Apache process. =head4 Shared memory for multiple apps If you need to run several Catalyst apps on the same server, mod_perl will share the memory for common modules. =head3 Cons =head4 Memory usage Since your application is fully loaded in memory, every Apache process will be rather large. This means a large Apache process will be tied up while serving static files, large files, or dealing with slow clients. For this reason, it is best to run a two-tiered web architecture with a lightweight frontend server passing dynamic requests to a large backend mod_perl server. =head4 Reloading Any changes made to the core code of your app require a full Apache restart. Catalyst does not support Apache::Reload or StatINC. This is another good reason to run a frontend web server where you can set up an C page to report that your app is down for maintenance. =head4 Cannot run multiple versions of the same app It is not possible to run two different versions of the same application in the same Apache instance because the namespaces will collide. =head4 Cannot run different versions of libraries. If you have two different applications which run on the same machine, which need two different versions of a library then the only way to do this is to have per-vhost perl interpreters (with different library paths). This is entirely possible, but nullifies all the memory sharing benefits that you get from having multiple applications sharing the same interpreter. =head4 Setup Now that we have that out of the way, let's talk about setting up mod_perl to run a Catalyst app. =head4 1. Install Catalyst::Engine::Apache You should install the latest versions of both Catalyst and Catalyst::Engine::Apache. The Apache engines were separated from the Catalyst core in version 5.50 to allow for updates to the engine without requiring a new Catalyst release. =head4 2. Install Apache with mod_perl Both Apache 1.3 and Apache 2 are supported, although Apache 2 is highly recommended. With Apache 2, make sure you are using the prefork MPM and not the worker MPM. The reason for this is that many Perl modules are not thread-safe and may have problems running within the threaded worker environment. Catalyst is thread-safe however, so if you know what you're doing, you may be able to run using worker. In Debian, the following commands should get you going. apt-get install apache2-mpm-prefork apt-get install libapache2-mod-perl2 =head4 3. Configure your application Every Catalyst application will automagically become a mod_perl handler when run within mod_perl. This makes the configuration extremely easy. Here is a basic Apache 2 configuration. PerlSwitches -I/var/www/MyApp/lib PerlModule MyApp SetHandler modperl PerlResponseHandler MyApp The most important line here is C. This causes mod_perl to preload your entire application into shared memory, including all of your controller, model, and view classes and configuration. If you have -Debug mode enabled, you will see the startup output scroll by when you first start Apache. For an example Apache 1.3 configuration, please see the documentation for L. =head3 Test It That's it, your app is now a full-fledged mod_perl application! Try it out by going to http://your.server.com/. =head3 Other Options =head4 Non-root location You may not always want to run your app at the root of your server or virtual host. In this case, it's a simple change to run at any non-root location of your choice. SetHandler modperl PerlResponseHandler MyApp When running this way, it is best to make use of the C method in Catalyst for constructing correct links. =head4 Static file handling Static files can be served directly by Apache for a performance boost. DocumentRoot /var/www/MyApp/root SetHandler default-handler This will let all files within root/static be handled directly by Apache. In a two-tiered setup, the frontend server should handle static files. The configuration to do this on the frontend will vary. The same is accomplished in lighttpd with the following snippet: $HTTP["url"] !~ "^/(?:img/|static/|css/|favicon.ico$)" { fastcgi.server = ( "" => ( "MyApp" => ( "socket" => "/tmp/myapp.socket", "check-local" => "disable", ) ) ) } Which serves everything in the img, static, css directories statically, as well as the favicon file. Note the path of the application needs to be stated explicitly in the web server configuration for both these recipes. =head2 Catalyst on shared hosting So, you want to put your Catalyst app out there for the whole world to see, but you don't want to break the bank. There is an answer - if you can get shared hosting with FastCGI and a shell, you can install your Catalyst app in a local directory on your shared host. First, run perl -MCPAN -e shell and go through the standard CPAN configuration process. Then exit out without installing anything. Next, open your .bashrc and add export PATH=$HOME/local/bin:$HOME/local/script:$PATH perlversion=`perl -v | grep 'built for' | awk '{print $4}' | sed -e 's/v//;'` export PERL5LIB=$HOME/local/share/perl/$perlversion:$HOME/local/lib/perl/$perlversion:$HOME/local/lib:$PERL5LIB and log out, then back in again (or run C<". .bashrc"> if you prefer). Finally, edit C<.cpan/CPAN/MyConfig.pm> and add 'make_install_arg' => qq[SITEPREFIX=$ENV{HOME}/local], 'makepl_arg' => qq[INSTALLDIRS=site install_base=$ENV{HOME}/local], Now you can install the modules you need using CPAN as normal; they will be installed into your local directory, and perl will pick them up. Finally, change directory into the root of your virtual host and symlink your application's script directory in: cd path/to/mydomain.com ln -s ~/lib/MyApp/script script And add the following lines to your .htaccess file (assuming the server is setup to handle .pl as fcgi - you may need to rename the script to myapp_fastcgi.fcgi and/or use a SetHandler directive): RewriteEngine On RewriteCond %{REQUEST_URI} !^/?script/myapp_fastcgi.pl RewriteRule ^(.*)$ script/myapp_fastcgi.pl/$1 [PT,L] Now C should now Just Work. Congratulations, now you can tell your friends about your new website (or in our case, tell the client it's time to pay the invoice :) ) =head2 FastCGI Deployment FastCGI is a high-performance extension to CGI. It is suitable for production environments. =head3 Pros =head4 Speed FastCGI performs equally as well as mod_perl. Don't let the 'CGI' fool you; your app runs as multiple persistent processes ready to receive connections from the web server. =head4 App Server When using external FastCGI servers, your application runs as a standalone application server. It may be restarted independently from the web server. This allows for a more robust environment and faster reload times when pushing new app changes. The frontend server can even be configured to display a friendly "down for maintenance" page while the application is restarting. =head4 Load-balancing You can launch your application on multiple backend servers and allow the frontend web server to load-balance between all of them. And of course, if one goes down, your app continues to run fine. =head4 Multiple versions of the same app Each FastCGI application is a separate process, so you can run different versions of the same app on a single server. =head4 Can run with threaded Apache Since your app is not running inside of Apache, the faster mpm_worker module can be used without worrying about the thread safety of your application. =head3 Cons You may have to disable mod_deflate. If you experience page hangs with mod_fastcgi then remove deflate.load and deflate.conf from mods-enabled/ =head4 More complex environment With FastCGI, there are more things to monitor and more processes running than when using mod_perl. =head3 Setup =head4 1. Install Apache with mod_fastcgi mod_fastcgi for Apache is a third party module, and can be found at L. It is also packaged in many distributions, for example, libapache2-mod-fastcgi in Debian. You will also need to install the L module from cpan. Important Note! If you experience difficulty properly rendering pages, try disabling Apache's mod_deflate (Deflate Module), e.g. 'a2dismod deflate'. =head4 2. Configure your application # Serve static content directly DocumentRoot /var/www/MyApp/root Alias /static /var/www/MyApp/root/static FastCgiServer /var/www/MyApp/script/myapp_fastcgi.pl -processes 3 Alias /myapp/ /var/www/MyApp/script/myapp_fastcgi.pl/ # Or, run at the root Alias / /var/www/MyApp/script/myapp_fastcgi.pl/ The above commands will launch 3 app processes and make the app available at /myapp/ =head3 Standalone server mode While not as easy as the previous method, running your app as an external server gives you much more flexibility. First, launch your app as a standalone server listening on a socket. script/myapp_fastcgi.pl -l /tmp/myapp.socket -n 5 -p /tmp/myapp.pid -d You can also listen on a TCP port if your web server is not on the same machine. script/myapp_fastcgi.pl -l :8080 -n 5 -p /tmp/myapp.pid -d You will probably want to write an init script to handle starting/stopping of the app using the pid file. Now, we simply configure Apache to connect to the running server. # 502 is a Bad Gateway error, and will occur if the backend server is down # This allows us to display a friendly static page that says "down for # maintenance" Alias /_errors /var/www/MyApp/root/error-pages ErrorDocument 502 /_errors/502.html FastCgiExternalServer /tmp/myapp.fcgi -socket /tmp/myapp.socket Alias /myapp/ /tmp/myapp.fcgi/ # Or, run at the root Alias / /tmp/myapp.fcgi/ =head3 More Info L. =head2 Development server deployment The development server is a mini web server written in perl. If you expect a low number of hits or you don't need mod_perl/FastCGI speed, you could use the development server as the application server with a lightweight proxy web server at the front. However, consider using L for this kind of deployment instead, since it can better handle multiple concurrent requests without forking, or can prefork a set number of servers for improved performance. =head3 Pros As this is an application server setup, the pros are the same as FastCGI (with the exception of speed). It is also: =head4 Simple The development server is what you create your code on, so if it works here, it should work in production! =head3 Cons =head4 Speed Not as fast as mod_perl or FastCGI. Needs to fork for each request that comes in - make sure static files are served by the web server to save forking. =head3 Setup =head4 Start up the development server script/myapp_server.pl -p 8080 -k -f -pidfile=/tmp/myapp.pid You will probably want to write an init script to handle stop/starting the app using the pid file. =head4 Configuring Apache Make sure mod_proxy is enabled and add: # Serve static content directly DocumentRoot /var/www/MyApp/root Alias /static /var/www/MyApp/root/static ProxyRequests Off Order deny,allow Allow from all # Need to specifically stop these paths from being passed to proxy ProxyPass /static ! ProxyPass /favicon.ico ! ProxyPass / http://localhost:8080/ ProxyPassReverse / http://localhost:8080/ # This is optional if you'd like to show a custom error page # if the proxy is not available ErrorDocument 502 /static/error_pages/http502.html You can wrap the above within a VirtualHost container if you want different apps served on the same host. =head2 Quick deployment: Building PAR Packages You have an application running on your development box, but then you have to quickly move it to another one for demonstration/deployment/testing... PAR packages can save you from a lot of trouble here. They are usual Zip files that contain a blib tree; you can even include all prereqs and a perl interpreter by setting a few flags! =head3 Follow these few points to try it out! 1. Install Catalyst and PAR 0.89 (or later) % perl -MCPAN -e 'install Catalyst' ... % perl -MCPAN -e 'install PAR' ... 2. Create a application % catalyst.pl MyApp ... % cd MyApp Recent versions of Catalyst (5.62 and up) include L, which simplifies the process greatly. From the shell in your application directory: % perl Makefile.PL % make catalyst_par You can customise the PAR creation process by special "catalyst_par_*" commands available from L. You can add these commands in your Makefile.PL just before the line containing "catalyst;" #Makefile.PL example with extra PAR options use inc::Module::Install; name 'MyApp'; all_from 'lib\MyApp.pm'; requires 'Catalyst::Runtime' => '5.80005'; ... catalyst_par_core(1); # bundle perl core modules in the resulting PAR catalyst_par_multiarch(1); # build a multi-architecture PAR file catalyst_par_classes(qw/ Some::Additional::Module Some::Other::Module /); # specify additional modules you want to be included into PAR catalyst; install_script glob('script/*.pl'); auto_install; WriteAll; Congratulations! Your package "myapp.par" is ready, the following steps are just optional. 3. Test your PAR package with "parl" (no typo) % parl myapp.par Usage: [parl] myapp[.par] [script] [arguments] Examples: parl myapp.par myapp_server.pl -r myapp myapp_cgi.pl Available scripts: myapp_cgi.pl myapp_create.pl myapp_fastcgi.pl myapp_server.pl myapp_test.pl % parl myapp.par myapp_server.pl You can connect to your server at http://localhost:3000 Yes, this nifty little starter application gets automatically included. You can also use "catalyst_par_script('myapp_server.pl')" to set a default script to execute. 6. Want to create a binary that includes the Perl interpreter? % pp -o myapp myapp.par % ./myapp myapp_server.pl You can connect to your server at http://localhost:3000 =head2 Serving static content Serving static content in Catalyst used to be somewhat tricky; the use of L makes everything much easier. This plugin will automatically serve your static content during development, but allows you to easily switch to Apache (or other server) in a production environment. =head3 Introduction to Static::Simple Static::Simple is a plugin that will help to serve static content for your application. By default, it will serve most types of files, excluding some standard Template Toolkit extensions, out of your B file directory. All files are served by path, so if B is requested, then B is found and served. =head3 Usage Using the plugin is as simple as setting your use line in MyApp.pm to include: use Catalyst qw/Static::Simple/; and already files will be served. =head3 Configuring Static content is best served from a single directory within your root directory. Having many different directories such as C and C requires more code to manage, because you must separately identify each static directory--if you decide to add a C directory, you'll need to change your code to account for it. In contrast, keeping all static directories as subdirectories of a main C directory makes things much easier to manage. Here's an example of a typical root directory structure: root/ root/content.tt root/controller/stuff.tt root/header.tt root/static/ root/static/css/main.css root/static/images/logo.jpg root/static/js/code.js All static content lives under C, with everything else being Template Toolkit files. =over 4 =item Include Path You may of course want to change the default locations, and make Static::Simple look somewhere else, this is as easy as: MyApp->config->{static}->{include_path} = [ MyApp->config->{root}, '/path/to/my/files' ]; When you override include_path, it will not automatically append the normal root path, so you need to add it yourself if you still want it. These will be searched in order given, and the first matching file served. =item Static directories If you want to force some directories to be only static, you can set them using paths relative to the root dir, or regular expressions: MyApp->config->{static}->{dirs} = [ 'static', qr/^(images|css)/, ]; =item File extensions By default, the following extensions are not served (that is, they will be processed by Catalyst): B. This list can be replaced easily: MyApp->config->{static}->{ignore_extensions} = [ qw/tmpl tt tt2 html xhtml/ ]; =item Ignoring directories Entire directories can be ignored. If used with include_path, directories relative to the include_path dirs will also be ignored: MyApp->config->{static}->{ignore_dirs} = [ qw/tmpl css/ ]; =back =head3 More information L =head3 Serving manually with the Static plugin with HTTP::Daemon (myapp_server.pl) In some situations you might want to control things more directly, using L. In your main application class (MyApp.pm), load the plugin: use Catalyst qw/-Debug FormValidator Static OtherPlugin/; You will also need to make sure your end method does I forward static content to the view, perhaps like this: sub end : Private { my ( $self, $c ) = @_; $c->forward( 'MyApp::View::TT' ) unless ( $c->res->body || !$c->stash->{template} ); } This code will only forward to the view if a template has been previously defined by a controller and if there is not already data in C<$c-Eres-Ebody>. Next, create a controller to handle requests for the /static path. Use the Helper to save time. This command will create a stub controller as C. $ script/myapp_create.pl controller Static Edit the file and add the following methods: # serve all files under /static as static files sub default : Path('/static') { my ( $self, $c ) = @_; # Optional, allow the browser to cache the content $c->res->headers->header( 'Cache-Control' => 'max-age=86400' ); $c->serve_static; # from Catalyst::Plugin::Static } # also handle requests for /favicon.ico sub favicon : Path('/favicon.ico') { my ( $self, $c ) = @_; $c->serve_static; } You can also define a different icon for the browser to use instead of favicon.ico by using this in your HTML header: =head3 Common problems with the Static plugin The Static plugin makes use of the C package to automatically determine MIME types. This package is notoriously difficult to install, especially on win32 and OS X. For OS X the easiest path might be to install Fink, then use C. Restart the server, and everything should be fine. Make sure you are using the latest version (>= 0.16) for best results. If you are having errors serving CSS files, or if they get served as text/plain instead of text/css, you may have an outdated shared-mime-info version. You may also wish to simply use the following code in your Static controller: if ($c->req->path =~ /css$/i) { $c->serve_static( "text/css" ); } else { $c->serve_static; } =head3 Serving Static Files with Apache When using Apache, you can bypass Catalyst and any Static plugins/controllers controller by intercepting requests for the C path at the server level. All that is required is to define a DocumentRoot and add a separate Location block for your static content. Here is a complete config for this application under mod_perl 1.x: use lib qw(/var/www/MyApp/lib); PerlModule MyApp ServerName myapp.example.com DocumentRoot /var/www/MyApp/root SetHandler perl-script PerlHandler MyApp SetHandler default-handler And here's a simpler example that'll get you started: Alias /static/ "/my/static/files/" SetHandler none =head2 Caching Catalyst makes it easy to employ several different types of caching to speed up your applications. =head3 Cache Plugins There are three wrapper plugins around common CPAN cache modules: Cache::FastMmap, Cache::FileCache, and Cache::Memcached. These can be used to cache the result of slow operations. The Catalyst Advent Calendar uses the FileCache plugin to cache the rendered XHTML version of the source POD document. This is an ideal application for a cache because the source document changes infrequently but may be viewed many times. use Catalyst qw/Cache::FileCache/; ... use File::stat; sub render_pod : Local { my ( self, $c ) = @_; # the cache is keyed on the filename and the modification time # to check for updates to the file. my $file = $c->path_to( 'root', '2005', '11.pod' ); my $mtime = ( stat $file )->mtime; my $cached_pod = $c->cache->get("$file $mtime"); if ( !$cached_pod ) { $cached_pod = do_slow_pod_rendering(); # cache the result for 12 hours $c->cache->set( "$file $mtime", $cached_pod, '12h' ); } $c->stash->{pod} = $cached_pod; } We could actually cache the result forever, but using a value such as 12 hours allows old entries to be automatically expired when they are no longer needed. =head3 Page Caching Another method of caching is to cache the entire HTML page. While this is traditionally handled by a front-end proxy server like Squid, the Catalyst PageCache plugin makes it trivial to cache the entire output from frequently-used or slow actions. Many sites have a busy content-filled front page that might look something like this. It probably takes a while to process, and will do the exact same thing for every single user who views the page. sub front_page : Path('/') { my ( $self, $c ) = @_; $c->forward( 'get_news_articles' ); $c->forward( 'build_lots_of_boxes' ); $c->forward( 'more_slow_stuff' ); $c->stash->{template} = 'index.tt'; } We can add the PageCache plugin to speed things up. use Catalyst qw/Cache::FileCache PageCache/; sub front_page : Path ('/') { my ( $self, $c ) = @_; $c->cache_page( 300 ); # same processing as above } Now the entire output of the front page, from to , will be cached for 5 minutes. After 5 minutes, the next request will rebuild the page and it will be re-cached. Note that the page cache is keyed on the page URI plus all parameters, so requests for / and /?foo=bar will result in different cache items. Also, only GET requests will be cached by the plugin. You can even get that front-end Squid proxy to help out by enabling HTTP headers for the cached page. MyApp->config->{page_cache}->{set_http_headers} = 1; This would now set the following headers so proxies and browsers may cache the content themselves. Cache-Control: max-age=($expire_time - time) Expires: $expire_time Last-Modified: $cache_created_time =head3 Template Caching Template Toolkit provides support for caching compiled versions of your templates. To enable this in Catalyst, use the following configuration. TT will cache compiled templates keyed on the file mtime, so changes will still be automatically detected. package MyApp::View::TT; use strict; use warnings; use base 'Catalyst::View::TT'; __PACKAGE__->config( COMPILE_DIR => '/tmp/template_cache', ); 1; =head3 More Info See the documentation for each cache plugin for more details and other available configuration options. L L L L L =head1 Testing Testing is an integral part of the web application development process. Tests make multi developer teams easier to coordinate, and they help ensure that there are no nasty surprises after upgrades or alterations. =head2 Testing Catalyst provides a convenient way of testing your application during development and before deployment in a real environment. C makes it possible to run the same tests both locally (without an external daemon) and against a remote server via HTTP. =head3 Tests Let's examine a skeleton application's C directory: mundus:~/MyApp chansen$ ls -l t/ total 24 -rw-r--r-- 1 chansen chansen 95 18 Dec 20:50 01app.t -rw-r--r-- 1 chansen chansen 190 18 Dec 20:50 02pod.t -rw-r--r-- 1 chansen chansen 213 18 Dec 20:50 03podcoverage.t =over 4 =item C<01app.t> Verifies that the application loads, compiles, and returns a successful response. =item C<02pod.t> Verifies that all POD is free from errors. Only executed if the C environment variable is true. =item C<03podcoverage.t> Verifies that all methods/functions have POD coverage. Only executed if the C environment variable is true. =back =head3 Creating tests mundus:~/MyApp chansen$ cat t/01app.t | perl -ne 'printf( "%2d %s", $., $_ )' 1 use Test::More tests => 2; 2 BEGIN { use_ok( Catalyst::Test, 'MyApp' ) } 3 4 ok( request('/')->is_success ); The first line declares how many tests we are going to run, in this case two. The second line tests and loads our application in test mode. The fourth line verifies that our application returns a successful response. C exports two functions, C and C. Each can take three different arguments: =over 4 =item A string which is a relative or absolute URI. request('/my/path'); request('http://www.host.com/my/path'); =item An instance of C. request( URI->new('http://www.host.com/my/path') ); =item An instance of C. request( HTTP::Request->new( GET => 'http://www.host.com/my/path') ); =back C returns an instance of C and C returns the content (body) of the response. =head3 Running tests locally mundus:~/MyApp chansen$ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib/ t/ t/01app............ok t/02pod............ok t/03podcoverage....ok All tests successful. Files=3, Tests=4, 2 wallclock secs ( 1.60 cusr + 0.36 csys = 1.96 CPU) C ensures that debugging is off; if it's enabled you will see debug logs between tests. C enables POD checking and coverage. C A command-line tool that makes it easy to run tests. You can find out more about it from the links below. =head3 Running tests remotely mundus:~/MyApp chansen$ CATALYST_SERVER=http://localhost:3000/ prove --lib lib/ t/01app.t t/01app....ok All tests successful. Files=1, Tests=2, 0 wallclock secs ( 0.40 cusr + 0.01 csys = 0.41 CPU) C is the absolute deployment URI of your application. In C or C it should be the host and path to the script. =head3 C and Catalyst Be sure to check out C. It makes it easy to test HTML, forms and links. A short example of usage: use Test::More tests => 6; BEGIN { use_ok( Test::WWW::Mechanize::Catalyst, 'MyApp' ) } my $mech = Test::WWW::Mechanize::Catalyst->new; $mech->get_ok("http://localhost/", 'Got index page'); $mech->title_like( qr/^MyApp on Catalyst/, 'Got right index title' ); ok( $mech->find_link( text_regex => qr/^Wiki/i ), 'Found link to Wiki' ); ok( $mech->find_link( text_regex => qr/^Mailing-List/i ), 'Found link to Mailing-List' ); ok( $mech->find_link( text_regex => qr/^IRC channel/i ), 'Found link to IRC channel' ); =head3 Further Reading =over 4 =item Catalyst::Test L =item Test::WWW::Mechanize::Catalyst L =item Test::WWW::Mechanize L =item WWW::Mechanize L =item LWP::UserAgent L =item HTML::Form L =item HTTP::Message L =item HTTP::Request L =item HTTP::Request::Common L =item HTTP::Response L =item HTTP::Status L =item URI L =item Test::More L =item Test::Pod L =item Test::Pod::Coverage L =item prove (Test::Harness) L =back =head3 More Information L L =head1 AUTHORS Catalyst Contributors, see Catalyst.pm =head1 COPYRIGHT This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself. =cut