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