stream_response_epoll: our most "special" concurrency option yet
[rainbows.git] / lib / rainbows / stream_response_epoll.rb
blob9ded81026fffddb0620c19eec027ebe68d600b34
1 # -*- encoding: binary -*-
2 require "sleepy_penguin"
3 require "raindrops"
5 # Like \Unicorn itself, this concurrency model is only intended for use
6 # behind nginx and completely unsupported otherwise.
8 # It does NOT require a thread-safe Rack application at any point, but
9 # allows streaming data asynchronously via nginx (using the the
10 # "X-Accel-Buffering: no" header).
12 # Unlike Rainbows::Base, this does NOT support persistent
13 # connections or pipelining.  All \Rainbows! specific configuration
14 # options are ignored (except Rainbows::Configurator#use).
16 # === RubyGem Requirements
18 # * raindrops 0.6.0 or later
19 # * sleepy_penguin 3.0.1 or later
20 module Rainbows::StreamResponseEpoll
21   # :stopdoc:
22   CODES = Unicorn::HttpResponse::CODES
23   HEADER_END = "X-Accel-Buffering: no\r\n\r\n"
24   autoload :Client, "rainbows/stream_response_epoll/client"
26   def http_response_write(socket, status, headers, body)
27     status = CODES[status.to_i] || status
28     ep_client = false
30     if headers
31       buf = "HTTP/1.0 #{status}\r\nStatus: #{status}\r\n"
32       headers.each do |key, value|
33         if value =~ /\n/
34           # avoiding blank, key-only cookies with /\n+/
35           buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
36         else
37           buf << "#{key}: #{value}\r\n"
38         end
39       end
40       buf << HEADER_END
42       case rv = socket.kgio_trywrite(buf)
43       when nil then break
44       when String # retry, socket buffer may grow
45         buf = rv
46       when :wait_writable
47         ep_client = Client.new(socket, buf)
48         body.each { |chunk| ep_client.write(chunk) }
49         return ep_client.close
50       end while true
51     end
53     body.each do |chunk|
54       if ep_client
55         ep_client.write(chunk)
56       else
57         case rv = socket.kgio_trywrite(chunk)
58         when nil then break
59         when String # retry, socket buffer may grow
60           chunk = rv
61         when :wait_writable
62           ep_client = Client.new(socket, chunk)
63           break
64         end while true
65       end
66     end
67     ep_client.close if ep_client
68     ensure
69       body.respond_to?(:close) and body.close
70   end
71   # :startdoc:
72 end