rainbows 0.91.1 → 0.92.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +1 -1
- data/.gitignore +1 -0
- data/.manifest +13 -4
- data/ChangeLog +237 -219
- data/DEPLOY +13 -13
- data/Documentation/comparison.haml +1 -1
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +18 -7
- data/NEWS +29 -0
- data/README +5 -4
- data/Rakefile +7 -0
- data/SIGNALS +5 -1
- data/TODO +2 -5
- data/config/.gitignore +1 -0
- data/config/isolate.rb +25 -0
- data/lib/rainbows/base.rb +32 -8
- data/lib/rainbows/const.rb +3 -2
- data/lib/rainbows/ev_core.rb +42 -2
- data/lib/rainbows/event_machine.rb +48 -5
- data/lib/rainbows/fiber/base.rb +4 -2
- data/lib/rainbows/fiber/rev.rb +1 -1
- data/lib/rainbows/http_response.rb +21 -20
- data/lib/rainbows/http_server.rb +13 -6
- data/lib/rainbows/max_body.rb +82 -0
- data/lib/rainbows/rev/deferred_response.rb +10 -11
- data/lib/rainbows/revactor.rb +38 -4
- data/lib/rainbows/tee_input.rb +17 -0
- data/lib/rainbows.rb +26 -2
- data/local.mk.sample +13 -23
- data/rainbows.gemspec +2 -2
- data/t/app_deferred.ru +23 -0
- data/t/async_examples/async_app.ru +8 -8
- data/t/rack-fiber_pool/app.ru +5 -0
- data/t/{t0100-rack-input-hammer.sh → t0100-rack-input-hammer-chunked.sh} +1 -1
- data/t/t0100-rack-input-hammer-content-length.sh +50 -0
- data/t/t0103-rack-input-limit.sh +60 -0
- data/t/t0104-rack-input-limit-tiny.sh +62 -0
- data/t/t0105-rack-input-limit-bigger.sh +105 -0
- data/t/t0401-em-async-tailer.sh +1 -1
- data/t/t0600-rack-fiber_pool.sh +49 -0
- data/t/t0700-app-deferred.sh +45 -0
- data/t/test-lib.sh +1 -0
- metadata +22 -10
- data/lib/rainbows/event_machine_defer.rb +0 -59
- data/lib/rainbows/revactor/tee_input.rb +0 -52
- data/t/simple-http_EventMachineDefer.ru +0 -11
@@ -0,0 +1,82 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
module Rainbows
|
3
|
+
|
4
|
+
# middleware used to enforce client_max_body_size for TeeInput users,
|
5
|
+
# there is no need to configure this middleware manually, it will
|
6
|
+
# automatically be configured for you based on the client_max_body_size
|
7
|
+
# setting
|
8
|
+
class MaxBody < Struct.new(:app)
|
9
|
+
|
10
|
+
# this is meant to be included in Rainbows::TeeInput (and derived
|
11
|
+
# classes) to limit body sizes
|
12
|
+
module Limit
|
13
|
+
Util = Unicorn::Util
|
14
|
+
|
15
|
+
def initialize(socket, req, parser, buf)
|
16
|
+
self.len = parser.content_length
|
17
|
+
|
18
|
+
max = Rainbows.max_bytes # never nil, see MaxBody.setup
|
19
|
+
if len && len > max
|
20
|
+
socket.write(Const::ERROR_413_RESPONSE)
|
21
|
+
socket.close
|
22
|
+
raise IOError, "Content-Length too big: #{len} > #{max}", []
|
23
|
+
end
|
24
|
+
|
25
|
+
self.req = req
|
26
|
+
self.parser = parser
|
27
|
+
self.buf = buf
|
28
|
+
self.socket = socket
|
29
|
+
self.buf2 = ""
|
30
|
+
if buf.size > 0
|
31
|
+
parser.filter_body(buf2, buf) and finalize_input
|
32
|
+
buf2.size > max and raise IOError, "chunked request body too big", []
|
33
|
+
end
|
34
|
+
self.tmp = len && len < Const::MAX_BODY ? StringIO.new("") : Util.tmpio
|
35
|
+
if buf2.size > 0
|
36
|
+
tmp.write(buf2)
|
37
|
+
tmp.seek(0)
|
38
|
+
max -= buf2.size
|
39
|
+
end
|
40
|
+
@max_body = max
|
41
|
+
end
|
42
|
+
|
43
|
+
def tee(length, dst)
|
44
|
+
rv = super
|
45
|
+
if rv && ((@max_body -= rv.size) < 0)
|
46
|
+
# make HttpParser#keepalive? => false to force an immediate disconnect
|
47
|
+
# after we write
|
48
|
+
parser.reset
|
49
|
+
throw :rainbows_EFBIG
|
50
|
+
end
|
51
|
+
rv
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
# this is called after forking, so it won't ever affect the master
|
57
|
+
# if it's reconfigured
|
58
|
+
def self.setup
|
59
|
+
Rainbows.max_bytes or return
|
60
|
+
case G.server.use
|
61
|
+
when :Rev, :EventMachine, :NeverBlock
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
65
|
+
TeeInput.class_eval { include Limit }
|
66
|
+
|
67
|
+
# force ourselves to the outermost middleware layer
|
68
|
+
G.server.app = MaxBody.new(G.server.app)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Rack response returned when there's an error
|
72
|
+
def err(env)
|
73
|
+
[ 413, [ %w(Content-Length 0), %w(Content-Type text/plain) ], [] ]
|
74
|
+
end
|
75
|
+
|
76
|
+
# our main Rack middleware endpoint
|
77
|
+
def call(env)
|
78
|
+
catch(:rainbows_EFBIG) { app.call(env) } || err(env)
|
79
|
+
end
|
80
|
+
|
81
|
+
end # class
|
82
|
+
end # module
|
@@ -10,9 +10,13 @@ module Rainbows
|
|
10
10
|
G = Rainbows::G
|
11
11
|
HH = Rack::Utils::HeaderHash
|
12
12
|
|
13
|
-
def self.
|
14
|
-
body = response
|
15
|
-
|
13
|
+
def self.write(client, response, out)
|
14
|
+
status, headers, body = response
|
15
|
+
|
16
|
+
body.respond_to?(:to_path) or
|
17
|
+
return HttpResponse.write(client, response, out)
|
18
|
+
|
19
|
+
headers = HH.new(headers)
|
16
20
|
|
17
21
|
# to_io is not part of the Rack spec, but make an exception
|
18
22
|
# here since we can't get here without checking to_path first
|
@@ -39,16 +43,11 @@ module Rainbows
|
|
39
43
|
headers.delete('Transfer-Encoding')
|
40
44
|
headers['Content-Length'] ||= st.size.to_s
|
41
45
|
else # char/block device, directory, whatever... nobody cares
|
42
|
-
return response
|
46
|
+
return HttpResponse.write(client, response, out)
|
43
47
|
end
|
44
48
|
client.defer_body(io, out)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
def self.write(client, response, out)
|
49
|
-
response.last.respond_to?(:to_path) and
|
50
|
-
response = defer!(client, response, out)
|
51
|
-
HttpResponse.write(client, response, out)
|
49
|
+
out.nil? or
|
50
|
+
client.write(HttpResponse.header_string(status, headers.to_hash, out))
|
52
51
|
end
|
53
52
|
|
54
53
|
def initialize(io, client, do_chunk, body)
|
data/lib/rainbows/revactor.rb
CHANGED
@@ -21,8 +21,6 @@ module Rainbows
|
|
21
21
|
# concurrency features this model provides.
|
22
22
|
|
23
23
|
module Revactor
|
24
|
-
require 'rainbows/revactor/tee_input'
|
25
|
-
|
26
24
|
RD_ARGS = {}
|
27
25
|
|
28
26
|
include Base
|
@@ -37,7 +35,7 @@ module Rainbows
|
|
37
35
|
rd_args << RD_ARGS
|
38
36
|
client.remote_addr
|
39
37
|
else
|
40
|
-
LOCALHOST
|
38
|
+
Unicorn::HttpRequest::LOCALHOST
|
41
39
|
end
|
42
40
|
buf = client.read(*rd_args)
|
43
41
|
hp = HttpParser.new
|
@@ -52,7 +50,7 @@ module Rainbows
|
|
52
50
|
env[Const::CLIENT_IO] = client
|
53
51
|
env[Const::RACK_INPUT] = 0 == hp.content_length ?
|
54
52
|
HttpRequest::NULL_IO :
|
55
|
-
|
53
|
+
TeeInput.new(PartialSocket.new(client), env, hp, buf)
|
56
54
|
env[Const::REMOTE_ADDR] = remote_addr
|
57
55
|
response = app.call(env.update(RACK_DEFAULTS))
|
58
56
|
|
@@ -137,5 +135,41 @@ module Rainbows
|
|
137
135
|
end
|
138
136
|
end
|
139
137
|
|
138
|
+
# Revactor Sockets do not implement readpartial, so we emulate just
|
139
|
+
# enough to avoid mucking with TeeInput internals. Fortunately
|
140
|
+
# this code is not heavily used so we can usually avoid the overhead
|
141
|
+
# of adding a userspace buffer.
|
142
|
+
class PartialSocket < Struct.new(:socket, :rbuf)
|
143
|
+
def initialize(socket)
|
144
|
+
# IO::Buffer is used internally by Rev which Revactor is based on
|
145
|
+
# so we'll always have it available
|
146
|
+
super(socket, IO::Buffer.new)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Revactor socket reads always return an unspecified amount,
|
150
|
+
# sometimes too much
|
151
|
+
def readpartial(length, dst = "")
|
152
|
+
return dst if length == 0
|
153
|
+
# always check and return from the userspace buffer first
|
154
|
+
rbuf.size > 0 and return dst.replace(rbuf.read(length))
|
155
|
+
|
156
|
+
# read off the socket since there was nothing in rbuf
|
157
|
+
tmp = socket.read
|
158
|
+
|
159
|
+
# we didn't read too much, good, just return it straight back
|
160
|
+
# to avoid needlessly wasting memory bandwidth
|
161
|
+
tmp.size <= length and return dst.replace(tmp)
|
162
|
+
|
163
|
+
# ugh, read returned too much, copy + reread to avoid slicing
|
164
|
+
rbuf << tmp[length, tmp.size]
|
165
|
+
dst.replace(tmp[0, length])
|
166
|
+
end
|
167
|
+
|
168
|
+
# just proxy any remaining methods TeeInput may use
|
169
|
+
def close
|
170
|
+
socket.close
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
140
174
|
end
|
141
175
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
module Rainbows
|
3
|
+
|
4
|
+
# acts like tee(1) on an input input to provide a input-like stream
|
5
|
+
# while providing rewindable semantics through a File/StringIO
|
6
|
+
# backing store. On the first pass, the input is only read on demand
|
7
|
+
# so your Rack application can use input notification (upload progress
|
8
|
+
# and like). This should fully conform to the Rack::InputWrapper
|
9
|
+
# specification on the public API. This class is intended to be a
|
10
|
+
# strict interpretation of Rack::InputWrapper functionality and will
|
11
|
+
# not support any deviations from it.
|
12
|
+
class TeeInput < Unicorn::TeeInput
|
13
|
+
|
14
|
+
# empty class, this is to avoid unecessarily modifying Unicorn::TeeInput
|
15
|
+
# when MaxBody::Limit is included
|
16
|
+
end
|
17
|
+
end
|
data/lib/rainbows.rb
CHANGED
@@ -22,15 +22,19 @@ module Rainbows
|
|
22
22
|
false
|
23
23
|
end
|
24
24
|
end
|
25
|
+
# :stopdoc:
|
25
26
|
G = State.new(true, 0, 0, 5)
|
26
27
|
O = {}
|
28
|
+
# :startdoc:
|
27
29
|
|
28
30
|
require 'rainbows/const'
|
29
31
|
require 'rainbows/http_server'
|
30
32
|
require 'rainbows/http_response'
|
31
33
|
require 'rainbows/base'
|
34
|
+
require 'rainbows/tee_input'
|
32
35
|
autoload :AppPool, 'rainbows/app_pool'
|
33
36
|
autoload :DevFdResponse, 'rainbows/dev_fd_response'
|
37
|
+
autoload :MaxBody, 'rainbows/max_body'
|
34
38
|
|
35
39
|
class << self
|
36
40
|
|
@@ -73,6 +77,20 @@ module Rainbows
|
|
73
77
|
rv
|
74
78
|
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
75
79
|
end
|
80
|
+
|
81
|
+
# returns a string representing the address of the given client +io+
|
82
|
+
# For local UNIX domain sockets, this will return a string referred
|
83
|
+
# to by the (non-frozen) Unicorn::HttpRequest::LOCALHOST constant.
|
84
|
+
def addr(io)
|
85
|
+
io.respond_to?(:peeraddr) ?
|
86
|
+
io.peeraddr.last : Unicorn::HttpRequest::LOCALHOST
|
87
|
+
end
|
88
|
+
|
89
|
+
# the default max body size is 1 megabyte (1024 * 1024 bytes)
|
90
|
+
@@max_bytes = 1024 * 1024
|
91
|
+
|
92
|
+
def max_bytes; @@max_bytes; end
|
93
|
+
def max_bytes=(nr); @@max_bytes = nr; end
|
76
94
|
end
|
77
95
|
|
78
96
|
# configures \Rainbows! with a given concurrency model to +use+ and
|
@@ -83,6 +101,7 @@ module Rainbows
|
|
83
101
|
# use :Revactor # this may also be :ThreadSpawn or :ThreadPool
|
84
102
|
# worker_connections 400
|
85
103
|
# keepalive_timeout 0 # zero disables keepalives entirely
|
104
|
+
# client_max_body_size 5*1024*1024 # 5 megabytes
|
86
105
|
# end
|
87
106
|
#
|
88
107
|
# # the rest of the Unicorn configuration
|
@@ -94,16 +113,21 @@ module Rainbows
|
|
94
113
|
# +worker_processes+ * +worker_connections+, so in the above example
|
95
114
|
# we can serve 8 * 400 = 3200 clients concurrently.
|
96
115
|
#
|
97
|
-
# The default is +keepalive_timeout+ is
|
116
|
+
# The default is +keepalive_timeout+ is 5 seconds, which should be
|
98
117
|
# enough under most conditions for browsers to render the page and
|
99
118
|
# start retrieving extra elements for. Increasing this beyond 5
|
100
119
|
# seconds is not recommended. Zero disables keepalive entirely
|
101
120
|
# (but pipelining fully-formed requests is still works).
|
121
|
+
#
|
122
|
+
# The default +client_max_body_size+ is 1 megabyte (1024 * 1024 bytes),
|
123
|
+
# setting this to +nil+ will disable body size checks and allow any
|
124
|
+
# size to be specified.
|
102
125
|
def Rainbows!(&block)
|
103
126
|
block_given? or raise ArgumentError, "Rainbows! requires a block"
|
104
127
|
HttpServer.setup(block)
|
105
128
|
end
|
106
129
|
|
130
|
+
# :stopdoc:
|
107
131
|
# maps models to default worker counts, default worker count numbers are
|
108
132
|
# pretty arbitrary and tuning them to your application and hardware is
|
109
133
|
# highly recommended
|
@@ -116,7 +140,6 @@ module Rainbows
|
|
116
140
|
:RevThreadSpawn => 50,
|
117
141
|
:RevThreadPool => 50,
|
118
142
|
:EventMachine => 50,
|
119
|
-
:EventMachineDefer => 50,
|
120
143
|
:FiberSpawn => 50,
|
121
144
|
:FiberPool => 50,
|
122
145
|
:ActorSpawn => 50,
|
@@ -126,6 +149,7 @@ module Rainbows
|
|
126
149
|
u = model.to_s.gsub(/([a-z0-9])([A-Z0-9])/) { "#{$1}_#{$2.downcase!}" }
|
127
150
|
autoload model, "rainbows/#{u.downcase!}"
|
128
151
|
end
|
152
|
+
# :startdoc:
|
129
153
|
autoload :Fiber, 'rainbows/fiber' # core class
|
130
154
|
|
131
155
|
end
|
data/local.mk.sample
CHANGED
@@ -6,39 +6,29 @@
|
|
6
6
|
|
7
7
|
RSYNC = rsync
|
8
8
|
DLEXT := so
|
9
|
-
gems := rack-1.1.0
|
10
|
-
# gems += unicorn-0.96.0 # installed via setup.rb
|
11
|
-
gems += rev-0.3.2
|
12
|
-
gems += iobuffer-0.1.3
|
13
|
-
gems += eventmachine-0.12.10
|
14
|
-
gems += async_sinatra-0.1.5 sinatra-0.9.4
|
15
|
-
gems += espace-neverblock-0.1.6.1
|
16
|
-
|
17
|
-
# Cramp isn't enabled by default since it depends on several prerelease gems
|
18
|
-
ifdef CRAMP
|
19
|
-
gems += cramp-0.7
|
20
|
-
gems += activesupport-3.0.pre
|
21
|
-
gems += activemodel-3.0.pre
|
22
|
-
gems += arel-0.2.pre
|
23
|
-
gems += usher-0.6.2
|
24
|
-
gems += fuzzyhash-0.0.11
|
25
|
-
gems += mysqlplus-0.1.1
|
26
|
-
endif
|
27
9
|
|
28
10
|
# Avoid loading rubygems to speed up tests because gmake is
|
29
11
|
# fork+exec heavy with Ruby.
|
30
12
|
prefix = $(HOME)
|
13
|
+
|
31
14
|
ifeq ($(r19),)
|
32
15
|
RUBY := $(prefix)/bin/ruby
|
33
|
-
gem_paths := $(addprefix $(prefix)/lib/ruby/gems/1.8/gems/,$(gems))
|
34
16
|
else
|
35
17
|
prefix := $(prefix)/ruby-1.9
|
36
18
|
export PATH := $(prefix)/bin:$(PATH)
|
37
19
|
RUBY := $(prefix)/bin/ruby --disable-gems
|
38
|
-
gems += case-0.5 revactor-0.1.5
|
39
|
-
gem_paths := $(addprefix $(prefix)/lib/ruby/gems/1.9.1/gems/,$(gems))
|
40
20
|
endif
|
41
21
|
|
22
|
+
ifndef NO_ISOLATE
|
23
|
+
x := $(shell test -d t/ && \
|
24
|
+
PATH=$(PATH) NO_ISOLATE=T $(MAKE) -s isolate RUBY:="$(RUBY)")
|
25
|
+
endif
|
26
|
+
|
27
|
+
RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
|
28
|
+
|
29
|
+
updir := $(shell git rev-parse --show-cdup)
|
30
|
+
gem_paths := $(wildcard $(updir)tmp/gems/$(RUBY_VERSION)/gems/*-*)
|
31
|
+
|
42
32
|
ifdef gem_paths
|
43
33
|
sp :=
|
44
34
|
sp +=
|
@@ -55,9 +45,9 @@ TRACER = /usr/bin/time -v -o $(t_pfx).time
|
|
55
45
|
|
56
46
|
full-test: test-18 test-19
|
57
47
|
test-18:
|
58
|
-
$(MAKE) test 2>&1 | sed -
|
48
|
+
$(MAKE) test 2>&1 | sed -e 's!^!1.8 !'
|
59
49
|
test-19:
|
60
|
-
$(MAKE) test r19=
|
50
|
+
$(MAKE) test r19=T 2>&1 | sed -e 's!^!1.9 !'
|
61
51
|
|
62
52
|
latest: NEWS
|
63
53
|
@awk 'BEGIN{RS="=== ";ORS=""}NR==2{sub(/\n$$/,"");print RS""$$0 }' < $<
|
data/rainbows.gemspec
CHANGED
@@ -43,13 +43,13 @@ Gem::Specification.new do |s|
|
|
43
43
|
# we need Unicorn for the HTTP parser and process management
|
44
44
|
# The HTTP parser in Unicorn <= 0.97.0 was vulnerable to a remote DoS
|
45
45
|
# when exposed directly to untrusted clients.
|
46
|
-
s.add_dependency(%q<unicorn>, ["
|
46
|
+
s.add_dependency(%q<unicorn>, [">= 0.97.1", "< 1.0.0"])
|
47
47
|
|
48
48
|
# Unicorn already depends on Rack
|
49
49
|
# s.add_dependency(%q<rack>)
|
50
50
|
|
51
51
|
# optional runtime dependencies depending on configuration
|
52
|
-
# see
|
52
|
+
# see config/isolate.rb for the exact versions we've tested with
|
53
53
|
#
|
54
54
|
# Revactor >= 0.1.5 includes UNIX domain socket support
|
55
55
|
# s.add_dependency(%q<revactor>, [">= 0.1.5"])
|
data/t/app_deferred.ru
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#\-E none
|
2
|
+
# can't use non-compatible middleware that doesn't pass "deferered?" calls
|
3
|
+
#
|
4
|
+
# used for testing deferred actions for Merb and possibly other frameworks
|
5
|
+
# ref: http://brainspl.at/articles/2008/04/18/deferred-requests-with-merb-ebb-and-thin
|
6
|
+
|
7
|
+
class DeferredApp < Struct.new(:app)
|
8
|
+
def deferred?(env)
|
9
|
+
env["PATH_INFO"] == "/deferred"
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
env["rack.multithread"] or raise RuntimeError, "rack.multithread not true"
|
14
|
+
body = "#{Thread.current.inspect}\n"
|
15
|
+
headers = {
|
16
|
+
"Content-Type" => "text/plain",
|
17
|
+
"Content-Length" => body.size.to_s,
|
18
|
+
}
|
19
|
+
[ 200, headers, [ body ] ]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
run DeferredApp.new
|
@@ -89,16 +89,16 @@ class AsyncApp
|
|
89
89
|
# Get the headers out there asap, let the client know we're alive...
|
90
90
|
EventMachine::next_tick { env['async.callback'].call [200, {'Content-Type' => 'text/plain'}, body] }
|
91
91
|
|
92
|
-
#
|
93
|
-
#
|
92
|
+
# Semi-emulate a long db request, instead of a timer, in reality we'd be
|
93
|
+
# waiting for the response data. Whilst this happens, other connections
|
94
94
|
# can be serviced.
|
95
95
|
# This could be any callback based thing though, a deferrable waiting on
|
96
|
-
#
|
96
|
+
# IO data, a db request, an http request, an smtp send, whatever.
|
97
97
|
EventMachine::add_timer(1) {
|
98
98
|
body.call ["Woah, async!\n"]
|
99
99
|
|
100
100
|
EventMachine::next_tick {
|
101
|
-
#
|
101
|
+
# This could actually happen any time, you could spawn off to new
|
102
102
|
# threads, pause as a good looking lady walks by, whatever.
|
103
103
|
# Just shows off how we can defer chunks of data in the body, you can
|
104
104
|
# even call this many times.
|
@@ -116,11 +116,11 @@ end
|
|
116
116
|
|
117
117
|
# The additions to env for async.connection and async.callback absolutely
|
118
118
|
# destroy the speed of the request if Lint is doing it's checks on env.
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
119
|
+
# It is also important to note that an async response will not pass through
|
120
|
+
# any further middleware, as the async response notification has been passed
|
121
|
+
# right up to the webserver, and the callback goes directly there too.
|
122
122
|
# Middleware could possibly catch :async, and also provide a different
|
123
|
-
#
|
123
|
+
# async.connection and async.callback.
|
124
124
|
|
125
125
|
# use Rack::Lint
|
126
126
|
run AsyncApp.new
|
@@ -7,7 +7,7 @@ test -r random_blob || die "random_blob required, run with 'make $0'"
|
|
7
7
|
# So we try to use things like curl and sha1sum that are implemented
|
8
8
|
# without the Ruby interpreter to validate our own Ruby internals.
|
9
9
|
|
10
|
-
t_plan 7 "concurrent rack.input hammer stress test"
|
10
|
+
t_plan 7 "concurrent rack.input hammer stress test (chunked)"
|
11
11
|
|
12
12
|
t_begin "setup and startup" && {
|
13
13
|
rtmpfiles curl_out curl_err
|
@@ -0,0 +1,50 @@
|
|
1
|
+
nr_client=${nr_client-4}
|
2
|
+
. ./test-lib.sh
|
3
|
+
test -r random_blob || die "random_blob required, run with 'make $0'"
|
4
|
+
|
5
|
+
# basically we don't trust our own implementation of content-md5-put
|
6
|
+
# nor our Ruby 1.9 knowledge nor proper use of encodings in Ruby.
|
7
|
+
# So we try to use things like curl and sha1sum that are implemented
|
8
|
+
# without the Ruby interpreter to validate our own Ruby internals.
|
9
|
+
|
10
|
+
t_plan 7 "concurrent rack.input hammer stress test (content-length)"
|
11
|
+
|
12
|
+
t_begin "setup and startup" && {
|
13
|
+
rtmpfiles curl_out curl_err
|
14
|
+
rainbows_setup $model
|
15
|
+
rainbows -D sha1.ru -c $unicorn_config
|
16
|
+
rainbows_wait_start
|
17
|
+
}
|
18
|
+
|
19
|
+
t_begin "send $nr_client concurrent requests" && {
|
20
|
+
start=$(date +%s)
|
21
|
+
for i in $(awk "BEGIN{for(i=0;i<$nr_client;++i) print i}" </dev/null)
|
22
|
+
do
|
23
|
+
(
|
24
|
+
curl -sSf -T random_blob http://$listen/$i \
|
25
|
+
>> $curl_out 2>> $curl_err
|
26
|
+
) &
|
27
|
+
done
|
28
|
+
wait
|
29
|
+
t_info elapsed=$(( $(date +%s) - $start ))
|
30
|
+
}
|
31
|
+
|
32
|
+
t_begin "kill server" && kill $rainbows_pid
|
33
|
+
|
34
|
+
t_begin "got $nr_client responses" && {
|
35
|
+
test $nr_client -eq $(wc -l < $curl_out)
|
36
|
+
}
|
37
|
+
|
38
|
+
t_begin "all responses identical" && {
|
39
|
+
test 1 -eq $(sort < $curl_out | uniq | wc -l)
|
40
|
+
}
|
41
|
+
|
42
|
+
t_begin "sha1 matches on-disk sha1" && {
|
43
|
+
blob_sha1=$(rsha1 < random_blob)
|
44
|
+
t_info blob_sha1=$blob_sha1
|
45
|
+
test x"$blob_sha1" = x"$(sort < $curl_out | uniq)"
|
46
|
+
}
|
47
|
+
|
48
|
+
t_begin "no errors in stderr log" && check_stderr
|
49
|
+
|
50
|
+
t_done
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
test -r random_blob || die "random_blob required, run with 'make $0'"
|
4
|
+
|
5
|
+
t_plan 6 "rack.input client_max_body_size default"
|
6
|
+
|
7
|
+
t_begin "setup and startup" && {
|
8
|
+
rtmpfiles curl_out curl_err cmbs_config
|
9
|
+
rainbows_setup $model
|
10
|
+
grep -v client_max_body_size < $unicorn_config > $cmbs_config
|
11
|
+
rainbows -D sha1-random-size.ru -c $cmbs_config
|
12
|
+
rainbows_wait_start
|
13
|
+
}
|
14
|
+
|
15
|
+
t_begin "regular request" && {
|
16
|
+
rm -f $ok
|
17
|
+
curl -vsSf -T random_blob -H Expect: \
|
18
|
+
http://$listen/ > $curl_out 2> $curl_err || > $ok
|
19
|
+
dbgcat curl_err
|
20
|
+
dbgcat curl_out
|
21
|
+
test -e $ok
|
22
|
+
}
|
23
|
+
|
24
|
+
t_begin "chunked request" && {
|
25
|
+
rm -f $ok
|
26
|
+
curl -vsSf -T- < random_blob -H Expect: \
|
27
|
+
http://$listen/ > $curl_out 2> $curl_err || > $ok
|
28
|
+
dbgcat curl_err
|
29
|
+
dbgcat curl_out
|
30
|
+
test -e $ok
|
31
|
+
}
|
32
|
+
|
33
|
+
t_begin "default size sha1 chunked" && {
|
34
|
+
blob_sha1=3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
|
35
|
+
rm -f $ok
|
36
|
+
> $r_err
|
37
|
+
dd if=/dev/zero bs=1048576 count=1 | \
|
38
|
+
curl -vsSf -T- -H Expect: \
|
39
|
+
http://$listen/ > $curl_out 2> $curl_err
|
40
|
+
test "$(cat $curl_out)" = $blob_sha1
|
41
|
+
dbgcat curl_err
|
42
|
+
dbgcat curl_out
|
43
|
+
}
|
44
|
+
|
45
|
+
t_begin "default size sha1 content-length" && {
|
46
|
+
blob_sha1=3b71f43ff30f4b15b5cd85dd9e95ebc7e84eb5a3
|
47
|
+
rm -f $ok
|
48
|
+
dd if=/dev/zero bs=1048576 count=1 of=$tmp
|
49
|
+
curl -vsSf -T $tmp -H Expect: \
|
50
|
+
http://$listen/ > $curl_out 2> $curl_err
|
51
|
+
test "$(cat $curl_out)" = $blob_sha1
|
52
|
+
dbgcat curl_err
|
53
|
+
dbgcat curl_out
|
54
|
+
}
|
55
|
+
|
56
|
+
t_begin "shutdown" && {
|
57
|
+
kill $rainbows_pid
|
58
|
+
}
|
59
|
+
|
60
|
+
t_done
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
test -r random_blob || die "random_blob required, run with 'make $0'"
|
4
|
+
|
5
|
+
t_plan 6 "rack.input client_max_body_size tiny"
|
6
|
+
|
7
|
+
t_begin "setup and startup" && {
|
8
|
+
rtmpfiles curl_out curl_err cmbs_config
|
9
|
+
rainbows_setup $model
|
10
|
+
sed -e 's/client_max_body_size.*/client_max_body_size 256/' \
|
11
|
+
< $unicorn_config > $cmbs_config
|
12
|
+
rainbows -D sha1-random-size.ru -c $cmbs_config
|
13
|
+
rainbows_wait_start
|
14
|
+
}
|
15
|
+
|
16
|
+
t_begin "stops a regular request" && {
|
17
|
+
rm -f $ok
|
18
|
+
dd if=/dev/zero bs=257 count=1 of=$tmp
|
19
|
+
curl -vsSf -T $tmp -H Expect: \
|
20
|
+
http://$listen/ > $curl_out 2> $curl_err || > $ok
|
21
|
+
dbgcat curl_err
|
22
|
+
dbgcat curl_out
|
23
|
+
test -e $ok
|
24
|
+
}
|
25
|
+
|
26
|
+
t_begin "stops a large chunked request" && {
|
27
|
+
rm -f $ok
|
28
|
+
dd if=/dev/zero bs=257 count=1 | \
|
29
|
+
curl -vsSf -T- -H Expect: \
|
30
|
+
http://$listen/ > $curl_out 2> $curl_err || > $ok
|
31
|
+
dbgcat curl_err
|
32
|
+
dbgcat curl_out
|
33
|
+
test -e $ok
|
34
|
+
}
|
35
|
+
|
36
|
+
t_begin "small size sha1 chunked ok" && {
|
37
|
+
blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
|
38
|
+
rm -f $ok
|
39
|
+
dd if=/dev/zero bs=256 count=1 | \
|
40
|
+
curl -vsSf -T- -H Expect: \
|
41
|
+
http://$listen/ > $curl_out 2> $curl_err
|
42
|
+
dbgcat curl_err
|
43
|
+
dbgcat curl_out
|
44
|
+
test "$(cat $curl_out)" = $blob_sha1
|
45
|
+
}
|
46
|
+
|
47
|
+
t_begin "small size sha1 content-length ok" && {
|
48
|
+
blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
|
49
|
+
rm -f $ok
|
50
|
+
dd if=/dev/zero bs=256 count=1 of=$tmp
|
51
|
+
curl -vsSf -T $tmp -H Expect: \
|
52
|
+
http://$listen/ > $curl_out 2> $curl_err
|
53
|
+
dbgcat curl_err
|
54
|
+
dbgcat curl_out
|
55
|
+
test "$(cat $curl_out)" = $blob_sha1
|
56
|
+
}
|
57
|
+
|
58
|
+
t_begin "shutdown" && {
|
59
|
+
kill $rainbows_pid
|
60
|
+
}
|
61
|
+
|
62
|
+
t_done
|