rainbows 1.0.0 → 2.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/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
|