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
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
@@ -62,7 +62,7 @@ NEWS: GIT-VERSION-FILE
|
|
62
62
|
$(RAKE) -s news_rdoc > $@+
|
63
63
|
mv $@+ $@
|
64
64
|
|
65
|
-
SINCE = 0.
|
65
|
+
SINCE = 0.95.0
|
66
66
|
ChangeLog: LOG_VERSION = \
|
67
67
|
$(shell git rev-parse -q "$(GIT_VERSION)" >/dev/null 2>&1 && \
|
68
68
|
echo $(GIT_VERSION) || git describe)
|
@@ -183,6 +183,7 @@ release: verify package $(release_notes) $(release_changes)
|
|
183
183
|
$(rfproject) $(rfpackage) $(VERSION) $(pkggem)
|
184
184
|
$(RAKE) raa_update VERSION=$(VERSION)
|
185
185
|
$(RAKE) fm_update VERSION=$(VERSION)
|
186
|
+
$(RAKE) publish_news VERSION=$(VERSION)
|
186
187
|
else
|
187
188
|
gem install-gem: GIT-VERSION-FILE
|
188
189
|
$(MAKE) $@ VERSION=$(GIT_VERSION)
|
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
2
|
autoload :Gem, 'rubygems'
|
3
|
+
autoload :Tempfile, 'tempfile'
|
3
4
|
|
4
5
|
# most tasks are in the GNUmakefile which offers better parallelism
|
5
6
|
|
@@ -105,8 +106,21 @@ end
|
|
105
106
|
desc "read news article from STDIN and post to rubyforge"
|
106
107
|
task :publish_news do
|
107
108
|
require 'rubyforge'
|
108
|
-
|
109
|
-
|
109
|
+
spec = Gem::Specification.load('rainbows.gemspec')
|
110
|
+
tmp = Tempfile.new('rf-news')
|
111
|
+
_, subject, body = `git cat-file tag v#{spec.version}`.split(/\n\n/, 3)
|
112
|
+
tmp.puts subject
|
113
|
+
tmp.puts
|
114
|
+
tmp.puts spec.description.strip
|
115
|
+
tmp.puts ""
|
116
|
+
tmp.puts "* #{spec.homepage}"
|
117
|
+
tmp.puts "* #{spec.email}"
|
118
|
+
tmp.puts "* #{git_url}"
|
119
|
+
tmp.print "\nChanges:\n\n"
|
120
|
+
tmp.puts body
|
121
|
+
tmp.flush
|
122
|
+
system(ENV["VISUAL"], tmp.path) or abort "#{ENV["VISUAL"]} failed: #$?"
|
123
|
+
msg = File.readlines(tmp.path)
|
110
124
|
subject = msg.shift
|
111
125
|
blank = msg.shift
|
112
126
|
blank == "\n" or abort "no newline after subject!"
|
@@ -157,7 +171,6 @@ end
|
|
157
171
|
|
158
172
|
desc "post to FM"
|
159
173
|
task :fm_update do
|
160
|
-
require 'tempfile'
|
161
174
|
require 'net/http'
|
162
175
|
require 'net/netrc'
|
163
176
|
require 'json'
|
data/Static_Files
CHANGED
@@ -10,7 +10,7 @@ to simplify your deployments and only deploy one server?
|
|
10
10
|
== {sendfile}[http://rubygems.org/gems/sendfile] RubyGem
|
11
11
|
|
12
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
|
13
|
+
"require" it in your \Rainbows!/Unicorn config file (not your Rack
|
14
14
|
config.ru):
|
15
15
|
|
16
16
|
require 'sendfile' # that's it! nothing else to do
|
@@ -24,9 +24,9 @@ config.ru):
|
|
24
24
|
end
|
25
25
|
|
26
26
|
The sendfile gem is works for all of our concurrency models except
|
27
|
-
|
27
|
+
NeverBlock and EventMachine (see below).
|
28
28
|
|
29
|
-
The sendfile gem is less buggy than current (Ruby 1.9.2-
|
29
|
+
The sendfile gem is less buggy than current (Ruby 1.9.2-rc2)
|
30
30
|
IO.copy_stream and supports FreeBSD and Solaris in addition to Linux.
|
31
31
|
This RubyGem also works under Ruby 1.8 (even with threads) and should
|
32
32
|
work with rubinius.git, too.
|
@@ -40,17 +40,18 @@ their Writer* variants use the core IO.copy_stream method under Ruby
|
|
40
40
|
1.9. IO.copy_stream uses sendfile() under Linux, and a pread()/write()
|
41
41
|
loop (implemented in C) on other systems.
|
42
42
|
|
43
|
-
IO.copy_stream under Linux with Ruby 1.9.
|
43
|
+
IO.copy_stream under Linux with Ruby 1.9.3 (and before) is also
|
44
44
|
subject to hanging indefinitely when a client disconnected prematurely.
|
45
|
-
This issue is fixed in Ruby trunk (July 2010)
|
46
|
-
Ruby 1.9.2 release.
|
45
|
+
This issue is fixed in Ruby trunk (July 2010).
|
47
46
|
|
48
47
|
\Rainbows! supports IO.copy_stream since v0.93.0
|
49
48
|
|
50
49
|
== EventMachine FileStreamer
|
51
50
|
|
52
|
-
EventMachine and NeverBlock users automatically take advantage of
|
53
|
-
|
51
|
+
EventMachine and NeverBlock users automatically take advantage of the
|
52
|
+
mmap()-based FileStreamer class distributed with EventMachine.
|
53
|
+
Unfortunately, as of EventMachine 0.12.10, FileStreamer cannot easily
|
54
|
+
support HTTP Range responses.
|
54
55
|
|
55
56
|
\Rainbows! supports EventMachine FileStreamer since v0.4.0
|
56
57
|
|
@@ -63,9 +64,8 @@ slower clients and smaller files.
|
|
63
64
|
|
64
65
|
== The Future...
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
cache (similar to nginx) which allows us to reuse open file descriptors.
|
67
|
+
We'll also support an open file cache (similar to nginx) which
|
68
|
+
allows us to reuse open file descriptors.
|
69
69
|
|
70
70
|
Under Linux, we'll support the splice(2) system call for zero-copy
|
71
71
|
proxying {io_splice}[http://bogomips.org/ruby_io_splice/], too.
|
data/TODO
CHANGED
@@ -11,13 +11,11 @@ care about.
|
|
11
11
|
* allow _OPTIONAL_ splice(2) with DevFdResponse under Linux
|
12
12
|
(splice is very broken under some older kernels)
|
13
13
|
|
14
|
-
* use IO#sendfile_nonblock for EventMachine/
|
14
|
+
* use IO#sendfile_nonblock for EventMachine/NeverBlock
|
15
15
|
|
16
16
|
* Open file cache Rack app/middleware (idea from nginx), since sendfile
|
17
17
|
(and IO.copy_stream) allows pread(2)-style offsets
|
18
18
|
|
19
|
-
* Byte range responses for static files + sendfile
|
20
|
-
|
21
19
|
* Improve test suite coverage. We won't waste cycles with puny
|
22
20
|
unit tests, only integration tests that exercise externally
|
23
21
|
visible parts.
|
@@ -36,7 +34,4 @@ care about.
|
|
36
34
|
|
37
35
|
* Packet - pure Ruby, EventMachine-like library
|
38
36
|
|
39
|
-
* Rubinius Actors - should be like Revactor and easily doable once
|
40
|
-
Rubinius gets more mature.
|
41
|
-
|
42
37
|
* test and improve performance (throughput/latency/memory usage)
|
data/lib/rainbows.rb
CHANGED
@@ -29,13 +29,14 @@ module Rainbows
|
|
29
29
|
end
|
30
30
|
G = State.new(true, 0, 0, 5)
|
31
31
|
O = {}
|
32
|
+
class Response416 < RangeError; end
|
32
33
|
# :startdoc:
|
33
34
|
|
34
35
|
require 'rainbows/const'
|
35
36
|
require 'rainbows/http_server'
|
36
37
|
require 'rainbows/response'
|
37
|
-
|
38
|
-
|
38
|
+
autoload :Base, 'rainbows/base'
|
39
|
+
autoload :TeeInput, 'rainbows/tee_input'
|
39
40
|
autoload :Sendfile, 'rainbows/sendfile'
|
40
41
|
autoload :AppPool, 'rainbows/app_pool'
|
41
42
|
autoload :DevFdResponse, 'rainbows/dev_fd_response'
|
data/lib/rainbows/base.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
# -*- encoding: binary -*-
|
2
|
-
require 'rainbows/tee_input'
|
3
2
|
|
4
3
|
# base class for Rainbows concurrency models, this is currently used by
|
5
4
|
# ThreadSpawn and ThreadPool models. Base is also its own
|
@@ -52,7 +51,6 @@ module Rainbows::Base
|
|
52
51
|
buf = client.readpartial(CHUNK_SIZE) # accept filters protect us here
|
53
52
|
hp = HttpParser.new
|
54
53
|
env = {}
|
55
|
-
alive = true
|
56
54
|
remote_addr = Rainbows.addr(client)
|
57
55
|
|
58
56
|
begin # loop
|
@@ -65,18 +63,23 @@ module Rainbows::Base
|
|
65
63
|
env[RACK_INPUT] = 0 == hp.content_length ?
|
66
64
|
NULL_IO : TeeInput.new(client, env, hp, buf)
|
67
65
|
env[REMOTE_ADDR] = remote_addr
|
68
|
-
|
66
|
+
status, headers, body = app.call(env.update(RACK_DEFAULTS))
|
69
67
|
|
70
|
-
if 100 ==
|
68
|
+
if 100 == status.to_i
|
71
69
|
client.write(EXPECT_100_RESPONSE)
|
72
70
|
env.delete(HTTP_EXPECT)
|
73
|
-
|
71
|
+
status, headers, body = app.call(env)
|
74
72
|
end
|
75
73
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
74
|
+
if hp.headers?
|
75
|
+
headers = HH.new(headers)
|
76
|
+
range = make_range!(env, status, headers) and status = range.shift
|
77
|
+
env = false unless hp.keepalive? && G.alive
|
78
|
+
headers[CONNECTION] = env ? KEEP_ALIVE : CLOSE
|
79
|
+
client.write(response_header(status, headers))
|
80
|
+
end
|
81
|
+
write_body(client, body, range)
|
82
|
+
end while env && env.clear && hp.reset.nil?
|
80
83
|
# if we get any error, try to write something back to the client
|
81
84
|
# assuming we haven't closed the socket, but don't get hung up
|
82
85
|
# if the socket is already closed or broken. We'll always ensure
|
data/lib/rainbows/const.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
module Rainbows
|
4
4
|
|
5
5
|
module Const
|
6
|
-
RAINBOWS_VERSION = '0.
|
6
|
+
RAINBOWS_VERSION = '0.96.0'
|
7
7
|
|
8
8
|
include Unicorn::Const
|
9
9
|
|
@@ -15,9 +15,6 @@ module Rainbows
|
|
15
15
|
# "rainbows.autochunk" => false,
|
16
16
|
})
|
17
17
|
|
18
|
-
CONN_CLOSE = "Connection: close\r\n"
|
19
|
-
CONN_ALIVE = "Connection: keep-alive\r\n"
|
20
|
-
|
21
18
|
# client IO object that supports reading and writing directly
|
22
19
|
# without filtering it through the HTTP chunk parser.
|
23
20
|
# Maybe we can get this renamed to "rack.io" if it becomes part
|
@@ -25,6 +22,7 @@ module Rainbows
|
|
25
22
|
CLIENT_IO = "hack.io".freeze
|
26
23
|
|
27
24
|
ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
|
25
|
+
ERROR_416_RESPONSE = "HTTP/1.1 416 Requested Range Not Satisfiable\r\n\r\n"
|
28
26
|
|
29
27
|
end
|
30
28
|
end
|
@@ -31,34 +31,38 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
31
31
|
return response if STATUS_WITH_NO_ENTITY_BODY.include?(status)
|
32
32
|
|
33
33
|
io = body.to_io if body.respond_to?(:to_io)
|
34
|
-
io ||= File.open(body.to_path
|
34
|
+
io ||= File.open(body.to_path) if body.respond_to?(:to_path)
|
35
35
|
return response if io.nil?
|
36
36
|
|
37
37
|
headers = HeaderHash.new(headers)
|
38
38
|
st = io.stat
|
39
|
+
fileno = io.fileno
|
39
40
|
if st.file?
|
40
41
|
headers['Content-Length'] ||= st.size.to_s
|
41
42
|
headers.delete('Transfer-Encoding')
|
42
43
|
elsif st.pipe? || st.socket? # epoll-able things
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
unless headers['Content-Length']
|
45
|
+
if env['rainbows.autochunk']
|
46
|
+
headers['Transfer-Encoding'] = 'chunked'
|
47
|
+
else
|
48
|
+
headers['X-Rainbows-Autochunk'] = 'no'
|
49
|
+
end
|
48
50
|
end
|
49
51
|
|
50
52
|
# we need to make sure our pipe output is Fiber-compatible
|
51
53
|
case env["rainbows.model"]
|
52
54
|
when :FiberSpawn, :FiberPool, :RevFiberSpawn
|
53
|
-
|
55
|
+
io = Rainbows::Fiber::IO.new(io,::Fiber.current)
|
56
|
+
when :Revactor
|
57
|
+
io = Rainbows::Revactor::Proxy.new(io)
|
54
58
|
end
|
55
59
|
else # unlikely, char/block device file, directory, ...
|
56
60
|
return response
|
57
61
|
end
|
58
|
-
[ status, headers, Body.new(io, "/dev/fd/#{
|
62
|
+
[ status, headers, Body.new(io, "/dev/fd/#{fileno}", body) ]
|
59
63
|
end
|
60
64
|
|
61
|
-
class Body < Struct.new(:to_io, :to_path)
|
65
|
+
class Body < Struct.new(:to_io, :to_path, :orig_body)
|
62
66
|
# called by the webserver or other middlewares if they can't
|
63
67
|
# handle #to_path
|
64
68
|
def each(&block)
|
@@ -73,10 +77,9 @@ class Rainbows::DevFdResponse < Struct.new(:app)
|
|
73
77
|
|
74
78
|
# called by the web server after #each
|
75
79
|
def close
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
80
|
+
to_io.close unless to_io.closed?
|
81
|
+
orig_body.close if orig_body.respond_to?(:close) # may not be an IO
|
82
|
+
rescue IOError # could've been IO::new()'ed and closed
|
80
83
|
end
|
81
84
|
end
|
82
85
|
#:startdoc:
|
data/lib/rainbows/error.rb
CHANGED
@@ -32,6 +32,8 @@ module Rainbows
|
|
32
32
|
when EOFError, Errno::ECONNRESET, Errno::EPIPE, Errno::EINVAL,
|
33
33
|
Errno::EBADF, Errno::ENOTCONN
|
34
34
|
# swallow error if client shuts down one end or disconnects
|
35
|
+
when Rainbows::Response416
|
36
|
+
Const::ERROR_416_RESPONSE
|
35
37
|
when Unicorn::HttpParserError
|
36
38
|
Const::ERROR_400_RESPONSE # try to tell the client they're bad
|
37
39
|
when IOError # HttpParserError is an IOError
|
data/lib/rainbows/ev_core.rb
CHANGED
@@ -6,6 +6,7 @@ module Rainbows
|
|
6
6
|
module EvCore
|
7
7
|
include Unicorn
|
8
8
|
include Rainbows::Const
|
9
|
+
include Rainbows::Response
|
9
10
|
G = Rainbows::G
|
10
11
|
NULL_IO = Unicorn::HttpRequest::NULL_IO
|
11
12
|
|
@@ -33,6 +34,18 @@ module Rainbows
|
|
33
34
|
quit
|
34
35
|
end
|
35
36
|
|
37
|
+
# returns whether to enable response chunking for autochunk models
|
38
|
+
def stream_response_headers(status, headers)
|
39
|
+
if headers['Content-Length']
|
40
|
+
rv = false
|
41
|
+
else
|
42
|
+
rv = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
|
43
|
+
rv = false if headers.delete('X-Rainbows-Autochunk') == 'no'
|
44
|
+
end
|
45
|
+
write(response_header(status, headers))
|
46
|
+
rv
|
47
|
+
end
|
48
|
+
|
36
49
|
# TeeInput doesn't map too well to this right now...
|
37
50
|
def on_read(data)
|
38
51
|
case @state
|
@@ -47,10 +47,13 @@ module Rainbows
|
|
47
47
|
module EventMachine
|
48
48
|
|
49
49
|
include Base
|
50
|
+
autoload :ResponsePipe, 'rainbows/event_machine/response_pipe'
|
51
|
+
autoload :ResponseChunkPipe, 'rainbows/event_machine/response_chunk_pipe'
|
52
|
+
autoload :TryDefer, 'rainbows/event_machine/try_defer'
|
50
53
|
|
51
54
|
class Client < EM::Connection # :nodoc: all
|
55
|
+
attr_writer :body
|
52
56
|
include Rainbows::EvCore
|
53
|
-
include Rainbows::Response
|
54
57
|
G = Rainbows::G
|
55
58
|
|
56
59
|
def initialize(io)
|
@@ -59,7 +62,19 @@ module Rainbows
|
|
59
62
|
end
|
60
63
|
|
61
64
|
alias write send_data
|
62
|
-
|
65
|
+
|
66
|
+
def receive_data(data)
|
67
|
+
# To avoid clobbering the current streaming response
|
68
|
+
# (often a static file), we do not attempt to process another
|
69
|
+
# request on the same connection until the first is complete
|
70
|
+
if @body
|
71
|
+
@buf << data
|
72
|
+
@_io.shutdown(Socket::SHUT_RD) if @buf.size > 0x1c000
|
73
|
+
return EM.next_tick { receive_data('') }
|
74
|
+
else
|
75
|
+
on_read(data)
|
76
|
+
end
|
77
|
+
end
|
63
78
|
|
64
79
|
def quit
|
65
80
|
super
|
@@ -68,122 +83,88 @@ module Rainbows
|
|
68
83
|
|
69
84
|
def app_call
|
70
85
|
set_comm_inactivity_timeout 0
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
@hp.reset
|
93
|
-
@state = :headers
|
94
|
-
# keepalive requests are always body-less, so @input is unchanged
|
95
|
-
@hp.headers(@env, @buf) and next
|
96
|
-
set_comm_inactivity_timeout G.kato
|
86
|
+
@env[RACK_INPUT] = @input
|
87
|
+
@env[REMOTE_ADDR] = @remote_addr
|
88
|
+
@env[ASYNC_CALLBACK] = method(:em_write_response)
|
89
|
+
@env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
|
90
|
+
|
91
|
+
response = catch(:async) { APP.call(@env.update(RACK_DEFAULTS)) }
|
92
|
+
|
93
|
+
# too tricky to support pipelining with :async since the
|
94
|
+
# second (pipelined) request could be a stuck behind a
|
95
|
+
# long-running async response
|
96
|
+
(response.nil? || -1 == response[0]) and return @state = :close
|
97
|
+
|
98
|
+
em_write_response(response, alive = @hp.keepalive? && G.alive)
|
99
|
+
if alive
|
100
|
+
@env.clear
|
101
|
+
@hp.reset
|
102
|
+
@state = :headers
|
103
|
+
if @buf.empty?
|
104
|
+
set_comm_inactivity_timeout(G.kato)
|
105
|
+
else
|
106
|
+
EM.next_tick { receive_data('') }
|
97
107
|
end
|
98
|
-
|
99
|
-
end while true
|
108
|
+
end
|
100
109
|
end
|
101
110
|
|
102
|
-
def em_write_response(response,
|
103
|
-
|
111
|
+
def em_write_response(response, alive = false)
|
112
|
+
status, headers, body = response
|
113
|
+
if @hp.headers?
|
114
|
+
headers = HH.new(headers)
|
115
|
+
headers[CONNECTION] = alive ? KEEP_ALIVE : CLOSE
|
116
|
+
else
|
117
|
+
headers = nil
|
118
|
+
end
|
119
|
+
|
104
120
|
if body.respond_to?(:errback) && body.respond_to?(:callback)
|
121
|
+
@body = body
|
105
122
|
body.callback { quit }
|
106
123
|
body.errback { quit }
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
elsif
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
else
|
132
|
-
out[0] = CONN_CLOSE
|
133
|
-
end
|
134
|
-
write_header(self, [ response[0], headers ], out)
|
135
|
-
if do_chunk
|
136
|
-
EM.watch(io, ResponseChunkPipe, self).notify_readable = true
|
137
|
-
else
|
138
|
-
EM.enable_proxy(EM.attach(io, ResponsePipe, self), self, 16384)
|
124
|
+
# async response, this could be a trickle as is in comet-style apps
|
125
|
+
headers[CONNECTION] = CLOSE if headers
|
126
|
+
alive = true
|
127
|
+
elsif body.respond_to?(:to_path)
|
128
|
+
st = File.stat(path = body.to_path)
|
129
|
+
|
130
|
+
if st.file?
|
131
|
+
write(response_header(status, headers)) if headers
|
132
|
+
@body = stream_file_data(path)
|
133
|
+
@body.errback do
|
134
|
+
body.close if body.respond_to?(:close)
|
135
|
+
quit
|
136
|
+
end
|
137
|
+
@body.callback do
|
138
|
+
body.close if body.respond_to?(:close)
|
139
|
+
@body = nil
|
140
|
+
alive ? receive_data('') : quit
|
141
|
+
end
|
142
|
+
return
|
143
|
+
elsif st.socket? || st.pipe?
|
144
|
+
@body = io = body_to_io(body)
|
145
|
+
chunk = stream_response_headers(status, headers) if headers
|
146
|
+
m = chunk ? ResponseChunkPipe : ResponsePipe
|
147
|
+
return EM.watch(io, m, self, alive, body).notify_readable = true
|
139
148
|
end
|
140
|
-
|
141
|
-
write_response(self, response, out)
|
149
|
+
# char or block device... WTF? fall through to body.each
|
142
150
|
end
|
151
|
+
|
152
|
+
write(response_header(status, headers)) if headers
|
153
|
+
write_body_each(self, body)
|
154
|
+
quit unless alive
|
143
155
|
end
|
144
156
|
|
145
157
|
def unbind
|
146
158
|
async_close = @env[ASYNC_CLOSE] and async_close.succeed
|
147
159
|
@body.respond_to?(:fail) and @body.fail
|
148
|
-
@_io.close
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
module ResponsePipe # :nodoc: all
|
153
|
-
def initialize(client)
|
154
|
-
@client = client
|
155
|
-
end
|
156
|
-
|
157
|
-
def unbind
|
158
|
-
@io.close
|
159
|
-
@client.quit
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
module ResponseChunkPipe # :nodoc: all
|
164
|
-
include ResponsePipe
|
165
|
-
|
166
|
-
def unbind
|
167
|
-
@client.write("0\r\n\r\n")
|
168
|
-
super
|
169
|
-
end
|
170
|
-
|
171
|
-
def notify_readable
|
172
160
|
begin
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
detach
|
181
|
-
return
|
182
|
-
end
|
183
|
-
@client.send_data(sprintf("%x\r\n", data.size))
|
184
|
-
@client.send_data(data)
|
185
|
-
@client.send_data("\r\n")
|
186
|
-
end while true
|
161
|
+
@_io.close
|
162
|
+
rescue Errno::EBADF
|
163
|
+
# EventMachine's EventableDescriptor::Close() may close
|
164
|
+
# the underlying file descriptor without invalidating the
|
165
|
+
# associated IO object on errors, so @_io.closed? isn't
|
166
|
+
# sufficient.
|
167
|
+
end
|
187
168
|
end
|
188
169
|
end
|
189
170
|
|
@@ -202,30 +183,6 @@ module Rainbows
|
|
202
183
|
end
|
203
184
|
end
|
204
185
|
|
205
|
-
# Middleware that will run the app dispatch in a separate thread.
|
206
|
-
# This middleware is automatically loaded by Rainbows! when using
|
207
|
-
# EventMachine and if the app responds to the +deferred?+ method.
|
208
|
-
class TryDefer < Struct.new(:app) # :nodoc: all
|
209
|
-
|
210
|
-
def initialize(app)
|
211
|
-
# the entire app becomes multithreaded, even the root (non-deferred)
|
212
|
-
# thread since any thread can share processes with others
|
213
|
-
Const::RACK_DEFAULTS['rack.multithread'] = true
|
214
|
-
super
|
215
|
-
end
|
216
|
-
|
217
|
-
def call(env)
|
218
|
-
if app.deferred?(env)
|
219
|
-
EM.defer(proc { catch(:async) { app.call(env) } },
|
220
|
-
env[EvCore::ASYNC_CALLBACK])
|
221
|
-
# all of the async/deferred stuff breaks Rack::Lint :<
|
222
|
-
nil
|
223
|
-
else
|
224
|
-
app.call(env)
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|
228
|
-
|
229
186
|
def init_worker_process(worker) # :nodoc:
|
230
187
|
Rainbows::Response.setup(Rainbows::EventMachine::Client)
|
231
188
|
super
|