Running a SproutCore Development Server with Rack (and Passenger)

UPDATE: Please understand this is only for development mode. It should not be used in Production. SproutCore provides sc-build for deployment.

Here at Centro we maintain a SproutCore application that sits in front of a number of Ruby applications on the backend. Our project has evolved heavily over the last year or so: we started with the standard sc-server, eventually moved to Unicorn, and finally to Passenger. There doesn't seem to be a lot of discussion around the use of the SproutCore build tools and I hope this writeup proves useful to others looking to do something similar.

In the early stages of development we designed our Ruby services to make requests to http://localhost:4020, as sc-server generously provides a Buildfile that allows for easy proxy configuration. At the time it seemed like a great way to DRY up our configuration in development mode. Why bother to track everybody's port number in each project?

# ===========================================================================
# Project:   Transis
# Copyright: ©2009 Centro
# ===========================================================================

config :all, :required => [:sproutcore, :blueprint, :transis],
  :test_required => [:test_runner_extension]

config :examples, :load_fixtures => true
config :main, :theme => 'transis', :layout => 'lib/index.rhtml'

proxy "/id",        :to => "localhost:3002"
proxy "/planning",  :to => "localhost:3003"
proxy "/templates", :to => "localhost:3004"
proxy "/research",  :to => "localhost:3005"
...


We started running into issues pretty quickly with sc-server being single threaded. For example, when a user's browser makes a request to /research that app will make a request to /id to verify their authenticity. Since everything is running through a single threaded server timeouts quickly start piling up and the app ground to a halt.

We experimented with a teeny Rack based proxy server but quickly bailed. There had to be a better way. All of our Ruby services play nice with Rack (thanks to Sinatra and Rails), and we know the SproutCore development server is written in Ruby. We wanted to unify our development environment as the collection of scripts required was getting out of hand. The next approach was to replace the default Thin server used in sc-server with a cluster of Unicorns. This meant we had to dive into the SproutCore source and figure out how sc-server really works.

After a brief look around, it became clear the place to start is lib/sproutcore/tools/server.rb. Check out the code snippet below.

# get project and start service.
project = requires_project!

...

SC.logger << "SproutCore v#{SC::VERSION} Development Server\n"
SC::Rack::Service.start(options.merge(:project => project))

Since SproutCore is built around Rack, this should be pretty easy. The last line is pointing to SC::Rack::Service.start, so our next step was to see what's going on in there. Below are a few crucial excerpts from the start method (a lot of configuration has been removed).

# Guess.
if ENV.include?("PHP_FCGI_CHILDREN")
  server = ::Rack::Handler::FastCGI

  # We already speak FastCGI
  options.delete :File
  options.delete :Port
elsif ENV.include?("REQUEST_METHOD")
  server = ::Rack::Handler::CGI
else
  begin
    server = ::Rack::Handler::Thin
  rescue LoadError => e
    begin
      server = ::Rack::Handler::Mongrel
    rescue LoadError => e
      server = ::Rack::Handler::WEBrick
    end
  end
end

...

app = self.new(*projects)

...

SC.logger << "Starting server at http://#{opts[:Host] || '0.0.0.0'}:#{opts[:Port]} in #{SC.build_mode} mode\n"
SC.logger << "To quit sc-server, press Control-C\n"
server.run app, opts

Now that we've seen sc-server is nothing more than a simple Rack app, the only piece of this puzzle still unsolved was the projects. How does sc-server generate that array? Back in lib/sproutcore/tool/server.rb (see the snippet above) it seems SC::Tools implements require_project! which eventually relies on SC::Project to do all the dirty work.

Putting all this together we managed to to design a very concise config.ru file. Check it out.

require 'rubygems'
require 'sproutcore'

root = Pathname(Dir.pwd).expand_path
project = SC::Project.load_nearest_project(root, :parent => SC.builtin_project)
service = SC::Rack::Service.new([project])
run service

We ran Unicorn with 6 workers for a while and it was going pretty well, but as our dev tools matured we decided to start running our backends in Passenger locally. Converting from Unicorn to Passenger was easy, here's where the awesome nature of Rack really shines. The above config.ru file just works with Passenger, nothing had to change. All you need is a vhost similar to the example below.

<VirtualHost *:80>
  ServerName transis.local
  DocumentRoot "/Users/abloom/Sites/centro/transis-ui/public"

  ErrorLog /var/log/apache2/transis-error.log
  CustomLog /var/log/apache2/transis-access.log combined

  # Rails application URIs
  RailsBaseURI /planning
  RailsBaseURI /research
  RailsEnv development

  # Rack application URIs
  RackBaseURI /id
  RackBaseURI /files
  RackBaseURI /templates
  RackEnv development
</VirtualHost>

By moving our entire development system to Passenger we were able to more closely replicate our production environment as well as simplify our startup and shutdown scripts. No longer must we rely on PID files or the output of lsof. Simply touch tmp/restart.txt in any of the projects to get Apache to reload your code. Life is good when things are easy.