rainbows 0.97.0 → 1.0.0pre1
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/.manifest +14 -2
- data/ChangeLog +87 -118
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +1 -1
- data/README +1 -1
- data/bin/rainbows +15 -20
- data/lib/rainbows/actor_spawn.rb +20 -22
- data/lib/rainbows/app_pool.rb +89 -93
- data/lib/rainbows/base.rb +4 -61
- data/lib/rainbows/client.rb +9 -0
- data/lib/rainbows/configurator.rb +37 -39
- data/lib/rainbows/const.rb +18 -18
- data/lib/rainbows/dev_fd_response.rb +2 -1
- data/lib/rainbows/error.rb +39 -37
- data/lib/rainbows/ev_core.rb +103 -109
- data/lib/rainbows/event_machine.rb +188 -196
- data/lib/rainbows/fiber/base.rb +69 -88
- data/lib/rainbows/fiber/io/compat.rb +13 -0
- data/lib/rainbows/fiber/io/methods.rb +49 -0
- data/lib/rainbows/fiber/io/pipe.rb +7 -0
- data/lib/rainbows/fiber/io/socket.rb +7 -0
- data/lib/rainbows/fiber/io.rb +125 -84
- data/lib/rainbows/fiber/rev/heartbeat.rb +8 -0
- data/lib/rainbows/fiber/rev/kato.rb +22 -0
- data/lib/rainbows/fiber/rev/methods.rb +55 -0
- data/lib/rainbows/fiber/rev/server.rb +32 -0
- data/lib/rainbows/fiber/rev/sleeper.rb +15 -0
- data/lib/rainbows/fiber/rev.rb +6 -164
- data/lib/rainbows/fiber.rb +23 -5
- data/lib/rainbows/fiber_pool.rb +31 -37
- data/lib/rainbows/fiber_spawn.rb +21 -28
- data/lib/rainbows/http_server.rb +80 -80
- data/lib/rainbows/max_body.rb +26 -28
- data/lib/rainbows/process_client.rb +61 -0
- data/lib/rainbows/queue_pool.rb +19 -22
- data/lib/rainbows/read_timeout.rb +28 -0
- data/lib/rainbows/rev/client.rb +10 -10
- data/lib/rainbows/rev/core.rb +2 -3
- data/lib/rainbows/rev/thread.rb +1 -1
- data/lib/rainbows/rev_fiber_spawn.rb +21 -24
- data/lib/rainbows/revactor.rb +18 -15
- data/lib/rainbows/thread_pool.rb +2 -4
- data/lib/rainbows/thread_spawn.rb +1 -2
- data/lib/rainbows/writer_thread_pool.rb +14 -4
- data/lib/rainbows/writer_thread_spawn.rb +14 -4
- data/lib/rainbows.rb +7 -15
- data/local.mk.sample +3 -11
- data/rainbows.gemspec +2 -4
- data/t/kgio-pipe-response.ru +10 -0
- data/t/t0035-kgio-pipe-response.sh +70 -0
- data/t/test_isolate.rb +2 -1
- metadata +46 -30
- data/lib/rainbows/acceptor.rb +0 -26
- data/lib/rainbows/byte_slice.rb +0 -17
data/lib/rainbows/fiber/rev.rb
CHANGED
@@ -4,168 +4,10 @@ require 'rev'
|
|
4
4
|
require 'rainbows/fiber'
|
5
5
|
require 'rainbows/fiber/io'
|
6
6
|
|
7
|
-
module Rainbows::Fiber
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
class Kato < ::Rev::TimerWatcher
|
13
|
-
def initialize
|
14
|
-
@watch = []
|
15
|
-
super(1, true)
|
16
|
-
end
|
17
|
-
|
18
|
-
def <<(fiber)
|
19
|
-
@watch << fiber
|
20
|
-
enable unless enabled?
|
21
|
-
end
|
22
|
-
|
23
|
-
def on_timer
|
24
|
-
@watch.uniq!
|
25
|
-
while f = @watch.shift
|
26
|
-
f.resume if f.alive?
|
27
|
-
end
|
28
|
-
disable
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class Heartbeat < ::Rev::TimerWatcher
|
33
|
-
def on_timer
|
34
|
-
exit if (! G.tick && G.cur <= 0)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
class Sleeper < ::Rev::TimerWatcher
|
39
|
-
|
40
|
-
def initialize(seconds)
|
41
|
-
@f = ::Fiber.current
|
42
|
-
super(seconds, false)
|
43
|
-
attach(::Rev::Loop.default)
|
44
|
-
::Fiber.yield
|
45
|
-
end
|
46
|
-
|
47
|
-
def on_timer
|
48
|
-
@f.resume
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class Server < ::Rev::IOWatcher
|
53
|
-
include Unicorn
|
54
|
-
include Rainbows
|
55
|
-
include Rainbows::Const
|
56
|
-
include Rainbows::Response
|
57
|
-
include Rainbows::Acceptor
|
58
|
-
FIO = Rainbows::Fiber::IO
|
59
|
-
|
60
|
-
def to_io
|
61
|
-
@io
|
62
|
-
end
|
63
|
-
|
64
|
-
def initialize(io)
|
65
|
-
@io = io
|
66
|
-
super(self, :r)
|
67
|
-
end
|
68
|
-
|
69
|
-
def close
|
70
|
-
detach if attached?
|
71
|
-
@io.close
|
72
|
-
end
|
73
|
-
|
74
|
-
def on_readable
|
75
|
-
return if G.cur >= MAX
|
76
|
-
c = accept(@io) and ::Fiber.new { process(c) }.resume
|
77
|
-
end
|
78
|
-
|
79
|
-
def process(io)
|
80
|
-
G.cur += 1
|
81
|
-
client = FIO.new(io, ::Fiber.current)
|
82
|
-
buf = client.read_timeout or return
|
83
|
-
hp = HttpParser.new
|
84
|
-
env = {}
|
85
|
-
remote_addr = Rainbows.addr(io)
|
86
|
-
|
87
|
-
begin # loop
|
88
|
-
buf << (client.read_timeout or return) until hp.headers(env, buf)
|
89
|
-
|
90
|
-
env[CLIENT_IO] = client
|
91
|
-
env[RACK_INPUT] = 0 == hp.content_length ?
|
92
|
-
HttpRequest::NULL_IO : TeeInput.new(client, env, hp, buf)
|
93
|
-
env[REMOTE_ADDR] = remote_addr
|
94
|
-
status, headers, body = APP.call(env.update(RACK_DEFAULTS))
|
95
|
-
|
96
|
-
if 100 == status.to_i
|
97
|
-
client.write(EXPECT_100_RESPONSE)
|
98
|
-
env.delete(HTTP_EXPECT)
|
99
|
-
status, headers, body = APP.call(env)
|
100
|
-
end
|
101
|
-
|
102
|
-
if hp.headers?
|
103
|
-
headers = HH.new(headers)
|
104
|
-
range = make_range!(env, status, headers) and status = range.shift
|
105
|
-
headers[CONNECTION] = if hp.keepalive? && G.alive
|
106
|
-
KEEP_ALIVE
|
107
|
-
else
|
108
|
-
env = false
|
109
|
-
CLOSE
|
110
|
-
end
|
111
|
-
client.write(response_header(status, headers))
|
112
|
-
end
|
113
|
-
write_body(client, body, range)
|
114
|
-
end while env && env.clear && hp.reset.nil?
|
115
|
-
rescue => e
|
116
|
-
Error.write(io, e)
|
117
|
-
ensure
|
118
|
-
G.cur -= 1
|
119
|
-
client.close
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
class IO # see rainbows/fiber/io for original definition
|
125
|
-
|
126
|
-
class Watcher < ::Rev::IOWatcher
|
127
|
-
def initialize(fio, flag)
|
128
|
-
@fiber = fio.f
|
129
|
-
super(fio, flag)
|
130
|
-
attach(::Rev::Loop.default)
|
131
|
-
end
|
132
|
-
|
133
|
-
def on_readable
|
134
|
-
@fiber.resume
|
135
|
-
end
|
136
|
-
|
137
|
-
alias on_writable on_readable
|
138
|
-
end
|
139
|
-
|
140
|
-
undef_method :wait_readable
|
141
|
-
undef_method :wait_writable
|
142
|
-
undef_method :close
|
143
|
-
|
144
|
-
def initialize(*args)
|
145
|
-
super
|
146
|
-
@r = @w = false
|
147
|
-
end
|
148
|
-
|
149
|
-
def close
|
150
|
-
@w.detach if @w
|
151
|
-
@r.detach if @r
|
152
|
-
@r = @w = false
|
153
|
-
to_io.close unless to_io.closed?
|
154
|
-
end
|
155
|
-
|
156
|
-
def wait_writable
|
157
|
-
@w ||= Watcher.new(self, :w)
|
158
|
-
@w.enable unless @w.enabled?
|
159
|
-
::Fiber.yield
|
160
|
-
@w.disable
|
161
|
-
end
|
162
|
-
|
163
|
-
def wait_readable
|
164
|
-
@r ||= Watcher.new(self, :r)
|
165
|
-
@r.enable unless @r.enabled?
|
166
|
-
KATO << f
|
167
|
-
::Fiber.yield
|
168
|
-
@r.disable
|
169
|
-
end
|
170
|
-
end
|
7
|
+
module Rainbows::Fiber::Rev
|
8
|
+
autoload :Heartbeat, 'rainbows/fiber/rev/heartbeat'
|
9
|
+
autoload :Kato, 'rainbows/fiber/rev/kato'
|
10
|
+
autoload :Server, 'rainbows/fiber/rev/server'
|
11
|
+
autoload :Sleeper, 'rainbows/fiber/rev/sleeper'
|
171
12
|
end
|
13
|
+
require 'rainbows/fiber/rev/methods'
|
data/lib/rainbows/fiber.rb
CHANGED
@@ -6,11 +6,29 @@ rescue LoadError
|
|
6
6
|
defined?(NeverBlock) or raise
|
7
7
|
end
|
8
8
|
|
9
|
-
module Rainbows
|
9
|
+
# core module for all things that use Fibers in Rainbows!
|
10
|
+
module Rainbows::Fiber
|
10
11
|
|
11
|
-
#
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
# blocked readers (key: fileno, value: Rainbows::Fiber::IO object)
|
13
|
+
RD = []
|
14
|
+
|
15
|
+
# blocked writers (key: fileno, value: Rainbows::Fiber::IO object)
|
16
|
+
WR = []
|
17
|
+
|
18
|
+
# sleeping fibers go here (key: Fiber object, value: wakeup time)
|
19
|
+
ZZ = {}
|
20
|
+
|
21
|
+
# puts the current Fiber into uninterruptible sleep for at least
|
22
|
+
# +seconds+. Unlike Kernel#sleep, this it is not possible to sleep
|
23
|
+
# indefinitely to be woken up (nobody wants that in a web server,
|
24
|
+
# right?). Calling this directly is deprecated, use
|
25
|
+
# Rainbows.sleep(seconds) instead.
|
26
|
+
def self.sleep(seconds)
|
27
|
+
ZZ[::Fiber.current] = Time.now + seconds
|
28
|
+
::Fiber.yield
|
15
29
|
end
|
30
|
+
|
31
|
+
autoload :Base, 'rainbows/fiber/base'
|
32
|
+
autoload :Queue, 'rainbows/fiber/queue'
|
33
|
+
autoload :IO, 'rainbows/fiber/io'
|
16
34
|
end
|
data/lib/rainbows/fiber_pool.rb
CHANGED
@@ -1,45 +1,39 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'rainbows/fiber'
|
3
3
|
|
4
|
-
|
4
|
+
# A Fiber-based concurrency model for Ruby 1.9. This uses a pool of
|
5
|
+
# Fibers to handle client IO to run the application and the root Fiber
|
6
|
+
# for scheduling and connection acceptance. The pool size is equal to
|
7
|
+
# the number of +worker_connections+. Compared to the ThreadPool
|
8
|
+
# model, Fibers are very cheap in terms of memory usage so you can
|
9
|
+
# have more active connections. This model supports a streaming
|
10
|
+
# "rack.input" with lightweight concurrency. Applications are
|
11
|
+
# strongly advised to wrap all slow IO objects (sockets, pipes) using
|
12
|
+
# the Rainbows::Fiber::IO class whenever possible.
|
13
|
+
module Rainbows::FiberPool
|
14
|
+
include Rainbows::Fiber::Base
|
5
15
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
16
|
+
def worker_loop(worker) # :nodoc:
|
17
|
+
init_worker_process(worker)
|
18
|
+
pool = []
|
19
|
+
worker_connections.times {
|
20
|
+
Fiber.new {
|
21
|
+
process(Fiber.yield) while pool << Fiber.current
|
22
|
+
}.resume # resume to hit ::Fiber.yield so it waits on a client
|
23
|
+
}
|
24
|
+
Rainbows::Fiber::Base.setup(self.class, app)
|
15
25
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
worker_connections.times {
|
24
|
-
::Fiber.new {
|
25
|
-
process_client(::Fiber.yield) while pool << ::Fiber.current
|
26
|
-
}.resume # resume to hit ::Fiber.yield so it waits on a client
|
27
|
-
}
|
28
|
-
Fiber::Base.setup(self.class, app)
|
29
|
-
|
30
|
-
begin
|
31
|
-
schedule do |l|
|
32
|
-
fib = pool.shift or break # let another worker process take it
|
33
|
-
if io = accept(l)
|
34
|
-
fib.resume(Fiber::IO.new(io, fib))
|
35
|
-
else
|
36
|
-
pool << fib
|
37
|
-
end
|
26
|
+
begin
|
27
|
+
schedule do |l|
|
28
|
+
fib = pool.shift or break # let another worker process take it
|
29
|
+
if io = l.kgio_tryaccept
|
30
|
+
fib.resume(io)
|
31
|
+
else
|
32
|
+
pool << fib
|
38
33
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
|
34
|
+
end
|
35
|
+
rescue => e
|
36
|
+
Rainbows::Error.listen_loop(e)
|
37
|
+
end while G.alive || G.cur > 0
|
44
38
|
end
|
45
39
|
end
|
data/lib/rainbows/fiber_spawn.rb
CHANGED
@@ -1,35 +1,28 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'rainbows/fiber'
|
3
3
|
|
4
|
-
|
4
|
+
# Simple Fiber-based concurrency model for 1.9. This spawns a new
|
5
|
+
# Fiber for every incoming client connection and the root Fiber for
|
6
|
+
# scheduling and connection acceptance. This exports a streaming
|
7
|
+
# "rack.input" with lightweight concurrency. Applications are
|
8
|
+
# strongly advised to wrap all slow IO objects (sockets, pipes) using
|
9
|
+
# the Rainbows::Fiber::IO class whenever possible.
|
10
|
+
module Rainbows::FiberSpawn
|
11
|
+
include Rainbows::Fiber::Base
|
5
12
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# strongly advised to wrap all slow IO objects (sockets, pipes) using
|
11
|
-
# the Rainbows::Fiber::IO class whenever possible.
|
12
|
-
|
13
|
-
module FiberSpawn
|
14
|
-
include Fiber::Base
|
15
|
-
include Rainbows::Acceptor
|
16
|
-
|
17
|
-
def worker_loop(worker) # :nodoc:
|
18
|
-
init_worker_process(worker)
|
19
|
-
Fiber::Base.setup(self.class, app)
|
20
|
-
limit = worker_connections
|
21
|
-
fio = Rainbows::Fiber::IO
|
22
|
-
|
23
|
-
begin
|
24
|
-
schedule do |l|
|
25
|
-
break if G.cur >= limit
|
26
|
-
io = accept(l) or next
|
27
|
-
::Fiber.new { process_client(fio.new(io, ::Fiber.current)) }.resume
|
28
|
-
end
|
29
|
-
rescue => e
|
30
|
-
Error.listen_loop(e)
|
31
|
-
end while G.alive || G.cur > 0
|
32
|
-
end
|
13
|
+
def worker_loop(worker) # :nodoc:
|
14
|
+
init_worker_process(worker)
|
15
|
+
Rainbows::Fiber::Base.setup(self.class, app)
|
16
|
+
limit = worker_connections
|
33
17
|
|
18
|
+
begin
|
19
|
+
schedule do |l|
|
20
|
+
break if G.cur >= limit
|
21
|
+
io = l.kgio_tryaccept or next
|
22
|
+
Fiber.new { process(io) }.resume
|
23
|
+
end
|
24
|
+
rescue => e
|
25
|
+
Rainbows::Error.listen_loop(e)
|
26
|
+
end while G.alive || G.cur > 0
|
34
27
|
end
|
35
28
|
end
|
data/lib/rainbows/http_server.rb
CHANGED
@@ -1,97 +1,97 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
# :enddoc:
|
3
|
-
module Rainbows
|
4
3
|
|
5
|
-
|
6
|
-
|
4
|
+
class Rainbows::HttpServer < Unicorn::HttpServer
|
5
|
+
G = Rainbows::G
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def initialize(app, options)
|
15
|
-
G.server = self
|
16
|
-
rv = super(app, options)
|
17
|
-
defined?(@use) or use(:Base)
|
18
|
-
@worker_connections ||= MODEL_WORKER_CONNECTIONS[@use]
|
19
|
-
end
|
7
|
+
def self.setup(block)
|
8
|
+
G.server.instance_eval(&block)
|
9
|
+
end
|
20
10
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
11
|
+
def initialize(app, options)
|
12
|
+
G.server = self
|
13
|
+
@logger = Unicorn::Configurator::DEFAULTS[:logger]
|
14
|
+
rv = super(app, options)
|
15
|
+
defined?(@use) or use(:Base)
|
16
|
+
@worker_connections ||= Rainbows::MODEL_WORKER_CONNECTIONS[@use]
|
17
|
+
end
|
28
18
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
# periods of time means we have to chmod at a fixed interval.
|
37
|
-
def timeout=(nr)
|
38
|
-
super(nr + 1)
|
39
|
-
end
|
40
|
-
#:startdoc:
|
19
|
+
def reopen_worker_logs(worker_nr)
|
20
|
+
logger.info "worker=#{worker_nr} reopening logs..."
|
21
|
+
Unicorn::Util.reopen_logs
|
22
|
+
logger.info "worker=#{worker_nr} done reopening logs"
|
23
|
+
rescue
|
24
|
+
G.quit! # let the master reopen and refork us
|
25
|
+
end
|
41
26
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
27
|
+
#:stopdoc:
|
28
|
+
#
|
29
|
+
# Add one second to the timeout since our fchmod heartbeat is less
|
30
|
+
# precise (and must be more conservative) than Unicorn does. We
|
31
|
+
# handle many clients per process and can't chmod on every
|
32
|
+
# connection we accept without wasting cycles. That added to the
|
33
|
+
# fact that we let clients keep idle connections open for long
|
34
|
+
# periods of time means we have to chmod at a fixed interval.
|
35
|
+
def timeout=(nr)
|
36
|
+
@timeout = nr + 1
|
37
|
+
end
|
38
|
+
#:startdoc:
|
49
39
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
60
|
-
mod.setup if mod.respond_to?(:setup)
|
61
|
-
Const::RACK_DEFAULTS['rainbows.model'] = @use = model.to_sym
|
62
|
-
Const::RACK_DEFAULTS['rack.multithread'] = !!(model.to_s =~ /Thread/)
|
40
|
+
def use(*args)
|
41
|
+
model = args.shift or return @use
|
42
|
+
mod = begin
|
43
|
+
Rainbows.const_get(model)
|
44
|
+
rescue NameError => e
|
45
|
+
logger.error "error loading #{model.inspect}: #{e}"
|
46
|
+
e.backtrace.each { |l| logger.error l }
|
47
|
+
raise ArgumentError, "concurrency model #{model.inspect} not supported"
|
48
|
+
end
|
63
49
|
|
64
|
-
|
65
|
-
|
66
|
-
|
50
|
+
Module === mod or
|
51
|
+
raise ArgumentError, "concurrency model #{model.inspect} not supported"
|
52
|
+
extend(mod)
|
53
|
+
args.each do |opt|
|
54
|
+
case opt
|
55
|
+
when Hash; O.update(opt)
|
56
|
+
when Symbol; O[opt] = true
|
57
|
+
else; raise ArgumentError, "can't handle option: #{opt.inspect}"
|
67
58
|
end
|
68
59
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
60
|
+
mod.setup if mod.respond_to?(:setup)
|
61
|
+
new_defaults = {
|
62
|
+
'rainbows.model' => (@use = model.to_sym),
|
63
|
+
'rack.multithread' => !!(model.to_s =~ /Thread/),
|
64
|
+
}
|
65
|
+
case @use
|
66
|
+
when :Rev, :EventMachine, :NeverBlock
|
67
|
+
new_defaults['rainbows.autochunk'] = true
|
76
68
|
end
|
69
|
+
Rainbows::Const::RACK_DEFAULTS.update(new_defaults)
|
70
|
+
end
|
77
71
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
72
|
+
def worker_connections(*args)
|
73
|
+
return @worker_connections if args.empty?
|
74
|
+
nr = args[0]
|
75
|
+
(Integer === nr && nr > 0) or
|
76
|
+
raise ArgumentError, "worker_connections must be a positive Integer"
|
77
|
+
@worker_connections = nr
|
78
|
+
end
|
83
79
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
when Integer
|
89
|
-
nr >= 0 or raise ArgumentError, err
|
90
|
-
else
|
91
|
-
raise ArgumentError, err
|
92
|
-
end
|
93
|
-
Rainbows.max_bytes = nr
|
94
|
-
end
|
80
|
+
def keepalive_timeout(nr)
|
81
|
+
(Integer === nr && nr >= 0) or
|
82
|
+
raise ArgumentError, "keepalive must be a non-negative Integer"
|
83
|
+
G.kato = nr
|
95
84
|
end
|
96
85
|
|
86
|
+
def client_max_body_size(nr)
|
87
|
+
err = "client_max_body_size must be nil or a non-negative Integer"
|
88
|
+
case nr
|
89
|
+
when nil
|
90
|
+
when Integer
|
91
|
+
nr >= 0 or raise ArgumentError, err
|
92
|
+
else
|
93
|
+
raise ArgumentError, err
|
94
|
+
end
|
95
|
+
Rainbows.max_bytes = nr
|
96
|
+
end
|
97
97
|
end
|
data/lib/rainbows/max_body.rb
CHANGED
@@ -1,42 +1,42 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
# :enddoc:
|
3
|
-
module Rainbows
|
4
3
|
|
5
4
|
# middleware used to enforce client_max_body_size for TeeInput users,
|
6
5
|
# there is no need to configure this middleware manually, it will
|
7
6
|
# automatically be configured for you based on the client_max_body_size
|
8
7
|
# setting
|
9
|
-
class MaxBody < Struct.new(:app)
|
8
|
+
class Rainbows::MaxBody < Struct.new(:app)
|
10
9
|
|
11
10
|
# this is meant to be included in Rainbows::TeeInput (and derived
|
12
11
|
# classes) to limit body sizes
|
13
12
|
module Limit
|
14
|
-
|
13
|
+
TmpIO = Unicorn::TmpIO
|
14
|
+
MAX_BODY = Rainbows::Const::MAX_BODY
|
15
15
|
|
16
|
-
def initialize(socket,
|
17
|
-
|
16
|
+
def initialize(socket, request)
|
17
|
+
@parser = request
|
18
|
+
@buf = request.buf
|
19
|
+
@env = request.env
|
20
|
+
@len = request.content_length
|
18
21
|
|
19
22
|
max = Rainbows.max_bytes # never nil, see MaxBody.setup
|
20
|
-
if len && len > max
|
21
|
-
socket.write(Const::ERROR_413_RESPONSE)
|
23
|
+
if @len && @len > max
|
24
|
+
socket.write(Rainbows::Const::ERROR_413_RESPONSE)
|
22
25
|
socket.close
|
23
|
-
raise IOError, "Content-Length too big:
|
26
|
+
raise IOError, "Content-Length too big: #@len > #{max}", []
|
24
27
|
end
|
25
28
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
if buf.size > 0
|
32
|
-
parser.filter_body(buf2, buf) and finalize_input
|
33
|
-
buf2.size > max and raise IOError, "chunked request body too big", []
|
29
|
+
@socket = socket
|
30
|
+
@buf2 = ""
|
31
|
+
if @buf.size > 0
|
32
|
+
parser.filter_body(@buf2, @buf) and finalize_input
|
33
|
+
@buf2.size > max and raise IOError, "chunked request body too big", []
|
34
34
|
end
|
35
|
-
|
36
|
-
if buf2.size > 0
|
37
|
-
tmp.write(buf2)
|
38
|
-
tmp.
|
39
|
-
max -= buf2.size
|
35
|
+
@tmp = @len && @len < MAX_BODY ? StringIO.new("") : TmpIO.new
|
36
|
+
if @buf2.size > 0
|
37
|
+
@tmp.write(@buf2)
|
38
|
+
@tmp.rewind
|
39
|
+
max -= @buf2.size
|
40
40
|
end
|
41
41
|
@max_body = max
|
42
42
|
end
|
@@ -46,7 +46,7 @@ class MaxBody < Struct.new(:app)
|
|
46
46
|
if rv && ((@max_body -= rv.size) < 0)
|
47
47
|
# make HttpParser#keepalive? => false to force an immediate disconnect
|
48
48
|
# after we write
|
49
|
-
parser.reset
|
49
|
+
@parser.reset
|
50
50
|
throw :rainbows_EFBIG
|
51
51
|
end
|
52
52
|
rv
|
@@ -58,15 +58,15 @@ class MaxBody < Struct.new(:app)
|
|
58
58
|
# if it's reconfigured
|
59
59
|
def self.setup
|
60
60
|
Rainbows.max_bytes or return
|
61
|
-
case G.server.use
|
61
|
+
case Rainbows::G.server.use
|
62
62
|
when :Rev, :EventMachine, :NeverBlock
|
63
63
|
return
|
64
64
|
end
|
65
65
|
|
66
|
-
TeeInput.
|
66
|
+
Rainbows::TeeInput.__send__(:include, Limit)
|
67
67
|
|
68
68
|
# force ourselves to the outermost middleware layer
|
69
|
-
G.server.app =
|
69
|
+
Rainbows::G.server.app = self.new(Rainbows::G.server.app)
|
70
70
|
end
|
71
71
|
|
72
72
|
# Rack response returned when there's an error
|
@@ -78,6 +78,4 @@ class MaxBody < Struct.new(:app)
|
|
78
78
|
def call(env)
|
79
79
|
catch(:rainbows_EFBIG) { app.call(env) } || err(env)
|
80
80
|
end
|
81
|
-
|
82
|
-
end # class
|
83
|
-
end # module
|
81
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
module Rainbows::ProcessClient
|
4
|
+
G = Rainbows::G
|
5
|
+
include Rainbows::Response
|
6
|
+
HttpParser = Unicorn::HttpParser
|
7
|
+
NULL_IO = Unicorn::HttpRequest::NULL_IO
|
8
|
+
RACK_INPUT = Unicorn::HttpRequest::RACK_INPUT
|
9
|
+
TeeInput = Rainbows::TeeInput
|
10
|
+
include Rainbows::Const
|
11
|
+
|
12
|
+
def wait_headers_readable(client)
|
13
|
+
IO.select([client], nil, nil, G.kato)
|
14
|
+
end
|
15
|
+
|
16
|
+
# once a client is accepted, it is processed in its entirety here
|
17
|
+
# in 3 easy steps: read request, call app, write app response
|
18
|
+
# this is used by synchronous concurrency models
|
19
|
+
# Base, ThreadSpawn, ThreadPool
|
20
|
+
def process_client(client) # :nodoc:
|
21
|
+
hp = HttpParser.new
|
22
|
+
client.kgio_read!(16384, buf = hp.buf)
|
23
|
+
remote_addr = client.kgio_addr
|
24
|
+
|
25
|
+
begin # loop
|
26
|
+
until env = hp.parse
|
27
|
+
wait_headers_readable(client) or return
|
28
|
+
buf << client.kgio_read!(16384)
|
29
|
+
end
|
30
|
+
|
31
|
+
env[CLIENT_IO] = client
|
32
|
+
env[RACK_INPUT] = 0 == hp.content_length ?
|
33
|
+
NULL_IO : TeeInput.new(client, hp)
|
34
|
+
env[REMOTE_ADDR] = remote_addr
|
35
|
+
status, headers, body = APP.call(env.update(RACK_DEFAULTS))
|
36
|
+
|
37
|
+
if 100 == status.to_i
|
38
|
+
client.write(EXPECT_100_RESPONSE)
|
39
|
+
env.delete(HTTP_EXPECT)
|
40
|
+
status, headers, body = APP.call(env)
|
41
|
+
end
|
42
|
+
|
43
|
+
if hp.headers?
|
44
|
+
headers = HH.new(headers)
|
45
|
+
range = make_range!(env, status, headers) and status = range.shift
|
46
|
+
env = hp.keepalive? && G.alive
|
47
|
+
headers[CONNECTION] = env ? KEEP_ALIVE : CLOSE
|
48
|
+
client.write(response_header(status, headers))
|
49
|
+
end
|
50
|
+
write_body(client, body, range)
|
51
|
+
end while env && hp.reset.nil?
|
52
|
+
# if we get any error, try to write something back to the client
|
53
|
+
# assuming we haven't closed the socket, but don't get hung up
|
54
|
+
# if the socket is already closed or broken. We'll always ensure
|
55
|
+
# the socket is closed at the end of this function
|
56
|
+
rescue => e
|
57
|
+
Rainbows::Error.write(client, e)
|
58
|
+
ensure
|
59
|
+
client.close unless client.closed?
|
60
|
+
end
|
61
|
+
end
|