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.
Files changed (57) hide show
  1. data/Documentation/comparison.haml +1 -1
  2. data/GIT-VERSION-GEN +1 -1
  3. data/GNUmakefile +2 -1
  4. data/Rakefile +16 -3
  5. data/Static_Files +11 -11
  6. data/TODO +1 -6
  7. data/lib/rainbows.rb +3 -2
  8. data/lib/rainbows/base.rb +12 -9
  9. data/lib/rainbows/const.rb +2 -4
  10. data/lib/rainbows/dev_fd_response.rb +16 -13
  11. data/lib/rainbows/error.rb +2 -0
  12. data/lib/rainbows/ev_core.rb +13 -0
  13. data/lib/rainbows/event_machine.rb +85 -128
  14. data/lib/rainbows/event_machine/response_chunk_pipe.rb +25 -0
  15. data/lib/rainbows/event_machine/response_pipe.rb +30 -0
  16. data/lib/rainbows/event_machine/try_defer.rb +27 -0
  17. data/lib/rainbows/fiber/body.rb +6 -4
  18. data/lib/rainbows/fiber/io.rb +4 -4
  19. data/lib/rainbows/fiber/rev.rb +16 -8
  20. data/lib/rainbows/http_response.rb +5 -6
  21. data/lib/rainbows/response.rb +37 -22
  22. data/lib/rainbows/response/body.rb +19 -16
  23. data/lib/rainbows/response/range.rb +34 -0
  24. data/lib/rainbows/rev.rb +2 -1
  25. data/lib/rainbows/rev/client.rb +105 -77
  26. data/lib/rainbows/rev/deferred_chunk_response.rb +16 -0
  27. data/lib/rainbows/rev/deferred_response.rb +16 -24
  28. data/lib/rainbows/rev/sendfile.rb +4 -13
  29. data/lib/rainbows/rev/thread.rb +3 -12
  30. data/lib/rainbows/rev_thread_pool.rb +2 -2
  31. data/lib/rainbows/revactor.rb +16 -9
  32. data/lib/rainbows/revactor/body.rb +42 -0
  33. data/lib/rainbows/revactor/proxy.rb +55 -0
  34. data/lib/rainbows/sendfile.rb +12 -14
  35. data/lib/rainbows/stream_file.rb +3 -3
  36. data/lib/rainbows/writer_thread_pool.rb +12 -12
  37. data/lib/rainbows/writer_thread_spawn.rb +6 -7
  38. data/t/GNUmakefile +2 -2
  39. data/t/close-pipe-response.ru +25 -0
  40. data/t/cramp/rainsocket.ru +1 -1
  41. data/t/fast-pipe-response.ru +10 -0
  42. data/t/file-wrap-to_path.ru +24 -0
  43. data/t/t0015-working_directory.sh +5 -1
  44. data/t/t0020-large-sendfile-response.sh +3 -3
  45. data/t/t0021-sendfile-wrap-to_path.sh +108 -0
  46. data/t/t0022-copy_stream-byte-range.sh +139 -0
  47. data/t/t0023-sendfile-byte-range.sh +63 -0
  48. data/t/t0024-pipelined-sendfile-response.sh +89 -0
  49. data/t/t0030-fast-pipe-response.sh +63 -0
  50. data/t/t0031-close-pipe-response.sh +96 -0
  51. data/t/t0034-pipelined-pipe-response.sh +87 -0
  52. data/t/t0105-rack-input-limit-bigger.sh +5 -2
  53. data/t/t0500-cramp-streaming.sh +0 -1
  54. data/t/t0501-cramp-rainsocket.sh +1 -0
  55. data/t/t9000-rack-app-pool.sh +1 -1
  56. data/t/test_isolate.rb +1 -0
  57. metadata +29 -5
@@ -89,7 +89,7 @@
89
89
  %td.mod RevThreadPool
90
90
  %td.tee No
91
91
  %td.r18 Yes
92
- %td.r19 Yes
92
+ %td.r19 No
93
93
  %td.rbx No
94
94
  %td.slow Yes
95
95
  %tr.comp_row
data/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.95.1.GIT
4
+ DEF_VER=v0.96.0.GIT
5
5
 
6
6
  LF='
