rainbows 0.95.1 → 0.96.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/Documentation/comparison.haml +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +2 -1
- data/Rakefile +16 -3
- data/Static_Files +11 -11
- data/TODO +1 -6
- data/lib/rainbows.rb +3 -2
- data/lib/rainbows/base.rb +12 -9
- data/lib/rainbows/const.rb +2 -4
- data/lib/rainbows/dev_fd_response.rb +16 -13
- data/lib/rainbows/error.rb +2 -0
- data/lib/rainbows/ev_core.rb +13 -0
- data/lib/rainbows/event_machine.rb +85 -128
- data/lib/rainbows/event_machine/response_chunk_pipe.rb +25 -0
- data/lib/rainbows/event_machine/response_pipe.rb +30 -0
- data/lib/rainbows/event_machine/try_defer.rb +27 -0
- data/lib/rainbows/fiber/body.rb +6 -4
- data/lib/rainbows/fiber/io.rb +4 -4
- data/lib/rainbows/fiber/rev.rb +16 -8
- data/lib/rainbows/http_response.rb +5 -6
- data/lib/rainbows/response.rb +37 -22
- data/lib/rainbows/response/body.rb +19 -16
- data/lib/rainbows/response/range.rb +34 -0
- data/lib/rainbows/rev.rb +2 -1
- data/lib/rainbows/rev/client.rb +105 -77
- data/lib/rainbows/rev/deferred_chunk_response.rb +16 -0
- data/lib/rainbows/rev/deferred_response.rb +16 -24
- data/lib/rainbows/rev/sendfile.rb +4 -13
- data/lib/rainbows/rev/thread.rb +3 -12
- data/lib/rainbows/rev_thread_pool.rb +2 -2
- data/lib/rainbows/revactor.rb +16 -9
- data/lib/rainbows/revactor/body.rb +42 -0
- data/lib/rainbows/revactor/proxy.rb +55 -0
- data/lib/rainbows/sendfile.rb +12 -14
- data/lib/rainbows/stream_file.rb +3 -3
- data/lib/rainbows/writer_thread_pool.rb +12 -12
- data/lib/rainbows/writer_thread_spawn.rb +6 -7
- data/t/GNUmakefile +2 -2
- data/t/close-pipe-response.ru +25 -0
- data/t/cramp/rainsocket.ru +1 -1
- data/t/fast-pipe-response.ru +10 -0
- data/t/file-wrap-to_path.ru +24 -0
- data/t/t0015-working_directory.sh +5 -1
- data/t/t0020-large-sendfile-response.sh +3 -3
- data/t/t0021-sendfile-wrap-to_path.sh +108 -0
- data/t/t0022-copy_stream-byte-range.sh +139 -0
- data/t/t0023-sendfile-byte-range.sh +63 -0
- data/t/t0024-pipelined-sendfile-response.sh +89 -0
- data/t/t0030-fast-pipe-response.sh +63 -0
- data/t/t0031-close-pipe-response.sh +96 -0
- data/t/t0034-pipelined-pipe-response.sh +87 -0
- data/t/t0105-rack-input-limit-bigger.sh +5 -2
- data/t/t0500-cramp-streaming.sh +0 -1
- data/t/t0501-cramp-rainsocket.sh +1 -0
- data/t/t9000-rack-app-pool.sh +1 -1
- data/t/test_isolate.rb +1 -0
- metadata +29 -5
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
module Rainbows::EventMachine::ResponseChunkPipe
|
4
|
+
include Rainbows::EventMachine::ResponsePipe
|
5
|
+
|
6
|
+
def unbind
|
7
|
+
@client.write("0\r\n\r\n")
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def notify_readable
|
12
|
+
begin
|
13
|
+
data = @io.read_nonblock(16384, BUF)
|
14
|
+
@client.write("#{data.size.to_s(16)}\r\n")
|
15
|
+
@client.write(data)
|
16
|
+
@client.write("\r\n")
|
17
|
+
rescue Errno::EINTR
|
18
|
+
rescue Errno::EAGAIN
|
19
|
+
return
|
20
|
+
rescue EOFError
|
21
|
+
detach
|
22
|
+
return
|
23
|
+
end while true
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
module Rainbows::EventMachine::ResponsePipe
|
4
|
+
# garbage avoidance, EM always uses this in a single thread,
|
5
|
+
# so a single buffer for all clients will work safely
|
6
|
+
BUF = ''
|
7
|
+
|
8
|
+
def initialize(client, alive, body)
|
9
|
+
@client, @alive, @body = client, alive, body
|
10
|
+
end
|
11
|
+
|
12
|
+
def notify_readable
|
13
|
+
begin
|
14
|
+
@client.write(@io.read_nonblock(16384, BUF))
|
15
|
+
rescue Errno::EINTR
|
16
|
+
rescue Errno::EAGAIN
|
17
|
+
return
|
18
|
+
rescue EOFError
|
19
|
+
detach
|
20
|
+
return
|
21
|
+
end while true
|
22
|
+
end
|
23
|
+
|
24
|
+
def unbind
|
25
|
+
@client.body = nil
|
26
|
+
@alive ? @client.on_read('') : @client.quit
|
27
|
+
@body.close if @body.respond_to?(:close)
|
28
|
+
@io.close unless @io.closed?
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
|
4
|
+
# Middleware that will run the app dispatch in a separate thread.
|
5
|
+
# This middleware is automatically loaded by Rainbows! when using
|
6
|
+
# EventMachine and if the app responds to the +deferred?+ method.
|
7
|
+
class Rainbows::EventMachine::TryDefer < Struct.new(:app)
|
8
|
+
# shortcuts
|
9
|
+
ASYNC_CALLBACK = Rainbows::EvCore::ASYNC_CALLBACK
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
# the entire app becomes multithreaded, even the root (non-deferred)
|
13
|
+
# thread since any thread can share processes with others
|
14
|
+
Rainbows::Const::RACK_DEFAULTS['rack.multithread'] = true
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(env)
|
19
|
+
if app.deferred?(env)
|
20
|
+
EM.defer(proc { catch(:async) { app.call(env) } }, env[ASYNC_CALLBACK])
|
21
|
+
# all of the async/deferred stuff breaks Rack::Lint :<
|
22
|
+
nil
|
23
|
+
else
|
24
|
+
app.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/rainbows/fiber/body.rb
CHANGED
@@ -12,15 +12,17 @@ module Rainbows::Fiber::Body # :nodoc:
|
|
12
12
|
|
13
13
|
# the sendfile 1.0.0+ gem includes IO#sendfile_nonblock
|
14
14
|
if ::IO.method_defined?(:sendfile_nonblock)
|
15
|
-
def write_body_file(client, body)
|
16
|
-
sock,
|
15
|
+
def write_body_file(client, body, range)
|
16
|
+
sock, n = client.to_io, nil
|
17
|
+
offset, count = range ? range : [ 0, body.stat.size ]
|
17
18
|
begin
|
18
|
-
|
19
|
+
offset += (n = sock.sendfile_nonblock(body, offset, count))
|
19
20
|
rescue Errno::EAGAIN
|
20
21
|
client.wait_writable
|
22
|
+
retry
|
21
23
|
rescue EOFError
|
22
24
|
break
|
23
|
-
end while
|
25
|
+
end while (count -= n) > 0
|
24
26
|
end
|
25
27
|
else
|
26
28
|
ALIASES[:write_body] = :write_body_each
|
data/lib/rainbows/fiber/io.rb
CHANGED
@@ -24,11 +24,11 @@ module Rainbows
|
|
24
24
|
|
25
25
|
# for wrapping output response bodies
|
26
26
|
def each(&block)
|
27
|
-
|
28
|
-
yield
|
27
|
+
if buf = readpartial(16384)
|
28
|
+
yield buf
|
29
|
+
yield buf while readpartial(16384, buf)
|
30
|
+
end
|
29
31
|
rescue EOFError
|
30
|
-
break
|
31
|
-
end while true
|
32
32
|
self
|
33
33
|
end
|
34
34
|
|
data/lib/rainbows/fiber/rev.rb
CHANGED
@@ -81,7 +81,6 @@ module Rainbows::Fiber
|
|
81
81
|
buf = client.read_timeout or return
|
82
82
|
hp = HttpParser.new
|
83
83
|
env = {}
|
84
|
-
alive = true
|
85
84
|
remote_addr = Rainbows.addr(io)
|
86
85
|
|
87
86
|
begin # loop
|
@@ -91,18 +90,27 @@ module Rainbows::Fiber
|
|
91
90
|
env[RACK_INPUT] = 0 == hp.content_length ?
|
92
91
|
HttpRequest::NULL_IO : TeeInput.new(client, env, hp, buf)
|
93
92
|
env[REMOTE_ADDR] = remote_addr
|
94
|
-
|
93
|
+
status, headers, body = APP.call(env.update(RACK_DEFAULTS))
|
95
94
|
|
96
|
-
if 100 ==
|
95
|
+
if 100 == status.to_i
|
97
96
|
client.write(EXPECT_100_RESPONSE)
|
98
97
|
env.delete(HTTP_EXPECT)
|
99
|
-
|
98
|
+
status, headers, body = APP.call(env)
|
100
99
|
end
|
101
100
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
101
|
+
if hp.headers?
|
102
|
+
headers = HH.new(headers)
|
103
|
+
range = make_range!(env, status, headers) and status = range.shift
|
104
|
+
headers[CONNECTION] = if hp.keepalive? && G.alive
|
105
|
+
KEEP_ALIVE
|
106
|
+
else
|
107
|
+
env = false
|
108
|
+
CLOSE
|
109
|
+
end
|
110
|
+
client.write(response_header(status, headers))
|
111
|
+
end
|
112
|
+
write_body(client, body, range)
|
113
|
+
end while env && env.clear && hp.reset.nil?
|
106
114
|
rescue => e
|
107
115
|
Error.write(io, e)
|
108
116
|
ensure
|
@@ -4,20 +4,19 @@
|
|
4
4
|
# Cramp 0.11 relies on this, and is only activated by Cramp
|
5
5
|
if defined?(Cramp) && defined?(Rainbows::EventMachine::Client)
|
6
6
|
class Rainbows::HttpResponse
|
7
|
-
|
8
|
-
|
9
|
-
alias write write_response
|
7
|
+
# dummy method for Cramp to alias_method_chain
|
8
|
+
def self.write(client, response, out)
|
10
9
|
end
|
11
10
|
end
|
12
11
|
|
13
12
|
module Rainbows::EventMachine::CrampSocket
|
14
|
-
def
|
13
|
+
def em_write_response(response, alive = false)
|
15
14
|
if websocket?
|
16
15
|
write web_socket_upgrade_data
|
17
16
|
web_socket_handshake!
|
18
|
-
|
17
|
+
response[1] = nil # disable response headers
|
19
18
|
end
|
20
|
-
super
|
19
|
+
super
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
data/lib/rainbows/response.rb
CHANGED
@@ -3,41 +3,56 @@
|
|
3
3
|
require 'time' # for Time#httpdate
|
4
4
|
|
5
5
|
module Rainbows::Response
|
6
|
+
autoload :Body, 'rainbows/response/body'
|
7
|
+
autoload :Range, 'rainbows/response/range'
|
6
8
|
|
7
9
|
CODES = Unicorn::HttpResponse::CODES
|
10
|
+
CRLF = "\r\n"
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
+
# freeze headers we may set as hash keys for a small speedup
|
13
|
+
CONNECTION = "Connection".freeze
|
14
|
+
CLOSE = "close"
|
15
|
+
KEEP_ALIVE = "keep-alive"
|
16
|
+
HH = Rack::Utils::HeaderHash
|
12
17
|
|
18
|
+
def response_header(status, headers)
|
19
|
+
status = CODES[status.to_i] || status
|
20
|
+
rv = "HTTP/1.1 #{status}\r\n" \
|
21
|
+
"Date: #{Time.now.httpdate}\r\n" \
|
22
|
+
"Status: #{status}\r\n"
|
13
23
|
headers.each do |key, value|
|
14
|
-
next if %r{\A(?:X-Rainbows-|
|
24
|
+
next if %r{\A(?:X-Rainbows-|Date\z|Status\z)}i =~ key
|
15
25
|
if value =~ /\n/
|
16
26
|
# avoiding blank, key-only cookies with /\n+/
|
17
|
-
|
27
|
+
rv << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join('')
|
18
28
|
else
|
19
|
-
|
29
|
+
rv << "#{key}: #{value}\r\n"
|
20
30
|
end
|
21
31
|
end
|
22
|
-
|
23
|
-
"HTTP/1.1 #{status}\r\n" \
|
24
|
-
"Date: #{Time.now.httpdate}\r\n" \
|
25
|
-
"Status: #{status}\r\n" \
|
26
|
-
"#{out.join('')}\r\n"
|
27
|
-
end
|
28
|
-
|
29
|
-
def write_header(socket, response, out)
|
30
|
-
out and socket.write(response_header(response, out))
|
31
|
-
end
|
32
|
-
|
33
|
-
def write_response(socket, response, out)
|
34
|
-
write_header(socket, response, out)
|
35
|
-
write_body(socket, response[2])
|
32
|
+
rv << CRLF
|
36
33
|
end
|
37
34
|
|
38
35
|
# called after forking
|
39
36
|
def self.setup(klass)
|
40
|
-
|
41
|
-
|
37
|
+
range_class = body_class = klass
|
38
|
+
case Rainbows::Const::RACK_DEFAULTS['rainbows.model']
|
39
|
+
when :WriterThreadSpawn
|
40
|
+
body_class = Rainbows::WriterThreadSpawn::MySocket
|
41
|
+
range_class = Rainbows::HttpServer
|
42
|
+
when :EventMachine, :NeverBlock
|
43
|
+
range_class = nil # :<
|
44
|
+
end
|
45
|
+
return if body_class.included_modules.include?(Body)
|
46
|
+
body_class.__send__(:include, Body)
|
47
|
+
sf = IO.respond_to?(:copy_stream) || IO.method_defined?(:sendfile_nonblock)
|
48
|
+
if range_class
|
49
|
+
range_class.__send__(:include, sf ? Range : NoRange)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module NoRange
|
54
|
+
# dummy method if we can't send range responses
|
55
|
+
def make_range!(env, status, headers)
|
56
|
+
end
|
42
57
|
end
|
43
58
|
end
|
@@ -41,27 +41,28 @@ module Rainbows::Response::Body # :nodoc:
|
|
41
41
|
# try to take advantage of Rainbows::DevFdResponse, calling File.open
|
42
42
|
# is a last resort
|
43
43
|
path = body.to_path
|
44
|
-
path =~ %r{\A/dev/fd/(\d+)\z} ? IO.new($1.to_i) : File.open(path
|
44
|
+
path =~ %r{\A/dev/fd/(\d+)\z} ? IO.new($1.to_i) : File.open(path)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
48
|
if IO.method_defined?(:sendfile_nonblock)
|
49
|
-
def write_body_file(sock, body)
|
50
|
-
sock.sendfile(body, 0)
|
49
|
+
def write_body_file(sock, body, range)
|
50
|
+
range ? sock.sendfile(body, range[0], range[1]) : sock.sendfile(body, 0)
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
54
|
if IO.respond_to?(:copy_stream)
|
55
55
|
unless method_defined?(:write_body_file)
|
56
56
|
# try to use sendfile() via IO.copy_stream, otherwise pread()+write()
|
57
|
-
def write_body_file(sock, body)
|
58
|
-
IO.copy_stream(body, sock,
|
57
|
+
def write_body_file(sock, body, range)
|
58
|
+
range ? IO.copy_stream(body, sock, range[1], range[0]) :
|
59
|
+
IO.copy_stream(body, sock, nil, 0)
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
62
63
|
# only used when body is a pipe or socket that can't handle
|
63
64
|
# pread() semantics
|
64
|
-
def write_body_stream(sock, body)
|
65
|
+
def write_body_stream(sock, body, range)
|
65
66
|
IO.copy_stream(body, sock)
|
66
67
|
ensure
|
67
68
|
body.respond_to?(:close) and body.close
|
@@ -74,38 +75,40 @@ module Rainbows::Response::Body # :nodoc:
|
|
74
75
|
if method_defined?(:write_body_file)
|
75
76
|
|
76
77
|
# middlewares/apps may return with a body that responds to +to_path+
|
77
|
-
def write_body_path(sock, body)
|
78
|
+
def write_body_path(sock, body, range)
|
78
79
|
inp = body_to_io(body)
|
79
80
|
if inp.stat.file?
|
80
81
|
begin
|
81
|
-
write_body_file(sock, inp)
|
82
|
+
write_body_file(sock, inp, range)
|
82
83
|
ensure
|
83
84
|
inp.close if inp != body
|
84
85
|
end
|
85
86
|
else
|
86
|
-
write_body_stream(sock, inp)
|
87
|
+
write_body_stream(sock, inp, range)
|
87
88
|
end
|
88
89
|
ensure
|
89
90
|
body.respond_to?(:close) && inp != body and body.close
|
90
91
|
end
|
91
|
-
|
92
|
-
def write_body_path(sock, body)
|
93
|
-
write_body_stream(sock, body_to_io(body))
|
92
|
+
elsif method_defined?(:write_body_stream)
|
93
|
+
def write_body_path(sock, body, range)
|
94
|
+
write_body_stream(sock, inp = body_to_io(body), range)
|
95
|
+
ensure
|
96
|
+
body.respond_to?(:close) && inp != body and body.close
|
94
97
|
end
|
95
98
|
end
|
96
99
|
|
97
100
|
if method_defined?(:write_body_path)
|
98
|
-
def write_body(client, body)
|
101
|
+
def write_body(client, body, range)
|
99
102
|
body.respond_to?(:to_path) ?
|
100
|
-
write_body_path(client, body) :
|
101
|
-
write_body_each(client, body)
|
103
|
+
write_body_path(client, body, range) :
|
104
|
+
write_body_each(client, body, range)
|
102
105
|
end
|
103
106
|
else
|
104
107
|
ALIASES[:write_body] = :write_body_each
|
105
108
|
end
|
106
109
|
|
107
110
|
# generic body writer, used for most dynamically generated responses
|
108
|
-
def write_body_each(socket, body)
|
111
|
+
def write_body_each(socket, body, range = nil)
|
109
112
|
body.each { |chunk| socket.write(chunk) }
|
110
113
|
ensure
|
111
114
|
body.respond_to?(:close) and body.close
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# :enddoc:
|
3
|
+
module Rainbows::Response::Range
|
4
|
+
HTTP_RANGE = 'HTTP_RANGE'
|
5
|
+
Content_Range = 'Content-Range'.freeze
|
6
|
+
Content_Length = 'Content-Length'.freeze
|
7
|
+
|
8
|
+
# This does not support multipart responses (does anybody actually
|
9
|
+
# use those?) +headers+ is always a Rack::Utils::HeaderHash
|
10
|
+
def make_range!(env, status, headers)
|
11
|
+
if 200 == status.to_i &&
|
12
|
+
(clen = headers[Content_Length]) &&
|
13
|
+
/\Abytes=(\d+-\d*|\d*-\d+)\z/ =~ env[HTTP_RANGE]
|
14
|
+
a, b = $1.split(/-/)
|
15
|
+
clen = clen.to_i
|
16
|
+
if b.nil? # bytes=M-
|
17
|
+
offset = a.to_i
|
18
|
+
count = clen - offset
|
19
|
+
elsif a.empty? # bytes=-N
|
20
|
+
offset = clen - b.to_i
|
21
|
+
count = clen - offset
|
22
|
+
else # bytes=M-N
|
23
|
+
offset = a.to_i
|
24
|
+
count = b.to_i + 1 - offset
|
25
|
+
end
|
26
|
+
raise Rainbows::Response416 if count <= 0 || offset >= clen
|
27
|
+
count = clen if count > clen
|
28
|
+
headers[Content_Length] = count.to_s
|
29
|
+
headers[Content_Range] = "bytes #{offset}-#{offset+count-1}/#{clen}"
|
30
|
+
[ status, offset, count ]
|
31
|
+
end
|
32
|
+
# nil if no status
|
33
|
+
end
|
34
|
+
end
|
data/lib/rainbows/rev.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
require 'rainbows/rev/core'
|
3
3
|
require 'rainbows/rev/client'
|
4
|
-
require 'rainbows/rev/deferred_response'
|
5
4
|
|
6
5
|
module Rainbows
|
7
6
|
|
@@ -37,6 +36,8 @@ module Rainbows
|
|
37
36
|
KATO.compare_by_identity
|
38
37
|
end
|
39
38
|
|
39
|
+
autoload :DeferredResponse,'rainbows/rev/deferred_response'
|
40
|
+
autoload :DeferredChunkResponse,'rainbows/rev/deferred_chunk_response'
|
40
41
|
include Core
|
41
42
|
# :startdoc:
|
42
43
|
end
|
data/lib/rainbows/rev/client.rb
CHANGED
@@ -7,20 +7,19 @@ module Rainbows
|
|
7
7
|
class Client < ::Rev::IO
|
8
8
|
include Rainbows::ByteSlice
|
9
9
|
include Rainbows::EvCore
|
10
|
-
include Rainbows::Response
|
11
10
|
G = Rainbows::G
|
12
|
-
|
11
|
+
F = Rainbows::StreamFile
|
13
12
|
|
14
13
|
def initialize(io)
|
15
14
|
CONN[self] = false
|
16
15
|
super(io)
|
17
16
|
post_init
|
18
|
-
@
|
17
|
+
@deferred = nil
|
19
18
|
end
|
20
19
|
|
21
20
|
def quit
|
22
21
|
super
|
23
|
-
close if @
|
22
|
+
close if @deferred.nil? && @_write_buffer.empty?
|
24
23
|
end
|
25
24
|
|
26
25
|
# override the ::Rev::IO#write method try to write directly to the
|
@@ -30,16 +29,14 @@ module Rainbows
|
|
30
29
|
if @_write_buffer.empty?
|
31
30
|
begin
|
32
31
|
w = @_io.write_nonblock(buf)
|
33
|
-
if w == Rack::Utils.bytesize(buf)
|
34
|
-
return on_write_complete
|
35
|
-
end
|
32
|
+
return enable_write_watcher if w == Rack::Utils.bytesize(buf)
|
36
33
|
# we never care for the return value, but yes, we may return
|
37
34
|
# a "fake" short write from super(buf) if anybody cares.
|
38
35
|
buf = byte_slice(buf, w..-1)
|
39
36
|
rescue Errno::EAGAIN
|
40
37
|
break # fall through to super(buf)
|
41
|
-
rescue
|
42
|
-
return
|
38
|
+
rescue => e
|
39
|
+
return handle_error(e)
|
43
40
|
end while true
|
44
41
|
end
|
45
42
|
super(buf)
|
@@ -49,98 +46,129 @@ module Rainbows
|
|
49
46
|
# devices where read(2) is uninterruptable. Unfortunately, NFS and ilk
|
50
47
|
# are also part of this. We'll also stick DeferredResponse bodies in
|
51
48
|
# here to prevent connections from being closed on us.
|
52
|
-
def defer_body(io
|
53
|
-
@
|
54
|
-
|
49
|
+
def defer_body(io)
|
50
|
+
@deferred = io
|
51
|
+
enable_write_watcher
|
55
52
|
end
|
56
53
|
|
57
|
-
|
58
|
-
|
54
|
+
# allows enabling of write watcher even when read watcher is disabled
|
55
|
+
def evloop
|
56
|
+
Rainbows::Rev::Server::LOOP
|
59
57
|
end
|
60
58
|
|
61
|
-
def
|
62
|
-
|
59
|
+
def next!
|
60
|
+
@deferred = nil
|
61
|
+
on_write_complete
|
62
|
+
end
|
63
63
|
|
64
|
-
|
65
|
-
|
64
|
+
def timeout?
|
65
|
+
@deferred.nil? && @_write_buffer.empty? and close.nil?
|
66
|
+
end
|
66
67
|
|
67
|
-
|
68
|
-
|
69
|
-
|
68
|
+
# used for streaming sockets and pipes
|
69
|
+
def stream_response(status, headers, io, body)
|
70
|
+
c = stream_response_headers(status, headers) if headers
|
71
|
+
# we only want to attach to the Rev::Loop belonging to the
|
72
|
+
# main thread in Ruby 1.9
|
73
|
+
io = (c ? DeferredChunkResponse : DeferredResponse).new(io, self, body)
|
74
|
+
defer_body(io.attach(Server::LOOP))
|
75
|
+
end
|
70
76
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
def rev_write_response(response, alive)
|
78
|
+
status, headers, body = response
|
79
|
+
headers = @hp.headers? ? HH.new(headers) : nil
|
80
|
+
|
81
|
+
headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE if headers
|
82
|
+
if body.respond_to?(:to_path)
|
83
|
+
io = body_to_io(body)
|
84
|
+
st = io.stat
|
85
|
+
|
86
|
+
if st.file?
|
87
|
+
offset, count = 0, st.size
|
88
|
+
if headers
|
89
|
+
if range = make_range!(@env, status, headers)
|
90
|
+
status, offset, count = range
|
91
|
+
end
|
92
|
+
write(response_header(status, headers))
|
93
|
+
end
|
94
|
+
return defer_body(F.new(offset, count, io, body))
|
95
|
+
elsif st.socket? || st.pipe?
|
96
|
+
return stream_response(status, headers, io, body)
|
80
97
|
end
|
81
|
-
|
82
|
-
# we only want to attach to the Rev::Loop belonging to the
|
83
|
-
# main thread in Ruby 1.9
|
84
|
-
io = DeferredResponse.new(io, self, do_chunk, body).
|
85
|
-
attach(Server::LOOP)
|
86
|
-
elsif st.file?
|
87
|
-
headers.delete('Transfer-Encoding')
|
88
|
-
headers['Content-Length'] ||= st.size.to_s
|
89
|
-
io = to_sendfile(io)
|
90
|
-
else # char/block device, directory, whatever... nobody cares
|
91
|
-
return write_response(self, response, out)
|
98
|
+
# char or block device... WTF? fall through to body.each
|
92
99
|
end
|
93
|
-
|
94
|
-
|
100
|
+
write(response_header(status, headers)) if headers
|
101
|
+
write_body_each(self, body, nil)
|
95
102
|
end
|
96
103
|
|
97
104
|
def app_call
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
105
|
+
KATO.delete(self)
|
106
|
+
@env[RACK_INPUT] = @input
|
107
|
+
@env[REMOTE_ADDR] = @remote_addr
|
108
|
+
response = APP.call(@env.update(RACK_DEFAULTS))
|
109
|
+
|
110
|
+
rev_write_response(response, alive = @hp.keepalive? && G.alive)
|
111
|
+
return quit unless alive && :close != @state
|
112
|
+
@env.clear
|
113
|
+
@hp.reset
|
114
|
+
@state = :headers
|
115
|
+
disable if enabled?
|
116
|
+
end
|
117
|
+
|
118
|
+
def on_write_complete
|
119
|
+
case @deferred
|
120
|
+
when DeferredResponse then return
|
121
|
+
when NilClass # fall through
|
122
|
+
else
|
123
|
+
begin
|
124
|
+
return rev_sendfile(@deferred)
|
125
|
+
rescue EOFError # expected at file EOF
|
126
|
+
close_deferred
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
case @state
|
131
|
+
when :close
|
132
|
+
close if @_write_buffer.empty?
|
133
|
+
when :headers
|
134
|
+
if @hp.headers(@env, @buf)
|
135
|
+
app_call
|
114
136
|
else
|
115
|
-
|
137
|
+
unless enabled?
|
138
|
+
enable
|
139
|
+
KATO[self] = Time.now
|
140
|
+
end
|
116
141
|
end
|
117
|
-
|
118
|
-
|
142
|
+
end
|
143
|
+
rescue => e
|
144
|
+
handle_error(e)
|
119
145
|
end
|
120
146
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
147
|
+
def handle_error(e)
|
148
|
+
close_deferred
|
149
|
+
if msg = Error.response(e)
|
150
|
+
@_io.write_nonblock(msg) rescue nil
|
151
|
+
end
|
152
|
+
@_write_buffer.clear
|
153
|
+
ensure
|
154
|
+
quit
|
155
|
+
end
|
125
156
|
|
157
|
+
def close_deferred
|
158
|
+
case @deferred
|
159
|
+
when DeferredResponse, NilClass
|
160
|
+
else
|
126
161
|
begin
|
127
|
-
|
128
|
-
rescue EOFError # expected at file EOF
|
129
|
-
@deferred_bodies.shift
|
130
|
-
body.close
|
131
|
-
close if :close == @state && @deferred_bodies.empty?
|
162
|
+
@deferred.close
|
132
163
|
rescue => e
|
133
|
-
|
164
|
+
G.server.logger.error("closing #@deferred: #{e}")
|
134
165
|
end
|
135
|
-
|
136
|
-
close if :close == @state
|
166
|
+
@deferred = nil
|
137
167
|
end
|
138
168
|
end
|
139
169
|
|
140
170
|
def on_close
|
141
|
-
|
142
|
-
DeferredResponse === f or f.close
|
143
|
-
end
|
171
|
+
close_deferred
|
144
172
|
CONN.delete(self)
|
145
173
|
end
|
146
174
|
|