Asynchronous responses in Rack
With the Rack synchronous interface protocol, the entire body must be prepared immediately, or be otherwise quickly available, for example by reading from file. A response that must be waited upon will tie up the process or thread executing the HTTP request. In a multi-process Rack server such as Unicorn, this will block the entire worker process, making in unavailable to server other requests.
Some Rack servers provide an alternate interface that allows the request to be suspended, unblocking the worker. At some later time, the request may be resumed, and the response sent to the client.
While there is not yet an async interface in the Rack specification, several Rack servers have implemented James Tucker's async scheme.
Rather than returning [status, headers, body]
, the app returns a
status of -1
, or throws the symbol :async
.
The server provides
env['async.callback']
which the app saves and later calls with the
usual [status, headers, body]
to send the response.
Note: returning a status of -1
is illegal as far as Rack::Lint
is
concerned. throw :async
is not flagged as an error
.
class AsyncApp def call(env) Thread.new do sleep 5 # simulate waiting for some event response = [200, {'Content-Type' => 'text/plain'}, ['Hello, World!']] env['async.callback'].call response end [-1, {}, []] # or throw :async end end
In the example above, the request is suspended, nothing is sent back to the client, the connection remains open, and the client waits for a response.
The app returns the special status, and the worker process is able to handle more HTTP requests (i.e. it is not blocked). Later, inside the thread, the full response is prepared and sent to the client.