Rack::Async

Rack::Async is a rack middleware that makes available a thin like async api on most rack compatible web servers.

It works with mongrel, thin, and all the related mongrel-derived servers I’ve tried, additionally ebb works fine.

Although WEBrick won’t support sending asynchronous replies, using Rack::Async will successfully send everything you ask it to, just bundled up all together like a normal synchronous reply.

It won’t work with MacRuby’s ControlTower at all.

Installation

gem install rack-async

Common problems

Rack::Lint::LintError

Rack::Lint doesn’t know anything about async responses, so can’t validate them properly, you’ll have to turn it off.

The response doesn’t seem asynchronous in my browser

Most browsers won’t start rendering anything until they have around 1024 bytes of data. One way to get around this is to simply send 1024 bytes of whitespace at the start of your response. If you’re just looking to see your response come in chunk by chunk, ‘curl` doesn’t suffer this problem.

Async Response with EventMachine

Here’s how to send an async response with EventMachine that will work across thin, mongrel, ebb, etc

config.ru

require 'rubygems'
require 'rack/async'
require 'eventmachine'

class EMAsyncApp
  def call(env)
    event_machine do
      EM.add_timer(5) do
        env['async.callback'].call([200, {}, ["Hello world!"]])
      end
    end

    # returning this signals to the server we are sending an async response
    Rack::Async::RESPONSE
  end

  private
  # make sure EventMachine is running (if we're on thin it'll be up and
  # running, but this isn't the case on other servers).
  def event_machine(&block)
    if EM.reactor_running?
      block.call
    else
      Thread.new {EM.run}
      EM.next_tick(block)
    end
  end
end

use Rack::Async
run EMAsyncApp.new

Async Response with Threads

Here’s how to send an async response using threads that will work across thin, mongrel, ebb, etc

(we’ll just give you the #call method this time)

def call(env)
  Thread.new do
    sleep 5
    env['async.callback'].call([200, {}, ["Hello world!"]])
  end

  Rack::Async::RESPONSE
end

Async Body with EventMachine

Sure sending an asynchronous response is neat, but what really cool is streaming out the body to the client, here’s how to do that

config.ru

require 'rubygems'
require 'rack/async'
require 'eventmachine'

class EMAsyncBodyApp
  def call(env)
    body = env['async.body']

    event_machine do
      i = 0
      timer = EM.add_periodic_timer(1) do
        body << "Hello world!\r\n"
        i += 1
        if i >= 5
          body.finish
          timer.cancel
        end
      end
    end

    [200, {}, body]
  end

  private
  def event_machine(&block)
    if EM.reactor_running?
      block.call
    else
      Thread.new {EM.run}
      EM.next_tick(block)
    end
  end
end

use Rack::Async
run EMAsyncBodyApp.new

Async Everything with Threads

You can combine the two like so (threaded example this time, but it works just fine with event machine too).

require 'rubygems'
require 'rack/async'
require 'thread'

class ThreadedAsyncApp
  def call(env)
    Thread.new do
      sleep 1
      body = env['async.body']
      env['async.callback'].call([200, {}, body])
      5.times {sleep 1; body << "Hello world!\r\n";}
      body.finish
    end

    Rack::Async::RESPONSE
  end
end

use Rack::Async
run ThreadedAsyncApp.new

Licence

(The MIT License)

Copyright © 2011 Matthew Sadler

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.