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
@@ -5,8 +5,8 @@ module Rainbows::EventMachine::ResponsePipe
|
|
5
5
|
# so a single buffer for all clients will work safely
|
6
6
|
BUF = ''
|
7
7
|
|
8
|
-
def initialize(client
|
9
|
-
@client
|
8
|
+
def initialize(client)
|
9
|
+
@client = client
|
10
10
|
end
|
11
11
|
|
12
12
|
def notify_readable
|
@@ -22,7 +22,6 @@ module Rainbows::EventMachine::ResponsePipe
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def unbind
|
25
|
-
@body.close if @body.respond_to?(:close)
|
26
25
|
@client.next!
|
27
26
|
@io.close unless @io.closed?
|
28
27
|
end
|
data/lib/rainbows/fiber/base.rb
CHANGED
@@ -19,7 +19,7 @@ module Rainbows::Fiber::Base
|
|
19
19
|
# will cause it.
|
20
20
|
def schedule(&block)
|
21
21
|
begin
|
22
|
-
|
22
|
+
Rainbows.tick
|
23
23
|
t = schedule_sleepers
|
24
24
|
ret = select(RD.compact.concat(LISTENERS), WR.compact, nil, t)
|
25
25
|
rescue Errno::EINTR
|
@@ -56,16 +56,16 @@ module Rainbows::Fiber::Base
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def process(client)
|
59
|
-
|
60
|
-
|
59
|
+
Rainbows.cur += 1
|
60
|
+
client.process_loop
|
61
61
|
ensure
|
62
|
-
|
62
|
+
Rainbows.cur -= 1
|
63
63
|
ZZ.delete(client.f)
|
64
64
|
end
|
65
65
|
|
66
66
|
def self.setup(klass, app)
|
67
67
|
require 'rainbows/fiber/body'
|
68
|
-
|
68
|
+
Rainbows::Client.__send__(:include, Rainbows::Fiber::Body)
|
69
69
|
self.const_set(:APP, app)
|
70
70
|
end
|
71
71
|
end
|
data/lib/rainbows/fiber/body.rb
CHANGED
@@ -5,20 +5,15 @@
|
|
5
5
|
# this is meant to be included _after_ Rainbows::Response::Body
|
6
6
|
module Rainbows::Fiber::Body # :nodoc:
|
7
7
|
|
8
|
-
# TODO non-blocking splice(2) under Linux
|
9
|
-
ALIASES = {
|
10
|
-
:write_body_stream => :write_body_each
|
11
|
-
}
|
12
|
-
|
13
8
|
# the sendfile 1.0.0+ gem includes IO#sendfile_nonblock
|
14
9
|
if IO.method_defined?(:sendfile_nonblock)
|
15
|
-
def write_body_file(
|
16
|
-
sock, n, body =
|
10
|
+
def write_body_file(body, range)
|
11
|
+
sock, n, body = to_io, nil, body_to_io(body)
|
17
12
|
offset, count = range ? range : [ 0, body.stat.size ]
|
18
13
|
begin
|
19
14
|
offset += (n = sock.sendfile_nonblock(body, offset, count))
|
20
15
|
rescue Errno::EAGAIN
|
21
|
-
|
16
|
+
kgio_wait_writable
|
22
17
|
retry
|
23
18
|
rescue EOFError
|
24
19
|
break
|
@@ -26,13 +21,9 @@ module Rainbows::Fiber::Body # :nodoc:
|
|
26
21
|
ensure
|
27
22
|
close_if_private(body)
|
28
23
|
end
|
29
|
-
else
|
30
|
-
ALIASES[:write_body] = :write_body_each
|
31
24
|
end
|
32
25
|
|
33
26
|
def self.included(klass)
|
34
|
-
|
35
|
-
klass.__send__(:alias_method, new_method, orig_method)
|
36
|
-
end
|
27
|
+
klass.__send__ :alias_method, :write_body_stream, :write_body_each
|
37
28
|
end
|
38
29
|
end
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
# :enddoc:
|
3
3
|
class Rainbows::Fiber::Coolio::Heartbeat < Coolio::TimerWatcher
|
4
|
-
G = Rainbows::G
|
5
|
-
|
6
4
|
# ZZ gets populated by read_expire in rainbows/fiber/io/methods
|
7
5
|
ZZ = Rainbows::Fiber::ZZ
|
8
6
|
def on_timer
|
9
|
-
exit if (!
|
7
|
+
exit if (! Rainbows.tick && Rainbows.cur <= 0)
|
10
8
|
now = Time.now
|
11
9
|
fibs = []
|
12
10
|
ZZ.delete_if { |fib, time| now >= time ? fibs << fib : ! fib.alive? }
|
@@ -1,9 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
# :enddoc:
|
3
3
|
class Rainbows::Fiber::Coolio::Server < Coolio::IOWatcher
|
4
|
-
G = Rainbows::G
|
5
|
-
include Rainbows::ProcessClient
|
6
|
-
|
7
4
|
def to_io
|
8
5
|
@io
|
9
6
|
end
|
@@ -19,14 +16,14 @@ class Rainbows::Fiber::Coolio::Server < Coolio::IOWatcher
|
|
19
16
|
end
|
20
17
|
|
21
18
|
def on_readable
|
22
|
-
return if
|
19
|
+
return if Rainbows.cur >= MAX
|
23
20
|
c = @io.kgio_tryaccept and Fiber.new { process(c) }.resume
|
24
21
|
end
|
25
22
|
|
26
23
|
def process(io)
|
27
|
-
|
28
|
-
|
24
|
+
Rainbows.cur += 1
|
25
|
+
io.process_loop
|
29
26
|
ensure
|
30
|
-
|
27
|
+
Rainbows.cur -= 1
|
31
28
|
end
|
32
29
|
end
|
data/lib/rainbows/fiber_pool.rb
CHANGED
data/lib/rainbows/fiber_spawn.rb
CHANGED
@@ -17,12 +17,12 @@ module Rainbows::FiberSpawn
|
|
17
17
|
|
18
18
|
begin
|
19
19
|
schedule do |l|
|
20
|
-
break if
|
20
|
+
break if Rainbows.cur >= limit
|
21
21
|
io = l.kgio_tryaccept or next
|
22
22
|
Fiber.new { process(io) }.resume
|
23
23
|
end
|
24
24
|
rescue => e
|
25
25
|
Rainbows::Error.listen_loop(e)
|
26
|
-
end while
|
26
|
+
end while Rainbows.cur_alive
|
27
27
|
end
|
28
28
|
end
|
data/lib/rainbows/http_server.rb
CHANGED
@@ -2,14 +2,12 @@
|
|
2
2
|
# :enddoc:
|
3
3
|
|
4
4
|
class Rainbows::HttpServer < Unicorn::HttpServer
|
5
|
-
G = Rainbows::G
|
6
|
-
|
7
5
|
def self.setup(block)
|
8
|
-
|
6
|
+
Rainbows.server.instance_eval(&block)
|
9
7
|
end
|
10
8
|
|
11
9
|
def initialize(app, options)
|
12
|
-
|
10
|
+
Rainbows.server = self
|
13
11
|
@logger = Unicorn::Configurator::DEFAULTS[:logger]
|
14
12
|
rv = super(app, options)
|
15
13
|
defined?(@use) or use(:Base)
|
@@ -21,7 +19,7 @@ class Rainbows::HttpServer < Unicorn::HttpServer
|
|
21
19
|
Unicorn::Util.reopen_logs
|
22
20
|
logger.info "worker=#{worker_nr} done reopening logs"
|
23
21
|
rescue
|
24
|
-
|
22
|
+
Rainbows.quit! # let the master reopen and refork us
|
25
23
|
end
|
26
24
|
|
27
25
|
# Add one second to the timeout since our fchmod heartbeat is less
|
@@ -36,7 +34,7 @@ class Rainbows::HttpServer < Unicorn::HttpServer
|
|
36
34
|
|
37
35
|
def load_config!
|
38
36
|
use :Base
|
39
|
-
|
37
|
+
Rainbows.keepalive_timeout = 5
|
40
38
|
Rainbows.max_bytes = 1024 * 1024
|
41
39
|
@worker_connections = nil
|
42
40
|
super
|
@@ -91,7 +89,7 @@ class Rainbows::HttpServer < Unicorn::HttpServer
|
|
91
89
|
def keepalive_timeout(nr)
|
92
90
|
(Integer === nr && nr >= 0) or
|
93
91
|
raise ArgumentError, "keepalive_timeout must be a non-negative Integer"
|
94
|
-
|
92
|
+
Rainbows.keepalive_timeout = nr
|
95
93
|
end
|
96
94
|
|
97
95
|
def keepalive_requests(nr)
|
data/lib/rainbows/max_body.rb
CHANGED
@@ -52,7 +52,7 @@ class Rainbows::MaxBody
|
|
52
52
|
# if it's reconfigured
|
53
53
|
def self.setup # :nodoc:
|
54
54
|
Rainbows.max_bytes or return
|
55
|
-
case Rainbows
|
55
|
+
case Rainbows.server.use
|
56
56
|
when :Rev, :Coolio, :EventMachine, :NeverBlock,
|
57
57
|
:RevThreadSpawn, :RevThreadPool,
|
58
58
|
:CoolioThreadSpawn, :CoolioThreadPool
|
@@ -60,7 +60,7 @@ class Rainbows::MaxBody
|
|
60
60
|
end
|
61
61
|
|
62
62
|
# force ourselves to the outermost middleware layer
|
63
|
-
Rainbows
|
63
|
+
Rainbows.server.app = self.new(Rainbows.server.app)
|
64
64
|
end
|
65
65
|
|
66
66
|
# Rack response returned when there's an error
|
@@ -8,7 +8,7 @@ module Rainbows::NeverBlock::Core
|
|
8
8
|
base = o[:backend].to_s.gsub!(/([a-z])([A-Z])/, '\1_\2').downcase!
|
9
9
|
require "rainbows/never_block/#{base}"
|
10
10
|
client_class = Rainbows::NeverBlock::Client
|
11
|
-
client_class.superclass.const_set(:APP, Rainbows
|
11
|
+
client_class.superclass.const_set(:APP, Rainbows.server.app)
|
12
12
|
client_class.const_set(:POOL, pool)
|
13
13
|
logger.info "NeverBlock/#{o[:backend]} pool_size=#{o[:pool_size]}"
|
14
14
|
end
|
@@ -1,55 +1,41 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
# :enddoc:
|
3
|
-
require 'rainbows/rack_input'
|
4
2
|
module Rainbows::ProcessClient
|
5
|
-
G = Rainbows::G
|
6
3
|
include Rainbows::Response
|
7
|
-
HttpParser = Unicorn::HttpParser
|
8
4
|
include Rainbows::RackInput
|
9
5
|
include Rainbows::Const
|
10
6
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# Base, ThreadSpawn, ThreadPool
|
15
|
-
def process_client(client) # :nodoc:
|
16
|
-
hp = HttpParser.new
|
17
|
-
client.kgio_read!(16384, buf = hp.buf)
|
18
|
-
remote_addr = client.kgio_addr
|
19
|
-
alive = false
|
7
|
+
def process_loop
|
8
|
+
@hp = hp = Rainbows::HttpParser.new
|
9
|
+
kgio_read!(16384, buf = hp.buf) or return
|
20
10
|
|
21
11
|
begin # loop
|
22
12
|
until env = hp.parse
|
23
|
-
|
13
|
+
timed_read(buf2 ||= "") or return
|
24
14
|
buf << buf2
|
25
15
|
end
|
26
16
|
|
27
|
-
set_input(env, hp
|
28
|
-
env[REMOTE_ADDR] =
|
29
|
-
status, headers, body = APP.call(env.
|
17
|
+
set_input(env, hp)
|
18
|
+
env[REMOTE_ADDR] = kgio_addr
|
19
|
+
status, headers, body = APP.call(env.merge!(RACK_DEFAULTS))
|
30
20
|
|
31
21
|
if 100 == status.to_i
|
32
|
-
|
22
|
+
write(EXPECT_100_RESPONSE)
|
33
23
|
env.delete(HTTP_EXPECT)
|
34
24
|
status, headers, body = APP.call(env)
|
35
25
|
end
|
36
|
-
|
37
|
-
if hp.headers?
|
38
|
-
headers = HH.new(headers)
|
39
|
-
range = make_range!(env, status, headers) and status = range.shift
|
40
|
-
alive = hp.next? && G.alive
|
41
|
-
headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
|
42
|
-
client.write(response_header(status, headers))
|
43
|
-
end
|
44
|
-
write_body(client, body, range)
|
26
|
+
write_response(status, headers, body, alive = @hp.next?)
|
45
27
|
end while alive
|
46
28
|
# if we get any error, try to write something back to the client
|
47
29
|
# assuming we haven't closed the socket, but don't get hung up
|
48
30
|
# if the socket is already closed or broken. We'll always ensure
|
49
31
|
# the socket is closed at the end of this function
|
50
32
|
rescue => e
|
51
|
-
|
33
|
+
handle_error(e)
|
52
34
|
ensure
|
53
|
-
|
35
|
+
close unless closed?
|
36
|
+
end
|
37
|
+
|
38
|
+
def handle_error(e)
|
39
|
+
Rainbows::Error.write(self, e)
|
54
40
|
end
|
55
41
|
end
|
data/lib/rainbows/queue_pool.rb
CHANGED
@@ -6,8 +6,6 @@ require 'thread'
|
|
6
6
|
# This is NOT used for the ThreadPool class, since that class does not
|
7
7
|
# need a userspace Queue.
|
8
8
|
class Rainbows::QueuePool < Struct.new(:queue, :threads)
|
9
|
-
G = Rainbows::G
|
10
|
-
|
11
9
|
def initialize(size = 20, &block)
|
12
10
|
q = Queue.new
|
13
11
|
self.threads = (1..size).map do
|
@@ -23,7 +21,7 @@ class Rainbows::QueuePool < Struct.new(:queue, :threads)
|
|
23
21
|
def quit!
|
24
22
|
threads.each { |_| queue << nil }
|
25
23
|
threads.delete_if do |t|
|
26
|
-
|
24
|
+
Rainbows.tick
|
27
25
|
t.alive? ? t.join(0.01) : true
|
28
26
|
end until threads.empty?
|
29
27
|
end
|
data/lib/rainbows/rack_input.rb
CHANGED
@@ -10,8 +10,8 @@ module Rainbows::RackInput
|
|
10
10
|
const_set(:IC, Unicorn::HttpRequest.input_class)
|
11
11
|
end
|
12
12
|
|
13
|
-
def set_input(env, hp
|
14
|
-
env[RACK_INPUT] = 0 == hp.content_length ? NULL_IO : IC.new(
|
15
|
-
env[CLIENT_IO] =
|
13
|
+
def set_input(env, hp)
|
14
|
+
env[RACK_INPUT] = 0 == hp.content_length ? NULL_IO : IC.new(self, hp)
|
15
|
+
env[CLIENT_IO] = self
|
16
16
|
end
|
17
17
|
end
|
data/lib/rainbows/response.rb
CHANGED
@@ -1,59 +1,185 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
# :enddoc:
|
3
|
-
require 'time' # for Time#httpdate
|
4
|
-
|
5
3
|
module Rainbows::Response
|
6
|
-
|
7
|
-
|
4
|
+
include Unicorn::HttpResponse
|
5
|
+
Close = "close"
|
6
|
+
KeepAlive = "keep-alive"
|
7
|
+
Content_Length = "Content-Length".freeze
|
8
|
+
Transfer_Encoding = "Transfer-Encoding".freeze
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
# private file class for IO objects opened by Rainbows! itself (and not
|
11
|
+
# the app or middleware)
|
12
|
+
class F < File; end
|
11
13
|
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
# called after forking
|
15
|
+
def self.setup(klass)
|
16
|
+
Kgio.accept_class = Rainbows::Client
|
17
|
+
0 == Rainbows.keepalive_timeout and
|
18
|
+
Rainbows::HttpParser.keepalive_requests = 0
|
19
|
+
end
|
17
20
|
|
18
|
-
def
|
21
|
+
def write_headers(status, headers, alive)
|
22
|
+
@hp.headers? or return
|
19
23
|
status = CODES[status.to_i] || status
|
20
|
-
|
21
|
-
|
22
|
-
|
24
|
+
buf = "HTTP/1.1 #{status}\r\n" \
|
25
|
+
"Date: #{httpdate}\r\n" \
|
26
|
+
"Status: #{status}\r\n" \
|
27
|
+
"Connection: #{alive ? KeepAlive : Close}\r\n"
|
23
28
|
headers.each do |key, value|
|
24
|
-
next if %r{\A(?:X-Rainbows-|Date\z|
|
29
|
+
next if %r{\A(?:X-Rainbows-|Date\z|Connection\z)}i =~ key
|
25
30
|
if value =~ /\n/
|
26
31
|
# avoiding blank, key-only cookies with /\n+/
|
27
|
-
|
32
|
+
buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
|
28
33
|
else
|
29
|
-
|
34
|
+
buf << "#{key}: #{value}\r\n"
|
30
35
|
end
|
31
36
|
end
|
32
|
-
|
37
|
+
write(buf << CRLF)
|
33
38
|
end
|
34
39
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
40
|
+
def close_if_private(io)
|
41
|
+
io.close if F === io
|
42
|
+
end
|
43
|
+
|
44
|
+
def io_for_fd(fd)
|
45
|
+
Rainbows::FD_MAP.delete(fd) || F.for_fd(fd)
|
46
|
+
end
|
47
|
+
|
48
|
+
# to_io is not part of the Rack spec, but make an exception here
|
49
|
+
# since we can conserve path lookups and file descriptors.
|
50
|
+
# \Rainbows! will never get here without checking for the existence
|
51
|
+
# of body.to_path first.
|
52
|
+
def body_to_io(body)
|
53
|
+
if body.respond_to?(:to_io)
|
54
|
+
body.to_io
|
55
|
+
else
|
56
|
+
# try to take advantage of Rainbows::DevFdResponse, calling F.open
|
57
|
+
# is a last resort
|
58
|
+
path = body.to_path
|
59
|
+
%r{\A/dev/fd/(\d+)\z} =~ path ? io_for_fd($1.to_i) : F.open(path)
|
51
60
|
end
|
52
61
|
end
|
53
62
|
|
54
|
-
module
|
55
|
-
#
|
56
|
-
def
|
63
|
+
module Each
|
64
|
+
# generic body writer, used for most dynamically-generated responses
|
65
|
+
def write_body_each(body)
|
66
|
+
body.each { |chunk| write(chunk) }
|
67
|
+
end
|
68
|
+
|
69
|
+
# generic response writer, used for most dynamically-generated responses
|
70
|
+
# and also when IO.copy_stream and/or IO#sendfile_nonblock is unavailable
|
71
|
+
def write_response(status, headers, body, alive)
|
72
|
+
write_headers(status, headers, alive)
|
73
|
+
write_body_each(body)
|
74
|
+
ensure
|
75
|
+
body.close if body.respond_to?(:close)
|
57
76
|
end
|
58
77
|
end
|
78
|
+
include Each
|
79
|
+
|
80
|
+
if IO.method_defined?(:sendfile_nonblock)
|
81
|
+
module Sendfile
|
82
|
+
def write_body_file(body, range)
|
83
|
+
io = body_to_io(body)
|
84
|
+
range ? sendfile(io, range[0], range[1]) : sendfile(io, 0)
|
85
|
+
ensure
|
86
|
+
close_if_private(io)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
include Sendfile
|
90
|
+
end
|
91
|
+
|
92
|
+
if IO.respond_to?(:copy_stream)
|
93
|
+
unless IO.method_defined?(:sendfile_nonblock)
|
94
|
+
module CopyStream
|
95
|
+
def write_body_file(body, range)
|
96
|
+
range ? IO.copy_stream(body, self, range[1], range[0]) :
|
97
|
+
IO.copy_stream(body, self, nil, 0)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
include CopyStream
|
101
|
+
end
|
102
|
+
|
103
|
+
# write_body_stream is an alias for write_body_each if IO.copy_stream
|
104
|
+
# isn't used or available.
|
105
|
+
def write_body_stream(body)
|
106
|
+
IO.copy_stream(io = body_to_io(body), self)
|
107
|
+
ensure
|
108
|
+
close_if_private(io)
|
109
|
+
end
|
110
|
+
else # ! IO.respond_to?(:copy_stream)
|
111
|
+
alias write_body_stream write_body_each
|
112
|
+
end # ! IO.respond_to?(:copy_stream)
|
113
|
+
|
114
|
+
if IO.method_defined?(:sendfile_nonblock) || IO.respond_to?(:copy_stream)
|
115
|
+
HTTP_RANGE = 'HTTP_RANGE'
|
116
|
+
Content_Range = 'Content-Range'.freeze
|
117
|
+
|
118
|
+
# This does not support multipart responses (does anybody actually
|
119
|
+
# use those?)
|
120
|
+
def sendfile_range(status, headers)
|
121
|
+
200 == status.to_i &&
|
122
|
+
/\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ @hp.env[HTTP_RANGE] or
|
123
|
+
return
|
124
|
+
a, b = $1.split(/-/)
|
125
|
+
|
126
|
+
# HeaderHash is quite expensive, and Rack::File currently
|
127
|
+
# uses a regular Ruby Hash with properly-cased headers the
|
128
|
+
# same way they're presented in rfc2616.
|
129
|
+
headers = Rack::Utils::HeaderHash.new(headers) unless Hash === headers
|
130
|
+
clen = headers[Content_Length] or return
|
131
|
+
size = clen.to_i
|
132
|
+
|
133
|
+
if b.nil? # bytes=M-
|
134
|
+
offset = a.to_i
|
135
|
+
count = size - offset
|
136
|
+
elsif a.empty? # bytes=-N
|
137
|
+
offset = size - b.to_i
|
138
|
+
count = size - offset
|
139
|
+
else # bytes=M-N
|
140
|
+
offset = a.to_i
|
141
|
+
count = b.to_i + 1 - offset
|
142
|
+
end
|
143
|
+
|
144
|
+
if 0 > count || offset >= size
|
145
|
+
headers[Content_Length] = "0"
|
146
|
+
headers[Content_Range] = "bytes */#{clen}"
|
147
|
+
return 416, headers, nil
|
148
|
+
else
|
149
|
+
count = size if count > size
|
150
|
+
headers[Content_Length] = count.to_s
|
151
|
+
headers[Content_Range] = "bytes #{offset}-#{offset+count-1}/#{clen}"
|
152
|
+
return 206, headers, [ offset, count ]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def write_response_path(status, headers, body, alive)
|
157
|
+
if File.file?(body.to_path)
|
158
|
+
if r = sendfile_range(status, headers)
|
159
|
+
status, headers, range = r
|
160
|
+
write_headers(status, headers, alive)
|
161
|
+
write_body_file(body, range) if range
|
162
|
+
else
|
163
|
+
write_headers(status, headers, alive)
|
164
|
+
write_body_file(body, nil)
|
165
|
+
end
|
166
|
+
else
|
167
|
+
write_headers(status, headers, alive)
|
168
|
+
write_body_stream(body)
|
169
|
+
end
|
170
|
+
ensure
|
171
|
+
body.close if body.respond_to?(:close)
|
172
|
+
end
|
173
|
+
|
174
|
+
module ToPath
|
175
|
+
def write_response(status, headers, body, alive)
|
176
|
+
if body.respond_to?(:to_path)
|
177
|
+
write_response_path(status, headers, body, alive)
|
178
|
+
else
|
179
|
+
super
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
include ToPath
|
184
|
+
end # IO.respond_to?(:copy_stream) || IO.method_defined?(:sendfile_nonblock)
|
59
185
|
end
|