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