rainbows 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/GIT-VERSION-GEN +1 -1
- data/lib/rainbows.rb +0 -1
- data/lib/rainbows/client.rb +2 -2
- data/lib/rainbows/const.rb +1 -1
- data/lib/rainbows/dev_fd_response.rb +1 -1
- data/lib/rainbows/fiber/base.rb +0 -13
- data/lib/rainbows/fiber/body.rb +1 -1
- data/lib/rainbows/fiber/io.rb +33 -18
- data/lib/rainbows/fiber/io/methods.rb +2 -2
- data/lib/rainbows/fiber/rev/methods.rb +8 -15
- data/lib/rainbows/max_body.rb +51 -54
- data/lib/rainbows/max_body/rewindable_wrapper.rb +18 -0
- data/lib/rainbows/max_body/wrapper.rb +70 -0
- data/lib/rainbows/process_client.rb +7 -10
- data/lib/rainbows/rev/client.rb +14 -1
- data/lib/rainbows/rev/master.rb +9 -6
- data/lib/rainbows/revactor.rb +5 -4
- data/lib/rainbows/{read_timeout.rb → timed_read.rb} +6 -10
- data/lib/rainbows/writer_thread_pool.rb +4 -0
- data/lib/rainbows/writer_thread_spawn.rb +4 -0
- data/rainbows.gemspec +1 -1
- data/t/sha1-random-size.ru +19 -5
- data/t/t0104-rack-input-limit-tiny.sh +133 -1
- data/t/t0105-rack-input-limit-bigger.sh +2 -0
- data/t/test_isolate.rb +2 -2
- metadata +13 -11
- data/lib/rainbows/tee_input.rb +0 -18
data/GIT-VERSION-GEN
CHANGED
data/lib/rainbows.rb
CHANGED
data/lib/rainbows/client.rb
CHANGED
data/lib/rainbows/const.rb
CHANGED
@@ -54,7 +54,7 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
54
54
|
# we need to make sure our pipe output is Fiber-compatible
|
55
55
|
case env["rainbows.model"]
|
56
56
|
when :FiberSpawn, :FiberPool, :RevFiberSpawn
|
57
|
-
io.respond_to?(:
|
57
|
+
io.respond_to?(:kgio_wait_readable) or
|
58
58
|
io = Rainbows::Fiber::IO.new(io)
|
59
59
|
when :Revactor
|
60
60
|
io = Rainbows::Revactor::Proxy.new(io)
|
data/lib/rainbows/fiber/base.rb
CHANGED
@@ -56,19 +56,6 @@ module Rainbows::Fiber::Base
|
|
56
56
|
max.nil? || max > (now + 1) ? 1 : max - now
|
57
57
|
end
|
58
58
|
|
59
|
-
def wait_headers_readable(client)
|
60
|
-
io = client.to_io
|
61
|
-
expire = nil
|
62
|
-
begin
|
63
|
-
return io.recv_nonblock(1, Socket::MSG_PEEK)
|
64
|
-
rescue Errno::EAGAIN
|
65
|
-
return if expire && expire < Time.now
|
66
|
-
expire ||= Time.now + G.kato
|
67
|
-
client.wait_readable
|
68
|
-
retry
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
59
|
def process(client)
|
73
60
|
G.cur += 1
|
74
61
|
process_client(client)
|
data/lib/rainbows/fiber/body.rb
CHANGED
data/lib/rainbows/fiber/io.rb
CHANGED
@@ -52,8 +52,8 @@ class Rainbows::Fiber::IO
|
|
52
52
|
return
|
53
53
|
when String
|
54
54
|
buf = rv
|
55
|
-
when
|
56
|
-
|
55
|
+
when :wait_writable
|
56
|
+
kgio_wait_writable
|
57
57
|
end
|
58
58
|
end while true
|
59
59
|
else
|
@@ -61,7 +61,7 @@ class Rainbows::Fiber::IO
|
|
61
61
|
(rv = @to_io.write_nonblock(buf)) == buf.bytesize and return
|
62
62
|
buf = byte_slice(buf, rv..-1)
|
63
63
|
rescue Errno::EAGAIN
|
64
|
-
|
64
|
+
kgio_wait_writable
|
65
65
|
end while true
|
66
66
|
end
|
67
67
|
end
|
@@ -75,15 +75,28 @@ class Rainbows::Fiber::IO
|
|
75
75
|
end
|
76
76
|
|
77
77
|
# used for reading headers (respecting keepalive_timeout)
|
78
|
-
def
|
78
|
+
def timed_read(buf)
|
79
79
|
expire = nil
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
80
|
+
if @to_io.respond_to?(:kgio_tryread)
|
81
|
+
begin
|
82
|
+
case rv = @to_io.kgio_tryread(16384, buf)
|
83
|
+
when :wait_readable
|
84
|
+
return if expire && expire < Time.now
|
85
|
+
expire ||= Time.now + G.kato
|
86
|
+
kgio_wait_readable
|
87
|
+
else
|
88
|
+
return rv
|
89
|
+
end
|
90
|
+
end while true
|
91
|
+
else
|
92
|
+
begin
|
93
|
+
return @to_io.read_nonblock(16384, buf)
|
94
|
+
rescue Errno::EAGAIN
|
95
|
+
return if expire && expire < Time.now
|
96
|
+
expire ||= Time.now + G.kato
|
97
|
+
kgio_wait_readable
|
98
|
+
end while true
|
99
|
+
end
|
87
100
|
end
|
88
101
|
|
89
102
|
def readpartial(length, buf = "")
|
@@ -93,8 +106,8 @@ class Rainbows::Fiber::IO
|
|
93
106
|
case rv
|
94
107
|
when nil
|
95
108
|
raise EOFError, "end of file reached", []
|
96
|
-
when
|
97
|
-
|
109
|
+
when :wait_readable
|
110
|
+
kgio_wait_readable
|
98
111
|
else
|
99
112
|
return rv
|
100
113
|
end
|
@@ -103,7 +116,7 @@ class Rainbows::Fiber::IO
|
|
103
116
|
begin
|
104
117
|
return @to_io.read_nonblock(length, buf)
|
105
118
|
rescue Errno::EAGAIN
|
106
|
-
|
119
|
+
kgio_wait_readable
|
107
120
|
end while true
|
108
121
|
end
|
109
122
|
end
|
@@ -128,7 +141,9 @@ end
|
|
128
141
|
require 'rainbows/fiber/io/methods'
|
129
142
|
require 'rainbows/fiber/io/compat'
|
130
143
|
Rainbows::Client.__send__(:include, Rainbows::Fiber::IO::Methods)
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
144
|
+
class Rainbows::Fiber::IO
|
145
|
+
include Rainbows::Fiber::IO::Compat
|
146
|
+
include Rainbows::Fiber::IO::Methods
|
147
|
+
alias_method :wait_readable, :kgio_wait_readable
|
148
|
+
alias_method :wait_writable, :kgio_wait_writable
|
149
|
+
end
|
@@ -25,7 +25,7 @@ module Rainbows::Fiber::IO::Methods
|
|
25
25
|
super
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
28
|
+
def kgio_wait_readable
|
29
29
|
fd = fileno
|
30
30
|
@f = Fiber.current
|
31
31
|
RD[fd] = self
|
@@ -33,7 +33,7 @@ module Rainbows::Fiber::IO::Methods
|
|
33
33
|
RD[fd] = nil
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
36
|
+
def kgio_wait_writable
|
37
37
|
fd = fileno
|
38
38
|
@f = Fiber.current
|
39
39
|
WR[fd] = self
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Rainbows::Fiber::Rev::Methods
|
4
4
|
class Watcher < Rev::IOWatcher
|
5
5
|
def initialize(fio, flag)
|
6
|
-
@f =
|
6
|
+
@f = Fiber.current
|
7
7
|
super(fio, flag)
|
8
8
|
attach(Rev::Loop.default)
|
9
9
|
end
|
@@ -15,30 +15,23 @@ module Rainbows::Fiber::Rev::Methods
|
|
15
15
|
alias on_writable on_readable
|
16
16
|
end
|
17
17
|
|
18
|
-
def initialize(*args)
|
19
|
-
@f = Fiber.current
|
20
|
-
super(*args)
|
21
|
-
@r = @w = false
|
22
|
-
end
|
23
|
-
|
24
18
|
def close
|
25
|
-
@w.detach if @w
|
26
|
-
@r.detach if @r
|
27
|
-
@r = @w = false
|
19
|
+
@w.detach if defined?(@w) && @w.attached?
|
20
|
+
@r.detach if defined?(@r) && @r.attached?
|
28
21
|
super
|
29
22
|
end
|
30
23
|
|
31
|
-
def
|
32
|
-
@w
|
24
|
+
def kgio_wait_writable
|
25
|
+
@w = Watcher.new(self, :w) unless defined?(@w)
|
33
26
|
@w.enable unless @w.enabled?
|
34
27
|
Fiber.yield
|
35
28
|
@w.disable
|
36
29
|
end
|
37
30
|
|
38
|
-
def
|
39
|
-
@r
|
31
|
+
def kgio_wait_readable
|
32
|
+
@r = Watcher.new(self, :r) unless defined?(@r)
|
40
33
|
@r.enable unless @r.enabled?
|
41
|
-
KATO <<
|
34
|
+
KATO << Fiber.current
|
42
35
|
Fiber.yield
|
43
36
|
@r.disable
|
44
37
|
end
|
data/lib/rainbows/max_body.rb
CHANGED
@@ -1,81 +1,78 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
# :enddoc:
|
3
2
|
|
4
|
-
#
|
5
|
-
#
|
3
|
+
# Middleware used to enforce client_max_body_size for TeeInput users.
|
4
|
+
#
|
5
|
+
# There is no need to configure this middleware manually, it will
|
6
6
|
# automatically be configured for you based on the client_max_body_size
|
7
|
-
# setting
|
8
|
-
|
7
|
+
# setting.
|
8
|
+
#
|
9
|
+
# For more fine-grained conrol, you may also define it per-endpoint in
|
10
|
+
# your Rack config.ru like this:
|
11
|
+
#
|
12
|
+
# map "/limit_1M" do
|
13
|
+
# use Rainbows::MaxBody, 1024*1024
|
14
|
+
# run MyApp
|
15
|
+
# end
|
16
|
+
# map "/limit_10M" do
|
17
|
+
# use Rainbows::MaxBody, 1024*1024*10
|
18
|
+
# run MyApp
|
19
|
+
# end
|
9
20
|
|
10
|
-
|
11
|
-
# classes) to limit body sizes
|
12
|
-
module Limit
|
13
|
-
TmpIO = Unicorn::TmpIO
|
14
|
-
MAX_BODY = Rainbows::Const::MAX_BODY
|
21
|
+
class Rainbows::MaxBody
|
15
22
|
|
16
|
-
def initialize(socket, request)
|
17
|
-
@parser = request
|
18
|
-
@buf = request.buf
|
19
|
-
@env = request.env
|
20
|
-
@len = request.content_length
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
24
|
+
# :call-seq:
|
25
|
+
# # in config.ru:
|
26
|
+
# use Rainbows::MaxBody, 4096
|
27
|
+
# run YourApplication.new
|
28
|
+
def initialize(app, limit = Rainbows.max_bytes)
|
29
|
+
Integer === limit or raise ArgumentError, "limit not an Integer"
|
30
|
+
@app, @limit = app, limit
|
31
|
+
end
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@buf2.size > max and raise IOError, "chunked request body too big", []
|
34
|
-
end
|
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
|
-
end
|
41
|
-
@max_body = max
|
42
|
-
end
|
33
|
+
# :stopdoc:
|
34
|
+
RACK_INPUT = "rack.input".freeze
|
35
|
+
CONTENT_LENGTH = "CONTENT_LENGTH"
|
36
|
+
HTTP_TRANSFER_ENCODING = "HTTP_TRANSFER_ENCODING"
|
43
37
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
38
|
+
# our main Rack middleware endpoint
|
39
|
+
def call(env)
|
40
|
+
catch(:rainbows_EFBIG) do
|
41
|
+
len = env[CONTENT_LENGTH]
|
42
|
+
if len && len.to_i > @limit
|
43
|
+
return err
|
44
|
+
elsif /\Achunked\z/i =~ env[HTTP_TRANSFER_ENCODING]
|
45
|
+
limit_input!(env)
|
51
46
|
end
|
52
|
-
|
53
|
-
end
|
54
|
-
|
47
|
+
@app.call(env)
|
48
|
+
end || err
|
55
49
|
end
|
56
50
|
|
57
51
|
# this is called after forking, so it won't ever affect the master
|
58
52
|
# if it's reconfigured
|
59
|
-
def self.setup
|
53
|
+
def self.setup # :nodoc:
|
60
54
|
Rainbows.max_bytes or return
|
61
55
|
case Rainbows::G.server.use
|
62
|
-
when :Rev, :EventMachine, :NeverBlock
|
56
|
+
when :Rev, :EventMachine, :NeverBlock, :RevThreadSpawn, :RevThreadPool
|
63
57
|
return
|
64
58
|
end
|
65
59
|
|
66
|
-
Rainbows::TeeInput.__send__(:include, Limit)
|
67
|
-
|
68
60
|
# force ourselves to the outermost middleware layer
|
69
61
|
Rainbows::G.server.app = self.new(Rainbows::G.server.app)
|
70
62
|
end
|
71
63
|
|
72
64
|
# Rack response returned when there's an error
|
73
|
-
def err
|
74
|
-
[ 413,
|
65
|
+
def err # :nodoc:
|
66
|
+
[ 413, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
|
75
67
|
end
|
76
68
|
|
77
|
-
|
78
|
-
|
79
|
-
|
69
|
+
def limit_input!(env)
|
70
|
+
input = env[RACK_INPUT]
|
71
|
+
klass = input.respond_to?(:rewind) ? RewindableWrapper : Wrapper
|
72
|
+
env[RACK_INPUT] = klass.new(input, @limit)
|
80
73
|
end
|
74
|
+
|
75
|
+
# :startdoc:
|
81
76
|
end
|
77
|
+
require 'rainbows/max_body/wrapper'
|
78
|
+
require 'rainbows/max_body/rewindable_wrapper'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
class Rainbows::MaxBody::RewindableWrapper < Rainbows::MaxBody::Wrapper
|
4
|
+
def initialize(rack_input, limit)
|
5
|
+
@orig_limit = limit
|
6
|
+
super
|
7
|
+
end
|
8
|
+
|
9
|
+
def rewind
|
10
|
+
@limit = @orig_limit
|
11
|
+
@rbuf = ''
|
12
|
+
@input.rewind
|
13
|
+
end
|
14
|
+
|
15
|
+
def size
|
16
|
+
@input.size
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
#
|
4
|
+
# This is only used for chunked request bodies, which are rare
|
5
|
+
class Rainbows::MaxBody::Wrapper
|
6
|
+
def initialize(rack_input, limit)
|
7
|
+
@input, @limit, @rbuf = rack_input, limit, ''
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(&block)
|
11
|
+
while line = gets
|
12
|
+
yield line
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# chunked encoding means this method behaves more like readpartial,
|
17
|
+
# since Rack does not support a method named "readpartial"
|
18
|
+
def read(length = nil, rv = '')
|
19
|
+
if length
|
20
|
+
if length <= @rbuf.size
|
21
|
+
length < 0 and raise ArgumentError, "negative length #{length} given"
|
22
|
+
rv.replace(@rbuf.slice!(0, length))
|
23
|
+
elsif @rbuf.empty?
|
24
|
+
checked_read(length, rv) or return
|
25
|
+
else
|
26
|
+
rv.replace(@rbuf.slice!(0, @rbuf.size))
|
27
|
+
end
|
28
|
+
rv.empty? && length != 0 ? nil : rv
|
29
|
+
else
|
30
|
+
rv.replace(read_all)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def gets
|
35
|
+
sep = $/
|
36
|
+
if sep.nil?
|
37
|
+
rv = read_all
|
38
|
+
return rv.empty? ? nil : rv
|
39
|
+
end
|
40
|
+
re = /\A(.*?#{Regexp.escape(sep)})/
|
41
|
+
|
42
|
+
begin
|
43
|
+
@rbuf.sub!(re, '') and return $1
|
44
|
+
|
45
|
+
if tmp = checked_read(16384)
|
46
|
+
@rbuf << tmp
|
47
|
+
elsif @rbuf.empty? # EOF
|
48
|
+
return nil
|
49
|
+
else # EOF, return whatever is left
|
50
|
+
return @rbuf.slice!(0, @rbuf.size)
|
51
|
+
end
|
52
|
+
end while true
|
53
|
+
end
|
54
|
+
|
55
|
+
def checked_read(length = 16384, buf = '')
|
56
|
+
if @input.read(length, buf)
|
57
|
+
throw :rainbows_EFBIG if ((@limit -= buf.size) < 0)
|
58
|
+
return buf
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def read_all
|
63
|
+
rv = @rbuf.slice!(0, @rbuf.size)
|
64
|
+
tmp = ''
|
65
|
+
while checked_read(16384, tmp)
|
66
|
+
rv << tmp
|
67
|
+
end
|
68
|
+
rv
|
69
|
+
end
|
70
|
+
end
|
@@ -6,13 +6,9 @@ module Rainbows::ProcessClient
|
|
6
6
|
HttpParser = Unicorn::HttpParser
|
7
7
|
NULL_IO = Unicorn::HttpRequest::NULL_IO
|
8
8
|
RACK_INPUT = Unicorn::HttpRequest::RACK_INPUT
|
9
|
-
TeeInput =
|
9
|
+
TeeInput = Unicorn::TeeInput
|
10
10
|
include Rainbows::Const
|
11
11
|
|
12
|
-
def wait_headers_readable(client)
|
13
|
-
IO.select([client], nil, nil, G.kato)
|
14
|
-
end
|
15
|
-
|
16
12
|
# once a client is accepted, it is processed in its entirety here
|
17
13
|
# in 3 easy steps: read request, call app, write app response
|
18
14
|
# this is used by synchronous concurrency models
|
@@ -21,11 +17,12 @@ module Rainbows::ProcessClient
|
|
21
17
|
hp = HttpParser.new
|
22
18
|
client.kgio_read!(16384, buf = hp.buf)
|
23
19
|
remote_addr = client.kgio_addr
|
20
|
+
alive = false
|
24
21
|
|
25
22
|
begin # loop
|
26
23
|
until env = hp.parse
|
27
|
-
|
28
|
-
buf <<
|
24
|
+
client.timed_read(buf2 ||= "") or return
|
25
|
+
buf << buf2
|
29
26
|
end
|
30
27
|
|
31
28
|
env[CLIENT_IO] = client
|
@@ -43,12 +40,12 @@ module Rainbows::ProcessClient
|
|
43
40
|
if hp.headers?
|
44
41
|
headers = HH.new(headers)
|
45
42
|
range = make_range!(env, status, headers) and status = range.shift
|
46
|
-
|
47
|
-
headers[CONNECTION] =
|
43
|
+
alive = hp.next? && G.alive
|
44
|
+
headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
|
48
45
|
client.write(response_header(status, headers))
|
49
46
|
end
|
50
47
|
write_body(client, body, range)
|
51
|
-
end while
|
48
|
+
end while alive
|
52
49
|
# if we get any error, try to write something back to the client
|
53
50
|
# assuming we haven't closed the socket, but don't get hung up
|
54
51
|
# if the socket is already closed or broken. We'll always ensure
|
data/lib/rainbows/rev/client.rb
CHANGED
@@ -30,7 +30,7 @@ module Rainbows
|
|
30
30
|
case rv = @_io.kgio_trywrite(buf)
|
31
31
|
when nil
|
32
32
|
return enable_write_watcher
|
33
|
-
when
|
33
|
+
when :wait_writable
|
34
34
|
break # fall through to super(buf)
|
35
35
|
when String
|
36
36
|
buf = rv # retry, skb could grow or been drained
|
@@ -42,6 +42,19 @@ module Rainbows
|
|
42
42
|
super(buf)
|
43
43
|
end
|
44
44
|
|
45
|
+
def on_readable
|
46
|
+
buf = @_io.kgio_tryread(16384)
|
47
|
+
case buf
|
48
|
+
when :wait_readable
|
49
|
+
when nil # eof
|
50
|
+
close
|
51
|
+
else
|
52
|
+
on_read buf
|
53
|
+
end
|
54
|
+
rescue Errno::ECONNRESET
|
55
|
+
close
|
56
|
+
end
|
57
|
+
|
45
58
|
# queued, optional response bodies, it should only be unpollable "fast"
|
46
59
|
# devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
|
47
60
|
# are also part of this. We'll also stick DeferredResponse bodies in
|
data/lib/rainbows/rev/master.rb
CHANGED
@@ -2,20 +2,23 @@
|
|
2
2
|
# :enddoc:
|
3
3
|
require 'rainbows/rev'
|
4
4
|
|
5
|
-
class Rainbows::Rev::Master < Rev::
|
5
|
+
class Rainbows::Rev::Master < Rev::IOWatcher
|
6
6
|
|
7
7
|
def initialize(queue)
|
8
|
-
|
8
|
+
@reader, @writer = Kgio::Pipe.new
|
9
|
+
super(@reader)
|
9
10
|
@queue = queue
|
10
11
|
end
|
11
12
|
|
12
13
|
def <<(output)
|
13
14
|
@queue << output
|
14
|
-
|
15
|
+
@writer.kgio_trywrite("\0")
|
15
16
|
end
|
16
17
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
18
|
+
def on_readable
|
19
|
+
if String === @reader.kgio_tryread(1)
|
20
|
+
client, response = @queue.pop
|
21
|
+
client.response_write(response)
|
22
|
+
end
|
20
23
|
end
|
21
24
|
end
|
data/lib/rainbows/revactor.rb
CHANGED
@@ -43,6 +43,7 @@ module Rainbows::Revactor
|
|
43
43
|
end
|
44
44
|
hp = Unicorn::HttpParser.new
|
45
45
|
buf = hp.buf
|
46
|
+
alive = false
|
46
47
|
|
47
48
|
begin
|
48
49
|
until env = hp.parse
|
@@ -51,7 +52,7 @@ module Rainbows::Revactor
|
|
51
52
|
|
52
53
|
env[CLIENT_IO] = client
|
53
54
|
env[RACK_INPUT] = 0 == hp.content_length ?
|
54
|
-
NULL_IO : TeeInput.new(TeeSocket.new(client), hp)
|
55
|
+
NULL_IO : Unicorn::TeeInput.new(TeeSocket.new(client), hp)
|
55
56
|
env[REMOTE_ADDR] = remote_addr
|
56
57
|
status, headers, body = app.call(env.update(RACK_DEFAULTS))
|
57
58
|
|
@@ -64,12 +65,12 @@ module Rainbows::Revactor
|
|
64
65
|
if hp.headers?
|
65
66
|
headers = HH.new(headers)
|
66
67
|
range = make_range!(env, status, headers) and status = range.shift
|
67
|
-
|
68
|
-
headers[CONNECTION] =
|
68
|
+
alive = hp.next? && G.alive && G.kato > 0
|
69
|
+
headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
|
69
70
|
client.write(response_header(status, headers))
|
70
71
|
end
|
71
72
|
write_body(client, body, range)
|
72
|
-
end while
|
73
|
+
end while alive
|
73
74
|
rescue ::Revactor::TCP::ReadError
|
74
75
|
rescue => e
|
75
76
|
Rainbows::Error.write(io, e)
|
@@ -1,25 +1,21 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
# :enddoc:
|
3
|
-
module Rainbows::
|
3
|
+
module Rainbows::TimedRead
|
4
4
|
G = Rainbows::G # :nodoc:
|
5
5
|
|
6
|
-
def
|
6
|
+
def kgio_wait_readable
|
7
7
|
IO.select([self], nil, nil, G.kato)
|
8
8
|
end
|
9
9
|
|
10
10
|
# used for reading headers (respecting keepalive_timeout)
|
11
|
-
def
|
11
|
+
def timed_read(buf)
|
12
12
|
expire = nil
|
13
13
|
begin
|
14
14
|
case rv = kgio_tryread(16384, buf)
|
15
15
|
when :wait_readable
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
else
|
20
|
-
expire = now + G.kato
|
21
|
-
end
|
22
|
-
wait_readable
|
16
|
+
return if expire && expire < Time.now
|
17
|
+
expire ||= Time.now + G.kato
|
18
|
+
kgio_wait_readable
|
23
19
|
else
|
24
20
|
return rv
|
25
21
|
end
|
@@ -47,6 +47,10 @@ module Rainbows
|
|
47
47
|
to_io.kgio_trywrite(buf)
|
48
48
|
end
|
49
49
|
|
50
|
+
def timed_read(buf)
|
51
|
+
to_io.timed_read(buf)
|
52
|
+
end
|
53
|
+
|
50
54
|
def queue_writer
|
51
55
|
# not using Thread.pass here because that spins the CPU during
|
52
56
|
# I/O wait and will eat cycles from other worker processes.
|
data/rainbows.gemspec
CHANGED
@@ -44,7 +44,7 @@ Gem::Specification.new do |s|
|
|
44
44
|
s.add_dependency(%q<rack>, ['~> 1.1'])
|
45
45
|
|
46
46
|
# we need Unicorn for the HTTP parser and process management
|
47
|
-
s.add_dependency(%q<unicorn>, ["~>
|
47
|
+
s.add_dependency(%q<unicorn>, ["~> 3.0.0"])
|
48
48
|
s.add_development_dependency(%q<isolate>, "~> 3.0.0")
|
49
49
|
|
50
50
|
# optional runtime dependencies depending on configuration
|
data/t/sha1-random-size.ru
CHANGED
@@ -7,11 +7,25 @@ app = lambda do |env|
|
|
7
7
|
return [ 100, {}, [] ]
|
8
8
|
digest = Digest::SHA1.new
|
9
9
|
input = env['rack.input']
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
case env["PATH_INFO"]
|
11
|
+
when "/gets_read_mix"
|
12
|
+
warn "GETS_READ_MIX #{env['HTTP_TRANSFER_ENCODING'].inspect}"
|
13
|
+
if buf = input.gets
|
14
|
+
warn "input.rbuf: #{input.instance_variable_get(:@rbuf).inspect}"
|
15
|
+
begin
|
16
|
+
digest.update(buf)
|
17
|
+
warn "buf.size : #{buf.size}"
|
18
|
+
end while input.read(rand(cap), buf)
|
19
|
+
end
|
20
|
+
when "/each"
|
21
|
+
input.each { |buf| digest.update(buf) }
|
22
|
+
else
|
23
|
+
if buf = input.read(rand(cap))
|
24
|
+
begin
|
25
|
+
raise "#{buf.size} > #{cap}" if buf.size > cap
|
26
|
+
digest.update(buf)
|
27
|
+
end while input.read(rand(cap), buf)
|
28
|
+
end
|
15
29
|
end
|
16
30
|
|
17
31
|
[ 200, {'Content-Type' => 'text/plain'}, [ digest.hexdigest << "\n" ] ]
|
@@ -3,7 +3,7 @@
|
|
3
3
|
test -r random_blob || die "random_blob required, run with 'make $0'"
|
4
4
|
req_curl_chunked_upload_err_check
|
5
5
|
|
6
|
-
t_plan
|
6
|
+
t_plan 18 "rack.input client_max_body_size tiny"
|
7
7
|
|
8
8
|
t_begin "setup and startup" && {
|
9
9
|
rtmpfiles curl_out curl_err cmbs_config
|
@@ -21,6 +21,7 @@ t_begin "stops a regular request" && {
|
|
21
21
|
http://$listen/ > $curl_out 2> $curl_err || > $ok
|
22
22
|
dbgcat curl_err
|
23
23
|
dbgcat curl_out
|
24
|
+
grep 413 $curl_err
|
24
25
|
test -e $ok
|
25
26
|
}
|
26
27
|
|
@@ -31,6 +32,7 @@ t_begin "stops a large chunked request" && {
|
|
31
32
|
http://$listen/ > $curl_out 2> $curl_err || > $ok
|
32
33
|
dbgcat curl_err
|
33
34
|
dbgcat curl_out
|
35
|
+
grep 413 $curl_err
|
34
36
|
test -e $ok
|
35
37
|
}
|
36
38
|
|
@@ -56,6 +58,136 @@ t_begin "small size sha1 content-length ok" && {
|
|
56
58
|
test "$(cat $curl_out)" = $blob_sha1
|
57
59
|
}
|
58
60
|
|
61
|
+
t_begin "stops a regular request (gets_read_mix)" && {
|
62
|
+
rm -f $ok
|
63
|
+
dd if=/dev/zero bs=257 count=1 of=$tmp
|
64
|
+
curl -vsSf -T $tmp -H Expect: \
|
65
|
+
http://$listen/gets_read_mix > $curl_out 2> $curl_err || > $ok
|
66
|
+
dbgcat curl_err
|
67
|
+
dbgcat curl_out
|
68
|
+
grep 413 $curl_err
|
69
|
+
test -e $ok
|
70
|
+
}
|
71
|
+
|
72
|
+
t_begin "stops a large chunked request (gets_read_mix)" && {
|
73
|
+
rm -f $ok
|
74
|
+
dd if=/dev/zero bs=257 count=1 | \
|
75
|
+
curl -vsSf -T- -H Expect: \
|
76
|
+
http://$listen/gets_read_mix > $curl_out 2> $curl_err || > $ok
|
77
|
+
dbgcat curl_err
|
78
|
+
dbgcat curl_out
|
79
|
+
grep 413 $curl_err
|
80
|
+
test -e $ok
|
81
|
+
}
|
82
|
+
|
83
|
+
t_begin "stops a large line-based chunked request (gets_read_mix)" && {
|
84
|
+
rm -f $ok
|
85
|
+
</dev/null awk 'BEGIN{for(i=22;--i>=0;) print "hello world"}' | \
|
86
|
+
curl -vsSf -T- -H Expect: \
|
87
|
+
http://$listen/gets_read_mix > $curl_out 2> $curl_err || > $ok
|
88
|
+
dbgcat curl_err
|
89
|
+
dbgcat curl_out
|
90
|
+
grep 413 $curl_err
|
91
|
+
test -e $ok
|
92
|
+
}
|
93
|
+
|
94
|
+
t_begin "OK with line-based chunked request (gets_read_mix)" && {
|
95
|
+
rm -f $ok
|
96
|
+
</dev/null awk 'BEGIN{for(i=21;--i>=0;) print "hello world"}' | \
|
97
|
+
curl -vsSf -T- -H Expect: \
|
98
|
+
http://$listen/gets_read_mix > $curl_out 2> $curl_err
|
99
|
+
dbgcat curl_err
|
100
|
+
dbgcat curl_out
|
101
|
+
test x"$(cat $curl_out)" = x23eab3cebcbe22a0456c8462e3d3bb01ae761702
|
102
|
+
}
|
103
|
+
|
104
|
+
t_begin "small size sha1 chunked ok (gets_read_mix)" && {
|
105
|
+
blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
|
106
|
+
rm -f $ok
|
107
|
+
dd if=/dev/zero bs=256 count=1 | \
|
108
|
+
curl -vsSf -T- -H Expect: \
|
109
|
+
http://$listen/gets_read_mix > $curl_out 2> $curl_err
|
110
|
+
dbgcat curl_err
|
111
|
+
dbgcat curl_out
|
112
|
+
test "$(cat $curl_out)" = $blob_sha1
|
113
|
+
}
|
114
|
+
|
115
|
+
t_begin "small size sha1 content-length ok (gets_read_mix)" && {
|
116
|
+
blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
|
117
|
+
rm -f $ok
|
118
|
+
dd if=/dev/zero bs=256 count=1 of=$tmp
|
119
|
+
curl -vsSf -T $tmp -H Expect: \
|
120
|
+
http://$listen/gets_read_mix > $curl_out 2> $curl_err
|
121
|
+
dbgcat curl_err
|
122
|
+
dbgcat curl_out
|
123
|
+
test "$(cat $curl_out)" = $blob_sha1
|
124
|
+
}
|
125
|
+
|
126
|
+
t_begin "stops a regular request (each)" && {
|
127
|
+
rm -f $ok
|
128
|
+
dd if=/dev/zero bs=257 count=1 of=$tmp
|
129
|
+
curl -vsSf -T $tmp -H Expect: \
|
130
|
+
http://$listen/each > $curl_out 2> $curl_err || > $ok
|
131
|
+
dbgcat curl_err
|
132
|
+
dbgcat curl_out
|
133
|
+
grep 413 $curl_err
|
134
|
+
test -e $ok
|
135
|
+
}
|
136
|
+
|
137
|
+
t_begin "stops a large chunked request (each)" && {
|
138
|
+
rm -f $ok
|
139
|
+
dd if=/dev/zero bs=257 count=1 | \
|
140
|
+
curl -vsSf -T- -H Expect: \
|
141
|
+
http://$listen/each > $curl_out 2> $curl_err || > $ok
|
142
|
+
dbgcat curl_err
|
143
|
+
dbgcat curl_out
|
144
|
+
grep 413 $curl_err
|
145
|
+
test -e $ok
|
146
|
+
}
|
147
|
+
|
148
|
+
t_begin "small size sha1 chunked ok (each)" && {
|
149
|
+
blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
|
150
|
+
rm -f $ok
|
151
|
+
dd if=/dev/zero bs=256 count=1 | \
|
152
|
+
curl -vsSf -T- -H Expect: \
|
153
|
+
http://$listen/each > $curl_out 2> $curl_err
|
154
|
+
dbgcat curl_err
|
155
|
+
dbgcat curl_out
|
156
|
+
test "$(cat $curl_out)" = $blob_sha1
|
157
|
+
}
|
158
|
+
|
159
|
+
t_begin "small size sha1 content-length ok (each)" && {
|
160
|
+
blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
|
161
|
+
rm -f $ok
|
162
|
+
dd if=/dev/zero bs=256 count=1 of=$tmp
|
163
|
+
curl -vsSf -T $tmp -H Expect: \
|
164
|
+
http://$listen/each > $curl_out 2> $curl_err
|
165
|
+
dbgcat curl_err
|
166
|
+
dbgcat curl_out
|
167
|
+
test "$(cat $curl_out)" = $blob_sha1
|
168
|
+
}
|
169
|
+
|
170
|
+
t_begin "stops a large line-based chunked request (each)" && {
|
171
|
+
rm -f $ok
|
172
|
+
</dev/null awk 'BEGIN{for(i=22;--i>=0;) print "hello world"}' | \
|
173
|
+
curl -vsSf -T- -H Expect: \
|
174
|
+
http://$listen/each > $curl_out 2> $curl_err || > $ok
|
175
|
+
dbgcat curl_err
|
176
|
+
dbgcat curl_out
|
177
|
+
grep 413 $curl_err
|
178
|
+
test -e $ok
|
179
|
+
}
|
180
|
+
|
181
|
+
t_begin "OK with line-based chunked request (each)" && {
|
182
|
+
rm -f $ok
|
183
|
+
</dev/null awk 'BEGIN{for(i=21;--i>=0;) print "hello world"}' | \
|
184
|
+
curl -vsSf -T- -H Expect: \
|
185
|
+
http://$listen/each > $curl_out 2> $curl_err
|
186
|
+
dbgcat curl_err
|
187
|
+
dbgcat curl_out
|
188
|
+
test x"$(cat $curl_out)" = x23eab3cebcbe22a0456c8462e3d3bb01ae761702
|
189
|
+
}
|
190
|
+
|
59
191
|
t_begin "shutdown" && {
|
60
192
|
kill $rainbows_pid
|
61
193
|
}
|
@@ -22,6 +22,7 @@ t_begin "stops a regular request" && {
|
|
22
22
|
rm -f $tmp
|
23
23
|
dbgcat curl_err
|
24
24
|
dbgcat curl_out
|
25
|
+
grep 413 $curl_err
|
25
26
|
test -e $ok
|
26
27
|
}
|
27
28
|
|
@@ -32,6 +33,7 @@ t_begin "stops a large chunked request" && {
|
|
32
33
|
http://$listen/ > $curl_out 2> $curl_err || > $ok
|
33
34
|
dbgcat curl_err
|
34
35
|
dbgcat curl_out
|
36
|
+
grep 413 $curl_err
|
35
37
|
test -e $ok
|
36
38
|
}
|
37
39
|
|
data/t/test_isolate.rb
CHANGED
@@ -15,8 +15,8 @@ $stdout.reopen($stderr)
|
|
15
15
|
|
16
16
|
Isolate.now!(opts) do
|
17
17
|
gem 'rack', '1.1.0' # Cramp currently requires ~> 1.1.0
|
18
|
-
gem 'kgio', '
|
19
|
-
gem 'unicorn', '
|
18
|
+
gem 'kgio', '2.0.0'
|
19
|
+
gem 'unicorn', '3.0.0'
|
20
20
|
gem 'kcar', '0.1.1'
|
21
21
|
|
22
22
|
if engine == "ruby"
|
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: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
9
|
- 0
|
10
|
-
version:
|
10
|
+
version: 2.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: 2010-
|
18
|
+
date: 2010-11-20 00:00:00 +00:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -41,12 +41,12 @@ dependencies:
|
|
41
41
|
requirements:
|
42
42
|
- - ~>
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
hash:
|
44
|
+
hash: 7
|
45
45
|
segments:
|
46
|
-
-
|
46
|
+
- 3
|
47
47
|
- 0
|
48
48
|
- 0
|
49
|
-
version:
|
49
|
+
version: 3.0.0
|
50
50
|
type: :runtime
|
51
51
|
version_requirements: *id002
|
52
52
|
- !ruby/object:Gem::Dependency
|
@@ -112,11 +112,12 @@ extra_rdoc_files:
|
|
112
112
|
- lib/rainbows/http_response.rb
|
113
113
|
- lib/rainbows/http_server.rb
|
114
114
|
- lib/rainbows/max_body.rb
|
115
|
+
- lib/rainbows/max_body/rewindable_wrapper.rb
|
116
|
+
- lib/rainbows/max_body/wrapper.rb
|
115
117
|
- lib/rainbows/never_block.rb
|
116
118
|
- lib/rainbows/never_block/event_machine.rb
|
117
119
|
- lib/rainbows/process_client.rb
|
118
120
|
- lib/rainbows/queue_pool.rb
|
119
|
-
- lib/rainbows/read_timeout.rb
|
120
121
|
- lib/rainbows/response.rb
|
121
122
|
- lib/rainbows/response/body.rb
|
122
123
|
- lib/rainbows/response/range.rb
|
@@ -138,10 +139,10 @@ extra_rdoc_files:
|
|
138
139
|
- lib/rainbows/sendfile.rb
|
139
140
|
- lib/rainbows/server_token.rb
|
140
141
|
- lib/rainbows/stream_file.rb
|
141
|
-
- lib/rainbows/tee_input.rb
|
142
142
|
- lib/rainbows/thread_pool.rb
|
143
143
|
- lib/rainbows/thread_spawn.rb
|
144
144
|
- lib/rainbows/thread_timeout.rb
|
145
|
+
- lib/rainbows/timed_read.rb
|
145
146
|
- lib/rainbows/writer_thread_pool.rb
|
146
147
|
- lib/rainbows/writer_thread_spawn.rb
|
147
148
|
- LICENSE
|
@@ -215,11 +216,12 @@ files:
|
|
215
216
|
- lib/rainbows/http_response.rb
|
216
217
|
- lib/rainbows/http_server.rb
|
217
218
|
- lib/rainbows/max_body.rb
|
219
|
+
- lib/rainbows/max_body/rewindable_wrapper.rb
|
220
|
+
- lib/rainbows/max_body/wrapper.rb
|
218
221
|
- lib/rainbows/never_block.rb
|
219
222
|
- lib/rainbows/never_block/event_machine.rb
|
220
223
|
- lib/rainbows/process_client.rb
|
221
224
|
- lib/rainbows/queue_pool.rb
|
222
|
-
- lib/rainbows/read_timeout.rb
|
223
225
|
- lib/rainbows/response.rb
|
224
226
|
- lib/rainbows/response/body.rb
|
225
227
|
- lib/rainbows/response/range.rb
|
@@ -241,10 +243,10 @@ files:
|
|
241
243
|
- lib/rainbows/sendfile.rb
|
242
244
|
- lib/rainbows/server_token.rb
|
243
245
|
- lib/rainbows/stream_file.rb
|
244
|
-
- lib/rainbows/tee_input.rb
|
245
246
|
- lib/rainbows/thread_pool.rb
|
246
247
|
- lib/rainbows/thread_spawn.rb
|
247
248
|
- lib/rainbows/thread_timeout.rb
|
249
|
+
- lib/rainbows/timed_read.rb
|
248
250
|
- lib/rainbows/writer_thread_pool.rb
|
249
251
|
- lib/rainbows/writer_thread_spawn.rb
|
250
252
|
- local.mk.sample
|
data/lib/rainbows/tee_input.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# -*- encoding: binary -*-
|
2
|
-
# :enddoc:
|
3
|
-
module Rainbows
|
4
|
-
|
5
|
-
# acts like tee(1) on an input input to provide a input-like stream
|
6
|
-
# while providing rewindable semantics through a File/StringIO
|
7
|
-
# backing store. On the first pass, the input is only read on demand
|
8
|
-
# so your Rack application can use input notification (upload progress
|
9
|
-
# and like). This should fully conform to the Rack::InputWrapper
|
10
|
-
# specification on the public API. This class is intended to be a
|
11
|
-
# strict interpretation of Rack::InputWrapper functionality and will
|
12
|
-
# not support any deviations from it.
|
13
|
-
class TeeInput < Unicorn::TeeInput
|
14
|
-
|
15
|
-
# empty class, this is to avoid unecessarily modifying Unicorn::TeeInput
|
16
|
-
# when MaxBody::Limit is included
|
17
|
-
end
|
18
|
-
end
|