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
@@ -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