rainbows 0.91.1 → 0.92.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/.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
data/GIT-VERSION-FILE
CHANGED
@@ -1 +1 @@
|
|
1
|
-
GIT_VERSION = 0.
|
1
|
+
GIT_VERSION = 0.92.0
|
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
@@ -3,6 +3,7 @@ all::
|
|
3
3
|
RUBY = ruby
|
4
4
|
RAKE = rake
|
5
5
|
GIT_URL = git://git.bogomips.org/rainbows.git
|
6
|
+
ISOLATE_CONFIG = config/isolate.rb
|
6
7
|
|
7
8
|
GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
|
8
9
|
@./GIT-VERSION-GEN
|
@@ -15,8 +16,15 @@ ifeq ($(RUBY_VERSION),)
|
|
15
16
|
RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
|
16
17
|
endif
|
17
18
|
|
19
|
+
# rake takes forever to start
|
20
|
+
isolate: tmp/gems/$(RUBY_VERSION)/.isolate
|
21
|
+
tmp/gems/$(RUBY_VERSION)/.isolate: $(ISOLATE_CONFIG)
|
22
|
+
ISOLATE_CONFIG=$(ISOLATE_CONFIG) $(RAKE) isolate
|
23
|
+
> $@
|
24
|
+
|
18
25
|
base_bins := rainbows
|
19
26
|
bins := $(addprefix bin/, $(base_bins))
|
27
|
+
man1_rdoc := $(addsuffix _1, $(base_bins))
|
20
28
|
man1_bins := $(addsuffix .1, $(base_bins))
|
21
29
|
man1_paths := $(addprefix man/man1/, $(man1_bins))
|
22
30
|
|
@@ -58,7 +66,7 @@ NEWS: GIT-VERSION-FILE
|
|
58
66
|
$(RAKE) -s news_rdoc > $@+
|
59
67
|
mv $@+ $@
|
60
68
|
|
61
|
-
SINCE = 0.
|
69
|
+
SINCE = 0.91.1
|
62
70
|
ChangeLog: LOG_VERSION = \
|
63
71
|
$(shell git rev-parse -q "$(GIT_VERSION)" >/dev/null 2>&1 && \
|
64
72
|
echo $(GIT_VERSION) || git describe)
|
@@ -74,18 +82,21 @@ cgit_atom := http://git.bogomips.org/cgit/rainbows.git/atom/?h=master
|
|
74
82
|
atom = <link rel="alternate" title="Atom feed" href="$(1)" \
|
75
83
|
type="application/atom+xml"/>
|
76
84
|
|
77
|
-
# using rdoc 2.
|
85
|
+
# using rdoc 2.5.x+
|
78
86
|
doc: .document NEWS ChangeLog
|
79
|
-
for i in $(
|
87
|
+
for i in $(man1_rdoc); do echo > $$i; done
|
80
88
|
find bin lib -type f -name '*.rbc' -exec rm -f '{}' ';'
|
81
|
-
rdoc -
|
89
|
+
rdoc -a -t "$(shell sed -ne '1s/^= //p' README)"
|
82
90
|
install -m644 COPYING doc/COPYING
|
83
91
|
install -m644 $(shell grep '^[A-Z]' .document) doc/
|
84
92
|
$(MAKE) -C Documentation install-html install-man
|
85
93
|
install -m644 $(man1_paths) doc/
|
86
94
|
cd doc && for i in $(base_bins); do \
|
95
|
+
$(RM) 1.html $${i}.1.html; \
|
87
96
|
sed -e '/"documentation">/r man1/'$$i'.1.html' \
|
88
|
-
< $${i}_1.html > tmp && mv tmp $${i}_1.html;
|
97
|
+
< $${i}_1.html > tmp && mv tmp $${i}_1.html; \
|
98
|
+
ln $${i}_1.html $${i}.1.html; \
|
99
|
+
done
|
89
100
|
$(RUBY) -i -p -e \
|
90
101
|
'$$_.gsub!("</title>",%q{\&$(call atom,$(cgit_atom))})' \
|
91
102
|
doc/ChangeLog.html
|
@@ -99,7 +110,7 @@ doc: .document NEWS ChangeLog
|
|
99
110
|
'$$_.gsub!(/INCLUDE/){File.read("Documentation/comparison.html")}' \
|
100
111
|
doc/Summary.html
|
101
112
|
cat Documentation/comparison.css >> doc/rdoc.css
|
102
|
-
$(RM) $(
|
113
|
+
$(RM) $(man1_rdoc)
|
103
114
|
|
104
115
|
ifneq ($(VERSION),)
|
105
116
|
rfproject := rainbows
|
@@ -156,7 +167,7 @@ release: verify package $(release_notes) $(release_changes)
|
|
156
167
|
# make tgz release on RubyForge
|
157
168
|
rubyforge add_release -f -n $(release_notes) -a $(release_changes) \
|
158
169
|
$(rfproject) $(rfpackage) $(VERSION) $(pkgtgz)
|
159
|
-
# push gem to
|
170
|
+
# push gem to RubyGems.org
|
160
171
|
gem push $(pkggem)
|
161
172
|
# in case of gem downloads from RubyForge releases page
|
162
173
|
-rubyforge add_file \
|
data/NEWS
CHANGED
@@ -1,3 +1,32 @@
|
|
1
|
+
=== 0.92.0 / 2010-05-04 21:58 UTC
|
2
|
+
|
3
|
+
Mostly internal cleanups and small improvements.
|
4
|
+
|
5
|
+
The only backwards incompatible change was the addition of the
|
6
|
+
"client_max_body_size" parameter to limit upload sizes to
|
7
|
+
prevent DoS. This defaults to one megabyte (same as nginx), so
|
8
|
+
any apps relying on the limit-less behavior of previous will
|
9
|
+
have to configure this in the Unicorn/Rainbows! config file:
|
10
|
+
|
11
|
+
Rainbows! do
|
12
|
+
# nil for unlimited, or any number in bytes
|
13
|
+
client_max_body_size nil
|
14
|
+
end
|
15
|
+
|
16
|
+
The ThreadSpawn and ThreadPool models are now optimized for serving
|
17
|
+
large static files under Ruby 1.9 using IO.copy_stream[1].
|
18
|
+
|
19
|
+
The EventMachine model has always had optimized static file
|
20
|
+
serving (using EM::Connection#stream_file_data[2]).
|
21
|
+
|
22
|
+
The EventMachine model (finally) gets conditionally deferred app
|
23
|
+
dispatch in a separate thread, as described by Ezra Zygmuntowicz
|
24
|
+
for Merb, Ebb and Thin[3].
|
25
|
+
|
26
|
+
[1] - http://euruko2008.csrug.cz/system/assets/documents/0000/0007/tanaka-IOcopy_stream-euruko2008.pdf
|
27
|
+
[2] - http://eventmachine.rubyforge.org/EventMachine/Connection.html#M000312
|
28
|
+
[3] - http://brainspl.at/articles/2008/04/18/deferred-requests-with-merb-ebb-and-thin
|
29
|
+
|
1
30
|
=== 0.91.1 / 2010-04-19 21:13 UTC
|
2
31
|
|
3
32
|
This release fixes a denial-of-service vector for deployments
|
data/README
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
= Rainbows! Unicorn for sleepy apps and slow clients
|
1
|
+
= Rainbows! - Unicorn for sleepy apps and slow clients
|
2
2
|
|
3
|
-
Rainbows! is an HTTP server for sleepy Rack applications. It is based on
|
3
|
+
\Rainbows! is an HTTP server for sleepy Rack applications. It is based on
|
4
4
|
Unicorn, but designed to handle applications that expect long
|
5
5
|
request/response times and/or slow clients. For Rack applications not
|
6
6
|
heavily bound by slow external network dependencies, consider Unicorn
|
@@ -92,7 +92,7 @@ and run setup.rb after unpacking it:
|
|
92
92
|
|
93
93
|
http://rubyforge.org/frs/?group_id=8977
|
94
94
|
|
95
|
-
You may also install it via RubyGems on
|
95
|
+
You may also install it via RubyGems on RubyGems.org:
|
96
96
|
|
97
97
|
gem install rainbows
|
98
98
|
|
@@ -123,7 +123,8 @@ config file:
|
|
123
123
|
worker_connections 100
|
124
124
|
end
|
125
125
|
|
126
|
-
See the {Rainbows! configuration
|
126
|
+
See the {Rainbows! configuration}[link:Rainbows.html#method-i-Rainbows!]
|
127
|
+
{documentation}[link:Rainbows.html#method-i-Rainbows!]
|
127
128
|
for more details.
|
128
129
|
|
129
130
|
== Development
|
data/Rakefile
CHANGED
@@ -183,3 +183,10 @@ task :fm_update do
|
|
183
183
|
p http.post(uri.path, req, {'Content-Type'=>'application/json'})
|
184
184
|
end
|
185
185
|
end
|
186
|
+
|
187
|
+
desc 'isolate gems for development'
|
188
|
+
task :isolate do
|
189
|
+
require 'isolate'
|
190
|
+
Isolate.gems "tmp/gems/#{RUBY_VERSION}",
|
191
|
+
:file => ENV['ISOLATE_CONFIG']
|
192
|
+
end
|
data/SIGNALS
CHANGED
@@ -14,7 +14,9 @@ between \Rainbows!, Unicorn and nginx.
|
|
14
14
|
* INT/TERM - quick shutdown, kills all workers immediately
|
15
15
|
|
16
16
|
* QUIT - graceful shutdown, waits for workers to finish their
|
17
|
-
current request before finishing.
|
17
|
+
current request before finishing. This currently does not
|
18
|
+
wait for requests deferred to a separate thread when using
|
19
|
+
EventMachine (when app.deferred?(env) => true)
|
18
20
|
|
19
21
|
* USR1 - reopen all logs owned by the master and all workers
|
20
22
|
See Unicorn::Util.reopen_logs for what is considered a log.
|
@@ -43,6 +45,8 @@ automatically respawned.
|
|
43
45
|
* QUIT - Gracefully exit after finishing the current request.
|
44
46
|
Unless WINCH has been sent to the master (or the master is killed),
|
45
47
|
the master process will respawn a worker to replace this one.
|
48
|
+
This currently does not wait for requests deferred to a separate
|
49
|
+
thread when using EventMachine (when app.deferred?(env) => true)
|
46
50
|
|
47
51
|
* USR1 - Reopen all logs owned by the worker process.
|
48
52
|
See Unicorn::Util.reopen_logs for what is considered a log.
|
data/TODO
CHANGED
@@ -10,12 +10,9 @@ care about.
|
|
10
10
|
unit tests, only integration tests that exercise externally
|
11
11
|
visible parts.
|
12
12
|
|
13
|
-
* EventMachine.spawn - should be like Revactor, maybe?
|
14
|
-
|
15
|
-
* conditional app.deferred?(env) support
|
16
|
-
Merb uses it, some other servers support it
|
17
|
-
|
18
13
|
* EventMachine+Fibers+streaming input
|
14
|
+
(those who do not require streaming input can use
|
15
|
+
{rack-fiber_pool}[http://github.com/mperham/rack-fiber_pool])
|
19
16
|
|
20
17
|
* RevFiberPool
|
21
18
|
|
data/config/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/isolate_*.rb
|
data/config/isolate.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# this the default config file used by John Barnette's isolate gem
|
2
|
+
# you can create a config/isolate_local.rb file to override this
|
3
|
+
# See the corresponding tasks in Rakefile and GNUmakefile
|
4
|
+
# `rake isolate' or (faster in the unmodified case, `make isolate')
|
5
|
+
|
6
|
+
gem 'rack', '1.1.0'
|
7
|
+
gem 'unicorn', '0.97.1'
|
8
|
+
|
9
|
+
gem 'iobuffer', '0.1.3'
|
10
|
+
gem 'rev', '0.3.2'
|
11
|
+
|
12
|
+
gem 'eventmachine', '0.12.10'
|
13
|
+
|
14
|
+
gem 'sinatra', '0.9.4'
|
15
|
+
gem 'async_sinatra', '0.1.5'
|
16
|
+
|
17
|
+
gem 'neverblock', '0.1.6.2'
|
18
|
+
|
19
|
+
if defined?(::Fiber)
|
20
|
+
gem 'case', '0.5'
|
21
|
+
gem 'revactor', '0.1.5'
|
22
|
+
gem 'rack-fiber_pool', '0.9.0'
|
23
|
+
end
|
24
|
+
|
25
|
+
gem 'cramp', '0.10'
|
data/lib/rainbows/base.rb
CHANGED
@@ -12,6 +12,7 @@ module Rainbows
|
|
12
12
|
|
13
13
|
def init_worker_process(worker)
|
14
14
|
super(worker)
|
15
|
+
MaxBody.setup
|
15
16
|
G.tmp = worker.tmp
|
16
17
|
|
17
18
|
# avoid spurious wakeups and blocking-accept() with 1.8 green threads
|
@@ -29,14 +30,35 @@ module Rainbows
|
|
29
30
|
logger.info "Rainbows! #@use worker_connections=#@worker_connections"
|
30
31
|
end
|
31
32
|
|
33
|
+
if IO.respond_to?(:copy_stream)
|
34
|
+
def write_body(client, body)
|
35
|
+
if body.respond_to?(:to_path)
|
36
|
+
io = body.respond_to?(:to_io) ? body.to_io : body.to_path
|
37
|
+
IO.copy_stream(io, client)
|
38
|
+
else
|
39
|
+
body.each { |chunk| client.write(chunk) }
|
40
|
+
end
|
41
|
+
ensure
|
42
|
+
body.respond_to?(:close) and body.close
|
43
|
+
end
|
44
|
+
else
|
45
|
+
def write_body(client, body)
|
46
|
+
body.each { |chunk| client.write(chunk) }
|
47
|
+
ensure
|
48
|
+
body.respond_to?(:close) and body.close
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
32
52
|
# once a client is accepted, it is processed in its entirety here
|
33
53
|
# in 3 easy steps: read request, call app, write app response
|
54
|
+
# this is used by synchronous concurrency models
|
55
|
+
# Base, ThreadSpawn, ThreadPool
|
34
56
|
def process_client(client)
|
35
57
|
buf = client.readpartial(CHUNK_SIZE) # accept filters protect us here
|
36
58
|
hp = HttpParser.new
|
37
59
|
env = {}
|
38
60
|
alive = true
|
39
|
-
remote_addr =
|
61
|
+
remote_addr = Rainbows.addr(client)
|
40
62
|
|
41
63
|
begin # loop
|
42
64
|
while ! hp.headers(env, buf)
|
@@ -46,20 +68,22 @@ module Rainbows
|
|
46
68
|
|
47
69
|
env[CLIENT_IO] = client
|
48
70
|
env[RACK_INPUT] = 0 == hp.content_length ?
|
49
|
-
HttpRequest::NULL_IO :
|
50
|
-
Unicorn::TeeInput.new(client, env, hp, buf)
|
71
|
+
HttpRequest::NULL_IO : TeeInput.new(client, env, hp, buf)
|
51
72
|
env[REMOTE_ADDR] = remote_addr
|
52
|
-
|
73
|
+
status, headers, body = app.call(env.update(RACK_DEFAULTS))
|
53
74
|
|
54
|
-
if 100 ==
|
75
|
+
if 100 == status.to_i
|
55
76
|
client.write(EXPECT_100_RESPONSE)
|
56
77
|
env.delete(HTTP_EXPECT)
|
57
|
-
|
78
|
+
status, headers, body = app.call(env)
|
58
79
|
end
|
59
80
|
|
60
81
|
alive = hp.keepalive? && G.alive
|
61
|
-
|
62
|
-
|
82
|
+
if hp.headers?
|
83
|
+
out = [ alive ? CONN_ALIVE : CONN_CLOSE ]
|
84
|
+
client.write(HttpResponse.header_string(status, headers, out))
|
85
|
+
end
|
86
|
+
write_body(client, body)
|
63
87
|
end while alive and hp.reset.nil? and env.clear
|
64
88
|
# if we get any error, try to write something back to the client
|
65
89
|
# assuming we haven't closed the socket, but don't get hung up
|
data/lib/rainbows/const.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Rainbows
|
4
4
|
|
5
5
|
module Const
|
6
|
-
RAINBOWS_VERSION = '0.
|
6
|
+
RAINBOWS_VERSION = '0.92.0'
|
7
7
|
|
8
8
|
include Unicorn::Const
|
9
9
|
|
@@ -17,7 +17,6 @@ module Rainbows
|
|
17
17
|
|
18
18
|
CONN_CLOSE = "Connection: close\r\n"
|
19
19
|
CONN_ALIVE = "Connection: keep-alive\r\n"
|
20
|
-
LOCALHOST = Unicorn::HttpRequest::LOCALHOST
|
21
20
|
|
22
21
|
# client IO object that supports reading and writing directly
|
23
22
|
# without filtering it through the HTTP chunk parser.
|
@@ -25,5 +24,7 @@ module Rainbows
|
|
25
24
|
# of the official spec, but for now it is "hack.io"
|
26
25
|
CLIENT_IO = "hack.io".freeze
|
27
26
|
|
27
|
+
ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
|
28
|
+
|
28
29
|
end
|
29
30
|
end
|
data/lib/rainbows/ev_core.rb
CHANGED
@@ -14,7 +14,7 @@ module Rainbows
|
|
14
14
|
ASYNC_CLOSE = "async.close".freeze
|
15
15
|
|
16
16
|
def post_init
|
17
|
-
@remote_addr =
|
17
|
+
@remote_addr = Rainbows.addr(@_io)
|
18
18
|
@env = {}
|
19
19
|
@hp = HttpParser.new
|
20
20
|
@state = :headers # [ :body [ :trailers ] ] :app_call :close
|
@@ -49,7 +49,7 @@ module Rainbows
|
|
49
49
|
write(EXPECT_100_RESPONSE)
|
50
50
|
@env.delete(HTTP_EXPECT)
|
51
51
|
end
|
52
|
-
@input =
|
52
|
+
@input = CapInput.new(len, self)
|
53
53
|
@hp.filter_body(@buf2 = "", @buf)
|
54
54
|
@input << @buf2
|
55
55
|
on_read("")
|
@@ -73,5 +73,45 @@ module Rainbows
|
|
73
73
|
handle_error(e)
|
74
74
|
end
|
75
75
|
|
76
|
+
class CapInput < Struct.new(:io, :client, :bytes_left)
|
77
|
+
MAX_BODY = Unicorn::Const::MAX_BODY
|
78
|
+
Util = Unicorn::Util
|
79
|
+
|
80
|
+
def self.err(client, msg)
|
81
|
+
client.write(Const::ERROR_413_RESPONSE)
|
82
|
+
client.quit
|
83
|
+
|
84
|
+
# zip back up the stack
|
85
|
+
raise IOError, msg, []
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.new(len, client)
|
89
|
+
max = Rainbows.max_bytes
|
90
|
+
if len
|
91
|
+
if max && (len > max)
|
92
|
+
err(client, "Content-Length too big: #{len} > #{max}")
|
93
|
+
end
|
94
|
+
len <= MAX_BODY ? StringIO.new("") : Util.tmpio
|
95
|
+
else
|
96
|
+
max ? super(Util.tmpio, client, max) : Util.tmpio
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def <<(buf)
|
101
|
+
if (self.bytes_left -= buf.size) < 0
|
102
|
+
io.close
|
103
|
+
CapInput.err(client, "chunked request body too big")
|
104
|
+
end
|
105
|
+
io << buf
|
106
|
+
end
|
107
|
+
|
108
|
+
def gets; io.gets; end
|
109
|
+
def each(&block); io.each(&block); end
|
110
|
+
def size; io.size; end
|
111
|
+
def rewind; io.rewind; end
|
112
|
+
def read(*args); io.read(*args); end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
76
116
|
end
|
77
117
|
end
|
@@ -8,11 +8,14 @@ module Rainbows
|
|
8
8
|
# Implements a basic single-threaded event model with
|
9
9
|
# {EventMachine}[http://rubyeventmachine.com/]. It is capable of
|
10
10
|
# handling thousands of simultaneous client connections, but with only
|
11
|
-
# a single-threaded app dispatch. It is suited for slow clients
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
11
|
+
# a single-threaded app dispatch. It is suited for slow clients,
|
12
|
+
# and can work with slow applications via asynchronous libraries such as
|
13
|
+
# {async_sinatra}[http://github.com/raggi/async_sinatra],
|
14
|
+
# {Cramp}[http://m.onkey.org/2010/1/7/introducing-cramp],
|
15
|
+
# and {rack-fiber_pool}[http://github.com/mperham/rack-fiber_pool].
|
16
|
+
#
|
17
|
+
# It does not require your Rack application to be thread-safe,
|
18
|
+
# reentrancy is only required for the DevFdResponse body
|
16
19
|
# generator.
|
17
20
|
#
|
18
21
|
# Compatibility: Whatever \EventMachine ~> 0.12.10 and Unicorn both
|
@@ -22,6 +25,20 @@ module Rainbows
|
|
22
25
|
# environment such as
|
23
26
|
# {async_sinatra}[http://github.com/raggi/async_sinatra].
|
24
27
|
#
|
28
|
+
# For a complete asynchronous framework,
|
29
|
+
# {Cramp}[http://m.onkey.org/2010/1/7/introducing-cramp] is fully
|
30
|
+
# supported when using this concurrency model.
|
31
|
+
#
|
32
|
+
# This model is fully-compatible with
|
33
|
+
# {rack-fiber_pool}[http://github.com/mperham/rack-fiber_pool]
|
34
|
+
# which allows each request to run inside its own \Fiber after
|
35
|
+
# all request processing is complete.
|
36
|
+
#
|
37
|
+
# Merb (and other frameworks/apps) supporting +deferred?+ execution as
|
38
|
+
# documented at http://brainspl.at/articles/2008/04/18/deferred-requests-with-merb-ebb-and-thin
|
39
|
+
# will also get the ability to conditionally defer request processing
|
40
|
+
# to a separate thread.
|
41
|
+
#
|
25
42
|
# This model does not implement as streaming "rack.input" which allows
|
26
43
|
# the Rack application to process data as it arrives. This means
|
27
44
|
# "rack.input" will be fully buffered in memory or to a temporary file
|
@@ -188,11 +205,37 @@ module Rainbows
|
|
188
205
|
end
|
189
206
|
end
|
190
207
|
|
208
|
+
# Middleware that will run the app dispatch in a separate thread.
|
209
|
+
# This middleware is automatically loaded by Rainbows! when using
|
210
|
+
# EventMachine and if the app responds to the +deferred?+ method.
|
211
|
+
class TryDefer < Struct.new(:app)
|
212
|
+
|
213
|
+
def initialize(app)
|
214
|
+
# the entire app becomes multithreaded, even the root (non-deferred)
|
215
|
+
# thread since any thread can share processes with others
|
216
|
+
Const::RACK_DEFAULTS['rack.multithread'] = true
|
217
|
+
super
|
218
|
+
end
|
219
|
+
|
220
|
+
def call(env)
|
221
|
+
if app.deferred?(env)
|
222
|
+
EM.defer(proc { catch(:async) { app.call(env) } },
|
223
|
+
env[EvCore::ASYNC_CALLBACK])
|
224
|
+
# all of the async/deferred stuff breaks Rack::Lint :<
|
225
|
+
nil
|
226
|
+
else
|
227
|
+
app.call(env)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
191
232
|
# runs inside each forked worker, this sits around and waits
|
192
233
|
# for connections and doesn't die until the parent dies (or is
|
193
234
|
# given a INT, QUIT, or TERM signal)
|
194
235
|
def worker_loop(worker)
|
195
236
|
init_worker_process(worker)
|
237
|
+
G.server.app.respond_to?(:deferred?) and
|
238
|
+
G.server.app = TryDefer[G.server.app]
|
196
239
|
|
197
240
|
# enable them both, should be non-fatal if not supported
|
198
241
|
EM.epoll
|
data/lib/rainbows/fiber/base.rb
CHANGED
@@ -57,15 +57,17 @@ module Rainbows
|
|
57
57
|
def schedule_sleepers
|
58
58
|
max = nil
|
59
59
|
now = Time.now
|
60
|
+
fibs = []
|
60
61
|
ZZ.delete_if { |fib, time|
|
61
62
|
if now >= time
|
62
|
-
fib
|
63
|
+
fibs << fib
|
63
64
|
now = Time.now
|
64
65
|
else
|
65
66
|
max = time
|
66
67
|
false
|
67
68
|
end
|
68
69
|
}
|
70
|
+
fibs.each { |fib| fib.resume }
|
69
71
|
max.nil? || max > (now + 1) ? 1 : max - now
|
70
72
|
end
|
71
73
|
|
@@ -76,7 +78,7 @@ module Rainbows
|
|
76
78
|
hp = HttpParser.new
|
77
79
|
env = {}
|
78
80
|
alive = true
|
79
|
-
remote_addr =
|
81
|
+
remote_addr = Rainbows.addr(io)
|
80
82
|
|
81
83
|
begin # loop
|
82
84
|
while ! hp.headers(env, buf)
|
data/lib/rainbows/fiber/rev.rb
CHANGED
@@ -80,7 +80,7 @@ module Rainbows::Fiber
|
|
80
80
|
hp = HttpParser.new
|
81
81
|
env = {}
|
82
82
|
alive = true
|
83
|
-
remote_addr =
|
83
|
+
remote_addr = Rainbows.addr(io)
|
84
84
|
|
85
85
|
begin # loop
|
86
86
|
buf << (client.read_timeout or return) until hp.headers(env, buf)
|
@@ -1,34 +1,35 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'time'
|
3
|
-
require 'rainbows'
|
4
3
|
|
5
4
|
module Rainbows
|
6
5
|
|
7
6
|
class HttpResponse < ::Unicorn::HttpResponse
|
8
7
|
|
9
|
-
def self.
|
10
|
-
status
|
11
|
-
|
12
|
-
if Array === out
|
13
|
-
status = CODES[status.to_i] || status
|
8
|
+
def self.header_string(status, headers, out)
|
9
|
+
status = CODES[status.to_i] || status
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
11
|
+
headers.each do |key, value|
|
12
|
+
next if %r{\AX-Rainbows-}i =~ key
|
13
|
+
next if SKIP.include?(key.downcase)
|
14
|
+
if value =~ /\n/
|
15
|
+
# avoiding blank, key-only cookies with /\n+/
|
16
|
+
out.concat(value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" })
|
17
|
+
else
|
18
|
+
out << "#{key}: #{value}\r\n"
|
24
19
|
end
|
25
|
-
|
26
|
-
socket.write("HTTP/1.1 #{status}\r\n" \
|
27
|
-
"Date: #{Time.now.httpdate}\r\n" \
|
28
|
-
"Status: #{status}\r\n" \
|
29
|
-
"#{out.join('')}\r\n")
|
30
20
|
end
|
31
21
|
|
22
|
+
"HTTP/1.1 #{status}\r\n" \
|
23
|
+
"Date: #{Time.now.httpdate}\r\n" \
|
24
|
+
"Status: #{status}\r\n" \
|
25
|
+
"#{out.join('')}\r\n"
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.write(socket, rack_response, out = [])
|
29
|
+
status, headers, body = rack_response
|
30
|
+
out.instance_of?(Array) and
|
31
|
+
socket.write(header_string(status, headers, out))
|
32
|
+
|
32
33
|
body.each { |chunk| socket.write(chunk) }
|
33
34
|
ensure
|
34
35
|
body.respond_to?(:close) and body.close
|
data/lib/rainbows/http_server.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
require 'rainbows'
|
3
2
|
module Rainbows
|
4
3
|
|
5
4
|
class HttpServer < ::Unicorn::HttpServer
|
@@ -61,11 +60,7 @@ module Rainbows
|
|
61
60
|
end
|
62
61
|
mod.setup if mod.respond_to?(:setup)
|
63
62
|
Const::RACK_DEFAULTS['rainbows.model'] = @use = model.to_sym
|
64
|
-
|
65
|
-
Const::RACK_DEFAULTS['rack.multithread'] = case model.to_s
|
66
|
-
when /Thread/, "EventMachineDefer"; true
|
67
|
-
else false
|
68
|
-
end
|
63
|
+
Const::RACK_DEFAULTS['rack.multithread'] = !!(model.to_s =~ /Thread/)
|
69
64
|
|
70
65
|
case @use
|
71
66
|
when :Rev, :EventMachine, :NeverBlock
|
@@ -86,6 +81,18 @@ module Rainbows
|
|
86
81
|
raise ArgumentError, "keepalive must be a non-negative Integer"
|
87
82
|
G.kato = nr
|
88
83
|
end
|
84
|
+
|
85
|
+
def client_max_body_size(nr)
|
86
|
+
err = "client_max_body_size must be nil or a non-negative Integer"
|
87
|
+
case nr
|
88
|
+
when nil
|
89
|
+
when Integer
|
90
|
+
nr >= 0 or raise ArgumentError, err
|
91
|
+
else
|
92
|
+
raise ArgumentError, err
|
93
|
+
end
|
94
|
+
Rainbows.max_bytes = nr
|
95
|
+
end
|
89
96
|
end
|
90
97
|
|
91
98
|
end
|