s/max_bytes/client_max_body_size/ for consistency
[rainbows.git] / lib / rainbows / ev_core.rb
blobfb19b04f6249e5ed8baa2f948c5de47ece2a6581
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   HBUFSIZ = Rainbows.client_header_buffer_size
14   # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
15   ASYNC_CALLBACK = "async.callback".freeze
16   ASYNC_CLOSE = "async.close".freeze
18   def write_async_response(response)
19     status, headers, body = response
20     if alive = @hp.next?
21       # we can't do HTTP keepalive without Content-Length or
22       # "Transfer-Encoding: chunked", and the async.callback stuff
23       # isn't Rack::Lint-compatible, so we have to enforce it here.
24       headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
25       alive = headers.include?(Content_Length) ||
26               !!(%r{\Achunked\z}i =~ headers[Transfer_Encoding])
27     end
28     @deferred = nil
29     ev_write_response(status, headers, body, alive)
30   end
32   def post_init
33     @hp = HttpParser.new
34     @env = @hp.env
35     @buf = @hp.buf
36     @state = :headers # [ :body [ :trailers ] ] :app_call :close
37   end
39   # graceful exit, like SIGQUIT
40   def quit
41     @state = :close
42   end
44   def want_more
45   end
47   def handle_error(e)
48     msg = Rainbows::Error.response(e) and write(msg)
49     ensure
50       quit
51   end
53   # returns whether to enable response chunking for autochunk models
54   def stream_response_headers(status, headers, alive)
55     headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
56     if headers.include?(Content_Length)
57       rv = false
58     else
59       rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
60       rv = false unless @env["rainbows.autochunk"]
61     end
62     write_headers(status, headers, alive)
63     rv
64   end
66   def prepare_request_body
67     # since we don't do streaming input, we have no choice but
68     # to take over 100-continue handling from the Rack application
69     if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
70       write(EXPECT_100_RESPONSE)
71       @env.delete(HTTP_EXPECT)
72     end
73     @input = mkinput
74     @hp.filter_body(@buf2 = "", @buf)
75     @input << @buf2
76     on_read(Z)
77   end
79   # TeeInput doesn't map too well to this right now...
80   def on_read(data)
81     case @state
82     when :headers
83       @buf << data
84       @hp.parse or return want_more
85       @state = :body
86       if 0 == @hp.content_length
87         app_call NULL_IO # common case
88       else # nil or len > 0
89         prepare_request_body
90       end
91     when :body
92       if @hp.body_eof?
93         if @hp.content_length
94           @input.rewind
95           app_call @input
96         else
97           @state = :trailers
98           on_read(data)
99         end
100       elsif data.size > 0
101         @hp.filter_body(@buf2, @buf << data)
102         @input << @buf2
103         on_read(Z)
104       else
105         want_more
106       end
107     when :trailers
108       if @hp.trailers(@env, @buf << data)
109         @input.rewind
110         app_call @input
111       else
112         want_more
113       end
114     end
115     rescue => e
116       handle_error(e)
117   end
119   ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
121   def err_413(msg)
122     write(ERROR_413_RESPONSE)
123     quit
124     # zip back up the stack
125     raise IOError, msg, []
126   end
128   TmpIO = Unicorn::TmpIO
129   CBB = Unicorn::TeeInput.client_body_buffer_size
131   def io_for(bytes)
132     bytes <= CBB ? StringIO.new("") : TmpIO.new
133   end
135   def mkinput
136     max = Rainbows.client_max_body_size
137     len = @hp.content_length
138     if len
139       if max && (len > max)
140         err_413("Content-Length too big: #{len} > #{max}")
141       end
142       io_for(len)
143     else
144       max ? CapInput.new(io_for(max), self, max) : TmpIO.new
145     end
146   end