46feaff7d248d76681091bf8df3c3a68ebbd20a0
[rainbows.git] / lib / rainbows / ev_core.rb
blob46feaff7d248d76681091bf8df3c3a68ebbd20a0
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   def stream_response_headers(status, headers, alive)
56     headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
57     if headers.include?(Content_Length)
58       write_headers(status, headers, alive)
59       return false
60     end
62     case @env[HTTP_VERSION]
63     when "HTTP/1.0" # disable HTTP/1.0 keepalive to stream
64       write_headers(status, headers, false)
65       @hp.clear
66       false
67     when nil # "HTTP/0.9"
68       false
69     else
70       rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
71       rv = false unless @env["rainbows.autochunk"]
72       write_headers(status, headers, alive)
73       rv
74     end
75   end
77   def prepare_request_body
78     # since we don't do streaming input, we have no choice but
79     # to take over 100-continue handling from the Rack application
80     if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
81       write(EXPECT_100_RESPONSE)
82       @env.delete(HTTP_EXPECT)
83     end
84     @input = mkinput
85     @hp.filter_body(@buf2 = "", @buf)
86     @input << @buf2
87     on_read(Z)
88   end
90   # TeeInput doesn't map too well to this right now...
91   def on_read(data)
92     case @state
93     when :headers
94       @hp.add_parse(data) or return want_more
95       @state = :body
96       if 0 == @hp.content_length
97         app_call NULL_IO # common case
98       else # nil or len > 0
99         prepare_request_body
100       end
101     when :body
102       if @hp.body_eof?
103         if @hp.content_length
104           @input.rewind
105           app_call @input
106         else
107           @state = :trailers
108           on_read(data)
109         end
110       elsif data.size > 0
111         @hp.filter_body(@buf2, @buf << data)
112         @input << @buf2
113         on_read(Z)
114       else
115         want_more
116       end
117     when :trailers
118       if @hp.add_parse(data)
119         @input.rewind
120         app_call @input
121       else
122         want_more
123       end
124     end
125     rescue => e
126       handle_error(e)
127   end
129   ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
131   def err_413(msg)
132     write(ERROR_413_RESPONSE)
133     quit
134     # zip back up the stack
135     raise IOError, msg, []
136   end
138   TmpIO = Unicorn::TmpIO
139   CBB = Unicorn::TeeInput.client_body_buffer_size
141   def io_for(bytes)
142     bytes <= CBB ? StringIO.new("") : TmpIO.new
143   end
145   def mkinput
146     max = Rainbows.server.client_max_body_size
147     len = @hp.content_length
148     if len
149       if max && (len > max)
150         err_413("Content-Length too big: #{len} > #{max}")
151       end
152       io_for(len)
153     else
154       max ? CapInput.new(io_for(max), self, max) : TmpIO.new
155     end
156   end