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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
| | # -*- encoding: binary -*-
# :enddoc:
# base module for evented models like Rev and EventMachine
module Rainbows::EvCore
include Rainbows::Const
include Rainbows::Response
NULL_IO = Unicorn::HttpRequest::NULL_IO
HttpParser = Rainbows::HttpParser
autoload :CapInput, 'rainbows/ev_core/cap_input'
RBUF = ""
Z = "".freeze
Rainbows.config!(self, :client_header_buffer_size)
HTTP_VERSION = "HTTP_VERSION"
# Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
ASYNC_CALLBACK = "async.callback".freeze
ASYNC_CLOSE = "async.close".freeze
def write_async_response(response)
status, headers, body = response
if alive = @hp.next?
# we can't do HTTP keepalive without Content-Length or
# "Transfer-Encoding: chunked", and the async.callback stuff
# isn't Rack::Lint-compatible, so we have to enforce it here.
headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
alive = headers.include?(Content_Length) ||
!!(%r{\Achunked\z}i =~ headers[Transfer_Encoding])
end
@deferred = nil
ev_write_response(status, headers, body, alive)
end
def post_init
@hp = HttpParser.new
@env = @hp.env
@buf = @hp.buf
@state = :headers # [ :body [ :trailers ] ] :app_call :close
end
# graceful exit, like SIGQUIT
def quit
@state = :close
end
def want_more
end
def handle_error(e)
msg = Rainbows::Error.response(e) and write(msg)
ensure
quit
end
# returns whether to enable response chunking for autochunk models
# returns nil if request was hijacked in response stage
def stream_response_headers(status, headers, alive, body)
headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
if headers.include?(Content_Length)
write_headers(status, headers, alive, body) or return
return false
end
case @env[HTTP_VERSION]
when "HTTP/1.0" # disable HTTP/1.0 keepalive to stream
write_headers(status, headers, false, body) or return
@hp.clear
false
when nil # "HTTP/0.9"
false
else
rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
rv = false unless @env["rainbows.autochunk"]
write_headers(status, headers, alive, body) or return
rv
end
end
def prepare_request_body
# 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 = mkinput
@hp.filter_body(@buf2 = "", @buf)
@input << @buf2
on_read(Z)
end
# TeeInput doesn't map too well to this right now...
def on_read(data)
case @state
when :headers
@hp.add_parse(data) or return want_more
@state = :body
if 0 == @hp.content_length
app_call NULL_IO # common case
else # nil or len > 0
prepare_request_body
end
when :body
if @hp.body_eof?
if @hp.content_length
@input.rewind
app_call @input
else
@state = :trailers
on_read(data)
end
elsif data.size > 0
@hp.filter_body(@buf2, @buf << data)
@input << @buf2
on_read(Z)
else
want_more
end
when :trailers
if @hp.add_parse(data)
@input.rewind
app_call @input
else
want_more
end
end
rescue => e
handle_error(e)
end
ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
def err_413(msg)
write(ERROR_413_RESPONSE)
quit
# zip back up the stack
raise IOError, msg, []
end
TmpIO = Unicorn::TmpIO
CBB = Unicorn::TeeInput.client_body_buffer_size
def io_for(bytes)
bytes <= CBB ? StringIO.new("") : TmpIO.new
end
def mkinput
max = Rainbows.server.client_max_body_size
len = @hp.content_length
if len
if max && (len > max)
err_413("Content-Length too big: #{len} > #{max}")
end
io_for(len)
else
max ? CapInput.new(io_for(max), self, max) : TmpIO.new
end
end
end
|