I’ve been looking into using non-blocking web frameworks lately, and I started out with the poster-child for this movement; Node.js. While Node is very nice (especially when combined with CoffeeScript), it has some rough edges.
I currently prefer to use Ruby on the server side, and to keep my CoffeeScripts more towards the front end. (For example with Backbone.js)
Background
There are quite a few non-blocking Ruby web frameworks out there. I’ve looked at three of them; Sinatra::Synchrony, Goliath and Cramp.
I REALLY like the idea behind Sinatra::Synchrony, but it doesn’t seem to be as “battle hardened” as Goliath. It also has some issues with Ruby 1.9.3.
I’ve been thinking about using Goliath ever since I saw the presentation 0-60 with Goliath: Building High Performance Ruby Web-Services (video) at Øredev 2011.
Dependencies
Goliath is a young project, and it seems like most people use it straight from the master branch on GitHub, and so will I. (NOTE: My workaround for MiniTest does not work with Goliath 0.9.4)
I am also going to use the Ruby client library for Redis, which has built in support for EM-Synchrony.
Gemfile
source :rubygems
gem "goliath",
:git => "https://github.com/postrank-labs/goliath.git",
:require => ["goliath/api", "log4r"]
gem "hiredis", "~> 0.3.1"
gem "redis", "~> 2.2.2",
:require => ["redis/connection/synchrony", "redis"]
group :test do
gem "em-http-request", "~> 1.0.1"
gem "mock_redis", "~> 0.3.0"
end
Testing
You didn’t think I would skip on the testing, right?
My personal favorite among the myriad of Ruby test frameworks is minitest (The default test framework in Ruby 1.9) and that is what I’m going to use in this article.
I have managed to cobble together a spec/spec_helper.rb that makes it
possible to test Goliath using minitest/spec.
(The tests for Goliath applications I’ve seen so far all use RSpec)
spec/spec_helper.rb
require 'bundler'
Bundler.setup
Bundler.require
require 'goliath/test_helper'
require 'minitest/autorun'
require 'minitest/pride'
require 'minitest/spec'
require 'mock_redis'
$redis = MockRedis.new
class Goliath::Server
def load_config(file = nil)
config['redis'] = $redis
end
end
module Goliath
module TestHelper
def test_request(request_data = nil)
return if request_data.nil?
path = request_data.delete(:path) || ''
EM::HttpRequest.new("http://localhost:#{@test_server_port}#{path}")
end
end
end
spec/api_spec.rb
I hope that writing specs for Goliath will become less verbose in the
future, currently it seems like I need to wrap each request in a
with_api block.
require_relative 'spec_helper'
require_relative '../api'
describe Api do
include Goliath::TestHelper
it "responds to heartbeat" do
with_api Api, { log_stdout: false } do
get_request path: '/' do |api|
api.response.must_equal 'OK'
end
end
end
it "can set and retrieve data" do
with_api Api, { log_stdout: false } do
get_request path: '/foo' do |api|
api.response.must_equal ''
end
end
with_api Api, { log_stdout: false } do
put_request path: '/foo?value=bar' do |api|
api.response.must_equal 'OK'
end
end
with_api Api, { log_stdout: false } do
get_request path: '/foo' do |api|
api.response.must_equal 'bar'
end
end
end
end
The application
This is a very simple API, it can only do three things:
- Respond with “OK” and status code 200
GET / - Add new data
PUT /foo?value=bar - Retrieve a value
GET /bar
api.rb
require 'goliath/api'
class GetData < Goliath::API
def response(env)
[200, {}, redis.get('example_api:' + params[:key])]
end
end
class SetData < Goliath::API
use Goliath::Rack::Validation::RequiredParam, key: 'value'
def response(env)
[200, {}, redis.set('example_api:' + params[:key], params['value'])]
end
end
class Api < Goliath::API
use Goliath::Rack::Heartbeat, path: '/'
put '/:key', SetData
get '/:key', GetData
end
Starting the application
First you need to have a Redis server running.
The final two pieces of the puzzle is a config.rb that contains the connection to Redis via a
EM::Synchrony::ConnectionPool and a Goliath::Runner that takes care of running the application.
config.rb
config['redis'] = EM::Synchrony::ConnectionPool.new(size: 2) do
Redis.new
end
runner.rb
require "bundler"
Bundler.setup
Bundler.require
require 'goliath/runner'
require_relative 'api'
runner = Goliath::Runner.new(ARGV, nil)
runner.api = Api.new
runner.app = Goliath::Rack::Builder.build(Api, runner.api)
runner.run
Run ruby runner.rb -s -e prod -c config.rb and a server should spin up in production mode on port 9000.
Using the application
I like to use cURL when manually testing API's:
Adding a new key/value pair
curl -X PUT http://0.0.0.0:9000/foo -d "value=bar"
Retrieve a stored value
curl -X GET http://0.0.0.0:9000/foo