upgrade to Kgio 2.x and Unicorn 3.x
[rainbows.git] / lib / rainbows / ev_core.rb
blob3865d7944316a746998e73952d835b350c7f7b20
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
11   # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
12   ASYNC_CALLBACK = "async.callback".freeze
14   ASYNC_CLOSE = "async.close".freeze
16   def post_init
17     @hp = HttpParser.new
18     @env = @hp.env
19     @buf = @hp.buf
20     @state = :headers # [ :body [ :trailers ] ] :app_call :close
21   end
23   # graceful exit, like SIGQUIT
24   def quit
25     @state = :close
26   end
28   def handle_error(e)
29     msg = Rainbows::Error.response(e) and write(msg)
30     ensure
31       quit
32   end
34   # returns whether to enable response chunking for autochunk models
35   def stream_response_headers(status, headers)
36     if headers['Content-Length']
37       rv = false
38     else
39       rv = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
40       rv = false if headers.delete('X-Rainbows-Autochunk') == 'no'
41     end
42     write(response_header(status, headers))
43     rv
44   end
46   # TeeInput doesn't map too well to this right now...
47   def on_read(data)
48     case @state
49     when :headers
50       @buf << data
51       @hp.parse or return
52       @state = :body
53       len = @hp.content_length
54       if len == 0
55         @input = NULL_IO
56         app_call # common case
57       else # nil or len > 0
58         # since we don't do streaming input, we have no choice but
59         # to take over 100-continue handling from the Rack application
60         if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
61           write(EXPECT_100_RESPONSE)
62           @env.delete(HTTP_EXPECT)
63         end
64         @input = CapInput.new(len, self)
65         @hp.filter_body(@buf2 = "", @buf)
66         @input << @buf2
67         on_read("")
68       end
69     when :body
70       if @hp.body_eof?
71         @state = :trailers
72         on_read(data)
73       elsif data.size > 0
74         @hp.filter_body(@buf2, @buf << data)
75         @input << @buf2
76         on_read("")
77       end
78     when :trailers
79       if @hp.trailers(@env, @buf << data)
80         @input.rewind
81         app_call
82       end
83     end
84     rescue => e
85       handle_error(e)
86   end
88   class CapInput < Struct.new(:io, :client, :bytes_left)
89     MAX_BODY = Unicorn::Const::MAX_BODY
90     TmpIO = Unicorn::TmpIO
92     def self.err(client, msg)
93       client.write(Rainbows::Const::ERROR_413_RESPONSE)
94       client.quit
96       # zip back up the stack
97       raise IOError, msg, []
98     end
100     def self.new(len, client)
101       max = Rainbows.max_bytes
102       if len
103         if max && (len > max)
104           err(client, "Content-Length too big: #{len} > #{max}")
105         end
106         len <= MAX_BODY ? StringIO.new("") : TmpIO.new
107       else
108         max ? super(TmpIO.new, client, max) : TmpIO.new
109       end
110     end
112     def <<(buf)
113       if (self.bytes_left -= buf.size) < 0
114         io.close
115         CapInput.err(client, "chunked request body too big")
116       end
117       io << buf
118     end
120     def gets; io.gets; end
121     def each(&block); io.each(&block); end
122     def size; io.size; end
123     def rewind; io.rewind; end
124     def read(*args); io.read(*args); end
125   end