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