hijacking support for Rack 1.5.x users
[rainbows.git] / lib / rainbows / stream_response_epoll.rb
blob33d7386f658d71d2768f7f8b8d77496012436b5a
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.  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
22   # :stopdoc:
23   CODES = Unicorn::HttpResponse::CODES
24   HEADER_END = "X-Accel-Buffering: no\r\n\r\n"
25   autoload :Client, "rainbows/stream_response_epoll/client"
27   def http_response_write(socket, status, headers, body)
28     status = CODES[status.to_i] || status
29     hijack = ep_client = false
31     if headers
32       # don't set extra headers here, this is only intended for
33       # consuming by nginx.
34       buf = "HTTP/1.0 #{status}\r\nStatus: #{status}\r\n"
35       headers.each do |key, value|
36         case key
37         when "rack.hijack"
38           hijack = hijack_prepare(value)
39           body = nil # ensure we do not close body
40         else
41           if /\n/ =~ value
42             # avoiding blank, key-only cookies with /\n+/
43             buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
44           else
45             buf << "#{key}: #{value}\r\n"
46           end
47         end
48       end
49       buf << HEADER_END
51       case rv = socket.kgio_trywrite(buf)
52       when nil then break
53       when String # retry, socket buffer may grow
54         buf = rv
55       when :wait_writable
56         ep_client = Client.new(socket, buf)
57         if hijack
58           ep_client.hijack(hijack)
59         else
60           body.each { |chunk| ep_client.write(chunk) }
61           ep_client.close
62         end
63         # body is nil on hijack, in which case ep_client is never closed by us
64         return
65       end while true
66     end
68     if hijack
69       hijack.call(socket)
70       return
71     end
73     body.each do |chunk|
74       if ep_client
75         ep_client.write(chunk)
76       else
77         case rv = socket.kgio_trywrite(chunk)
78         when nil then break
79         when String # retry, socket buffer may grow
80           chunk = rv
81         when :wait_writable
82           ep_client = Client.new(socket, chunk)
83           break
84         end while true
85       end
86     end
87   ensure
88     return if hijack
89     body.respond_to?(:close) and body.close
90     if ep_client
91       ep_client.close
92     else
93       socket.shutdown
94       socket.close
95     end
96   end
98   # once a client is accepted, it is processed in its entirety here
99   # in 3 easy steps: read request, call app, write app response
100   def process_client(client)
101     status, headers, body = @app.call(env = @request.read(client))
103     if 100 == status.to_i
104       client.write(Unicorn::Const::EXPECT_100_RESPONSE)
105       env.delete(Unicorn::Const::HTTP_EXPECT)
106       status, headers, body = @app.call(env)
107     end
108     @request.headers? or headers = nil
109     return if @request.hijacked?
110     http_response_write(client, status, headers, body)
111   rescue => e
112     handle_error(client, e)
113   end
115   # :startdoc: