Cloud Zone is brought to you in partnership with:

Alan is what they call passionate about technology and software development. In his time he's done consulting, worked for large corporates, done product development, been part of an R&D/labs team and even done the startup thing. He's work in small teams and large, using methodologies that were Agile and no-so-Agile. You can always read his latest thoughts on his blog - skorks.com and he even puts in the occassional appearance on Twitter as @skorks. Alan is a DZone MVB and is not an employee of DZone and has posted 6 posts at DZone. You can read more from them at their website. View Full User Profile

When Developers Go To Great Length To Save Typing 4 Letters

09.21.2011
| 9658 views |
  • submit to reddit
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:

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:

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:

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:

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:

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

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:

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:

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.

References
Published at DZone with permission of Alan Skorkin, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Nabeel Manara replied on Fri, 2012/01/27 - 10:21am

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.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.