rainbows 0.95.1 → 0.96.0

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