rainbows 3.4.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|