unindent most files
[rainbows.git] / lib / rainbows / ev_core.rb
blob200bf790b6b09549f9d7107d28d2c58a5a879ba9
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     @env = {}
18     @hp = HttpParser.new
19     @state = :headers # [ :body [ :trailers ] ] :app_call :close
20     @buf = ""
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       @hp.headers(@env, @buf << data) or return
51       @state = :body
52       len = @hp.content_length
53       if len == 0
54         @input = NULL_IO
55         app_call # common case
56       else # nil or len > 0
57         # since we don't do streaming input, we have no choice but
58         # to take over 100-continue handling from the Rack application
59         if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
60           write(EXPECT_100_RESPONSE)
61           @env.delete(HTTP_EXPECT)
62         end
63         @input = CapInput.new(len, self)
64         @hp.filter_body(@buf2 = "", @buf)
65         @input << @buf2
66         on_read("")
67       end
68     when :body
69       if @hp.body_eof?
70         @state = :trailers
71         on_read(data)
72       elsif data.size > 0
73         @hp.filter_body(@buf2, @buf << data)
74         @input << @buf2
75         on_read("")
76       end
77     when :trailers
78       if @hp.trailers(@env, @buf << data)
79         @input.rewind
80         app_call
81       end
82     end
83     rescue => e
84       handle_error(e)
85   end
87   class CapInput < Struct.new(:io, :client, :bytes_left)
88     MAX_BODY = Unicorn::Const::MAX_BODY
89     TmpIO = Unicorn::TmpIO
91     def self.err(client, msg)
92       client.write(Rainbows::Const::ERROR_413_RESPONSE)
93       client.quit
95       # zip back up the stack
96       raise IOError, msg, []
97     end
99     def self.new(len, client)
100       max = Rainbows.max_bytes
101       if len
102         if max && (len > max)
103           err(client, "Content-Length too big: #{len} > #{max}")
104         end
105         len <= MAX_BODY ? StringIO.new("") : TmpIO.new
106       else
107         max ? super(TmpIO.new, client, max) : TmpIO.new
108       end
109     end
111     def <<(buf)
112       if (self.bytes_left -= buf.size) < 0
113         io.close
114         CapInput.err(client, "chunked request body too big")
115       end
116       io << buf
117     end
119     def gets; io.gets; end
120     def each(&block); io.each(&block); end
121     def size; io.size; end
122     def rewind; io.rewind; end
123     def read(*args); io.read(*args); end
124   end