1 # -*- encoding: binary -*-
2 require "sleepy_penguin"
5 # Like Unicorn itself, this concurrency model is only intended for use
6 # behind nginx and completely unsupported otherwise. Even further from
7 # Unicorn, this isn't even a good idea with normal LAN clients, only nginx!
9 # It does NOT require a thread-safe Rack application at any point, but
10 # allows streaming data asynchronously via nginx (using the
11 # "X-Accel-Buffering: no" header to disable buffering).
13 # Unlike Rainbows::Base, this does NOT support persistent
14 # connections or pipelining. All \Rainbows! specific configuration
15 # options are ignored (except Rainbows::Configurator#use).
17 # === RubyGem Requirements
19 # * raindrops 0.6.0 or later
20 # * sleepy_penguin 3.0.1 or later
21 module Rainbows::StreamResponseEpoll
23 autoload :Client, "rainbows/stream_response_epoll/client"
25 def http_response_write(socket, status, headers, body)
26 hijack = ep_client = false
29 # don't set extra headers here, this is only intended for
32 msg = Rack::Utils::HTTP_STATUS_CODES[code]
33 buf = "HTTP/1.0 #{msg ? %Q(#{code} #{msg}) : status}\r\n"
34 headers.each do |key, value|
38 body = nil # ensure we do not close body
41 # avoiding blank, key-only cookies with /\n+/
42 value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
44 buf << "#{key}: #{value}\r\n"
48 buf << "X-Accel-Buffering: no\r\n\r\n".freeze
50 case rv = socket.kgio_trywrite(buf)
52 when String # retry, socket buffer may grow
55 ep_client = Client.new(socket, buf)
57 ep_client.hijack(hijack)
59 body.each { |chunk| ep_client.write(chunk) }
62 # body is nil on hijack, in which case ep_client is never closed by us
74 ep_client.write(chunk)
76 case rv = socket.kgio_trywrite(chunk)
78 when String # retry, socket buffer may grow
81 ep_client = Client.new(socket, chunk)
88 body.respond_to?(:close) and body.close
97 # once a client is accepted, it is processed in its entirety here
98 # in 3 easy steps: read request, call app, write app response
99 def process_client(client)
100 status, headers, body = @app.call(env = @request.read(client))
102 if 100 == status.to_i
103 client.write("HTTP/1.1 100 Continue\r\n\r\n".freeze)
104 env.delete('HTTP_EXPECT'.freeze)
105 status, headers, body = @app.call(env)
107 @request.headers? or headers = nil
108 return if @request.hijacked?
109 http_response_write(client, status, headers, body)
111 handle_error(client, e)