rainbows 4.4.3 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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