0-60: Deploying Goliath on Heroku Cedar
Earlier this week Heroku rolled out a major upgrade to their webstack. The HTTP/1.1 support and the billing upgrades are both great improvements, but the "process model" definitely takes the crown: all of the sudden Heroku is much more than a Ruby+Rack hosting platform. Prior to this release, Heroku hosted Ruby only apps on top of the Thin web-server. Each "dyno" represented an instance of Thin, and as long as you deployed a Rack-compatible app, then Heroku would spin it up and run it for you. This was great, but also somewhat limiting - for one, you couldn't run non-Rack apps!
The process model changes all that. You can now run any application within their cloud, with the help of a simple Procfile. In fact, it doesn't even have to be Ruby! Node, Clojure apps, long-running Ruby workers, it is all fair game.
Goliath on Heroku Cedar
Goliath is an async web server and framework we developed at PostRank. While it does follow the Rack spec, it is also substantially different: it requires Ruby 1.9 runtime for Fiber support, and it uses an entirely different HTTP parser from Thin. Combined, these differences meant that up until now, deploying Goliath on any existing cloud environment was a no-go. However, with the new cedar stack, this is no longer an issue:
source :gemcutter
gem 'goliath', :git => 'git://github.com/postrank-labs/goliath.git'
gem 'em-http-request', :git => 'git://github.com/igrigorik/em-http-request.git'
gem 'em-synchrony', :git => 'git://github.com/igrigorik/em-synchrony.git'
gem 'yajl-ruby'
require 'goliath'
require 'em-synchrony/em-http'
require 'em-http/middleware/json_response'
require 'yajl'
# automatically parse the JSON HTTP response
EM::HttpRequest.use EventMachine::Middleware::JSONResponse
class Hello < Goliath::API
# parse query params and auto format JSON response
use Goliath::Rack::Params
use Goliath::Rack::Formatters::JSON
use Goliath::Rack::Render
def response(env)
resp = nil
if params['query']
# simple GitHub API proxy example
logger.info "Starting request for #{params['query']}"
conn = EM::HttpRequest.new("http://github.com/api/v2/json/repos/search/#{params['query']}").get
logger.info "Received #{conn.response_header.status} from Github"
resp = conn.response
else
resp = env # output the Goalith environment
end
[200, {'Content-Type' => 'application/json'}, resp]
end
end
web: bundle exec ruby hello.rb -sv -e prod -p $PORT
A simple "Hello World HTTP proxy" example. Our API will parse an incoming request, dispatch an async HTTP request and return the results back to the user - no callbacks! We specified our libraries in a Gemfile, and we provided a Procfile with the startup parameters for our API. Believe it or not, that is all what we need. To deploy it:
$ git init .
$ git add . && git commit -a -m 'hello world'
$ heroku create --stack cedar goliath-demo
$ git push heroku master
$ curl http://goliath.herokuapp.com/
$ curl http://goliath.herokuapp.com/?query=eventmachine
Running a simple benchmark and checking the logs (heroku logs) shows that an average request to our new Goliath API takes approximately 0.35ms (2500+ req/s). Not enough to handle the load? Simply start another API: heroku scale web=2.
Goliath, Heroku & the Cloud
Combine easy deployment, support for HTTP/1.1 and the async nature of Goliath, and all of the sudden you can develop and deploy simple streaming API's and async API endpoints with near minimal effort! If you can't tell, definitely something I am very excited about - a huge step for Heroku! Now, we just need to wait for a response from the CloudFoundry team - this should get interesting.