rainbows 3.4.0 → 4.0.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/Documentation/GNUmakefile +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/bin/rainbows +1 -1
- data/lib/rainbows.rb +3 -9
- data/lib/rainbows/base.rb +9 -5
- data/lib/rainbows/configurator.rb +13 -0
- data/lib/rainbows/const.rb +1 -1
- data/lib/rainbows/coolio_fiber_spawn.rb +1 -1
- data/lib/rainbows/dev_fd_response.rb +6 -13
- data/lib/rainbows/epoll/client.rb +1 -1
- data/lib/rainbows/ev_core.rb +16 -6
- data/lib/rainbows/fiber.rb +1 -4
- data/lib/rainbows/fiber/io.rb +1 -1
- data/lib/rainbows/fiber/io/methods.rb +0 -1
- data/lib/rainbows/fiber/io/pipe.rb +2 -0
- data/lib/rainbows/fiber/io/socket.rb +2 -0
- data/lib/rainbows/http_server.rb +4 -8
- data/lib/rainbows/response.rb +10 -2
- data/lib/rainbows/stream_response_epoll.rb +75 -0
- data/lib/rainbows/stream_response_epoll/client.rb +57 -0
- data/lib/rainbows/xepoll_thread_pool.rb +1 -1
- data/lib/rainbows/xepoll_thread_pool/client.rb +1 -2
- data/lib/rainbows/xepoll_thread_spawn.rb +1 -1
- data/lib/rainbows/xepoll_thread_spawn/client.rb +2 -4
- data/rainbows.gemspec +4 -3
- data/t/GNUmakefile +1 -0
- data/t/async-response-no-autochunk.ru +20 -10
- data/t/t0000-simple-http.sh +1 -0
- data/t/t0001-unix-http.sh +1 -0
- data/t/t0005-large-file-response.sh +1 -0
- data/t/t0009-broken-app.sh +1 -0
- data/t/t0010-keepalive-timeout-effective.sh +2 -0
- data/t/t0011-close-on-exec-set.sh +1 -0
- data/t/t0017-keepalive-timeout-zero.sh +1 -0
- data/t/t0019-keepalive-cpu-usage.sh +2 -0
- data/t/t0020-large-sendfile-response.sh +1 -0
- data/t/t0021-sendfile-wrap-to_path.sh +1 -0
- data/t/t0023-sendfile-byte-range.sh +1 -0
- data/t/t0024-pipelined-sendfile-response.sh +1 -0
- data/t/t0030-fast-pipe-response.sh +1 -0
- data/t/t0031-close-pipe-response.sh +1 -0
- data/t/t0032-close-pipe-to_path-response.sh +1 -0
- data/t/t0034-pipelined-pipe-response.sh +1 -0
- data/t/t0035-kgio-pipe-response.sh +1 -0
- data/t/t0040-keepalive_requests-setting.sh +1 -0
- data/t/t0044-autopush.sh +1 -0
- data/t/t0045-client_max_header_size.sh +90 -0
- data/t/t0050-response-body-close-has-env.sh +4 -1
- data/t/t0101-rack-input-trailer.sh +4 -6
- data/t/t0103-rack-input-limit.sh +1 -0
- data/t/t0104-rack-input-limit-tiny.sh +1 -0
- data/t/t0105-rack-input-limit-bigger.sh +1 -0
- data/t/t0106-rack-input-keepalive.sh +1 -0
- data/t/t0107-rack-input-limit-zero.sh +1 -0
- data/t/t0200-async-response.sh +1 -0
- data/t/t0202-async-response-one-oh.sh +1 -0
- data/t/t9001-sendfile-to-path.sh +1 -0
- data/t/t9002.ru +1 -0
- data/t/test-lib.sh +3 -0
- data/t/test_isolate.rb +5 -5
- metadata +17 -13
data/Documentation/GNUmakefile
CHANGED
data/GIT-VERSION-GEN
CHANGED
data/bin/rainbows
CHANGED
data/lib/rainbows.rb
CHANGED
|
@@ -61,17 +61,11 @@ module Rainbows
|
|
|
61
61
|
end
|
|
62
62
|
# :stopdoc:
|
|
63
63
|
|
|
64
|
-
# runs the Rainbows! HttpServer with +app+ and +options+ and does
|
|
65
|
-
# not return until the server has exited.
|
|
66
|
-
def self.run(app, options = {}) # :nodoc:
|
|
67
|
-
HttpServer.new(app, options).start.join
|
|
68
|
-
end
|
|
69
|
-
|
|
70
64
|
class << self
|
|
71
65
|
attr_accessor :server
|
|
72
66
|
attr_accessor :cur # may not always be used
|
|
73
67
|
attr_reader :alive
|
|
74
|
-
attr_writer :
|
|
68
|
+
attr_writer :worker
|
|
75
69
|
attr_writer :forked
|
|
76
70
|
end
|
|
77
71
|
|
|
@@ -84,7 +78,6 @@ module Rainbows
|
|
|
84
78
|
|
|
85
79
|
@alive = true
|
|
86
80
|
@cur = 0
|
|
87
|
-
@tick_mod = 0
|
|
88
81
|
@expire = nil
|
|
89
82
|
@at_quit = []
|
|
90
83
|
|
|
@@ -93,7 +86,7 @@ module Rainbows
|
|
|
93
86
|
end
|
|
94
87
|
|
|
95
88
|
def self.tick
|
|
96
|
-
@
|
|
89
|
+
@worker.tick = Time.now.to_i
|
|
97
90
|
exit!(2) if @expire && Time.now >= @expire
|
|
98
91
|
@alive && @server.master_pid == Process.ppid or quit!
|
|
99
92
|
end
|
|
@@ -136,6 +129,7 @@ module Rainbows
|
|
|
136
129
|
autoload :NeverBlock, "rainbows/never_block"
|
|
137
130
|
autoload :XEpollThreadSpawn, "rainbows/xepoll_thread_spawn"
|
|
138
131
|
autoload :XEpollThreadPool, "rainbows/xepoll_thread_pool"
|
|
132
|
+
autoload :StreamResponseEpoll, "rainbows/stream_response_epoll"
|
|
139
133
|
|
|
140
134
|
autoload :Fiber, 'rainbows/fiber' # core class
|
|
141
135
|
autoload :StreamFile, 'rainbows/stream_file'
|
data/lib/rainbows/base.rb
CHANGED
|
@@ -11,12 +11,9 @@ module Rainbows::Base
|
|
|
11
11
|
# this method is called by all current concurrency models
|
|
12
12
|
def init_worker_process(worker) # :nodoc:
|
|
13
13
|
super(worker)
|
|
14
|
-
Rainbows::Response.setup
|
|
14
|
+
Rainbows::Response.setup
|
|
15
15
|
Rainbows::MaxBody.setup
|
|
16
|
-
Rainbows.
|
|
17
|
-
|
|
18
|
-
listeners = Rainbows::HttpServer::LISTENERS
|
|
19
|
-
Rainbows::HttpServer::IO_PURGATORY.concat(listeners)
|
|
16
|
+
Rainbows.worker = worker
|
|
20
17
|
|
|
21
18
|
# we're don't use the self-pipe mechanism in the Rainbows! worker
|
|
22
19
|
# since we don't defer reopening logs
|
|
@@ -36,5 +33,12 @@ module Rainbows::Base
|
|
|
36
33
|
klass.const_set :LISTENERS, Rainbows::HttpServer::LISTENERS
|
|
37
34
|
end
|
|
38
35
|
|
|
36
|
+
def reopen_worker_logs(worker_nr)
|
|
37
|
+
logger.info "worker=#{worker_nr} reopening logs..."
|
|
38
|
+
Unicorn::Util.reopen_logs
|
|
39
|
+
logger.info "worker=#{worker_nr} done reopening logs"
|
|
40
|
+
rescue
|
|
41
|
+
Rainbows.quit! # let the master reopen and refork us
|
|
42
|
+
end
|
|
39
43
|
# :startdoc:
|
|
40
44
|
end
|
|
@@ -27,6 +27,7 @@ module Rainbows::Configurator
|
|
|
27
27
|
:keepalive_requests => 100,
|
|
28
28
|
:client_max_body_size => 1024 * 1024,
|
|
29
29
|
:client_header_buffer_size => 1024,
|
|
30
|
+
:client_max_header_size => 112 * 1024,
|
|
30
31
|
:copy_stream => IO.respond_to?(:copy_stream) ? IO : false,
|
|
31
32
|
})
|
|
32
33
|
|
|
@@ -147,6 +148,18 @@ module Rainbows::Configurator
|
|
|
147
148
|
set[:client_max_body_size] = bytes
|
|
148
149
|
end
|
|
149
150
|
|
|
151
|
+
# Limits the maximum size of a request header for all requests.
|
|
152
|
+
#
|
|
153
|
+
# Default: 112 kilobytes (114688 bytes)
|
|
154
|
+
#
|
|
155
|
+
# Lowering this will lower worst-case memory usage and mitigate some
|
|
156
|
+
# denial-of-service attacks. This should be larger than
|
|
157
|
+
# client_header_buffer_size.
|
|
158
|
+
def client_max_header_size(bytes)
|
|
159
|
+
check!
|
|
160
|
+
set_int(:client_max_header_size, bytes, 8)
|
|
161
|
+
end
|
|
162
|
+
|
|
150
163
|
# This governs the amount of memory allocated for an individual read(2) or
|
|
151
164
|
# recv(2) system call when reading headers. Applications that make minimal
|
|
152
165
|
# use of cookies should not increase this from the default.
|
data/lib/rainbows/const.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Rainbows::CoolioFiberSpawn
|
|
|
18
18
|
include Rainbows::Fiber::Coolio
|
|
19
19
|
|
|
20
20
|
def worker_loop(worker) # :nodoc:
|
|
21
|
-
Rainbows::Response.setup
|
|
21
|
+
Rainbows::Response.setup
|
|
22
22
|
init_worker_process(worker)
|
|
23
23
|
Server.const_set(:MAX, @worker_connections)
|
|
24
24
|
Rainbows::Fiber::Base.setup(Server, nil)
|
|
@@ -6,10 +6,6 @@
|
|
|
6
6
|
# objects. This may be used in conjunction with the #to_path method
|
|
7
7
|
# on servers that support it to pass arbitrary file descriptors into
|
|
8
8
|
# the HTTP response without additional open(2) syscalls
|
|
9
|
-
#
|
|
10
|
-
# This middleware is currently a no-op for Rubinius, as it lacks
|
|
11
|
-
# IO.copy_stream in 1.9 and also due to a bug here:
|
|
12
|
-
# http://github.com/evanphx/rubinius/issues/379
|
|
13
9
|
|
|
14
10
|
class Rainbows::DevFdResponse < Struct.new(:app)
|
|
15
11
|
|
|
@@ -19,15 +15,8 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
|
19
15
|
Transfer_Encoding = "Transfer-Encoding".freeze
|
|
20
16
|
Rainbows_autochunk = "rainbows.autochunk".freeze
|
|
21
17
|
Rainbows_model = "rainbows.model"
|
|
22
|
-
HTTP_1_0 = "HTTP/1.0"
|
|
23
18
|
HTTP_VERSION = "HTTP_VERSION"
|
|
24
19
|
Chunked = "chunked"
|
|
25
|
-
|
|
26
|
-
# make this a no-op under Rubinius, it's pointless anyways
|
|
27
|
-
# since Rubinius doesn't have IO.copy_stream
|
|
28
|
-
def self.new(app)
|
|
29
|
-
app
|
|
30
|
-
end if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
|
|
31
20
|
include Rack::Utils
|
|
32
21
|
|
|
33
22
|
# Rack middleware entry point, we'll just pass through responses
|
|
@@ -55,8 +44,12 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
|
55
44
|
headers.delete(Transfer_Encoding)
|
|
56
45
|
elsif st.pipe? || st.socket? # epoll-able things
|
|
57
46
|
unless headers.include?(Content_Length)
|
|
58
|
-
if env[Rainbows_autochunk]
|
|
59
|
-
|
|
47
|
+
if env[Rainbows_autochunk]
|
|
48
|
+
case env[HTTP_VERSION]
|
|
49
|
+
when "HTTP/1.0", nil
|
|
50
|
+
else
|
|
51
|
+
headers[Transfer_Encoding] = Chunked
|
|
52
|
+
end
|
|
60
53
|
else
|
|
61
54
|
env[Rainbows_autochunk] = false
|
|
62
55
|
end
|
|
@@ -96,12 +96,12 @@ module Rainbows::Epoll::Client
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def ev_write_response(status, headers, body, alive)
|
|
99
|
+
@state = alive ? :headers : :close
|
|
99
100
|
if body.respond_to?(:to_path)
|
|
100
101
|
write_response_path(status, headers, body, alive)
|
|
101
102
|
else
|
|
102
103
|
write_response(status, headers, body, alive)
|
|
103
104
|
end
|
|
104
|
-
@state = alive ? :headers : :close
|
|
105
105
|
on_read(Z) if alive && 0 == @wr_queue.size && 0 != @buf.size
|
|
106
106
|
end
|
|
107
107
|
|
data/lib/rainbows/ev_core.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Rainbows::EvCore
|
|
|
10
10
|
RBUF = ""
|
|
11
11
|
Z = "".freeze
|
|
12
12
|
Rainbows.config!(self, :client_header_buffer_size)
|
|
13
|
+
HTTP_VERSION = "HTTP_VERSION"
|
|
13
14
|
|
|
14
15
|
# Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
|
|
15
16
|
ASYNC_CALLBACK = "async.callback".freeze
|
|
@@ -54,13 +55,23 @@ module Rainbows::EvCore
|
|
|
54
55
|
def stream_response_headers(status, headers, alive)
|
|
55
56
|
headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
|
|
56
57
|
if headers.include?(Content_Length)
|
|
57
|
-
|
|
58
|
+
write_headers(status, headers, alive)
|
|
59
|
+
return false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
case @env[HTTP_VERSION]
|
|
63
|
+
when "HTTP/1.0" # disable HTTP/1.0 keepalive to stream
|
|
64
|
+
write_headers(status, headers, false)
|
|
65
|
+
@hp.clear
|
|
66
|
+
false
|
|
67
|
+
when nil # "HTTP/0.9"
|
|
68
|
+
false
|
|
58
69
|
else
|
|
59
70
|
rv = !!(headers[Transfer_Encoding] =~ %r{\Achunked\z}i)
|
|
60
71
|
rv = false unless @env["rainbows.autochunk"]
|
|
72
|
+
write_headers(status, headers, alive)
|
|
73
|
+
rv
|
|
61
74
|
end
|
|
62
|
-
write_headers(status, headers, alive)
|
|
63
|
-
rv
|
|
64
75
|
end
|
|
65
76
|
|
|
66
77
|
def prepare_request_body
|
|
@@ -80,8 +91,7 @@ module Rainbows::EvCore
|
|
|
80
91
|
def on_read(data)
|
|
81
92
|
case @state
|
|
82
93
|
when :headers
|
|
83
|
-
@
|
|
84
|
-
@hp.parse or return want_more
|
|
94
|
+
@hp.add_parse(data) or return want_more
|
|
85
95
|
@state = :body
|
|
86
96
|
if 0 == @hp.content_length
|
|
87
97
|
app_call NULL_IO # common case
|
|
@@ -105,7 +115,7 @@ module Rainbows::EvCore
|
|
|
105
115
|
want_more
|
|
106
116
|
end
|
|
107
117
|
when :trailers
|
|
108
|
-
if @hp.
|
|
118
|
+
if @hp.add_parse(data)
|
|
109
119
|
@input.rewind
|
|
110
120
|
app_call @input
|
|
111
121
|
else
|
data/lib/rainbows/fiber.rb
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# -*- encoding: binary -*-
|
|
2
|
-
# :
|
|
2
|
+
# :enddoc:
|
|
3
3
|
begin
|
|
4
4
|
require 'fiber'
|
|
5
5
|
rescue LoadError
|
|
6
6
|
defined?(NeverBlock) or raise
|
|
7
7
|
end
|
|
8
|
-
# :startdoc:
|
|
9
8
|
|
|
10
9
|
# core namespace for all things that use Fibers in \Rainbows!
|
|
11
10
|
#
|
|
@@ -15,7 +14,6 @@ end
|
|
|
15
14
|
# supporting these in the future.
|
|
16
15
|
module Rainbows::Fiber
|
|
17
16
|
|
|
18
|
-
# :stopdoc:
|
|
19
17
|
# blocked readers (key: fileno, value: Rainbows::Fiber::IO object)
|
|
20
18
|
RD = []
|
|
21
19
|
|
|
@@ -24,7 +22,6 @@ module Rainbows::Fiber
|
|
|
24
22
|
|
|
25
23
|
# sleeping fibers go here (key: Fiber object, value: wakeup time)
|
|
26
24
|
ZZ = {}
|
|
27
|
-
# :startdoc:
|
|
28
25
|
|
|
29
26
|
# puts the current Fiber into uninterruptible sleep for at least
|
|
30
27
|
# +seconds+. Unlike Kernel#sleep, this it is not possible to sleep
|
data/lib/rainbows/fiber/io.rb
CHANGED
data/lib/rainbows/http_server.rb
CHANGED
|
@@ -21,14 +21,6 @@ class Rainbows::HttpServer < Unicorn::HttpServer
|
|
|
21
21
|
@worker_connections ||= @use == :Base ? 1 : 50
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def reopen_worker_logs(worker_nr)
|
|
25
|
-
logger.info "worker=#{worker_nr} reopening logs..."
|
|
26
|
-
Unicorn::Util.reopen_logs
|
|
27
|
-
logger.info "worker=#{worker_nr} done reopening logs"
|
|
28
|
-
rescue
|
|
29
|
-
Rainbows.quit! # let the master reopen and refork us
|
|
30
|
-
end
|
|
31
|
-
|
|
32
24
|
# Add one second to the timeout since our fchmod heartbeat is less
|
|
33
25
|
# precise (and must be more conservative) than Unicorn does. We
|
|
34
26
|
# handle many clients per process and can't chmod on every
|
|
@@ -105,4 +97,8 @@ class Rainbows::HttpServer < Unicorn::HttpServer
|
|
|
105
97
|
def keepalive_requests
|
|
106
98
|
Unicorn::HttpRequest.keepalive_requests
|
|
107
99
|
end
|
|
100
|
+
|
|
101
|
+
def client_max_header_size=(bytes)
|
|
102
|
+
Unicorn::HttpParser.max_header_len = bytes
|
|
103
|
+
end
|
|
108
104
|
end
|
data/lib/rainbows/response.rb
CHANGED
|
@@ -13,7 +13,7 @@ module Rainbows::Response
|
|
|
13
13
|
class F < File; end
|
|
14
14
|
|
|
15
15
|
# called after forking
|
|
16
|
-
def self.setup
|
|
16
|
+
def self.setup
|
|
17
17
|
Kgio.accept_class = Rainbows::Client
|
|
18
18
|
0 == Rainbows.server.keepalive_timeout and
|
|
19
19
|
Rainbows::HttpParser.keepalive_requests = 0
|
|
@@ -119,7 +119,15 @@ module Rainbows::Response
|
|
|
119
119
|
# This does not support multipart responses (does anybody actually
|
|
120
120
|
# use those?)
|
|
121
121
|
def sendfile_range(status, headers)
|
|
122
|
-
|
|
122
|
+
status = status.to_i
|
|
123
|
+
if 206 == status
|
|
124
|
+
if %r{\Abytes (\d+)-(\d+)/\d+\z} =~ headers[Content_Range]
|
|
125
|
+
a, b = $1.to_i, $2.to_i
|
|
126
|
+
return 206, headers, [ a, b - a + 1 ]
|
|
127
|
+
end
|
|
128
|
+
return # wtf...
|
|
129
|
+
end
|
|
130
|
+
200 == status &&
|
|
123
131
|
/\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env[HTTP_RANGE] or
|
|
124
132
|
return
|
|
125
133
|
a, b = $1.split(/-/)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
require "sleepy_penguin"
|
|
3
|
+
require "raindrops"
|
|
4
|
+
|
|
5
|
+
# Like Unicorn itself, this concurrency model is only intended for use
|
|
6
|
+
# behind nginx and completely unsupported otherwise. Even further from
|
|
7
|
+
# Unicorn, this isn't even a good idea with normal LAN clients, only nginx!
|
|
8
|
+
#
|
|
9
|
+
# It does NOT require a thread-safe Rack application at any point, but
|
|
10
|
+
# allows streaming data asynchronously via nginx (using the
|
|
11
|
+
# "X-Accel-Buffering: no" header to disable buffering).
|
|
12
|
+
#
|
|
13
|
+
# Unlike Rainbows::Base, this does NOT support persistent
|
|
14
|
+
# connections or pipelining. All \Rainbows! specific configuration
|
|
15
|
+
# options are ignored (except Rainbows::Configurator#use).
|
|
16
|
+
#
|
|
17
|
+
# === RubyGem Requirements
|
|
18
|
+
#
|
|
19
|
+
# * raindrops 0.6.0 or later
|
|
20
|
+
# * sleepy_penguin 3.0.1 or later
|
|
21
|
+
module Rainbows::StreamResponseEpoll
|
|
22
|
+
# :stopdoc:
|
|
23
|
+
CODES = Unicorn::HttpResponse::CODES
|
|
24
|
+
HEADER_END = "X-Accel-Buffering: no\r\n\r\n"
|
|
25
|
+
autoload :Client, "rainbows/stream_response_epoll/client"
|
|
26
|
+
|
|
27
|
+
def http_response_write(socket, status, headers, body)
|
|
28
|
+
status = CODES[status.to_i] || status
|
|
29
|
+
ep_client = false
|
|
30
|
+
|
|
31
|
+
if headers
|
|
32
|
+
# don't set extra headers here, this is only intended for
|
|
33
|
+
# consuming by nginx.
|
|
34
|
+
buf = "HTTP/1.0 #{status}\r\nStatus: #{status}\r\n"
|
|
35
|
+
headers.each do |key, value|
|
|
36
|
+
if value =~ /\n/
|
|
37
|
+
# avoiding blank, key-only cookies with /\n+/
|
|
38
|
+
buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
|
|
39
|
+
else
|
|
40
|
+
buf << "#{key}: #{value}\r\n"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
buf << HEADER_END
|
|
44
|
+
|
|
45
|
+
case rv = socket.kgio_trywrite(buf)
|
|
46
|
+
when nil then break
|
|
47
|
+
when String # retry, socket buffer may grow
|
|
48
|
+
buf = rv
|
|
49
|
+
when :wait_writable
|
|
50
|
+
ep_client = Client.new(socket, buf)
|
|
51
|
+
body.each { |chunk| ep_client.write(chunk) }
|
|
52
|
+
return ep_client.close
|
|
53
|
+
end while true
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
body.each do |chunk|
|
|
57
|
+
if ep_client
|
|
58
|
+
ep_client.write(chunk)
|
|
59
|
+
else
|
|
60
|
+
case rv = socket.kgio_trywrite(chunk)
|
|
61
|
+
when nil then break
|
|
62
|
+
when String # retry, socket buffer may grow
|
|
63
|
+
chunk = rv
|
|
64
|
+
when :wait_writable
|
|
65
|
+
ep_client = Client.new(socket, chunk)
|
|
66
|
+
break
|
|
67
|
+
end while true
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
ep_client.close if ep_client
|
|
71
|
+
ensure
|
|
72
|
+
body.respond_to?(:close) and body.close
|
|
73
|
+
end
|
|
74
|
+
# :startdoc:
|
|
75
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :enddoc:
|
|
3
|
+
class Rainbows::StreamResponseEpoll::Client
|
|
4
|
+
OUT = SleepyPenguin::Epoll::OUT
|
|
5
|
+
N = Raindrops.new(1)
|
|
6
|
+
EP = SleepyPenguin::Epoll.new
|
|
7
|
+
timeout = Rainbows.server.timeout
|
|
8
|
+
thr = Thread.new do
|
|
9
|
+
begin
|
|
10
|
+
EP.wait(nil, timeout) { |_,client| client.epoll_run }
|
|
11
|
+
rescue Errno::EINTR
|
|
12
|
+
rescue => e
|
|
13
|
+
Rainbows::Error.listen_loop(e)
|
|
14
|
+
end while Rainbows.alive || N[0] > 0
|
|
15
|
+
end
|
|
16
|
+
Rainbows.at_quit { thr.join(timeout) }
|
|
17
|
+
|
|
18
|
+
attr_reader :to_io
|
|
19
|
+
|
|
20
|
+
def initialize(io, unwritten)
|
|
21
|
+
@closed = false
|
|
22
|
+
@to_io = io.dup
|
|
23
|
+
@wr_queue = [ unwritten.dup ]
|
|
24
|
+
EP.set(self, OUT)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def write(str)
|
|
28
|
+
@wr_queue << str.dup
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def close
|
|
32
|
+
@closed = true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def epoll_run
|
|
36
|
+
return if @to_io.closed?
|
|
37
|
+
buf = @wr_queue.shift or return on_write_complete
|
|
38
|
+
case rv = @to_io.kgio_trywrite(buf)
|
|
39
|
+
when nil
|
|
40
|
+
buf = @wr_queue.shift or return on_write_complete
|
|
41
|
+
when String # retry, socket buffer may grow
|
|
42
|
+
buf = rv
|
|
43
|
+
when :wait_writable
|
|
44
|
+
return @wr_queue.unshift(buf)
|
|
45
|
+
end while true
|
|
46
|
+
rescue => err
|
|
47
|
+
@to_io.close
|
|
48
|
+
N.decr(0, 1)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def on_write_complete
|
|
52
|
+
if @closed
|
|
53
|
+
@to_io.close
|
|
54
|
+
N.decr(0, 1)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -95,8 +95,7 @@ module Rainbows::XEpollThreadSpawn::Client
|
|
|
95
95
|
return kato_set
|
|
96
96
|
when String
|
|
97
97
|
kato_delete
|
|
98
|
-
@hp.buf
|
|
99
|
-
env = @hp.parse and return spawn(env, @hp)
|
|
98
|
+
env = @hp.add_parse(buf) and return spawn(env, @hp)
|
|
100
99
|
else
|
|
101
100
|
return close
|
|
102
101
|
end while true
|
|
@@ -115,8 +114,7 @@ module Rainbows::XEpollThreadSpawn::Client
|
|
|
115
114
|
kato_set
|
|
116
115
|
return false
|
|
117
116
|
when String
|
|
118
|
-
hp.buf
|
|
119
|
-
hp.parse and return true
|
|
117
|
+
hp.add_parse(buf) and return true
|
|
120
118
|
# continue loop
|
|
121
119
|
else
|
|
122
120
|
return close
|
data/rainbows.gemspec
CHANGED
|
@@ -28,8 +28,8 @@ Gem::Specification.new do |s|
|
|
|
28
28
|
s.add_dependency(%q<kgio>, ['~> 2.4'])
|
|
29
29
|
|
|
30
30
|
# we need Unicorn for the HTTP parser and process management
|
|
31
|
-
s.add_dependency(%q<unicorn>, ["~>
|
|
32
|
-
s.add_development_dependency(%q<isolate>, "~> 3.
|
|
31
|
+
s.add_dependency(%q<unicorn>, ["~> 4.0"])
|
|
32
|
+
s.add_development_dependency(%q<isolate>, "~> 3.1")
|
|
33
33
|
s.add_development_dependency(%q<wrongdoc>, "~> 1.5")
|
|
34
34
|
|
|
35
35
|
# optional runtime dependencies depending on configuration
|
|
@@ -53,5 +53,6 @@ Gem::Specification.new do |s|
|
|
|
53
53
|
# NeverBlock, currently only available on http://gems.github.com/
|
|
54
54
|
# s.add_dependency(%q<espace-neverblock>, ["~> 0.1.6.1"])
|
|
55
55
|
|
|
56
|
-
#
|
|
56
|
+
# accessor not compatible with older RubyGems
|
|
57
|
+
# s.licenses = %w(GPLv3 GPLv2 Ruby)
|
|
57
58
|
end
|
data/t/GNUmakefile
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
use Rack::Chunked
|
|
2
2
|
use Rainbows::DevFdResponse
|
|
3
|
-
|
|
3
|
+
script_chunked = <<-EOF
|
|
4
4
|
for i in 0 1 2 3 4 5 6 7 8 9
|
|
5
5
|
do
|
|
6
6
|
printf '1\r\n%s\r\n' $i
|
|
@@ -9,15 +9,25 @@ done
|
|
|
9
9
|
printf '0\r\n\r\n'
|
|
10
10
|
EOF
|
|
11
11
|
|
|
12
|
+
script_identity = <<-EOF
|
|
13
|
+
for i in 0 1 2 3 4 5 6 7 8 9
|
|
14
|
+
do
|
|
15
|
+
printf $i
|
|
16
|
+
sleep 1
|
|
17
|
+
done
|
|
18
|
+
EOF
|
|
19
|
+
|
|
12
20
|
run lambda { |env|
|
|
13
21
|
env['rainbows.autochunk'] = false
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
headers = { 'Content-Type' => 'text/plain' }
|
|
23
|
+
|
|
24
|
+
script = case env["HTTP_VERSION"]
|
|
25
|
+
when nil, "HTTP/1.0"
|
|
26
|
+
script_identity
|
|
27
|
+
else
|
|
28
|
+
headers['Transfer-Encoding'] = 'chunked'
|
|
29
|
+
script_chunked
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
[ 200, headers, IO.popen(script, 'rb') ].freeze
|
|
23
33
|
}
|
data/t/t0000-simple-http.sh
CHANGED
data/t/t0001-unix-http.sh
CHANGED
data/t/t0009-broken-app.sh
CHANGED
data/t/t0044-autopush.sh
CHANGED
|
@@ -15,6 +15,7 @@ fi
|
|
|
15
15
|
# these buffer internally in external libraries, so we can't detect when
|
|
16
16
|
# to use TCP_CORK
|
|
17
17
|
skip_models EventMachine NeverBlock
|
|
18
|
+
skip_models StreamResponseEpoll
|
|
18
19
|
skip_models Coolio CoolioThreadPool CoolioThreadSpawn
|
|
19
20
|
skip_models Revactor Rev RevThreadPool RevThreadSpawn
|
|
20
21
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
. ./test-lib.sh
|
|
3
|
+
skip_models StreamResponseEpoll
|
|
4
|
+
|
|
5
|
+
t_plan 11 "client_max_header_size tests for $model"
|
|
6
|
+
|
|
7
|
+
t_begin "setup Rainbows!" && {
|
|
8
|
+
rainbows_setup $model
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
t_begin "fails with zero size" && {
|
|
12
|
+
ed -s $unicorn_config <<EOF
|
|
13
|
+
,s/^ client_max_body_size.*/ client_max_header_size 0/
|
|
14
|
+
w
|
|
15
|
+
EOF
|
|
16
|
+
grep "client_max_header_size 0" $unicorn_config
|
|
17
|
+
rainbows -D env.ru -c $unicorn_config && die "should fail"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
t_begin "fails with negative value" && {
|
|
21
|
+
ed -s $unicorn_config <<EOF
|
|
22
|
+
,s/^ client_max_header_size.*/ client_max_header_size -1/
|
|
23
|
+
w
|
|
24
|
+
EOF
|
|
25
|
+
grep "client_max_header_size -1" $unicorn_config
|
|
26
|
+
rainbows -D env.ru -c $unicorn_config && die "should fail"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
t_begin "fails with small size" && {
|
|
30
|
+
ed -s $unicorn_config <<EOF
|
|
31
|
+
,s/^ client_max_header_size.*/ client_max_header_size 7/
|
|
32
|
+
w
|
|
33
|
+
EOF
|
|
34
|
+
grep "client_max_header_size 7" $unicorn_config
|
|
35
|
+
rainbows -D env.ru -c $unicorn_config && die "should fail"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
t_begin "starts with minimum value" && {
|
|
39
|
+
ed -s $unicorn_config <<EOF
|
|
40
|
+
,s/^ client_max_header_size.*/ client_max_header_size 8/
|
|
41
|
+
w
|
|
42
|
+
EOF
|
|
43
|
+
grep 'client_max_header_size 8$' $unicorn_config
|
|
44
|
+
rainbows -D env.ru -c $unicorn_config
|
|
45
|
+
rainbows_wait_start
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
t_begin "smallest HTTP/0.9 request works right" && {
|
|
49
|
+
(
|
|
50
|
+
cat $fifo > $tmp &
|
|
51
|
+
printf 'GET /\r\n'
|
|
52
|
+
wait
|
|
53
|
+
echo ok > $ok
|
|
54
|
+
) | socat - TCP:$listen > $fifo
|
|
55
|
+
wait
|
|
56
|
+
test xok = x"$(cat $ok)"
|
|
57
|
+
test 1 -eq $(wc -l < $tmp)
|
|
58
|
+
grep HTTP_VERSION $tmp && die "unexpected HTTP_VERSION in HTTP/0.9 request"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
t_begin "HTTP/1.1 request fails" && {
|
|
62
|
+
curl -vsSf http://$listen/ > $tmp 2>&1 && die "unexpected curl success"
|
|
63
|
+
grep '400$' $tmp
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
t_begin "increase client_max_header_size on reload" && {
|
|
67
|
+
ed -s $unicorn_config <<EOF
|
|
68
|
+
,s/^ client_max_header_size.*/ client_max_header_size 512/
|
|
69
|
+
w
|
|
70
|
+
EOF
|
|
71
|
+
grep 'client_max_header_size 512$' $unicorn_config
|
|
72
|
+
kill -HUP $rainbows_pid
|
|
73
|
+
test xSTART = x"$(cat $fifo)"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
t_begin "HTTP/1.1 request succeeds" && {
|
|
77
|
+
curl -sSf http://$listen/ > $tmp
|
|
78
|
+
test 1 -eq $(wc -l < $tmp)
|
|
79
|
+
dbgcat tmp
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
t_begin "no errors in stderr" && {
|
|
83
|
+
check_stderr
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
t_begin "shutdown" && {
|
|
87
|
+
kill $rainbows_pid
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
t_done
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
2
|
. ./test-lib.sh
|
|
3
|
+
skip_models StreamResponseEpoll
|
|
3
4
|
|
|
4
5
|
t_plan 29 "keepalive does not clear Rack env prematurely for $model"
|
|
5
6
|
|
|
@@ -15,6 +16,7 @@ req_pipelined () {
|
|
|
15
16
|
pfx=$1
|
|
16
17
|
t_begin "make pipelined requests to trigger $pfx response body" && {
|
|
17
18
|
> $r_out
|
|
19
|
+
rm -f $ok
|
|
18
20
|
(
|
|
19
21
|
cat $fifo > $tmp &
|
|
20
22
|
printf 'GET /%s/1 HTTP/1.1\r\n' $pfx
|
|
@@ -34,7 +36,8 @@ req_pipelined () {
|
|
|
34
36
|
reload () {
|
|
35
37
|
t_begin 'reloading Rainbows! to ensure writeout' && {
|
|
36
38
|
# ensure worker is loaded before HUP
|
|
37
|
-
|
|
39
|
+
rm -f $curl_err $curl_out
|
|
40
|
+
curl -vs http://$listen/ >$curl_out 2> $curl_err
|
|
38
41
|
# reload to ensure everything is flushed
|
|
39
42
|
kill -HUP $rainbows_pid
|
|
40
43
|
test xSTART = x"$(cat $fifo)"
|
|
@@ -29,7 +29,7 @@ t_begin "staggered trailer upload" && {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
t_begin "HTTP response is OK" && {
|
|
32
|
-
|
|
32
|
+
grep 'HTTP/1\.[01] 200 OK' $tmp
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
t_begin "upload small blob" && {
|
|
@@ -42,7 +42,7 @@ t_begin "upload small blob" && {
|
|
|
42
42
|
test xok = x"$(cat $ok)"
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
t_begin "HTTP response is OK" &&
|
|
45
|
+
t_begin "HTTP response is OK" && grep 'HTTP/1\.[01] 200 OK' $tmp
|
|
46
46
|
t_begin "no errors in stderr log" && check_stderr
|
|
47
47
|
|
|
48
48
|
t_begin "big blob request" && {
|
|
@@ -55,7 +55,7 @@ t_begin "big blob request" && {
|
|
|
55
55
|
test xok = x"$(cat $ok)"
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
t_begin "HTTP response is OK" &&
|
|
58
|
+
t_begin "HTTP response is OK" && grep 'HTTP/1\.[01] 200 OK' $tmp
|
|
59
59
|
t_begin "no errors in stderr log" && check_stderr
|
|
60
60
|
|
|
61
61
|
t_begin "staggered blob upload" && {
|
|
@@ -76,9 +76,7 @@ t_begin "staggered blob upload" && {
|
|
|
76
76
|
test xok = x"$(cat $ok)"
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
t_begin "HTTP response is OK" &&
|
|
80
|
-
fgrep 'HTTP/1.1 200 OK' $tmp
|
|
81
|
-
}
|
|
79
|
+
t_begin "HTTP response is OK" && grep 'HTTP/1\.[01] 200 OK' $tmp
|
|
82
80
|
|
|
83
81
|
t_begin "no errors in stderr log" && check_stderr
|
|
84
82
|
|
data/t/t0103-rack-input-limit.sh
CHANGED
data/t/t0200-async-response.sh
CHANGED
data/t/t9001-sendfile-to-path.sh
CHANGED
data/t/t9002.ru
CHANGED
data/t/test-lib.sh
CHANGED
data/t/test_isolate.rb
CHANGED
|
@@ -16,13 +16,13 @@ $stdout.reopen($stderr)
|
|
|
16
16
|
lock = File.open(__FILE__, "rb")
|
|
17
17
|
lock.flock(File::LOCK_EX)
|
|
18
18
|
Isolate.now!(opts) do
|
|
19
|
-
gem 'kgio', '2.
|
|
20
|
-
gem '
|
|
21
|
-
gem '
|
|
22
|
-
gem '
|
|
19
|
+
gem 'kgio', '2.5.0'
|
|
20
|
+
gem 'kcar', '0.3.0'
|
|
21
|
+
gem 'raindrops', '0.7.0'
|
|
22
|
+
gem 'unicorn', '4.0.0'
|
|
23
23
|
|
|
24
24
|
if engine == "ruby"
|
|
25
|
-
gem 'sendfile', '1.1.0'
|
|
25
|
+
gem 'sendfile', '1.1.0'
|
|
26
26
|
gem 'cool.io', '1.0.0'
|
|
27
27
|
|
|
28
28
|
gem 'eventmachine', '0.12.10'
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rainbows
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 63
|
|
5
5
|
prerelease:
|
|
6
6
|
segments:
|
|
7
|
-
- 3
|
|
8
7
|
- 4
|
|
9
8
|
- 0
|
|
10
|
-
|
|
9
|
+
- 0
|
|
10
|
+
version: 4.0.0
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- Rainbows! hackers
|
|
@@ -15,7 +15,7 @@ autorequire:
|
|
|
15
15
|
bindir: bin
|
|
16
16
|
cert_chain: []
|
|
17
17
|
|
|
18
|
-
date: 2011-
|
|
18
|
+
date: 2011-06-27 00:00:00 Z
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|
|
21
21
|
name: rack
|
|
@@ -55,11 +55,11 @@ dependencies:
|
|
|
55
55
|
requirements:
|
|
56
56
|
- - ~>
|
|
57
57
|
- !ruby/object:Gem::Version
|
|
58
|
-
hash:
|
|
58
|
+
hash: 27
|
|
59
59
|
segments:
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
version: "
|
|
60
|
+
- 4
|
|
61
|
+
- 0
|
|
62
|
+
version: "4.0"
|
|
63
63
|
type: :runtime
|
|
64
64
|
version_requirements: *id003
|
|
65
65
|
- !ruby/object:Gem::Dependency
|
|
@@ -70,12 +70,11 @@ dependencies:
|
|
|
70
70
|
requirements:
|
|
71
71
|
- - ~>
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
|
-
hash:
|
|
73
|
+
hash: 5
|
|
74
74
|
segments:
|
|
75
75
|
- 3
|
|
76
|
-
-
|
|
77
|
-
|
|
78
|
-
version: 3.0.0
|
|
76
|
+
- 1
|
|
77
|
+
version: "3.1"
|
|
79
78
|
type: :development
|
|
80
79
|
version_requirements: *id004
|
|
81
80
|
- !ruby/object:Gem::Dependency
|
|
@@ -193,6 +192,8 @@ extra_rdoc_files:
|
|
|
193
192
|
- lib/rainbows/server_token.rb
|
|
194
193
|
- lib/rainbows/socket_proxy.rb
|
|
195
194
|
- lib/rainbows/stream_file.rb
|
|
195
|
+
- lib/rainbows/stream_response_epoll.rb
|
|
196
|
+
- lib/rainbows/stream_response_epoll/client.rb
|
|
196
197
|
- lib/rainbows/sync_close.rb
|
|
197
198
|
- lib/rainbows/thread_pool.rb
|
|
198
199
|
- lib/rainbows/thread_spawn.rb
|
|
@@ -338,6 +339,8 @@ files:
|
|
|
338
339
|
- lib/rainbows/server_token.rb
|
|
339
340
|
- lib/rainbows/socket_proxy.rb
|
|
340
341
|
- lib/rainbows/stream_file.rb
|
|
342
|
+
- lib/rainbows/stream_response_epoll.rb
|
|
343
|
+
- lib/rainbows/stream_response_epoll/client.rb
|
|
341
344
|
- lib/rainbows/sync_close.rb
|
|
342
345
|
- lib/rainbows/thread_pool.rb
|
|
343
346
|
- lib/rainbows/thread_spawn.rb
|
|
@@ -460,6 +463,7 @@ files:
|
|
|
460
463
|
- t/t0042-client_header_buffer_size.sh
|
|
461
464
|
- t/t0043-quit-keepalive-disconnect.sh
|
|
462
465
|
- t/t0044-autopush.sh
|
|
466
|
+
- t/t0045-client_max_header_size.sh
|
|
463
467
|
- t/t0050-response-body-close-has-env.sh
|
|
464
468
|
- t/t0100-rack-input-hammer-chunked.sh
|
|
465
469
|
- t/t0100-rack-input-hammer-content-length.sh
|
|
@@ -534,7 +538,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
534
538
|
requirements: []
|
|
535
539
|
|
|
536
540
|
rubyforge_project: rainbows
|
|
537
|
-
rubygems_version: 1.8.
|
|
541
|
+
rubygems_version: 1.8.5
|
|
538
542
|
signing_key:
|
|
539
543
|
specification_version: 3
|
|
540
544
|
summary: "- Unicorn for sleepy apps and slow clients"
|