rev+revactor: fix LARGE pipelined uploads
[rainbows.git] / lib / rainbows / ev_core.rb
blob46fdedfb64b9d7efea311102a17cb5f2d3e02f58
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   G = Rainbows::G
8   NULL_IO = Unicorn::HttpRequest::NULL_IO
9   HttpParser = Unicorn::HttpParser
10   autoload :CapInput, 'rainbows/ev_core/cap_input'
12   # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
13   ASYNC_CALLBACK = "async.callback".freeze
15   ASYNC_CLOSE = "async.close".freeze
17   def post_init
18     @hp = HttpParser.new
19     @env = @hp.env
20     @buf = @hp.buf
21     @state = :headers # [ :body [ :trailers ] ] :app_call :close
22   end
24   # graceful exit, like SIGQUIT
25   def quit
26     @state = :close
27   end
29   def want_more
30   end
32   def handle_error(e)
33     msg = Rainbows::Error.response(e) and write(msg)
34     ensure
35       quit
36   end
38   # returns whether to enable response chunking for autochunk models
39   def stream_response_headers(status, headers)
40     if headers['Content-Length']
41       rv = false
42     else
43       rv = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
44       rv = false if headers.delete('X-Rainbows-Autochunk') == 'no'
45     end
46     write(response_header(status, headers))
47     rv
48   end
50   def prepare_request_body
51     # since we don't do streaming input, we have no choice but
52     # to take over 100-continue handling from the Rack application
53     if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
54       write(EXPECT_100_RESPONSE)
55       @env.delete(HTTP_EXPECT)
56     end
57     @input = mkinput
58     @hp.filter_body(@buf2 = "", @buf)
59     @input << @buf2
60     on_read("")
61   end
63   # TeeInput doesn't map too well to this right now...
64   def on_read(data)
65     case @state
66     when :headers
67       @buf << data
68       @hp.parse or return want_more
69       @state = :body
70       if 0 == @hp.content_length
71         @input = NULL_IO
72         app_call # common case
73       else # nil or len > 0
74         prepare_request_body
75       end
76     when :body
77       if @hp.body_eof?
78         if @hp.content_length
79           @input.rewind
80           app_call
81         else
82           @state = :trailers
83           on_read(data)
84         end
85       elsif data.size > 0
86         @hp.filter_body(@buf2, @buf << data)
87         @input << @buf2
88         on_read("")
89       else
90         want_more
91       end
92     when :trailers
93       if @hp.trailers(@env, @buf << data)
94         @input.rewind
95         app_call
96       else
97         want_more
98       end
99     end
100     rescue => e
101       handle_error(e)
102   end
104   def err_413(msg)
105     write(Rainbows::Const::ERROR_413_RESPONSE)
106     quit
107     # zip back up the stack
108     raise IOError, msg, []
109   end
111   MAX_BODY = Unicorn::Const::MAX_BODY
112   TmpIO = Unicorn::TmpIO
113   def mkinput
114     max = Rainbows.max_bytes
115     len = @hp.content_length
116     if len
117       if max && (len > max)
118         err_413("Content-Length too big: #{len} > #{max}")
119       end
120       len <= MAX_BODY ? StringIO.new("") : TmpIO.new
121     else
122       max ? CapInput.new(TmpIO.new, self) : TmpIO.new
123     end
124   end