Great Lengths

Heroku is a great platform. Long before I joined and when I say long, I mean in startup terms (i.e. a few weeks before I joined :)) – the decision was made that CrowdHired would be hosted on Heroku. Shortly after I came on board, Heroku released their new Cedar stack and we quickly migrated across to that. I find it kinda amusing that we’re currently in alpha, deploying to a platform that’s in beta. Latest and greatest FTW. While migrating to the new stack we also settled on Thin as our web server. The Cedar stack allows you to use whatever web server you want in production and will run on Webrick by default – not ideal. Since we were going to use Thin in production it made sense that we’d also use it in development instead of Webrick.

When you’re using Rails, swapping a new web server in is pretty painless. Just include the gem and then use the rails s command to launch your new server e.g

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
rails s thin```

Pretty simple right? Well, it is, but I am very used to typing `rails s` to launch my server and no matter what gem you include in your project, `rails s` still starts Webrick (_this is not entirely accurate but bare with me_). Muscle memory being what it is, after typing `rails s` instead of `rails s thin` a couple of times (_and only realising this a few hours later_) I decided to see if I could make Thin the default for the `rails s` command.

## Digging Into The Rails Command Line

The key thing here was to figure out how `rails s` actually works &#8211; only way to do that is to <a href="http://www.skorks.com/2010/05/why-i-love-reading-other-peoples-code-and-you-should-too/" target="_blank">read some code</a>. We know that there is a `script/rails` executable that lives in all our Rails project so it makes sense that this would be the entry point into figuring out `rails s`, but in reality it's not (_or at least it's not that simple_). We don't actually type `script/rails s`, we do `rails s`, so **there must be an executable called `rails` within the Rails gem itself** which is declared as such in rails' gemspec. This is indeed the case, it looks like this:

```ruby
#!/usr/bin/env ruby
 
if File.exists?(File.join(File.expand_path('../..', __FILE__), '.git'))
  railties_path = File.expand_path('../../railties/lib', __FILE__)
  $:.unshift(railties_path)
end
require "rails/cli"

But even that is not the start of the story. Apparently, when you have an executable in a gem, rubygems will not use it as is, but will generate another executable which wraps your one. For the rails command it looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env ruby
#
# This file was generated by RubyGems.
#
# The application 'rails' is installed as part of a gem, and
# this file is here to facilitate running it.
#
 
require 'rubygems'
 
version = "&gt;= 0"
 
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
  version = $1
  ARGV.shift
end
 
gem 'rails', version
load Gem.bin_path('rails', 'rails', version)

This is the true entry point you hit when you type rails s. Of course, all this does is load/call the original executable from the Rails gem.

The Rails source is broken up into several projects such as activerecord, activesupport etc. Probably the most important one of these is railties. This is where the rails executable takes us. Of course, before it does that it needs to put the lib/ folder of the railties project on the load path, but eventually we end up in railties/lib/rails/cli.rb. Here we almost immediately execute the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Rails::ScriptRailsLoader.exec_script_rails!```

All this does is essentially figure out if we're inside a Rails app and if we are it executes `script/rails` passing through the command line arguments that you supply. So, we're now back in our Rails app; `script/rails` is the real entry point after all (_although we're about to be taken straight back to **railties**_). The file looks like this:

```ruby
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
 
APP_PATH = File.expand_path('../../config/application',  __FILE__)
require File.expand_path('../../config/boot',  __FILE__)
require 'rails/commands'

