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
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
|