globally refactor Range handling for responses
[rainbows.git] / lib / rainbows / ev_core.rb
blob471f6a3c9c938efc5fc6bafb68992eab9f3ba48a
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 = Rainbows::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, alive)
40     headers = Rack::Utils::HeaderHash.new(headers)
41     if headers['Content-Length']
42       rv = false
43     else
44       rv = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
45       rv = false if headers.delete('X-Rainbows-Autochunk') == 'no'
46     end
47     write_headers(status, headers, alive)
48     rv
49   end
51   def prepare_request_body
52     # since we don't do streaming input, we have no choice but
53     # to take over 100-continue handling from the Rack application
54     if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
55       write(EXPECT_100_RESPONSE)
56       @env.delete(HTTP_EXPECT)
57     end
58     @input = mkinput
59     @hp.filter_body(@buf2 = "", @buf)
60     @input << @buf2
61     on_read("")
62   end
64   # TeeInput doesn't map too well to this right now...
65   def on_read(data)
66     case @state
67     when :headers
68       @buf << data
69       @hp.parse or return want_more
70       @state = :body
71       if 0 == @hp.content_length
72         @input = NULL_IO
73         app_call # common case
74       else # nil or len > 0
75         prepare_request_body
76       end
77     when :body
78       if @hp.body_eof?
79         if @hp.content_length
80           @input.rewind
81           app_call
82         else
83           @state = :trailers
84           on_read(data)
85         end
86       elsif data.size > 0
87         @hp.filter_body(@buf2, @buf << data)
88         @input << @buf2
89         on_read("")
90       else
91         want_more
92       end
93     when :trailers
94       if @hp.trailers(@env, @buf << data)
95         @input.rewind
96         app_call
97       else
98         want_more
99       end
100     end
101     rescue => e
102       handle_error(e)
103   end
105   def err_413(msg)
106     write(Rainbows::Const::ERROR_413_RESPONSE)
107     quit
108     # zip back up the stack
109     raise IOError, msg, []
110   end
112   TmpIO = Unicorn::TmpIO
114   def io_for(bytes)
115     bytes <= CBB ? StringIO.new("") : TmpIO.new
116   end
118   def mkinput
119     max = Rainbows.max_bytes
120     len = @hp.content_length
121     if len
122       if max && (len > max)
123         err_413("Content-Length too big: #{len} > #{max}")
124       end
125       io_for(len)
126     else
127       max ? CapInput.new(io_for(max), self, max) : TmpIO.new
128     end
129   end
131   def self.setup
132     const_set :CBB, Unicorn::TeeInput.client_body_buffer_size
133   end