We require boot.rb so that we can hook into Bundler and make sure the relevant gems are on the load path (such as rails for example), we then jump into railties/rails/lib/commands.rb. Here everything is pretty straight forward, we have a big case statement which has a clause for “server”. We instantiate a new Rails::Server and start it, which tells us very little by itself, but if we jump into railties/rails/lib/commands/server.rb we can see that Rails::Server simply extends Rack::Server (and delegates to Rack::Server&#39;s start method from its start method) all it adds is those familiar lines we’re all used to seeing e.g.:

1
2
3
4
=&gt; Booting Thin
=&gt; Rails 3.0.7 application starting in development on http://0.0.0.0:3000
=&gt; Call with -d to detach
=&gt; Ctrl-C to shutdown server

So, if we want to change which server is picked up by default when we type rails s we need to go look in Rack.

A Quick Glance At Rack

Luckily we can easily grab it from Github and crack it open (you have to admire the Ruby open source ecosystem, it is truly awesome, largely due to Github, so big props to those guys). We of course need to check out lib/rack/server.rb where we find the following method:

1
2
3
def server
  @_server ||= Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
end

So, if we don’t pass in the name of the server we want, Rack::Handler.default will try to determine it for us.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def self.default(options = {})
  # Guess.
  if ENV.include?("PHP_FCGI_CHILDREN")
    # We already speak FastCGI
    options.delete :File
    options.delete :Port
 
    Rack::Handler::FastCGI
  elsif ENV.include?("REQUEST_METHOD")
    Rack::Handler::CGI
  else
    begin
      Rack::Handler::Mongrel
    rescue LoadError
      Rack::Handler::WEBrick
    end
  end
end

As you can see, it turns out that the real default server is actually Mongrel. So if you included Mongrel in your Rails project, typing rails s would automatically pick that up without you having to do anything. Only if Mongrel fails, do we fall back to Webrick which is part of Ruby and therefore is always present. So what do we do if we want Thin to be one of the defaults? Well, first thing first, we need to check if Rack includes a handler for Thin. If we look in lib/rack/handlers/ we can see that Rack itself includes the following:

1
2
3
4
5
6
7
8
9
cgi.rb
evented_mongrel.rb
fastcgi.rb
lsws.rb
mongrel.rb
scgi.rb
swiftiplied_mongrel.rb
thin.rb
webrick.rb

Luckily Thin is there, so what we really want is that default method to look something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def self.default(options = {})
  # Guess.
  if ENV.include?("PHP_FCGI_CHILDREN")
    # We already speak FastCGI
    options.delete :File
    options.delete :Port
 
    Rack::Handler::FastCGI
  elsif ENV.include?("REQUEST_METHOD")
    Rack::Handler::CGI
  else
    begin
      Rack::Handler::Thin
    rescue LoadError
      begin
        Rack::Handler::Mongrel
      rescue LoadError
        Rack::Handler::WEBrick
      end
    end
  end
end

This way Thin will be the first default, followed by Mongrel and only then falling through to Webrick. Luckily, since we’re using Ruby, we can reopen the class and replace the method with our version. I think this is a perfect example where the ability to reopen classes comes in extremely handy, without any need to worry about “scary consequences.

Getting It Working

All we really need to figure out is where to put the code that reopens the class so that it gets picked up before Rails tries to launch the server. The only logical place is the script/rails executable itself, which will now look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
 
APP_PATH = File.expand_path('../../config/application',  __FILE__)
require File.expand_path('../../config/boot',  __FILE__)
require 'rack/handler'
Rack::Handler.class_eval do
  def self.default(options = {})
    # Guess.
    if ENV.include?("PHP_FCGI_CHILDREN")
      # We already speak FastCGI
      options.delete :File
      options.delete :Port
 
      Rack::Handler::FastCGI
    elsif ENV.include?("REQUEST_METHOD")
      Rack::Handler::CGI
    else
      begin
        Rack::Handler::Mongrel
      rescue LoadError
        begin
          Rack::Handler::Thin
        rescue LoadError
          Rack::Handler::WEBrick
        end
      end
    end
  end
end
require 'rails/commands'

This works without any problems, we type rails s and as long as Thin is in our Gemfile it starts up by default. As an aside, notice that I used class_eval to reopen Rack::Handler. Metaprogramming tricks like this should be part of every Ruby developer’s toolbox, I’ll talk more about this some other time (seeing as I am well into TL;DR land here).

Going through this exercise didn’t take long (under 30 minutes) and taught me a bit more about Rails and Rack. Shortly after doing this – in a curious case of the universe working in interesting ways – I came across a Stackoverflow question asking about this exact scenario and got an inordinate amount of satisfaction from being able to easily answer it :). In-fact, even the fact that shortly after Jason found Pow and we switched over to that, doesn’t really diminish the satisfaction of quickly solving a seemingly difficult problem in a neat way. The lesson here is this, no matter what problems you come across don’t automatically look for a library to handle it. Do spend a few minutes investigating – it might be enough to solve it (especially if you’re using Ruby) and you’ll certainly learn something.

Image by Gerard Stolk presque 64