rainbows 2.1.0 → 3.0.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/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +2 -3
- data/Rakefile +5 -2
- data/lib/rainbows.rb +72 -54
- data/lib/rainbows/base.rb +7 -9
- data/lib/rainbows/client.rb +25 -4
- data/lib/rainbows/const.rb +1 -1
- data/lib/rainbows/coolio.rb +6 -3
- data/lib/rainbows/coolio/client.rb +78 -57
- data/lib/rainbows/coolio/core.rb +1 -4
- data/lib/rainbows/coolio/heartbeat.rb +2 -3
- data/lib/rainbows/coolio/master.rb +3 -2
- data/lib/rainbows/coolio/{deferred_chunk_response.rb → response_chunk_pipe.rb} +1 -2
- data/lib/rainbows/coolio/{deferred_response.rb → response_pipe.rb} +1 -1
- data/lib/rainbows/coolio/thread_client.rb +4 -6
- data/lib/rainbows/coolio_fiber_spawn.rb +1 -1
- data/lib/rainbows/coolio_thread_pool.rb +1 -1
- data/lib/rainbows/coolio_thread_pool/watcher.rb +2 -4
- data/lib/rainbows/coolio_thread_spawn.rb +1 -1
- data/lib/rainbows/dev_fd_response.rb +2 -2
- data/lib/rainbows/error.rb +5 -7
- data/lib/rainbows/ev_core.rb +20 -7
- data/lib/rainbows/event_machine.rb +6 -5
- data/lib/rainbows/event_machine/client.rb +46 -53
- data/lib/rainbows/event_machine/response_pipe.rb +2 -3
- data/lib/rainbows/fiber/base.rb +5 -5
- data/lib/rainbows/fiber/body.rb +4 -13
- data/lib/rainbows/fiber/coolio/heartbeat.rb +1 -3
- data/lib/rainbows/fiber/coolio/server.rb +4 -7
- data/lib/rainbows/fiber_pool.rb +1 -1
- data/lib/rainbows/fiber_spawn.rb +2 -2
- data/lib/rainbows/http_parser.rb +12 -0
- data/lib/rainbows/http_server.rb +5 -7
- data/lib/rainbows/max_body.rb +2 -2
- data/lib/rainbows/never_block/core.rb +1 -1
- data/lib/rainbows/process_client.rb +15 -29
- data/lib/rainbows/queue_pool.rb +1 -3
- data/lib/rainbows/rack_input.rb +3 -3
- data/lib/rainbows/response.rb +164 -38
- data/lib/rainbows/revactor.rb +5 -65
- data/lib/rainbows/revactor/client.rb +60 -0
- data/lib/rainbows/revactor/{body.rb → client/methods.rb} +14 -14
- data/lib/rainbows/revactor/{tee_socket.rb → client/tee_socket.rb} +1 -1
- data/lib/rainbows/sendfile.rb +1 -2
- data/lib/rainbows/server_token.rb +1 -1
- data/lib/rainbows/thread_pool.rb +9 -9
- data/lib/rainbows/thread_spawn.rb +7 -6
- data/lib/rainbows/thread_timeout.rb +1 -1
- data/lib/rainbows/writer_thread_pool.rb +9 -25
- data/lib/rainbows/writer_thread_pool/client.rb +44 -1
- data/lib/rainbows/writer_thread_spawn.rb +2 -11
- data/lib/rainbows/writer_thread_spawn/client.rb +53 -13
- data/rainbows.gemspec +3 -12
- data/t/async_chunk_app.ru +62 -0
- data/t/byte-range-common.sh +142 -0
- data/t/t0022-copy_stream-byte-range.sh +2 -111
- data/t/t0023-sendfile-byte-range.sh +2 -32
- data/t/t0025-write-on-close.sh +23 -0
- data/t/t0040-keepalive_requests-setting.sh +0 -5
- data/t/t0402-async-keepalive.sh +146 -0
- data/t/t0500-cramp-streaming.sh +2 -0
- data/t/t0501-cramp-rainsocket.sh +2 -0
- data/t/t9000-rack-app-pool.sh +1 -1
- data/t/test_isolate.rb +5 -10
- data/t/test_isolate_cramp.rb +26 -0
- data/t/write-on-close.ru +11 -0
- metadata +33 -30
- data/lib/rainbows/coolio/sendfile.rb +0 -17
- data/lib/rainbows/response/body.rb +0 -127
- data/lib/rainbows/response/range.rb +0 -34
- data/lib/rainbows/timed_read.rb +0 -28
data/lib/rainbows/revactor.rb
CHANGED
@@ -19,77 +19,17 @@ Revactor::VERSION >= '0.1.5' or abort 'revactor 0.1.5 is required'
|
|
19
19
|
# \Revactor library as well, to take advantage of the networking
|
20
20
|
# concurrency features this model provides.
|
21
21
|
module Rainbows::Revactor
|
22
|
-
|
23
|
-
# :stopdoc:
|
24
|
-
RD_ARGS = {}
|
25
|
-
|
22
|
+
autoload :Client, 'rainbows/revactor/client'
|
26
23
|
autoload :Proxy, 'rainbows/revactor/proxy'
|
27
|
-
autoload :TeeSocket, 'rainbows/revactor/tee_socket'
|
28
24
|
|
29
25
|
include Rainbows::Base
|
30
|
-
LOCALHOST = Kgio::LOCALHOST
|
31
|
-
TCP = Revactor::TCP::Socket
|
32
|
-
|
33
|
-
# once a client is accepted, it is processed in its entirety here
|
34
|
-
# in 3 easy steps: read request, call app, write app response
|
35
|
-
def process_client(client) # :nodoc:
|
36
|
-
io = client.instance_variable_get(:@_io)
|
37
|
-
io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
38
|
-
rd_args = [ nil ]
|
39
|
-
remote_addr = if TCP === client
|
40
|
-
rd_args << RD_ARGS
|
41
|
-
client.remote_addr
|
42
|
-
else
|
43
|
-
LOCALHOST
|
44
|
-
end
|
45
|
-
hp = Unicorn::HttpParser.new
|
46
|
-
buf = hp.buf
|
47
|
-
alive = false
|
48
|
-
|
49
|
-
begin
|
50
|
-
ts = nil
|
51
|
-
until env = hp.parse
|
52
|
-
buf << client.read(*rd_args)
|
53
|
-
end
|
54
|
-
|
55
|
-
env[CLIENT_IO] = client
|
56
|
-
env[RACK_INPUT] = 0 == hp.content_length ?
|
57
|
-
NULL_IO : IC.new(ts = TeeSocket.new(client), hp)
|
58
|
-
env[REMOTE_ADDR] = remote_addr
|
59
|
-
status, headers, body = app.call(env.update(RACK_DEFAULTS))
|
60
|
-
|
61
|
-
if 100 == status.to_i
|
62
|
-
client.write(EXPECT_100_RESPONSE)
|
63
|
-
env.delete(HTTP_EXPECT)
|
64
|
-
status, headers, body = app.call(env)
|
65
|
-
end
|
66
|
-
|
67
|
-
if hp.headers?
|
68
|
-
headers = HH.new(headers)
|
69
|
-
range = make_range!(env, status, headers) and status = range.shift
|
70
|
-
alive = hp.next? && G.alive && G.kato > 0
|
71
|
-
headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
|
72
|
-
client.write(response_header(status, headers))
|
73
|
-
alive && ts and buf << ts.leftover
|
74
|
-
end
|
75
|
-
write_body(client, body, range)
|
76
|
-
end while alive
|
77
|
-
rescue Revactor::TCP::ReadError
|
78
|
-
rescue => e
|
79
|
-
Rainbows::Error.write(io, e)
|
80
|
-
ensure
|
81
|
-
client.close
|
82
|
-
end
|
83
26
|
|
84
27
|
# runs inside each forked worker, this sits around and waits
|
85
28
|
# for connections and doesn't die until the parent dies (or is
|
86
29
|
# given a INT, QUIT, or TERM signal)
|
87
30
|
def worker_loop(worker) #:nodoc:
|
31
|
+
Client.setup
|
88
32
|
init_worker_process(worker)
|
89
|
-
require 'rainbows/revactor/body'
|
90
|
-
self.class.__send__(:include, Rainbows::Revactor::Body)
|
91
|
-
self.class.const_set(:IC, Unicorn::HttpRequest.input_class)
|
92
|
-
RD_ARGS[:timeout] = G.kato if G.kato > 0
|
93
33
|
nr = 0
|
94
34
|
limit = worker_connections
|
95
35
|
actor_exit = Case[:exit, Actor, Object]
|
@@ -115,12 +55,12 @@ module Rainbows::Revactor
|
|
115
55
|
f.when(actor_exit) { nr -= 1 }
|
116
56
|
f.when(accept) do |_, _, s|
|
117
57
|
nr += 1
|
118
|
-
Actor.spawn_link(s) { |c|
|
58
|
+
Actor.spawn_link(s) { |c| Client.new(c).process_loop }
|
119
59
|
end
|
120
60
|
end
|
121
61
|
rescue => e
|
122
62
|
Rainbows::Error.listen_loop(e)
|
123
|
-
end while
|
63
|
+
end while Rainbows.alive
|
124
64
|
Actor.receive do |f|
|
125
65
|
f.when(close) {}
|
126
66
|
f.when(actor_exit) { nr -= 1 }
|
@@ -128,7 +68,7 @@ module Rainbows::Revactor
|
|
128
68
|
end
|
129
69
|
end
|
130
70
|
|
131
|
-
Actor.sleep 1 while
|
71
|
+
Actor.sleep 1 while Rainbows.tick || nr > 0
|
132
72
|
rescue Errno::EMFILE
|
133
73
|
# ignore, let another worker process take it
|
134
74
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
require 'fcntl'
|
4
|
+
class Rainbows::Revactor::Client
|
5
|
+
autoload :TeeSocket, 'rainbows/revactor/client/tee_socket'
|
6
|
+
RD_ARGS = {}
|
7
|
+
Rainbows.keepalive_timeout > 0 and
|
8
|
+
RD_ARGS[:timeout] = Rainbows.keepalive_timeout
|
9
|
+
attr_reader :kgio_addr
|
10
|
+
|
11
|
+
def initialize(client)
|
12
|
+
@client, @rd_args, @ts = client, [ nil ], nil
|
13
|
+
io = client.instance_variable_get(:@_io)
|
14
|
+
io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
15
|
+
@kgio_addr = if Revactor::TCP::Socket === client
|
16
|
+
@rd_args << RD_ARGS
|
17
|
+
client.remote_addr
|
18
|
+
else
|
19
|
+
Kgio::LOCALHOST
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def kgio_read!(nr, buf)
|
24
|
+
buf.replace(@client.read)
|
25
|
+
end
|
26
|
+
|
27
|
+
def write(buf)
|
28
|
+
@client.write(buf)
|
29
|
+
end
|
30
|
+
|
31
|
+
def write_nonblock(buf) # only used for errors
|
32
|
+
@client.instance_variable_get(:@_io).write_nonblock(buf)
|
33
|
+
end
|
34
|
+
|
35
|
+
def timed_read(buf2)
|
36
|
+
buf2.replace(@client.read(*@rd_args))
|
37
|
+
end
|
38
|
+
|
39
|
+
def set_input(env, hp)
|
40
|
+
env[RACK_INPUT] = 0 == hp.content_length ?
|
41
|
+
NULL_IO : IC.new(@ts = TeeSocket.new(@client), hp)
|
42
|
+
env[CLIENT_IO] = @client
|
43
|
+
end
|
44
|
+
|
45
|
+
def close
|
46
|
+
@client.close
|
47
|
+
@client = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def closed?
|
51
|
+
@client.nil?
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.setup
|
55
|
+
self.const_set(:IC, Unicorn::HttpRequest.input_class)
|
56
|
+
include Rainbows::ProcessClient
|
57
|
+
include Methods
|
58
|
+
end
|
59
|
+
end
|
60
|
+
require 'rainbows/revactor/client/methods'
|
@@ -1,15 +1,10 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
# :enddoc:
|
3
|
-
module Rainbows::Revactor::
|
4
|
-
# TODO non-blocking splice(2) under Linux
|
5
|
-
ALIASES = {
|
6
|
-
:write_body_stream => :write_body_each
|
7
|
-
}
|
8
|
-
|
3
|
+
module Rainbows::Revactor::Client::Methods
|
9
4
|
if IO.method_defined?(:sendfile_nonblock)
|
10
|
-
def write_body_file(
|
11
|
-
body = body_to_io(body)
|
12
|
-
sock = client.instance_variable_get(:@_io)
|
5
|
+
def write_body_file(body, range)
|
6
|
+
body, client = body_to_io(body), @client
|
7
|
+
sock = @client.instance_variable_get(:@_io)
|
13
8
|
pfx = Revactor::TCP::Socket === client ? :tcp : :unix
|
14
9
|
write_complete = T[:"#{pfx}_write_complete", client]
|
15
10
|
closed = T[:"#{pfx}_closed", client]
|
@@ -33,13 +28,18 @@ module Rainbows::Revactor::Body
|
|
33
28
|
ensure
|
34
29
|
close_if_private(body)
|
35
30
|
end
|
36
|
-
|
37
|
-
|
31
|
+
end
|
32
|
+
|
33
|
+
def handle_error(e)
|
34
|
+
Revactor::TCP::ReadError === e or super
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_response(status, headers, body, alive)
|
38
|
+
super(status, headers, body, alive)
|
39
|
+
alive && @ts and @hp.buf << @ts.leftover
|
38
40
|
end
|
39
41
|
|
40
42
|
def self.included(klass)
|
41
|
-
|
42
|
-
klass.__send__(:alias_method, new_method, orig_method)
|
43
|
-
end
|
43
|
+
klass.__send__ :alias_method, :write_body_stream, :write_body_each
|
44
44
|
end
|
45
45
|
end
|
@@ -5,7 +5,7 @@
|
|
5
5
|
# enough to avoid mucking with TeeInput internals. Fortunately
|
6
6
|
# this code is not heavily used so we can usually avoid the overhead
|
7
7
|
# of adding a userspace buffer.
|
8
|
-
class Rainbows::Revactor::TeeSocket
|
8
|
+
class Rainbows::Revactor::Client::TeeSocket
|
9
9
|
def initialize(socket)
|
10
10
|
# IO::Buffer is used internally by Rev which Revactor is based on
|
11
11
|
# so we'll always have it available
|
data/lib/rainbows/sendfile.rb
CHANGED
@@ -72,13 +72,12 @@ class Rainbows::Sendfile < Struct.new(:app)
|
|
72
72
|
end
|
73
73
|
|
74
74
|
# :stopdoc:
|
75
|
-
HH = Rack::Utils::HeaderHash
|
76
75
|
X_SENDFILE = 'X-Sendfile'
|
77
76
|
# :startdoc:
|
78
77
|
|
79
78
|
def call(env) # :nodoc:
|
80
79
|
status, headers, body = app.call(env)
|
81
|
-
headers =
|
80
|
+
headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
|
82
81
|
if path = headers.delete(X_SENDFILE)
|
83
82
|
body = Body.new(path, headers) unless body.respond_to?(:to_path)
|
84
83
|
end
|
@@ -30,7 +30,7 @@ class ServerToken < Struct.new(:app, :token)
|
|
30
30
|
|
31
31
|
def call(env)
|
32
32
|
status, headers, body = app.call(env)
|
33
|
-
headers = Rack::Utils::HeaderHash.new(headers)
|
33
|
+
headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
|
34
34
|
headers[SERVER] = token
|
35
35
|
[ status, headers, body ]
|
36
36
|
end
|
data/lib/rainbows/thread_pool.rb
CHANGED
@@ -28,11 +28,11 @@ module Rainbows::ThreadPool
|
|
28
28
|
Thread.new { LISTENERS.size == 1 ? sync_worker : async_worker }
|
29
29
|
end
|
30
30
|
|
31
|
-
while
|
31
|
+
while Rainbows.alive
|
32
32
|
# if any worker dies, something is serious wrong, bail
|
33
33
|
pool.each do |thr|
|
34
|
-
|
35
|
-
thr.join(1) and
|
34
|
+
Rainbows.tick or break
|
35
|
+
thr.join(1) and Rainbows.quit!
|
36
36
|
end
|
37
37
|
end
|
38
38
|
join_threads(pool)
|
@@ -41,10 +41,10 @@ module Rainbows::ThreadPool
|
|
41
41
|
def sync_worker # :nodoc:
|
42
42
|
s = LISTENERS[0]
|
43
43
|
begin
|
44
|
-
c = s.kgio_accept and
|
44
|
+
c = s.kgio_accept and c.process_loop
|
45
45
|
rescue => e
|
46
46
|
Rainbows::Error.listen_loop(e)
|
47
|
-
end while
|
47
|
+
end while Rainbows.alive
|
48
48
|
end
|
49
49
|
|
50
50
|
def async_worker # :nodoc:
|
@@ -55,18 +55,18 @@ module Rainbows::ThreadPool
|
|
55
55
|
# problem. On the other hand, a thundering herd may not
|
56
56
|
# even incur as much overhead as an extra Mutex#synchronize
|
57
57
|
ret = select(LISTENERS) and ret[0].each do |s|
|
58
|
-
s = s.kgio_tryaccept and
|
58
|
+
s = s.kgio_tryaccept and s.process_loop
|
59
59
|
end
|
60
60
|
rescue Errno::EINTR
|
61
61
|
rescue => e
|
62
62
|
Rainbows::Error.listen_loop(e)
|
63
|
-
end while
|
63
|
+
end while Rainbows.alive
|
64
64
|
end
|
65
65
|
|
66
66
|
def join_threads(threads) # :nodoc:
|
67
|
-
|
67
|
+
Rainbows.quit!
|
68
68
|
threads.delete_if do |thr|
|
69
|
-
|
69
|
+
Rainbows.tick
|
70
70
|
begin
|
71
71
|
thr.run
|
72
72
|
thr.join(0.01)
|
@@ -22,27 +22,28 @@ module Rainbows::ThreadSpawn
|
|
22
22
|
def accept_loop(klass) #:nodoc:
|
23
23
|
lock = Mutex.new
|
24
24
|
limit = worker_connections
|
25
|
+
nr = 0
|
25
26
|
LISTENERS.each do |l|
|
26
27
|
klass.new(l) do |l|
|
27
28
|
begin
|
28
|
-
if lock.synchronize {
|
29
|
+
if lock.synchronize { nr >= limit }
|
29
30
|
worker_yield
|
30
31
|
elsif c = l.kgio_accept
|
31
32
|
klass.new(c) do |c|
|
32
33
|
begin
|
33
|
-
lock.synchronize {
|
34
|
-
|
34
|
+
lock.synchronize { nr += 1 }
|
35
|
+
c.process_loop
|
35
36
|
ensure
|
36
|
-
lock.synchronize {
|
37
|
+
lock.synchronize { nr -= 1 }
|
37
38
|
end
|
38
39
|
end
|
39
40
|
end
|
40
41
|
rescue => e
|
41
42
|
Rainbows::Error.listen_loop(e)
|
42
|
-
end while
|
43
|
+
end while Rainbows.alive
|
43
44
|
end
|
44
45
|
end
|
45
|
-
sleep 1 while
|
46
|
+
sleep 1 while Rainbows.tick || lock.synchronize { nr > 0 }
|
46
47
|
end
|
47
48
|
|
48
49
|
def worker_loop(worker) #:nodoc:
|
@@ -47,7 +47,7 @@ class Rainbows::ThreadTimeout
|
|
47
47
|
@threshold == 0 and
|
48
48
|
raise ArgumentError, "threshold=0 does not make sense"
|
49
49
|
@threshold < 0 and
|
50
|
-
@threshold += Rainbows
|
50
|
+
@threshold += Rainbows.server.worker_connections
|
51
51
|
end
|
52
52
|
@app = app
|
53
53
|
@active = {}
|
@@ -19,30 +19,14 @@
|
|
19
19
|
module Rainbows::WriterThreadPool
|
20
20
|
# :stopdoc:
|
21
21
|
include Rainbows::Base
|
22
|
+
autoload :Client, 'rainbows/writer_thread_pool/client'
|
22
23
|
|
23
24
|
@@nr = 0
|
24
25
|
@@q = nil
|
25
26
|
|
26
|
-
def async_write_body(qclient, body, range)
|
27
|
-
if body.respond_to?(:close)
|
28
|
-
Rainbows::SyncClose.new(body) do |body|
|
29
|
-
qclient.q << [ qclient.to_io, :body, body, range ]
|
30
|
-
end
|
31
|
-
else
|
32
|
-
qclient.q << [ qclient.to_io, :body, body, range ]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
27
|
def process_client(client) # :nodoc:
|
37
28
|
@@nr += 1
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
def init_worker_process(worker)
|
42
|
-
super
|
43
|
-
self.class.__send__(:alias_method, :sync_write_body, :write_body)
|
44
|
-
Rainbows::WriterThreadPool.__send__(
|
45
|
-
:alias_method, :write_body, :async_write_body)
|
29
|
+
Client.new(client, @@q[@@nr %= @@q.size]).process_loop
|
46
30
|
end
|
47
31
|
|
48
32
|
def worker_loop(worker) # :nodoc:
|
@@ -51,12 +35,14 @@ module Rainbows::WriterThreadPool
|
|
51
35
|
qp = (1..worker_connections).map do |n|
|
52
36
|
Rainbows::QueuePool.new(1) do |response|
|
53
37
|
begin
|
54
|
-
io,
|
55
|
-
case
|
56
|
-
when
|
57
|
-
|
38
|
+
io, arg, *rest = response
|
39
|
+
case arg
|
40
|
+
when String
|
41
|
+
io.kgio_write(arg)
|
42
|
+
when :close
|
43
|
+
io.close unless io.closed?
|
58
44
|
else
|
59
|
-
io.
|
45
|
+
io.__send__(arg, *rest)
|
60
46
|
end
|
61
47
|
rescue => err
|
62
48
|
Rainbows::Error.write(io, err)
|
@@ -70,5 +56,3 @@ module Rainbows::WriterThreadPool
|
|
70
56
|
end
|
71
57
|
# :startdoc:
|
72
58
|
end
|
73
|
-
# :enddoc:
|
74
|
-
require 'rainbows/writer_thread_pool/client'
|
@@ -4,6 +4,49 @@
|
|
4
4
|
# this is compatible with IO.select
|
5
5
|
class Rainbows::WriterThreadPool::Client < Struct.new(:to_io, :q)
|
6
6
|
include Rainbows::SocketProxy
|
7
|
+
include Rainbows::ProcessClient
|
8
|
+
|
9
|
+
module Methods
|
10
|
+
def write_body_each(body)
|
11
|
+
q << [ to_io, :write_body_each, body ]
|
12
|
+
end
|
13
|
+
|
14
|
+
def write_response_close(status, headers, body, alive)
|
15
|
+
to_io.instance_variable_set(:@hp, @hp) # XXX ugh
|
16
|
+
Rainbows::SyncClose.new(body) { |sync_body|
|
17
|
+
q << [ to_io, :write_response, status, headers, sync_body, alive ]
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
if IO.respond_to?(:copy_stream) || IO.method_defined?(:sendfile_nonblock)
|
22
|
+
def write_response(status, headers, body, alive)
|
23
|
+
if body.respond_to?(:close)
|
24
|
+
write_response_close(status, headers, body, alive)
|
25
|
+
elsif body.respond_to?(:to_path)
|
26
|
+
write_response_path(status, headers, body, alive)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def write_body_file(body, range)
|
33
|
+
q << [ to_io, :write_body_file, body, range ]
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_body_stream(body)
|
37
|
+
q << [ to_io, :write_body_stream, body ]
|
38
|
+
end
|
39
|
+
else # each-only body response
|
40
|
+
def write_response(status, headers, body, alive)
|
41
|
+
if body.respond_to?(:close)
|
42
|
+
write_response_close(status, headers, body, alive)
|
43
|
+
else
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end # each-only body response
|
48
|
+
end # module Methods
|
49
|
+
include Methods
|
7
50
|
|
8
51
|
def write(buf)
|
9
52
|
q << [ to_io, buf ]
|
@@ -14,6 +57,6 @@ class Rainbows::WriterThreadPool::Client < Struct.new(:to_io, :q)
|
|
14
57
|
end
|
15
58
|
|
16
59
|
def closed?
|
17
|
-
|
60
|
+
to_io.closed?
|
18
61
|
end
|
19
62
|
end
|