rainbows 0.94.0 → 0.95.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|