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