Sinatra Diet

Warning: This is stuff I'm playing with, definitely not ready for anything production.

Sinatra on a Diet gets Thin and Skinny, asynchronously

Sometimes Sinatra can get a bit fat--he's squeezing through the doorway, gets stuck, and nobody else can get through for a while. It's time to go on a diet to get Thin and Skinny.

One of Thin's greatest strength is asynchronous responses. This adds two ways to do so from within Sinatra: plain asynchronous responses and WebSockets (via Skinny).

This is actually two Sinatra extensions:


I know there's already a sinatra-async extension but I felt it was overly complex and didn't quite add what I wanted. My take on asynchronous Sinatra tried to be a little simpler. For the timeless classic:

register Sinatra::Async

get '/' do
  async do
    "Hello, World"

This literally just delays response to the next available EventMachine tick.

If you actually want to wait on a long-running asynchronous operation you have a couple of options. You can yield a deferrable and succeed it with the response:

get '/long' do
  async do
    @deferrable =

# somewhere else:
@deferrable.succeed "Hello, world!"

You can also use a long-running operation which will call #async_respond explicitly. EM::Timers, EM::PeriodicTimers and nil responses to an async block mean you'll call #async_respond later:

get '/long' do
  async do do
      async_respond 'Hello, world!'


Build websockets simply and easily using a Sinatra-inspired DSL:

register Sinatra::Async

websocket do |client, message|
  client.send "You said: #{message}"

They catch GET websocket requests only, by default. You can also mount them on a path and give them explicit options:

websocket '/hello',
  :on_handshake => proc do |client|
    client.send "Hi!"

The clients are Skinny::WebSocket instances, and you can supply any options you would normally pass to an instance in the handler call:

websocket '/thing',
  :protocol => "adder",
  :on_message => proc do |client, message|
    client.send message.split(' '), &:+)

Keep in mind that the proc callbacks supplied as options here are executed in the scope in which they're defined, here in the class scope of your sinatra app. This is by design--executing each handler inside a Sinatra instance means that instance (which is copied for every request) must hang around for the WebSocket connection's entire lifetime. If you want this, please implement it yourself.

Don't forget that the websocket client connection has a copy of the request's environment (as #env) which you can use inside callbacks.


Be aware: Long-running requests will keep a whole copy of your Sinatra app around until you complete them. Be careful to close every request and websocket you handle asynchronously or you'll find yourself in memory leak city.

This stuff only works on Thin. Patches for other EventMachine-based servers are welcome. Other wild and exotic servers are also considered, if you're brave! I'm looking at ControlTower, mainly.


  • Lightweight WebSocket channels.
  • ???
  • Profit

Copyright (c) 2010 Samuel Cochran. See LICENSE for details.


Do I get points for taking a metaphor too far?