rainbows 4.4.3 → 4.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +1 -0
- data/.gitignore +1 -0
- data/Documentation/rainbows.1.txt +8 -4
- data/GIT-VERSION-GEN +34 -35
- data/GNUmakefile +4 -2
- data/HACKING +72 -0
- data/bin/rainbows +5 -0
- data/lib/rainbows/const.rb +3 -3
- data/lib/rainbows/coolio/client.rb +18 -6
- data/lib/rainbows/coolio/thread_client.rb +2 -0
- data/lib/rainbows/epoll/client.rb +35 -12
- data/lib/rainbows/ev_core.rb +5 -4
- data/lib/rainbows/event_machine/client.rb +9 -4
- data/lib/rainbows/process_client.rb +7 -3
- data/lib/rainbows/response.rb +54 -18
- data/lib/rainbows/revactor/client/methods.rb +1 -1
- data/lib/rainbows/stream_response_epoll.rb +34 -15
- data/lib/rainbows/stream_response_epoll/client.rb +11 -3
- data/lib/rainbows/writer_thread_pool/client.rb +2 -0
- data/lib/rainbows/xepoll/client.rb +0 -3
- data/lib/rainbows/xepoll_thread_pool/client.rb +1 -1
- data/lib/rainbows/xepoll_thread_spawn/client.rb +1 -1
- data/rainbows.gemspec +6 -3
- data/t/GNUmakefile +10 -3
- data/t/byte-range-common.sh +1 -1
- data/t/hijack.ru +56 -0
- data/t/t0000-simple-http.sh +9 -9
- data/t/t0001-unix-http.sh +8 -8
- data/t/t0003-reopen-logs.sh +8 -8
- data/t/t0004-heartbeat-timeout.sh +3 -3
- data/t/t0005-large-file-response.sh +1 -1
- data/t/t0010-keepalive-timeout-effective.sh +2 -2
- data/t/t0011-close-on-exec-set.sh +2 -2
- data/t/t0017-keepalive-timeout-zero.sh +2 -2
- data/t/t0024-pipelined-sendfile-response.sh +2 -2
- data/t/t0027-nil-copy_stream.sh +1 -1
- data/t/t0030-fast-pipe-response.sh +1 -1
- data/t/t0034-pipelined-pipe-response.sh +2 -2
- data/t/t0035-kgio-pipe-response.sh +1 -1
- data/t/t0040-keepalive_requests-setting.sh +4 -4
- data/t/t0043-quit-keepalive-disconnect.sh +3 -3
- data/t/t0044-autopush.sh +2 -2
- data/t/t0045-client_max_header_size.sh +2 -2
- data/t/t0100-rack-input-hammer-chunked.sh +4 -4
- data/t/t0100-rack-input-hammer-content-length.sh +4 -4
- data/t/t0106-rack-input-keepalive.sh +6 -6
- data/t/t0200-async-response.sh +5 -5
- data/t/t0202-async-response-one-oh.sh +5 -5
- data/t/t0300-async_sinatra.sh +5 -5
- data/t/t0400-em-async-app.sh +2 -2
- data/t/t0401-em-async-tailer.sh +2 -2
- data/t/t0402-async-keepalive.sh +23 -23
- data/t/t0500-cramp-streaming.sh +3 -3
- data/t/t0600-rack-fiber_pool.sh +1 -1
- data/t/t0700-app-deferred.sh +2 -2
- data/t/t0800-rack-hijack.sh +27 -0
- data/t/t9000-rack-app-pool.sh +3 -3
- data/t/t9001-sendfile-to-path.sh +1 -1
- data/t/t9100-thread-timeout.sh +1 -1
- data/t/t9101-thread-timeout-threshold.sh +1 -1
- data/t/test-lib.sh +15 -0
- data/t/test_isolate.rb +4 -3
- metadata +26 -6
- data/t/bin/utee +0 -12
@@ -10,6 +10,7 @@ class Rainbows::EventMachine::Client < EM::Connection
|
|
10
10
|
end
|
11
11
|
|
12
12
|
alias write send_data
|
13
|
+
alias hijacked detach
|
13
14
|
|
14
15
|
def receive_data(data)
|
15
16
|
# To avoid clobbering the current streaming response
|
@@ -37,9 +38,11 @@ class Rainbows::EventMachine::Client < EM::Connection
|
|
37
38
|
@env[REMOTE_ADDR] = @_io.kgio_addr
|
38
39
|
@env[ASYNC_CALLBACK] = method(:write_async_response)
|
39
40
|
@env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
|
41
|
+
@hp.hijack_setup(@env, @_io)
|
40
42
|
status, headers, body = catch(:async) {
|
41
43
|
APP.call(@env.merge!(RACK_DEFAULTS))
|
42
44
|
}
|
45
|
+
return hijacked if @hp.hijacked?
|
43
46
|
|
44
47
|
if (nil == status || -1 == status)
|
45
48
|
@deferred = true
|
@@ -67,8 +70,8 @@ class Rainbows::EventMachine::Client < EM::Connection
|
|
67
70
|
def ev_write_response(status, headers, body, alive)
|
68
71
|
@state = :headers if alive
|
69
72
|
if body.respond_to?(:errback) && body.respond_to?(:callback)
|
73
|
+
write_headers(status, headers, alive, body) or return hijacked
|
70
74
|
@deferred = body
|
71
|
-
write_headers(status, headers, alive)
|
72
75
|
write_body_each(body)
|
73
76
|
deferred_errback(body)
|
74
77
|
deferred_callback(body, alive)
|
@@ -77,21 +80,22 @@ class Rainbows::EventMachine::Client < EM::Connection
|
|
77
80
|
st = File.stat(path = body.to_path)
|
78
81
|
|
79
82
|
if st.file?
|
80
|
-
write_headers(status, headers, alive)
|
83
|
+
write_headers(status, headers, alive, body) or return hijacked
|
81
84
|
@deferred = stream_file_data(path)
|
82
85
|
deferred_errback(body)
|
83
86
|
deferred_callback(body, alive)
|
84
87
|
return
|
85
88
|
elsif st.socket? || st.pipe?
|
89
|
+
chunk = stream_response_headers(status, headers, alive, body)
|
90
|
+
return hijacked if nil == chunk
|
86
91
|
io = body_to_io(@deferred = body)
|
87
|
-
chunk = stream_response_headers(status, headers, alive)
|
88
92
|
m = chunk ? Rainbows::EventMachine::ResponseChunkPipe :
|
89
93
|
Rainbows::EventMachine::ResponsePipe
|
90
94
|
return EM.watch(io, m, self).notify_readable = true
|
91
95
|
end
|
92
96
|
# char or block device... WTF? fall through to body.each
|
93
97
|
end
|
94
|
-
write_response(status, headers, body, alive)
|
98
|
+
write_response(status, headers, body, alive) or return hijacked
|
95
99
|
if alive
|
96
100
|
if @deferred.nil?
|
97
101
|
if @buf.empty?
|
@@ -112,6 +116,7 @@ class Rainbows::EventMachine::Client < EM::Connection
|
|
112
116
|
end
|
113
117
|
|
114
118
|
def unbind
|
119
|
+
return if @hp.hijacked?
|
115
120
|
async_close = @env[ASYNC_CLOSE] and async_close.succeed
|
116
121
|
@deferred.respond_to?(:fail) and @deferred.fail
|
117
122
|
begin
|
@@ -40,6 +40,7 @@ module Rainbows::ProcessClient
|
|
40
40
|
|
41
41
|
set_input(env, hp)
|
42
42
|
env[REMOTE_ADDR] = kgio_addr
|
43
|
+
hp.hijack_setup(env, to_io)
|
43
44
|
status, headers, body = APP.call(env.merge!(RACK_DEFAULTS))
|
44
45
|
|
45
46
|
if 100 == status.to_i
|
@@ -47,7 +48,8 @@ module Rainbows::ProcessClient
|
|
47
48
|
env.delete(HTTP_EXPECT)
|
48
49
|
status, headers, body = APP.call(env)
|
49
50
|
end
|
50
|
-
|
51
|
+
return if hp.hijacked?
|
52
|
+
write_response(status, headers, body, alive = hp.next?) or return
|
51
53
|
end while alive
|
52
54
|
# if we get any error, try to write something back to the client
|
53
55
|
# assuming we haven't closed the socket, but don't get hung up
|
@@ -56,7 +58,7 @@ module Rainbows::ProcessClient
|
|
56
58
|
rescue => e
|
57
59
|
handle_error(e)
|
58
60
|
ensure
|
59
|
-
close unless closed?
|
61
|
+
close unless closed? || hp.hijacked?
|
60
62
|
end
|
61
63
|
|
62
64
|
def handle_error(e)
|
@@ -71,13 +73,15 @@ module Rainbows::ProcessClient
|
|
71
73
|
begin
|
72
74
|
set_input(env, hp)
|
73
75
|
env[REMOTE_ADDR] = kgio_addr
|
76
|
+
hp.hijack_setup(env, to_io)
|
74
77
|
status, headers, body = APP.call(env.merge!(RACK_DEFAULTS))
|
75
78
|
if 100 == status.to_i
|
76
79
|
write(EXPECT_100_RESPONSE)
|
77
80
|
env.delete(HTTP_EXPECT)
|
78
81
|
status, headers, body = APP.call(env)
|
79
82
|
end
|
80
|
-
|
83
|
+
return if hp.hijacked?
|
84
|
+
write_response(status, headers, body, alive = hp.next?) or return
|
81
85
|
end while alive && pipeline_ready(hp)
|
82
86
|
alive or close
|
83
87
|
rescue => e
|
data/lib/rainbows/response.rb
CHANGED
@@ -19,23 +19,56 @@ module Rainbows::Response
|
|
19
19
|
Rainbows::HttpParser.keepalive_requests = 0
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
# Rack 1.5.0 (protocol version 1.2) adds response hijacking support
|
23
|
+
if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
|
24
|
+
RACK_HIJACK = "rack.hijack"
|
25
|
+
|
26
|
+
def hijack_prepare(value)
|
27
|
+
value
|
28
|
+
end
|
29
|
+
|
30
|
+
def hijack_socket
|
31
|
+
@hp.env[RACK_HIJACK].call
|
32
|
+
end
|
33
|
+
else
|
34
|
+
def hijack_prepare(_)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# returns the original body on success
|
39
|
+
# returns nil if the headers hijacked the response body
|
40
|
+
def write_headers(status, headers, alive, body)
|
41
|
+
@hp.headers? or return body
|
42
|
+
hijack = nil
|
24
43
|
status = CODES[status.to_i] || status
|
25
44
|
buf = "HTTP/1.1 #{status}\r\n" \
|
26
45
|
"Date: #{httpdate}\r\n" \
|
27
|
-
"Status: #{status}\r\n"
|
28
|
-
"Connection: #{alive ? KeepAlive : Close}\r\n"
|
46
|
+
"Status: #{status}\r\n"
|
29
47
|
headers.each do |key, value|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
48
|
+
case key
|
49
|
+
when %r{\A(?:Date\z|Connection\z)}i
|
50
|
+
next
|
51
|
+
when "rack.hijack"
|
52
|
+
# this was an illegal key in Rack < 1.5, so it should be
|
53
|
+
# OK to silently discard it for those older versions
|
54
|
+
hijack = hijack_prepare(value)
|
55
|
+
alive = false # No persistent connections for hijacking
|
34
56
|
else
|
35
|
-
|
57
|
+
if /\n/ =~ value
|
58
|
+
# avoiding blank, key-only cookies with /\n+/
|
59
|
+
buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
|
60
|
+
else
|
61
|
+
buf << "#{key}: #{value}\r\n"
|
62
|
+
end
|
36
63
|
end
|
37
64
|
end
|
38
|
-
write(buf <<
|
65
|
+
write(buf << "Connection: #{alive ? KeepAlive : Close}\r\n\r\n")
|
66
|
+
|
67
|
+
if hijack
|
68
|
+
body = nil # ensure caller does not close body
|
69
|
+
hijack.call(hijack_socket)
|
70
|
+
end
|
71
|
+
body
|
39
72
|
end
|
40
73
|
|
41
74
|
def close_if_private(io)
|
@@ -70,8 +103,9 @@ module Rainbows::Response
|
|
70
103
|
# generic response writer, used for most dynamically-generated responses
|
71
104
|
# and also when copy_stream and/or IO#trysendfile is unavailable
|
72
105
|
def write_response(status, headers, body, alive)
|
73
|
-
write_headers(status, headers, alive)
|
74
|
-
write_body_each(body)
|
106
|
+
body = write_headers(status, headers, alive, body)
|
107
|
+
write_body_each(body) if body
|
108
|
+
body
|
75
109
|
ensure
|
76
110
|
body.close if body.respond_to?(:close)
|
77
111
|
end
|
@@ -166,21 +200,23 @@ module Rainbows::Response
|
|
166
200
|
if File.file?(body.to_path)
|
167
201
|
if r = sendfile_range(status, headers)
|
168
202
|
status, headers, range = r
|
169
|
-
write_headers(status, headers, alive)
|
170
|
-
write_body_file(body, range) if range
|
203
|
+
body = write_headers(status, headers, alive, body)
|
204
|
+
write_body_file(body, range) if body && range
|
171
205
|
else
|
172
|
-
write_headers(status, headers, alive)
|
173
|
-
write_body_file(body, nil)
|
206
|
+
body = write_headers(status, headers, alive, body)
|
207
|
+
write_body_file(body, nil) if body
|
174
208
|
end
|
175
209
|
else
|
176
|
-
write_headers(status, headers, alive)
|
177
|
-
write_body_stream(body)
|
210
|
+
body = write_headers(status, headers, alive, body)
|
211
|
+
write_body_stream(body) if body
|
178
212
|
end
|
213
|
+
body
|
179
214
|
ensure
|
180
215
|
body.close if body.respond_to?(:close)
|
181
216
|
end
|
182
217
|
|
183
218
|
module ToPath
|
219
|
+
# returns nil if hijacked
|
184
220
|
def write_response(status, headers, body, alive)
|
185
221
|
if body.respond_to?(:to_path)
|
186
222
|
write_response_path(status, headers, body, alive)
|
@@ -26,18 +26,24 @@ module Rainbows::StreamResponseEpoll
|
|
26
26
|
|
27
27
|
def http_response_write(socket, status, headers, body)
|
28
28
|
status = CODES[status.to_i] || status
|
29
|
-
ep_client = false
|
29
|
+
hijack = ep_client = false
|
30
30
|
|
31
31
|
if headers
|
32
32
|
# don't set extra headers here, this is only intended for
|
33
33
|
# consuming by nginx.
|
34
34
|
buf = "HTTP/1.0 #{status}\r\nStatus: #{status}\r\n"
|
35
35
|
headers.each do |key, value|
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
case key
|
37
|
+
when "rack.hijack"
|
38
|
+
hijack = hijack_prepare(value)
|
39
|
+
body = nil # ensure we do not close body
|
39
40
|
else
|
40
|
-
|
41
|
+
if /\n/ =~ value
|
42
|
+
# avoiding blank, key-only cookies with /\n+/
|
43
|
+
buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
|
44
|
+
else
|
45
|
+
buf << "#{key}: #{value}\r\n"
|
46
|
+
end
|
41
47
|
end
|
42
48
|
end
|
43
49
|
buf << HEADER_END
|
@@ -48,11 +54,22 @@ module Rainbows::StreamResponseEpoll
|
|
48
54
|
buf = rv
|
49
55
|
when :wait_writable
|
50
56
|
ep_client = Client.new(socket, buf)
|
51
|
-
|
52
|
-
|
57
|
+
if hijack
|
58
|
+
ep_client.hijack(hijack)
|
59
|
+
else
|
60
|
+
body.each { |chunk| ep_client.write(chunk) }
|
61
|
+
ep_client.close
|
62
|
+
end
|
63
|
+
# body is nil on hijack, in which case ep_client is never closed by us
|
64
|
+
return
|
53
65
|
end while true
|
54
66
|
end
|
55
67
|
|
68
|
+
if hijack
|
69
|
+
hijack.call(socket)
|
70
|
+
return
|
71
|
+
end
|
72
|
+
|
56
73
|
body.each do |chunk|
|
57
74
|
if ep_client
|
58
75
|
ep_client.write(chunk)
|
@@ -67,14 +84,15 @@ module Rainbows::StreamResponseEpoll
|
|
67
84
|
end while true
|
68
85
|
end
|
69
86
|
end
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
87
|
+
ensure
|
88
|
+
return if hijack
|
89
|
+
body.respond_to?(:close) and body.close
|
90
|
+
if ep_client
|
91
|
+
ep_client.close
|
92
|
+
else
|
93
|
+
socket.shutdown
|
94
|
+
socket.close
|
95
|
+
end
|
78
96
|
end
|
79
97
|
|
80
98
|
# once a client is accepted, it is processed in its entirety here
|
@@ -88,6 +106,7 @@ module Rainbows::StreamResponseEpoll
|
|
88
106
|
status, headers, body = @app.call(env)
|
89
107
|
end
|
90
108
|
@request.headers? or headers = nil
|
109
|
+
return if @request.hijacked?
|
91
110
|
http_response_write(client, status, headers, body)
|
92
111
|
rescue => e
|
93
112
|
handle_error(client, e)
|
@@ -18,7 +18,7 @@ class Rainbows::StreamResponseEpoll::Client
|
|
18
18
|
attr_reader :to_io
|
19
19
|
|
20
20
|
def initialize(io, unwritten)
|
21
|
-
@
|
21
|
+
@finish = false
|
22
22
|
@to_io = io
|
23
23
|
@wr_queue = [ unwritten.dup ]
|
24
24
|
EP.set(self, OUT)
|
@@ -29,7 +29,11 @@ class Rainbows::StreamResponseEpoll::Client
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def close
|
32
|
-
@
|
32
|
+
@finish = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def hijack(hijack)
|
36
|
+
@finish = hijack
|
33
37
|
end
|
34
38
|
|
35
39
|
def epoll_run
|
@@ -49,10 +53,14 @@ class Rainbows::StreamResponseEpoll::Client
|
|
49
53
|
end
|
50
54
|
|
51
55
|
def on_write_complete
|
52
|
-
if @
|
56
|
+
if true == @finish
|
53
57
|
@to_io.shutdown
|
54
58
|
@to_io.close
|
55
59
|
N.decr(0, 1)
|
60
|
+
elsif @finish.respond_to?(:call) # hijacked
|
61
|
+
EP.delete(self)
|
62
|
+
N.decr(0, 1)
|
63
|
+
@finish.call(@to_io)
|
56
64
|
end
|
57
65
|
end
|
58
66
|
end
|
@@ -8,11 +8,13 @@ class Rainbows::WriterThreadPool::Client < Struct.new(:to_io, :q)
|
|
8
8
|
|
9
9
|
module Methods
|
10
10
|
def write_body_each(body)
|
11
|
+
return if @hp.hijacked?
|
11
12
|
q << [ to_io, :write_body_each, body ]
|
12
13
|
end
|
13
14
|
|
14
15
|
def write_response_close(status, headers, body, alive)
|
15
16
|
to_io.instance_variable_set(:@hp, @hp) # XXX ugh
|
17
|
+
return if @hp.hijacked?
|
16
18
|
Rainbows::SyncClose.new(body) { |sync_body|
|
17
19
|
q << [ to_io, :write_response, status, headers, sync_body, alive ]
|
18
20
|
}
|
data/rainbows.gemspec
CHANGED
@@ -28,7 +28,9 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.add_dependency(%q<kgio>, ['~> 2.5'])
|
29
29
|
|
30
30
|
# we need Unicorn for the HTTP parser and process management
|
31
|
-
|
31
|
+
# 4.6.0+ supports hijacking, 4.6.2 fixes the chunk parser (for Ruby 2.0.0)
|
32
|
+
s.add_dependency(%q<unicorn>, ["~> 4.6", ">= 4.6.2"])
|
33
|
+
|
32
34
|
s.add_development_dependency(%q<isolate>, "~> 3.1")
|
33
35
|
s.add_development_dependency(%q<wrongdoc>, "~> 1.6")
|
34
36
|
|
@@ -53,6 +55,7 @@ Gem::Specification.new do |s|
|
|
53
55
|
# NeverBlock, currently only available on http://gems.github.com/
|
54
56
|
# s.add_dependency(%q<espace-neverblock>, ["~> 0.1.6.1"])
|
55
57
|
|
56
|
-
#
|
57
|
-
#
|
58
|
+
# We inherited the Ruby 1.8 license from Mongrel, so we're stuck with it.
|
59
|
+
# GPLv3 is preferred.
|
60
|
+
s.licenses = ["GPLv2", "GPLv3", "Ruby 1.8"]
|
58
61
|
end
|
data/t/GNUmakefile
CHANGED
@@ -30,13 +30,12 @@ models += WriterThreadSpawn
|
|
30
30
|
models += ThreadPool
|
31
31
|
models += ThreadSpawn
|
32
32
|
models += Coolio
|
33
|
-
|
34
|
-
models += NeverBlock
|
33
|
+
|
35
34
|
models += StreamResponseEpoll
|
36
35
|
|
37
36
|
ifeq ($(RUBY_ENGINE),ruby)
|
38
37
|
rp := )
|
39
|
-
ONENINE := $(shell case $(RUBY_VERSION) in 1.9.*$(rp) echo true;;esac)
|
38
|
+
ONENINE := $(shell case $(RUBY_VERSION) in 1.9.*|2.0.*$(rp) echo true;;esac)
|
40
39
|
ifeq ($(ONENINE),true)
|
41
40
|
ifeq ($(RUBY_VERSION),1.9.2)
|
42
41
|
models += Revactor
|
@@ -46,6 +45,14 @@ ifeq ($(RUBY_ENGINE),ruby)
|
|
46
45
|
models += CoolioThreadPool
|
47
46
|
models += CoolioThreadSpawn
|
48
47
|
models += CoolioFiberSpawn
|
48
|
+
|
49
|
+
# EventMachine 1.0.0 currently does not build on Ruby 2.0.0
|
50
|
+
# NeverBlock depends on 2.0.0
|
51
|
+
RBTWO := $(shell case $(RUBY_VERSION) in 2.0.*$(rp) echo true;;esac)
|
52
|
+
ifeq ($(RBTWO),)
|
53
|
+
models += EventMachine
|
54
|
+
models += NeverBlock
|
55
|
+
endif
|
49
56
|
endif
|
50
57
|
endif
|
51
58
|
|
data/t/byte-range-common.sh
CHANGED