3bb35404d91532c89902a940ce1f2613b5ae14f8
[rainbows.git] / lib / rainbows / stream_response_epoll.rb
blob3bb35404d91532c89902a940ce1f2613b5ae14f8
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     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         if value =~ /\n/
37           # avoiding blank, key-only cookies with /\n+/
38           buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
39         else
40           buf << "#{key}: #{value}\r\n"
41         end
42       end
43       buf << HEADER_END
45       case rv = socket.kgio_trywrite(buf)
46       when nil then break
47       when String # retry, socket buffer may grow
48         buf = rv
49       when :wait_writable
50         ep_client = Client.new(socket, buf)
51         body.each { |chunk| ep_client.write(chunk) }
52         return ep_client.close
53       end while true
54     end
56     body.each do |chunk|
57       if ep_client
58         ep_client.write(chunk)
59       else
60         case rv = socket.kgio_trywrite(chunk)
61         when nil then break
62         when String # retry, socket buffer may grow
63           chunk = rv
64         when :wait_writable
65           ep_client = Client.new(socket, chunk)
66           break
67         end while true
68       end
69     end
70     ensure
71       body.respond_to?(:close) and body.close
72       if ep_client
73         ep_client.close
74       else
75         socket.shutdown
76         socket.close
77       end
78   end
80   # once a client is accepted, it is processed in its entirety here
81   # in 3 easy steps: read request, call app, write app response
82   def process_client(client)
83     status, headers, body = @app.call(env = @request.read(client))
85     if 100 == status.to_i
86       client.write(Unicorn::Const::EXPECT_100_RESPONSE)
87       env.delete(Unicorn::Const::HTTP_EXPECT)
88       status, headers, body = @app.call(env)
89     end
90     @request.headers? or headers = nil
91     http_response_write(client, status, headers, body)
92   rescue => e
93     handle_error(client, e)
94   end
96   # :startdoc:
97 end