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.:
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 – only way to do that is to read some code. 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:
#!/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:
#!/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 = ">= 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:
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:
#!/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's start method from its start method) all it adds is those familiar lines we're all used to seeing e.g.:
=> Booting Thin => Rails 3.0.7 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => 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:
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.
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:
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:
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:
#!/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
Related posts:
- Digging Into A Ruby Installation, Require vs Load And Other Stuff
- Fetching RSS Feeds With Ruby From Behind A Proxy
- Using Ruby On Rails With Oracle And Deploying It All To Tomcat
- Method Arguments In Ruby
- Serializing (And Deserializing) Objects With Ruby
- Installing And Using SQLite With Ruby On Windows
- The Most Handy NetBeans Shortcuts For Ruby Development

{ 17 comments… read them below or add one }
Nice writeup :). Reading code has its rewards.
There is a small typo. It should be “bear with me” and not “bare with me”
Doh, so it should
Actually, he’s correct it should be ‘bear with me’. You can Google it to confirm http://www.google.com?q=define+bear
If you want to avoid reopening the class, you could also just:
ARGV << "thin"
at start of script/rails
I hadn’t even though of doing that, yeah you’re right it should work even if you provide some options (such as -d to deamonize it) – a very nice simple solution. Looking at it again now, it occurs to me that you could also override the default options method inside Rails::Server and provide the server that way:
This is also simpler than my original solution (although not as simple as appending to ARGV), I guess I was just having too much fun digging into Rails and Rack trying to figure out how things hang together :).
I hear ya, always good to dig into how things work, never know when what you discover will come in handy :)
Why not just create a Bash alias for “rails s thin”? Eg:
alias rst=”bundle exec rails server thin”
Hi Nick,
No reason, creating an alias is a valid way to get around the issue. I do have a couple of niggles against it. Firstly it is creating a totally different command rather than using rails s which everyone knows, not a biggie but nevertheless.
Secondly, every developer has to create one of these as it doesn’t live in the codebase but rather in your machine environment, also not a biggie but there it is.
Lastly if you’re using RVM with a separate gemsets per project, when you jump into a different project which doesn’t have thin as one of the installed gems, your alias is still active and will blow up if you try to invoke it, of course you know not to do that but once again there it is.
Solving the problem within the codebase avoids all these issues.
You set out this blog post as a configuration of a personal preference and now are arguing that it is an issue that it wouldn’t apply to the whole team. Which is it?
Creating an alias would actually save you *more* typing than your result because you can name it whatever you want. Further, regarding your last point, there’s no rule saying that it has to be an alias, it could be a bash script with some conditional environmental handling.
Aliases or scripts are exactly the right answer here, changing expected defaults is not.
Finally, I think your primary takeaway is nonsensical. It uncategorically states a position that isn’t defensible; there are absolutely correct times to use a library – you have proven this by using rails.
Similarly, I heard about this on the Ruby Show, and my first thought was “Great lengths? How hard is it to stick `alias r=’rails’` in your bash profile?” (=
Any thoughts about submitting that patch to the Rack folks so we all don’t have to monkey patch Rack if we want to use thin?
I can certainly do that, although it seems funny to me that it hasn’t been done by someone already, mongrel has been out of vogue for a while now so it seems weird to keep it as a default. Maybe I’ll take a punt and submit it like you suggest, it will only take a few minutes.
Nice hack. I’ve a few comments.
Firstly, I notice both in the default Rack handler, as well as in the list of handlers available through Rack that there’s no handler for Unicorn and also that there are lot of servers listed that I’d have thought were dead, e.g. all the mongrel variations.
Secondly, is there no better way than rescue to select a handler? Is there no check to be made if a Mongrel or a Thin handler is available before defaulting to Webrick? I mean the original code for that was a bit ugly and the addition of Thin uglified it more. I’m not sure if doing a begin/rescue block inside a begin/rescue block has a performance penalty, as well.
Other than that, great look inside a small part of Rails internals.
Hey,
Yeah, I found it curious as well that so many of the handlers were for servers which are essentially on the out. As to Unicorn, I’ve never used it, but from the 2 second look I had, it doesn’t look like it has a Rack handler. In-fact it doesn’t seem to be the way that the Unicorn people want Unicorn to be used, rather you should be using the executable provided with the unicorn gem to launch the server (instead of doing
rails s).You can definitely check (perhaps via
const_get) if a particular class/handler is defined, so a begin/rescue is not the only option.Is the primary takeaway from this blog post that you should never use a library, or that a library is not the correct solution to some, or even most problems? Where did you read that?
Have you considered using Pow? Then you wouldn’t need to use “rails s” at all :)
Yeah we ‘arrived’ at using Pow eventually :).