rainbows 0.94.0 → 0.95.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 -0
- data/.manifest +18 -0
- data/ChangeLog +394 -226
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +6 -4
- data/NEWS +18 -0
- data/README +13 -5
- data/Static_Files +71 -0
- data/TODO +12 -0
- data/Test_Suite +1 -1
- data/bin/rainbows +1 -4
- data/lib/rainbows/actor_spawn.rb +1 -1
- data/lib/rainbows/app_pool.rb +1 -1
- data/lib/rainbows/base.rb +79 -89
- data/lib/rainbows/byte_slice.rb +17 -0
- data/lib/rainbows/configurator.rb +46 -0
- data/lib/rainbows/const.rb +2 -2
- data/lib/rainbows/dev_fd_response.rb +52 -44
- data/lib/rainbows/error.rb +1 -0
- data/lib/rainbows/ev_core.rb +3 -2
- data/lib/rainbows/event_machine.rb +26 -24
- data/lib/rainbows/fiber/base.rb +30 -40
- data/lib/rainbows/fiber/body.rb +34 -0
- data/lib/rainbows/fiber/io.rb +28 -8
- data/lib/rainbows/fiber/queue.rb +1 -0
- data/lib/rainbows/fiber/rev.rb +4 -2
- data/lib/rainbows/fiber.rb +1 -0
- data/lib/rainbows/fiber_pool.rb +2 -2
- data/lib/rainbows/fiber_spawn.rb +2 -2
- data/lib/rainbows/http_response.rb +20 -31
- data/lib/rainbows/http_server.rb +3 -4
- data/lib/rainbows/max_body.rb +1 -0
- data/lib/rainbows/never_block/event_machine.rb +2 -0
- data/lib/rainbows/never_block.rb +5 -4
- data/lib/rainbows/queue_pool.rb +1 -0
- data/lib/rainbows/response/body.rb +119 -0
- data/lib/rainbows/response.rb +43 -0
- data/lib/rainbows/rev/client.rb +79 -9
- data/lib/rainbows/rev/core.rb +4 -0
- data/lib/rainbows/rev/deferred_response.rb +1 -44
- data/lib/rainbows/rev/heartbeat.rb +1 -0
- data/lib/rainbows/rev/master.rb +1 -0
- data/lib/rainbows/rev/sendfile.rb +26 -0
- data/lib/rainbows/rev/thread.rb +2 -1
- data/lib/rainbows/rev.rb +2 -0
- data/lib/rainbows/rev_fiber_spawn.rb +3 -1
- data/lib/rainbows/rev_thread_pool.rb +7 -5
- data/lib/rainbows/rev_thread_spawn.rb +2 -2
- data/lib/rainbows/revactor.rb +146 -146
- data/lib/rainbows/sendfile.rb +10 -21
- data/lib/rainbows/server_token.rb +39 -0
- data/lib/rainbows/stream_file.rb +14 -0
- data/lib/rainbows/tee_input.rb +1 -0
- data/lib/rainbows/thread_pool.rb +12 -7
- data/lib/rainbows/thread_spawn.rb +2 -3
- data/lib/rainbows/writer_thread_pool.rb +13 -7
- data/lib/rainbows/writer_thread_spawn.rb +12 -9
- data/lib/rainbows.rb +16 -45
- data/rainbows.gemspec +8 -8
- data/t/.gitignore +1 -1
- data/t/GNUmakefile +26 -16
- data/t/README +1 -1
- data/t/async-response-no-autochunk.ru +0 -1
- data/t/async-response.ru +0 -1
- data/t/cramp/rainsocket.ru +26 -0
- data/t/fork-sleep.ru +0 -1
- data/t/my-tap-lib.sh +3 -2
- data/t/simple-http_ActorSpawn.ru +9 -0
- data/t/t0009-broken-app.sh +1 -1
- data/t/t0009.ru +0 -1
- data/t/t0011-close-on-exec-set.sh +1 -1
- data/t/t0015-working_directory.sh +56 -0
- data/t/t0016-onenine-encoding-is-tricky.sh +28 -0
- data/t/t0016.rb +15 -0
- data/t/t0020-large-sendfile-response.sh +141 -0
- data/t/t0300-async_sinatra.sh +0 -6
- data/t/t0501-cramp-rainsocket.sh +38 -0
- data/t/t9001-sendfile-to-path.sh +5 -4
- data/t/t9002-server-token.sh +37 -0
- data/t/t9002.ru +4 -0
- data/t/test-lib.sh +1 -1
- data/t/test_isolate.rb +14 -11
- metadata +87 -18
data/GIT-VERSION-FILE
CHANGED
@@ -1 +1 @@
|
|
1
|
-
GIT_VERSION = 0.
|
1
|
+
GIT_VERSION = 0.95.0
|
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# use GNU Make to run tests in parallel, and without depending on RubyGems
|
2
2
|
all::
|
3
|
+
MRI = ruby
|
3
4
|
RUBY = ruby
|
4
5
|
RAKE = rake
|
5
6
|
RSYNC = rsync
|
@@ -15,6 +16,7 @@ endif
|
|
15
16
|
ifeq ($(RUBY_VERSION),)
|
16
17
|
RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
|
17
18
|
endif
|
19
|
+
RUBY_ENGINE := $(shell $(RUBY) -e 'puts((RUBY_ENGINE rescue "ruby"))')
|
18
20
|
|
19
21
|
base_bins := rainbows
|
20
22
|
bins := $(addprefix bin/, $(base_bins))
|
@@ -60,7 +62,7 @@ NEWS: GIT-VERSION-FILE
|
|
60
62
|
$(RAKE) -s news_rdoc > $@+
|
61
63
|
mv $@+ $@
|
62
64
|
|
63
|
-
SINCE = 0.
|
65
|
+
SINCE = 0.94.0
|
64
66
|
ChangeLog: LOG_VERSION = \
|
65
67
|
$(shell git rev-parse -q "$(GIT_VERSION)" >/dev/null 2>&1 && \
|
66
68
|
echo $(GIT_VERSION) || git describe)
|
@@ -91,16 +93,16 @@ doc: .document NEWS ChangeLog
|
|
91
93
|
< $${i}_1.html > tmp && mv tmp $${i}_1.html; \
|
92
94
|
ln $${i}_1.html $${i}.1.html; \
|
93
95
|
done
|
94
|
-
$(
|
96
|
+
$(MRI) -i -p -e \
|
95
97
|
'$$_.gsub!("</title>",%q{\&$(call atom,$(cgit_atom))})' \
|
96
98
|
doc/ChangeLog.html
|
97
|
-
$(
|
99
|
+
$(MRI) -i -p -e \
|
98
100
|
'$$_.gsub!("</title>",%q{\&$(call atom,$(news_atom))})' \
|
99
101
|
doc/NEWS.html doc/README.html
|
100
102
|
$(RAKE) -s news_atom > doc/NEWS.atom.xml
|
101
103
|
cd doc && ln README.html tmp && mv tmp index.html
|
102
104
|
$(MAKE) -C Documentation comparison.html
|
103
|
-
$(
|
105
|
+
$(MRI) -i -p -e \
|
104
106
|
'$$_.gsub!(/INCLUDE/){File.read("Documentation/comparison.html")}' \
|
105
107
|
doc/Summary.html
|
106
108
|
cat Documentation/comparison.css >> doc/rdoc.css
|
data/NEWS
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
=== 0.95.0 / 2010-07-10 08:45 UTC
|
2
|
+
|
3
|
+
In addition to the 1.9-only IO.copy_stream, the new sendfile
|
4
|
+
1.0.0 gem may optionally be used with most concurrency models
|
5
|
+
(even under 1.8).
|
6
|
+
|
7
|
+
See http://rainbows.rubyforge.org/Static_Files.html for more info
|
8
|
+
|
9
|
+
Other changes:
|
10
|
+
|
11
|
+
* 1.9 encoding bugfix for (Rev)FiberSpawn and FiberPool
|
12
|
+
* fixed potential rack.input corruption with Revactor
|
13
|
+
* ThreadPool graceful shutdown no longer blocks until timeout
|
14
|
+
* optional ServerToken middleware for to display Server: header
|
15
|
+
* Dependencies bumped to Rack 1.1+ and Unicorn 1.1.0+
|
16
|
+
* numerous internal cleanups, small bugfixes and speedups
|
17
|
+
* more concise website oriented at users
|
18
|
+
|
1
19
|
=== 0.94.0 / 2010-06-04 08:42 UTC
|
2
20
|
|
3
21
|
This release fixes corrupted large response bodies for Ruby 1.8
|
data/README
CHANGED
@@ -2,9 +2,17 @@
|
|
2
2
|
|
3
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
|
-
request/response times and/or slow clients.
|
6
|
-
|
7
|
-
|
5
|
+
request/response times and/or slow clients.
|
6
|
+
|
7
|
+
For Rack applications not heavily bound by slow external network
|
8
|
+
dependencies, consider Unicorn instead as it simpler and easier to
|
9
|
+
debug.
|
10
|
+
|
11
|
+
If you're on a small system, or write extremely tight and reliable code
|
12
|
+
and don't want multiple worker processes, check out
|
13
|
+
{Zbatery}[http://zbatery.bogomip.org/], too. Zbatery can use all the
|
14
|
+
crazy network concurrency options of \Rainbows! in a single worker
|
15
|
+
process.
|
8
16
|
|
9
17
|
== \Rainbows! is about Diversity
|
10
18
|
|
@@ -125,8 +133,8 @@ config file:
|
|
125
133
|
worker_connections 100
|
126
134
|
end
|
127
135
|
|
128
|
-
See the {Rainbows! configuration}[link:Rainbows.html
|
129
|
-
{documentation}[link:Rainbows.html
|
136
|
+
See the {Rainbows! configuration}[link:Rainbows/Configurator.html]
|
137
|
+
{documentation}[link:Rainbows/Configurator.html]
|
130
138
|
for more details.
|
131
139
|
|
132
140
|
== Development
|
data/Static_Files
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
= Static file serving with \Rainbows!
|
2
|
+
|
3
|
+
While Ruby application servers aren't traditionally used to serve static
|
4
|
+
files, it'll be fun for us to see how far we can go with \Rainbows!
|
5
|
+
|
6
|
+
We aren't delusional enough (yet :) to compete with C-based servers like
|
7
|
+
nginx or lighttpd in terms of raw performance, but wouldn't it be nice
|
8
|
+
to simplify your deployments and only deploy one server?
|
9
|
+
|
10
|
+
== {sendfile}[http://rubygems.org/gems/sendfile] RubyGem
|
11
|
+
|
12
|
+
To enable the "sendfile" gem, just make sure you have 1.0.0 or later and
|
13
|
+
"require" it in your Rainbows!/Unicorn config file (not your Rack
|
14
|
+
config.ru):
|
15
|
+
|
16
|
+
require 'sendfile' # that's it! nothing else to do
|
17
|
+
|
18
|
+
# the rest of you Rainbows! config goes below:
|
19
|
+
worker_processes 4
|
20
|
+
stderr_path "/var/log/app/rainbows.err.log"
|
21
|
+
Rainbows! do
|
22
|
+
use :RevFiberSpawn
|
23
|
+
worker_connections 100
|
24
|
+
end
|
25
|
+
|
26
|
+
The sendfile gem is works for all of our concurrency models except
|
27
|
+
Revactor, NeverBlock and EventMachine (see below).
|
28
|
+
|
29
|
+
The sendfile gem is less buggy than current (Ruby 1.9.2-rc1)
|
30
|
+
IO.copy_stream and supports FreeBSD and Solaris in addition to Linux.
|
31
|
+
This RubyGem also works under Ruby 1.8 (even with threads) and should
|
32
|
+
work with rubinius.git, too.
|
33
|
+
|
34
|
+
\Rainbows! supports the sendfile gem since v0.95.0
|
35
|
+
|
36
|
+
== IO.copy_stream (Ruby 1.9 only)
|
37
|
+
|
38
|
+
Users of pure-Ruby Thread-based models ThreadPool, ThreadSpawn, and
|
39
|
+
their Writer* variants use the core IO.copy_stream method under Ruby
|
40
|
+
1.9. IO.copy_stream uses sendfile() under Linux, and a pread()/write()
|
41
|
+
loop (implemented in C) on other systems.
|
42
|
+
|
43
|
+
IO.copy_stream under Linux with Ruby 1.9.2-rc1 (and before) is also
|
44
|
+
subject to hanging indefinitely when a client disconnected prematurely.
|
45
|
+
This issue is fixed in Ruby trunk (July 2010) and will be in the next
|
46
|
+
Ruby 1.9.2 release.
|
47
|
+
|
48
|
+
\Rainbows! supports IO.copy_stream since v0.93.0
|
49
|
+
|
50
|
+
== EventMachine FileStreamer
|
51
|
+
|
52
|
+
EventMachine and NeverBlock users automatically take advantage of
|
53
|
+
the mmap()-based FileStreamer class distributed with EventMachine.
|
54
|
+
|
55
|
+
\Rainbows! supports EventMachine FileStreamer since v0.4.0
|
56
|
+
|
57
|
+
== Performance
|
58
|
+
|
59
|
+
With large files and high-throughput clients, there should be little
|
60
|
+
performance difference compared to optimal C implementation such as
|
61
|
+
nginx and lighttpd. Ruby runtime overhead matters more when serving
|
62
|
+
slower clients and smaller files.
|
63
|
+
|
64
|
+
== The Future...
|
65
|
+
|
66
|
+
Future releases of \Rainbows! will have byte-range support to serve
|
67
|
+
partial and multipart responses, too. We'll also support an open file
|
68
|
+
cache (similar to nginx) which allows us to reuse open file descriptors.
|
69
|
+
|
70
|
+
Under Linux, we'll support the splice(2) system call for zero-copy
|
71
|
+
proxying {io_splice}[http://bogomips.org/ruby_io_splice/], too.
|
data/TODO
CHANGED
@@ -3,9 +3,21 @@
|
|
3
3
|
We're lazy and pick the easy items to do first, then the ones people
|
4
4
|
care about.
|
5
5
|
|
6
|
+
* documentation improvements
|
7
|
+
|
6
8
|
* Split out NeverBlock into NeverBlockEventMachine and NeverBlockReactor
|
7
9
|
NeverBlock will default to one of them (depending on NB upstream).
|
8
10
|
|
11
|
+
* allow _OPTIONAL_ splice(2) with DevFdResponse under Linux
|
12
|
+
(splice is very broken under some older kernels)
|
13
|
+
|
14
|
+
* use IO#sendfile_nonblock for EventMachine/Revactor/NeverBlock
|
15
|
+
|
16
|
+
* Open file cache Rack app/middleware (idea from nginx), since sendfile
|
17
|
+
(and IO.copy_stream) allows pread(2)-style offsets
|
18
|
+
|
19
|
+
* Byte range responses for static files + sendfile
|
20
|
+
|
9
21
|
* Improve test suite coverage. We won't waste cycles with puny
|
10
22
|
unit tests, only integration tests that exercise externally
|
11
23
|
visible parts.
|
data/Test_Suite
CHANGED
@@ -12,7 +12,7 @@ easily portable to non-Ruby web servers.
|
|
12
12
|
== Requirements
|
13
13
|
|
14
14
|
* {Ruby 1.8 or 1.9}[http://www.ruby-lang.org/] (duh!)
|
15
|
-
* {isolate ~> 2.0
|
15
|
+
* {isolate ~> 2.1.0}[http://github.com/jbarnette/isolate] - for dependencies
|
16
16
|
* {GNU make}[http://www.gnu.org/software/make/]
|
17
17
|
* {socat}[http://www.dest-unreach.org/socat/]
|
18
18
|
* {curl >= 7.18.0}[http://curl.haxx.se/]
|
data/bin/rainbows
CHANGED
@@ -110,10 +110,7 @@ opts = OptionParser.new("", 24, ' ') do |opts|
|
|
110
110
|
opts.parse! ARGV
|
111
111
|
end
|
112
112
|
|
113
|
-
|
114
|
-
abort "configuration file #{config} not found" unless File.exist?(config)
|
115
|
-
|
116
|
-
app = Unicorn.builder(config, opts)
|
113
|
+
app = Unicorn.builder(ARGV[0] || 'config.ru', opts)
|
117
114
|
listeners << "#{host}:#{port}" if set_listener
|
118
115
|
|
119
116
|
if $DEBUG
|
data/lib/rainbows/actor_spawn.rb
CHANGED
@@ -20,7 +20,7 @@ module Rainbows
|
|
20
20
|
# runs inside each forked worker, this sits around and waits
|
21
21
|
# for connections and doesn't die until the parent dies (or is
|
22
22
|
# given a INT, QUIT, or TERM signal)
|
23
|
-
def worker_loop(worker)
|
23
|
+
def worker_loop(worker) # :nodoc:
|
24
24
|
Const::RACK_DEFAULTS["rack.multithread"] = true # :(
|
25
25
|
init_worker_process(worker)
|
26
26
|
accept_loop(Actor)
|
data/lib/rainbows/app_pool.rb
CHANGED
@@ -82,7 +82,7 @@ module Rainbows
|
|
82
82
|
end
|
83
83
|
|
84
84
|
# Rack application endpoint, +env+ is the Rack environment
|
85
|
-
def call(env)
|
85
|
+
def call(env) # :nodoc:
|
86
86
|
|
87
87
|
# we have to do this check at call time (and not initialize)
|
88
88
|
# because of preload_app=true and models being changeable with SIGHUP
|
data/lib/rainbows/base.rb
CHANGED
@@ -1,106 +1,96 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
+
require 'rainbows/tee_input'
|
2
3
|
|
3
|
-
|
4
|
+
# base class for Rainbows concurrency models, this is currently used by
|
5
|
+
# ThreadSpawn and ThreadPool models. Base is also its own
|
6
|
+
# (non-)concurrency model which is basically Unicorn-with-keepalive, and
|
7
|
+
# not intended for production use, as keepalive with a pure prefork
|
8
|
+
# concurrency model is extremely expensive.
|
9
|
+
module Rainbows::Base
|
4
10
|
|
5
|
-
#
|
6
|
-
|
7
|
-
|
11
|
+
# :stopdoc:
|
12
|
+
include Rainbows::Const
|
13
|
+
include Rainbows::Response
|
8
14
|
|
9
|
-
|
10
|
-
|
11
|
-
|
15
|
+
# shortcuts...
|
16
|
+
G = Rainbows::G
|
17
|
+
NULL_IO = Unicorn::HttpRequest::NULL_IO
|
18
|
+
TeeInput = Rainbows::TeeInput
|
19
|
+
HttpParser = Unicorn::HttpParser
|
12
20
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
21
|
+
# this method is called by all current concurrency models
|
22
|
+
def init_worker_process(worker) # :nodoc:
|
23
|
+
super(worker)
|
24
|
+
Rainbows::Response.setup(self.class)
|
25
|
+
Rainbows::MaxBody.setup
|
26
|
+
G.tmp = worker.tmp
|
17
27
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
# we're don't use the self-pipe mechanism in the Rainbows! worker
|
25
|
-
# since we don't defer reopening logs
|
26
|
-
HttpServer::SELF_PIPE.each { |x| x.close }.clear
|
27
|
-
trap(:USR1) { reopen_worker_logs(worker.nr) }
|
28
|
-
trap(:QUIT) { G.quit! }
|
29
|
-
[:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
|
30
|
-
logger.info "Rainbows! #@use worker_connections=#@worker_connections"
|
28
|
+
# avoid spurious wakeups and blocking-accept() with 1.8 green threads
|
29
|
+
if ! defined?(RUBY_ENGINE) && RUBY_VERSION.to_f < 1.9
|
30
|
+
require "io/nonblock"
|
31
|
+
Rainbows::HttpServer::LISTENERS.each { |l| l.nonblock = true }
|
31
32
|
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
52
|
-
module_function :write_body
|
34
|
+
# we're don't use the self-pipe mechanism in the Rainbows! worker
|
35
|
+
# since we don't defer reopening logs
|
36
|
+
Rainbows::HttpServer::SELF_PIPE.each { |x| x.close }.clear
|
37
|
+
trap(:USR1) { reopen_worker_logs(worker.nr) }
|
38
|
+
trap(:QUIT) { G.quit! }
|
39
|
+
[:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
|
40
|
+
logger.info "Rainbows! #@use worker_connections=#@worker_connections"
|
41
|
+
end
|
53
42
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
# Base, ThreadSpawn, ThreadPool
|
58
|
-
def process_client(client)
|
59
|
-
buf = client.readpartial(CHUNK_SIZE) # accept filters protect us here
|
60
|
-
hp = HttpParser.new
|
61
|
-
env = {}
|
62
|
-
alive = true
|
63
|
-
remote_addr = Rainbows.addr(client)
|
43
|
+
def wait_headers_readable(client) # :nodoc:
|
44
|
+
IO.select([client], nil, nil, G.kato)
|
45
|
+
end
|
64
46
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
47
|
+
# once a client is accepted, it is processed in its entirety here
|
48
|
+
# in 3 easy steps: read request, call app, write app response
|
49
|
+
# this is used by synchronous concurrency models
|
50
|
+
# Base, ThreadSpawn, ThreadPool
|
51
|
+
def process_client(client) # :nodoc:
|
52
|
+
buf = client.readpartial(CHUNK_SIZE) # accept filters protect us here
|
53
|
+
hp = HttpParser.new
|
54
|
+
env = {}
|
55
|
+
alive = true
|
56
|
+
remote_addr = Rainbows.addr(client)
|
70
57
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
58
|
+
begin # loop
|
59
|
+
until hp.headers(env, buf)
|
60
|
+
wait_headers_readable(client) or return
|
61
|
+
buf << client.readpartial(CHUNK_SIZE)
|
62
|
+
end
|
76
63
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
64
|
+
env[CLIENT_IO] = client
|
65
|
+
env[RACK_INPUT] = 0 == hp.content_length ?
|
66
|
+
NULL_IO : TeeInput.new(client, env, hp, buf)
|
67
|
+
env[REMOTE_ADDR] = remote_addr
|
68
|
+
response = app.call(env.update(RACK_DEFAULTS))
|
82
69
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
write_body(client, body)
|
89
|
-
end while alive and hp.reset.nil? and env.clear
|
90
|
-
# if we get any error, try to write something back to the client
|
91
|
-
# assuming we haven't closed the socket, but don't get hung up
|
92
|
-
# if the socket is already closed or broken. We'll always ensure
|
93
|
-
# the socket is closed at the end of this function
|
94
|
-
rescue => e
|
95
|
-
Error.write(client, e)
|
96
|
-
ensure
|
97
|
-
client.close unless client.closed?
|
98
|
-
end
|
70
|
+
if 100 == response[0].to_i
|
71
|
+
client.write(EXPECT_100_RESPONSE)
|
72
|
+
env.delete(HTTP_EXPECT)
|
73
|
+
response = app.call(env)
|
74
|
+
end
|
99
75
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
end
|
76
|
+
alive = hp.keepalive? && G.alive
|
77
|
+
out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
|
78
|
+
write_response(client, response, out)
|
79
|
+
end while alive and hp.reset.nil? and env.clear
|
80
|
+
# if we get any error, try to write something back to the client
|
81
|
+
# assuming we haven't closed the socket, but don't get hung up
|
82
|
+
# if the socket is already closed or broken. We'll always ensure
|
83
|
+
# the socket is closed at the end of this function
|
84
|
+
rescue => e
|
85
|
+
Rainbows::Error.write(client, e)
|
86
|
+
ensure
|
87
|
+
client.close unless client.closed?
|
88
|
+
end
|
104
89
|
|
90
|
+
def self.included(klass) # :nodoc:
|
91
|
+
klass.const_set :LISTENERS, Rainbows::HttpServer::LISTENERS
|
92
|
+
klass.const_set :G, Rainbows::G
|
105
93
|
end
|
94
|
+
|
95
|
+
# :startdoc:
|
106
96
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
module Rainbows::ByteSlice
|
4
|
+
if String.method_defined?(:encoding)
|
5
|
+
def byte_slice(buf, range)
|
6
|
+
if buf.encoding != Encoding::BINARY
|
7
|
+
buf.dup.force_encoding(Encoding::BINARY)[range]
|
8
|
+
else
|
9
|
+
buf[range]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
else
|
13
|
+
def byte_slice(buf, range)
|
14
|
+
buf[range]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
module Rainbows
|
3
|
+
|
4
|
+
# This module adds \Rainbows! to the
|
5
|
+
# {Unicorn::Configurator}[http://unicorn.bogomips.org/Unicorn/Configurator.html]
|
6
|
+
module Configurator
|
7
|
+
|
8
|
+
# configures \Rainbows! with a given concurrency model to +use+ and
|
9
|
+
# a +worker_connections+ upper-bound. This method may be called
|
10
|
+
# inside a Unicorn/\Rainbows! configuration file:
|
11
|
+
#
|
12
|
+
# Rainbows! do
|
13
|
+
# use :ThreadSpawn # concurrency model to use
|
14
|
+
# worker_connections 400
|
15
|
+
# keepalive_timeout 0 # zero disables keepalives entirely
|
16
|
+
# client_max_body_size 5*1024*1024 # 5 megabytes
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # the rest of the Unicorn configuration
|
20
|
+
# worker_processes 8
|
21
|
+
#
|
22
|
+
# See the documentation for the respective Revactor, ThreadSpawn,
|
23
|
+
# and ThreadPool classes for descriptions and recommendations for
|
24
|
+
# each of them. The total number of clients we're able to serve is
|
25
|
+
# +worker_processes+ * +worker_connections+, so in the above example
|
26
|
+
# we can serve 8 * 400 = 3200 clients concurrently.
|
27
|
+
#
|
28
|
+
# The default is +keepalive_timeout+ is 5 seconds, which should be
|
29
|
+
# enough under most conditions for browsers to render the page and
|
30
|
+
# start retrieving extra elements for. Increasing this beyond 5
|
31
|
+
# seconds is not recommended. Zero disables keepalive entirely
|
32
|
+
# (but pipelining fully-formed requests is still works).
|
33
|
+
#
|
34
|
+
# The default +client_max_body_size+ is 1 megabyte (1024 * 1024 bytes),
|
35
|
+
# setting this to +nil+ will disable body size checks and allow any
|
36
|
+
# size to be specified.
|
37
|
+
def Rainbows!(&block)
|
38
|
+
block_given? or raise ArgumentError, "Rainbows! requires a block"
|
39
|
+
HttpServer.setup(block)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# inject the Rainbows! method into Unicorn::Configurator
|
46
|
+
Unicorn::Configurator.class_eval { include Rainbows::Configurator }
|
data/lib/rainbows/const.rb
CHANGED
@@ -1,56 +1,64 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
|
3
|
-
|
3
|
+
# Rack response middleware wrapping any IO-like object with an
|
4
|
+
# OS-level file descriptor associated with it. May also be used to
|
5
|
+
# create responses from integer file descriptors or existing +IO+
|
6
|
+
# objects. This may be used in conjunction with the #to_path method
|
7
|
+
# on servers that support it to pass arbitrary file descriptors into
|
8
|
+
# the HTTP response without additional open(2) syscalls
|
9
|
+
#
|
10
|
+
# This middleware is currently a no-op for Rubinius, as it lacks
|
11
|
+
# IO.copy_stream in 1.9 and also due to a bug here:
|
12
|
+
# http://github.com/evanphx/rubinius/issues/379
|
4
13
|
|
5
|
-
|
6
|
-
# OS-level file descriptor associated with it. May also be used to
|
7
|
-
# create responses from integer file descriptors or existing +IO+
|
8
|
-
# objects. This may be used in conjunction with the #to_path method
|
9
|
-
# on servers that support it to pass arbitrary file descriptors into
|
10
|
-
# the HTTP response without additional open(2) syscalls
|
14
|
+
class Rainbows::DevFdResponse < Struct.new(:app)
|
11
15
|
|
12
|
-
|
13
|
-
|
16
|
+
# :stopdoc:
|
17
|
+
#
|
18
|
+
# make this a no-op under Rubinius, it's pointless anyways
|
19
|
+
# since Rubinius doesn't have IO.copy_stream
|
20
|
+
def self.new(app)
|
21
|
+
app
|
22
|
+
end if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
|
23
|
+
include Rack::Utils
|
14
24
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
25
|
+
# Rack middleware entry point, we'll just pass through responses
|
26
|
+
# unless they respond to +to_io+ or +to_path+
|
27
|
+
def call(env)
|
28
|
+
status, headers, body = response = app.call(env)
|
19
29
|
|
20
|
-
|
21
|
-
|
30
|
+
# totally uninteresting to us if there's no body
|
31
|
+
return response if STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
22
32
|
|
23
|
-
|
24
|
-
|
25
|
-
|
33
|
+
io = body.to_io if body.respond_to?(:to_io)
|
34
|
+
io ||= File.open(body.to_path, 'rb') if body.respond_to?(:to_path)
|
35
|
+
return response if io.nil?
|
26
36
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
headers = HeaderHash.new(headers)
|
38
|
+
st = io.stat
|
39
|
+
if st.file?
|
40
|
+
headers['Content-Length'] ||= st.size.to_s
|
41
|
+
headers.delete('Transfer-Encoding')
|
42
|
+
elsif st.pipe? || st.socket? # epoll-able things
|
43
|
+
if env['rainbows.autochunk']
|
44
|
+
headers['Transfer-Encoding'] = 'chunked'
|
45
|
+
headers.delete('Content-Length')
|
46
|
+
else
|
47
|
+
headers['X-Rainbows-Autochunk'] = 'no'
|
48
|
+
end
|
39
49
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
45
|
-
else # unlikely, char/block device file, directory, ...
|
46
|
-
return response
|
50
|
+
# we need to make sure our pipe output is Fiber-compatible
|
51
|
+
case env["rainbows.model"]
|
52
|
+
when :FiberSpawn, :FiberPool, :RevFiberSpawn
|
53
|
+
return [ status, headers, Rainbows::Fiber::IO.new(io,::Fiber.current) ]
|
47
54
|
end
|
48
|
-
|
49
|
-
|
50
|
-
resp.to_io = io
|
51
|
-
[ status, headers.to_hash, resp ]
|
55
|
+
else # unlikely, char/block device file, directory, ...
|
56
|
+
return response
|
52
57
|
end
|
58
|
+
[ status, headers, Body.new(io, "/dev/fd/#{io.fileno}") ]
|
59
|
+
end
|
53
60
|
|
61
|
+
class Body < Struct.new(:to_io, :to_path)
|
54
62
|
# called by the webserver or other middlewares if they can't
|
55
63
|
# handle #to_path
|
56
64
|
def each(&block)
|
@@ -70,6 +78,6 @@ module Rainbows
|
|
70
78
|
rescue IOError # could've been IO::new()'ed and closed
|
71
79
|
end
|
72
80
|
end
|
73
|
-
|
74
|
-
|
75
|
-
end
|
81
|
+
end
|
82
|
+
#:startdoc:
|
83
|
+
end # class
|
data/lib/rainbows/error.rb
CHANGED
data/lib/rainbows/ev_core.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
|
2
|
+
# :enddoc:
|
3
3
|
module Rainbows
|
4
4
|
|
5
5
|
# base module for evented models like Rev and EventMachine
|
@@ -7,6 +7,7 @@ module Rainbows
|
|
7
7
|
include Unicorn
|
8
8
|
include Rainbows::Const
|
9
9
|
G = Rainbows::G
|
10
|
+
NULL_IO = Unicorn::HttpRequest::NULL_IO
|
10
11
|
|
11
12
|
# Apps may return this Rack response: AsyncResponse = [ -1, {}, [] ]
|
12
13
|
ASYNC_CALLBACK = "async.callback".freeze
|
@@ -40,7 +41,7 @@ module Rainbows
|
|
40
41
|
@state = :body
|
41
42
|
len = @hp.content_length
|
42
43
|
if len == 0
|
43
|
-
@input =
|
44
|
+
@input = NULL_IO
|
44
45
|
app_call # common case
|
45
46
|
else # nil or len > 0
|
46
47
|
# since we don't do streaming input, we have no choice but
|