hijacking support for Rack 1.5.x users
[rainbows.git] / lib / rainbows / ev_core.rb
blob5c3c5b8a500544fd0d22c7a49dfccdadfe716c26
1 # -*- encoding: binary -*-
2 # :enddoc:
3 # base module for evented models like Rev and EventMachine
4 module Rainbows::EvCore
5   include Rainbows::Const
6   include Rainbows::Response
7   NULL_IO = Unicorn::HttpRequest::NULL_IO
8   HttpParser = Rainbows::HttpParser
9   autoload :CapInput, 'rainbows/ev_core/cap_input'
10   RBUF = ""
11   Z = "".freeze
12   Rainbows.config!(self, :client_header_buffer_size)
13   HTTP_VERSION = "HTTP_VERSION"
15   # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
16   ASYNC_CALLBACK = "async.callback".freeze
17   ASYNC_CLOSE = "async.close".freeze
19   def write_async_response(response)
20     status, headers, body = response
21     if alive = @hp.next?
22       # we can't do HTTP keepalive without Content-Length or
23       # "Transfer-Encoding: chunked", and the async.callback stuff
24       # isn't Rack::Lint-compatible, so we have to enforce it here.
25       headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
26       alive = headers.include?(Content_Length) ||
27               !!(%r{\Achunked\z}i =~ headers[Transfer_Encoding])
28     end
29     @deferred = nil
30     ev_write_response(status, headers, body, alive)
31   end
33   def post_init
34     @hp = HttpParser.new
35     @env = @hp.env
36     @buf = @hp.buf
37     @state = :headers # [ :body [ :trailers ] ] :app_call :close
38   end
40   # graceful exit, like SIGQUIT
41   def quit
42     @state = :close
43   end
45   def want_more
46   end
48   def handle_error(e)
49     msg = Rainbows::Error.response(e) and write(msg)
50     ensure
51       quit
52   end
54   # returns whether to enable response chunking for autochunk models
55   # returns nil if request was hijacked in response stage
56   def stream_response_headers(status, headers, alive, body)
57     headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
58     if headers.include?(Content_Length)
59       write_headers(status, headers, alive, body) or return
60       return false
61     end
63     case @env[HTTP_VERSION]
64     when "HTTP/1.0" # disable HTTP/1.0 keepalive to stream
65       write_headers(status, headers, false, body) or return
66       @hp.clear
67       false
68     when nil # "HTTP/0.9"
69       false
70     else
71       rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
72       rv = false unless @env["rainbows.autochunk"]
73       write_headers(status, headers, alive, body) or return
74       rv
75     end
76   end
78   def prepare_request_body
79     # since we don't do streaming input, we have no choice but
80     # to take over 100-continue handling from the Rack application
81     if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
82       write(EXPECT_100_RESPONSE)
83       @env.delete(HTTP_EXPECT)
84     end
85     @input = mkinput
86     @hp.filter_body(@buf2 = "", @buf)
87     @input << @buf2
88     on_read(Z)
89   end
91   # TeeInput doesn't map too well to this right now...
92   def on_read(data)
93     case @state
94     when :headers
95       @hp.add_parse(data) or return want_more
96       @state = :body
97       if 0 == @hp.content_length
98         app_call NULL_IO # common case
99       else # nil or len > 0
100         prepare_request_body
101       end
102     when :body
103       if @hp.body_eof?
104         if @hp.content_length
105           @input.rewind
106           app_call @input
107         else
108           @state = :trailers
109           on_read(data)
110         end
111       elsif data.size > 0
112         @hp.filter_body(@buf2, @buf << data)
113         @input << @buf2
114         on_read(Z)
115       else
116         want_more
117       end
118     when :trailers
119       if @hp.add_parse(data)
120         @input.rewind
121         app_call @input
122       else
123         want_more
124       end
125     end
126     rescue => e
127       handle_error(e)
128   end
130   ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
132   def err_413(msg)
133     write(ERROR_413_RESPONSE)
134     quit
135     # zip back up the stack
136     raise IOError, msg, []
137   end
139   TmpIO = Unicorn::TmpIO
140   CBB = Unicorn::TeeInput.client_body_buffer_size
142   def io_for(bytes)
143     bytes <= CBB ? StringIO.new("") : TmpIO.new
144   end
146   def mkinput
147     max = Rainbows.server.client_max_body_size
148     len = @hp.content_length
149     if len
150       if max && (len > max)
151         err_413("Content-Length too big: #{len} > #{max}")
152       end
153       io_for(len)
154     else
155       max ? CapInput.new(io_for(max), self, max) : TmpIO.new
156     end
157   end