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