simplify per-client keepalive state checks
[rainbows.git] / lib / rainbows / ev_core.rb
blob60fbdca3e1ad196aec4a021970a761f494e90815
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)
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   TmpIO = Unicorn::TmpIO
113   def io_for(bytes)
114     bytes <= CBB ? StringIO.new("") : TmpIO.new
115   end
117   def mkinput
118     max = Rainbows.max_bytes
119     len = @hp.content_length
120     if len
121       if max && (len > max)
122         err_413("Content-Length too big: #{len} > #{max}")
123       end
124       io_for(len)
125     else
126       max ? CapInput.new(io_for(max), self, max) : TmpIO.new
127     end
128   end
130   def self.setup
131     const_set :CBB, Unicorn::TeeInput.client_body_buffer_size
132   end