7
7
  '
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.94.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
- IO.select([STDIN], nil, nil, 1) or abort "E: news must be read from stdin"
109
- msg = STDIN.readlines
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
- Revactor, NeverBlock and EventMachine (see below).
27
+ NeverBlock and EventMachine (see below).
28
28
 
29
- The sendfile gem is less buggy than current (Ruby 1.9.2-rc1)
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.2-rc1 (and before) is also
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) and will be in the next
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
- the mmap()-based FileStreamer class distributed with EventMachine.
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
- Future releases of \Rainbows! will have byte-range support to serve
67
- partial and multipart responses, too. We'll also support an open file
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/Revactor/NeverBlock
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
- require 'rainbows/base'
38
- require 'rainbows/tee_input'
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
- response = app.call(env.update(RACK_DEFAULTS))
66
+ status, headers, body = app.call(env.update(RACK_DEFAULTS))
69
67
 
70
- if 100 == response[0].to_i
68
+ if 100 == status.to_i
71
69
  client.write(EXPECT_100_RESPONSE)
72
70
  env.delete(HTTP_EXPECT)
73
- response = app.call(env)
71
+ status, headers, body = app.call(env)
74
72
  end
75
73
 
76
- alive = hp.keepalive? && G.alive
77
- out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if hp.headers?
78
- write_response(client, response, out)
79
- end while alive and hp.reset.nil? and env.clear
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
@@ -3,7 +3,7 @@
3
3
  module Rainbows
4
4
 
5
5
  module Const
6
- RAINBOWS_VERSION = '0.95.1'
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, 'rb') if body.respond_to?(: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
- if env['rainbows.autochunk']
44
- headers['Transfer-Encoding'] = 'chunked'
45
- headers.delete('Content-Length')
46
- else
47
- headers['X-Rainbows-Autochunk'] = 'no'
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
- return [ status, headers, Rainbows::Fiber::IO.new(io,::Fiber.current) ]
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/#{io.fileno}") ]
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
- begin
77
- to_io.close if to_io.respond_to?(:close)
78
- rescue IOError # could've been IO::new()'ed and closed
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:
@@ -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
@@ -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
- alias receive_data on_read
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
- begin
72
- @env[RACK_INPUT] = @input
73
- @env[REMOTE_ADDR] = @remote_addr
74
- @env[ASYNC_CALLBACK] = method(:em_write_response)
75
-
76
- # we're not sure if anybody uses this, but Thin sets it, too
77
- @env[ASYNC_CLOSE] = EM::DefaultDeferrable.new
78
-
79
- response = catch(:async) { APP.call(@env.update(RACK_DEFAULTS)) }
80
-
81
- # too tricky to support pipelining with :async since the
82
- # second (pipelined) request could be a stuck behind a
83
- # long-running async response
84
- (response.nil? || -1 == response[0]) and return @state = :close
85
-
86
- alive = @hp.keepalive? && G.alive
87
- out = [ alive ? CONN_ALIVE : CONN_CLOSE ] if @hp.headers?
88
- em_write_response(response, out, alive)
89
-
90
- if alive
91
- @env.clear
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
- return
99
- end while true
108
+ end
100
109
  end
101
110
 
102
- def em_write_response(response, out = [ CONN_CLOSE ], alive = false)
103
- @body = body = response[2]
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
- write_header(self, response, out)
108
- write_body_each(self, body)
109
- return
110
- elsif ! body.respond_to?(:to_path)
111
- write_response(self, response, out)
112
- quit unless alive
113
- return
114
- end
115
-
116
- headers = Rack::Utils::HeaderHash.new(response[1])
117
- io = body_to_io(body)
118
- st = io.stat
119
-
120
- if st.file?
121
- headers.delete('Transfer-Encoding')
122
- headers['Content-Length'] ||= st.size.to_s
123
- write_header(self, [ response[0], headers ], out)
124
- stream = stream_file_data(body.to_path)
125
- stream.callback { quit } unless alive
126
- elsif st.socket? || st.pipe?
127
- do_chunk = !!(headers['Transfer-Encoding'] =~ %r{\Achunked\z}i)
128
- do_chunk = false if headers.delete('X-Rainbows-Autochunk') == 'no'
129
- if out.nil?
130
- do_chunk = false
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
- else
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
- data = begin
174
- @io.read_nonblock(16384)
175
- rescue Errno::EINTR
176
- retry
177
- rescue Errno::EAGAIN
178
- return
179
- rescue EOFError
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