Thursday, December 16, 2010

Apache + Passenger + Sinatra

OK, this seems like a pretty normal situation, and like most normal situations it apparently never occurs in nature (judging by the available docs).

An existing Apache webserver is to have a new webapp added to it, in a subdirectory of the DocumentRoot. Sinatra is the framework to be used, and Passenger is going to route requests from Apache to Sinatra without going through any mod_proxy or mod_rewrite business.

Assume the following: Apache2, an Ubuntu system, and all of the relevant gems have been apt-get installed. The web server root directory is /var/www, and the webapp will be in /var/www/timon.

First, update the Apache config file (/etc/apache2/apache2/conf):

<VirtualHost *:80>
  ServerName Apemantus
  DocumentRoot /var/www

  <Directory />
    Options -Indexes
    AllowOverride None
  </Directory>

  <Directory /var/www/>
    AllowOverride AuthConfig
    Order allow,deny
  </Directory>

  SetEnv RUBYLIB '/var/www/timon'
  PassengerEnabled on
  PassengerAppRoot /var/www/timon
  RackBaseURI /timon

</VirtualHost>

The bold lines indicate what must be added.
  • SetEnv : This just allows the code in subdirectories of the webapp to be easily required.
  • PassengerEnabled : Seems fairly obvious.
  • PassengerAppRoot : The full filesystem path to the webapp directory.
  • RackBaseURI : The relative (to DocumentRoot) path to the webapp directory.

Next, verify that the Passenger options (/etc/apache2/mods-enabled/passenger.conf) are correct:


<IfModule mod_passenger.c>

  PassengerRoot /usr
  PassengerRuby /usr/bin/ruby
  PassengerMaxPoolSize 10
  PassengerDefaultUser www-data


</IfModule mod_passenger.c>

These options are pretty straightforward. The only thing to note is that PassengerRuby can be set to a specific version of Ruby, e.g. jruby or ruby1.9.

Now, create an empty Rack-friendly directory structure for the webapp:

bash# cd /var/www
bash# mkdir timon
bash# mkdir timon/public
bash# mkdir timon/tmp

The use of public for static pages and tmp for restart.txt is well-documented.

Next, a Rack config file must be provided. This will be named config.ru (/var/www/timon/config.ru) and will have the following contents:

#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra'

# Disable Sinatra's default Webrick instance
set :run, false

# Include timon.rb, the main webapp script
require 'timon'
run Sinatra::Application

Finally, the app itself must be provided. This will be named timon.rb (/var/www/timon/timon.rb) and will have the following contents:

#!/usr/bin/env ruby

require 'rubygems'
require 'sinatra'

get '/' do
  "<b>TIMON!</b>"
end

# Local 404 handler
not_found do
  "Timon NotFound exception"
end

# Local error handler
error do
  "Timon Error: " + env['sinatra_error'].name
end

Debugging can be made a bit more straightforward by adding some basic logging code to config.ru:


#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra'

# Disable Sinatra's default Webrick instance
set :run, false

# Local logging
FileUtils.mkdir_p 'log' unless File.exists?('log')
log = File.new('log/sinatra.log', 'a')
$stdout.reopen(log)
$stderr.reopen(log)

# Include timon.rb, the main webapp script
require 'timon'
run Sinatra::Application

That's it.

Nothing much to it, really, but the lack of RackBaseURI in the relevant examples really makes debugging this kind of thing difficult.