add client_max_body_size config directive
[rainbows.git] / lib / rainbows / ev_core.rb
blobd4ad0405e579b6b1517157e43c193c1ac29511db
1 # -*- encoding: binary -*-
3 module Rainbows
5   # base module for evented models like Rev and EventMachine
6   module EvCore
7     include Unicorn
8     include Rainbows::Const
9     G = Rainbows::G
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       @remote_addr = Rainbows.addr(@_io)
18       @env = {}
19       @hp = HttpParser.new
20       @state = :headers # [ :body [ :trailers ] ] :app_call :close
21       @buf = ""
22     end
24     # graceful exit, like SIGQUIT
25     def quit
26       @state = :close
27     end
29     def handle_error(e)
30       msg = Error.response(e) and write(msg)
31       ensure
32         quit
33     end
35     # TeeInput doesn't map too well to this right now...
36     def on_read(data)
37       case @state
38       when :headers
39         @hp.headers(@env, @buf << data) or return
40         @state = :body
41         len = @hp.content_length
42         if len == 0
43           @input = HttpRequest::NULL_IO
44           app_call # common case
45         else # nil or len > 0
46           # since we don't do streaming input, we have no choice but
47           # to take over 100-continue handling from the Rack application
48           if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
49             write(EXPECT_100_RESPONSE)
50             @env.delete(HTTP_EXPECT)
51           end
52           @input = CapInput.new(len, self)
53           @hp.filter_body(@buf2 = "", @buf)
54           @input << @buf2
55           on_read("")
56         end
57       when :body
58         if @hp.body_eof?
59           @state = :trailers
60           on_read(data)
61         elsif data.size > 0
62           @hp.filter_body(@buf2, @buf << data)
63           @input << @buf2
64           on_read("")
65         end
66       when :trailers
67         if @hp.trailers(@env, @buf << data)
68           @input.rewind
69           app_call
70         end
71       end
72       rescue => e
73         handle_error(e)
74     end
76     class CapInput < Struct.new(:io, :client, :bytes_left)
77       MAX_BODY = Unicorn::Const::MAX_BODY
78       Util = Unicorn::Util
80       def self.err(client, msg)
81         client.write(Const::ERROR_413_RESPONSE)
82         client.quit
84         # zip back up the stack
85         raise IOError, msg, []
86       end
88       def self.new(len, client)
89         max = Rainbows.max_bytes
90         if len
91           if max && (len > max)
92             err(client, "Content-Length too big: #{len} > #{max}")
93           end
94           len <= MAX_BODY ? StringIO.new("") : Util.tmpio
95         else
96           max ? super(Util.tmpio, client, max) : Util.tmpio
97         end
98       end
100       def <<(buf)
101         if (self.bytes_left -= buf.size) < 0
102           io.close
103           CapInput.err(client, "chunked request body too big")
104         end
105         io << buf
106       end
108       def gets; io.gets; end
109       def each(&block); io.each(&block); end
110       def size; io.size; end
111       def rewind; io.rewind; end
112       def read(*args); io.read(*args); end
114     end
116   end