start using kgio library
[rainbows.git] / lib / rainbows / ev_core.rb
blobbf00eed70a2021cd71dfd35b07f66258b6238ef4
1 # -*- encoding: binary -*-
2 # :enddoc:
3 module Rainbows
5   # base module for evented models like Rev and EventMachine
6   module EvCore
7     include Unicorn
8     include Rainbows::Const
9     include Rainbows::Response
10     G = Rainbows::G
11     NULL_IO = Unicorn::HttpRequest::NULL_IO
13     # Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
14     ASYNC_CALLBACK = "async.callback".freeze
16     ASYNC_CLOSE = "async.close".freeze
18     def post_init
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     # returns whether to enable response chunking for autochunk models
37     def stream_response_headers(status, headers)
38       if headers['Content-Length']
39         rv = false
40       else
41         rv = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
42         rv = false if headers.delete('X-Rainbows-Autochunk') == 'no'
43       end
44       write(response_header(status, headers))
45       rv
46     end
48     # TeeInput doesn't map too well to this right now...
49     def on_read(data)
50       case @state
51       when :headers
52         @hp.headers(@env, @buf << data) or return
53         @state = :body
54         len = @hp.content_length
55         if len == 0
56           @input = NULL_IO
57           app_call # common case
58         else # nil or len > 0
59           # since we don't do streaming input, we have no choice but
60           # to take over 100-continue handling from the Rack application
61           if @env[HTTP_EXPECT] =~ /\A100-continue\z/i
62             write(EXPECT_100_RESPONSE)
63             @env.delete(HTTP_EXPECT)
64           end
65           @input = CapInput.new(len, self)
66           @hp.filter_body(@buf2 = "", @buf)
67           @input << @buf2
68           on_read("")
69         end
70       when :body
71         if @hp.body_eof?
72           @state = :trailers
73           on_read(data)
74         elsif data.size > 0
75           @hp.filter_body(@buf2, @buf << data)
76           @input << @buf2
77           on_read("")
78         end
79       when :trailers
80         if @hp.trailers(@env, @buf << data)
81           @input.rewind
82           app_call
83         end
84       end
85       rescue => e
86         handle_error(e)
87     end
89     class CapInput < Struct.new(:io, :client, :bytes_left)
90       MAX_BODY = Unicorn::Const::MAX_BODY
91       Util = Unicorn::Util
93       def self.err(client, msg)
94         client.write(Const::ERROR_413_RESPONSE)
95         client.quit
97         # zip back up the stack
98         raise IOError, msg, []
99       end
101       def self.new(len, client)
102         max = Rainbows.max_bytes
103         if len
104           if max && (len > max)
105             err(client, "Content-Length too big: #{len} > #{max}")
106           end
107           len <= MAX_BODY ? StringIO.new("") : Util.tmpio
108         else
109           max ? super(Util.tmpio, client, max) : Util.tmpio
110         end
111       end
113       def <<(buf)
114         if (self.bytes_left -= buf.size) < 0
115           io.close
116           CapInput.err(client, "chunked request body too big")
117         end
118         io << buf
119       end
121       def gets; io.gets; end
122       def each(&block); io.each(&block); end
123       def size; io.size; end
124       def rewind; io.rewind; end
125       def read(*args); io.read(*args); end
127     end
129   end