yahns 0.0.1 → 0.0.2

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Documentation/.gitignore +5 -0
  4. data/Documentation/GNUmakefile +50 -0
  5. data/Documentation/yahns-rackup.txt +152 -0
  6. data/Documentation/yahns.txt +68 -0
  7. data/Documentation/yahns_config.txt +563 -0
  8. data/GIT-VERSION-GEN +1 -1
  9. data/GNUmakefile +14 -7
  10. data/HACKING +56 -0
  11. data/INSTALL +8 -0
  12. data/README +15 -2
  13. data/Rakefile +2 -2
  14. data/bin/yahns +1 -2
  15. data/bin/yahns-rackup +9 -0
  16. data/examples/yahns_multi.conf.rb +14 -4
  17. data/examples/yahns_rack_basic.conf.rb +17 -1
  18. data/extras/README +16 -0
  19. data/extras/autoindex.rb +151 -0
  20. data/extras/exec_cgi.rb +108 -0
  21. data/extras/proxy_pass.rb +210 -0
  22. data/extras/try_gzip_static.rb +208 -0
  23. data/lib/yahns.rb +5 -2
  24. data/lib/yahns/acceptor.rb +64 -22
  25. data/lib/yahns/cap_input.rb +2 -2
  26. data/lib/yahns/{client_expire_portable.rb → client_expire_generic.rb} +12 -11
  27. data/lib/yahns/{client_expire.rb → client_expire_tcpi.rb} +7 -6
  28. data/lib/yahns/config.rb +107 -22
  29. data/lib/yahns/daemon.rb +2 -0
  30. data/lib/yahns/fdmap.rb +28 -9
  31. data/lib/yahns/http_client.rb +123 -37
  32. data/lib/yahns/http_context.rb +21 -3
  33. data/lib/yahns/http_response.rb +80 -19
  34. data/lib/yahns/log.rb +23 -4
  35. data/lib/yahns/queue_epoll.rb +20 -9
  36. data/lib/yahns/queue_quitter.rb +16 -0
  37. data/lib/yahns/queue_quitter_pipe.rb +24 -0
  38. data/lib/yahns/rack.rb +0 -1
  39. data/lib/yahns/rackup_handler.rb +57 -0
  40. data/lib/yahns/server.rb +189 -59
  41. data/lib/yahns/server_mp.rb +43 -35
  42. data/lib/yahns/sigevent_pipe.rb +1 -0
  43. data/lib/yahns/socket_helper.rb +37 -11
  44. data/lib/yahns/stream_file.rb +14 -4
  45. data/lib/yahns/stream_input.rb +13 -7
  46. data/lib/yahns/tcp_server.rb +7 -0
  47. data/lib/yahns/tmpio.rb +10 -3
  48. data/lib/yahns/unix_server.rb +7 -0
  49. data/lib/yahns/wbuf.rb +19 -2
  50. data/lib/yahns/wbuf_common.rb +10 -3
  51. data/lib/yahns/wbuf_str.rb +24 -0
  52. data/lib/yahns/worker.rb +5 -26
  53. data/test/helper.rb +15 -5
  54. data/test/server_helper.rb +37 -1
  55. data/test/test_bin.rb +17 -8
  56. data/test/test_buffer_tmpdir.rb +103 -0
  57. data/test/test_client_expire.rb +71 -35
  58. data/test/test_client_max_body_size.rb +5 -13
  59. data/test/test_config.rb +1 -1
  60. data/test/test_expect_100.rb +176 -0
  61. data/test/test_extras_autoindex.rb +53 -0
  62. data/test/test_extras_exec_cgi.rb +81 -0
  63. data/test/test_extras_exec_cgi.sh +35 -0
  64. data/test/test_extras_try_gzip_static.rb +177 -0
  65. data/test/test_input.rb +128 -0
  66. data/test/test_mt_accept.rb +48 -0
  67. data/test/test_output_buffering.rb +90 -63
  68. data/test/test_rack.rb +1 -1
  69. data/test/test_rack_hijack.rb +2 -6
  70. data/test/test_reopen_logs.rb +2 -8
  71. data/test/test_serve_static.rb +104 -8
  72. data/test/test_server.rb +448 -73
  73. data/test/test_stream_file.rb +1 -1
  74. data/test/test_unix_socket.rb +72 -0
  75. data/test/test_wbuf.rb +20 -17
  76. data/yahns.gemspec +3 -0
  77. metadata +57 -5
@@ -4,35 +4,10 @@
4
4
  module Yahns::ServerMP # :nodoc:
5
5
  EXIT_SIGS = [ :QUIT, :TERM, :INT ]
6
6
 
7
- def mp_init
8
- trap(:CHLD) { @sev.sev_signal }
9
- end
10
-
11
- # reaps all unreaped workers
12
- def reap_all_workers
13
- begin
14
- wpid, status = Process.waitpid2(-1, Process::WNOHANG)
15
- wpid or return
16
- if @reexec_pid == wpid
17
- @logger.error "reaped #{status.inspect} exec()-ed"
18
- @reexec_pid = 0
19
- self.pid = @pid.chomp('.oldbin') if @pid
20
- proc_name 'master'
21
- else
22
- worker = @workers.delete(wpid)
23
- worker_id = worker ? worker.nr : "(unknown)"
24
- m = "reaped #{status.inspect} worker=#{worker_id}"
25
- status.success? ? @logger.info(m) : @logger.error(m)
26
- end
27
- rescue Errno::ECHILD
28
- return
29
- end while true
30
- end
31
-
32
7
  def maintain_worker_count
33
8
  (off = @workers.size - @worker_processes) == 0 and return
34
9
  off < 0 and return spawn_missing_workers
35
- @workers.each_pair do |wpid, worker|
10
+ @workers.each do |wpid, worker|
36
11
  worker.nr >= @worker_processes and Process.kill(:QUIT, wpid)
37
12
  end
38
13
  end
@@ -47,7 +22,7 @@ module Yahns::ServerMP # :nodoc:
47
22
  # to free some resources and drops all sig handlers.
48
23
  # traps for USR1, USR2, and HUP may be set in the after_fork Proc
49
24
  # by the user.
50
- def after_fork_internal(worker)
25
+ def worker_atfork_internal(worker)
51
26
  worker.atfork_child
52
27
 
53
28
  # daemon_pipe may be true for non-initial workers
@@ -71,8 +46,14 @@ module Yahns::ServerMP # :nodoc:
71
46
  Yahns::START.clear
72
47
  @sev.close
73
48
  @sev = Yahns::Sigevent.new
74
- worker.user(*@user) if @user
49
+ switch_user(*@user) if @user
75
50
  @user = @workers = nil
51
+ __call_hooks(@atfork_child, worker.nr)
52
+ @atfork_child = @atfork_parent = @atfork_prepare = nil
53
+ end
54
+
55
+ def __call_hooks(ary, worker_nr)
56
+ ary.each { |x| x.call(worker_nr) } if ary
76
57
  end
77
58
 
78
59
  def spawn_missing_workers
@@ -81,10 +62,13 @@ module Yahns::ServerMP # :nodoc:
81
62
  @workers.value?(worker_nr) and next
82
63
  worker = Yahns::Worker.new(worker_nr)
83
64
  @logger.info("worker=#{worker_nr} spawning...")
65
+ __call_hooks(@atfork_prepare, worker_nr)
84
66
  if pid = fork
85
67
  @workers[pid] = worker.atfork_parent
68
+ # XXX is this useful?
69
+ __call_hooks(@atfork_parent, worker_nr)
86
70
  else
87
- after_fork_internal(worker)
71
+ worker_atfork_internal(worker)
88
72
  run_mp_worker(worker)
89
73
  end
90
74
  end
@@ -106,10 +90,10 @@ module Yahns::ServerMP # :nodoc:
106
90
  begin
107
91
  @sev.kgio_wait_readable
108
92
  @sev.yahns_step
109
- reap_all_workers
93
+ reap_all
110
94
  case @sig_queue.shift
111
95
  when *EXIT_SIGS # graceful shutdown (twice for non graceful)
112
- self.listeners = []
96
+ @listeners.each(&:close).clear
113
97
  kill_each_worker(:QUIT)
114
98
  state = :QUIT
115
99
  when :USR1 # rotate logs
@@ -158,20 +142,23 @@ module Yahns::ServerMP # :nodoc:
158
142
  def run_mp_worker(worker)
159
143
  fdmap = fdmap_init_mp
160
144
  alive = true
145
+ watch = [ worker, @sev ]
161
146
  begin
162
- alive = mp_sig_handle(worker, alive)
147
+ alive = mp_sig_handle(watch, alive)
163
148
  rescue => e
164
149
  Yahns::Log.exception(@logger, "main worker loop", e)
165
- end while alive || fdmap.size > 0
150
+ end while alive || dropping(fdmap)
166
151
  exit
167
152
  ensure
168
153
  quit_finish
169
154
  end
170
155
 
171
- def mp_sig_handle(worker, alive)
156
+ def mp_sig_handle(watch, alive)
172
157
  # not performance critical
173
- r = IO.select([worker, @sev], nil, nil, alive ? nil : 0.01) and
158
+ watch.delete_if { |io| io.to_io.closed? }
159
+ if r = IO.select(watch, nil, nil, alive ? nil : 0.01)
174
160
  r[0].each { |io| io.yahns_step }
161
+ end
175
162
  case @sig_queue.shift
176
163
  when *EXIT_SIGS
177
164
  return quit_enter(alive)
@@ -180,4 +167,25 @@ module Yahns::ServerMP # :nodoc:
180
167
  end
181
168
  alive
182
169
  end
170
+
171
+ # reaps all unreaped workers/reexec processes
172
+ def reap_all
173
+ begin
174
+ wpid, status = Process.waitpid2(-1, Process::WNOHANG)
175
+ wpid or return
176
+ if @reexec_pid == wpid
177
+ @logger.error "reaped #{status.inspect} exec()-ed"
178
+ @reexec_pid = 0
179
+ self.pid = @pid.chomp('.oldbin') if @pid
180
+ proc_name('master')
181
+ else
182
+ worker = @workers.delete(wpid)
183
+ desc = worker ? "worker=#{worker.nr}" : "(unknown)"
184
+ m = "reaped #{status.inspect} #{desc}"
185
+ status.success? ? @logger.info(m) : @logger.error(m)
186
+ end
187
+ rescue Errno::ECHILD
188
+ return
189
+ end while true
190
+ end
183
191
  end
@@ -5,6 +5,7 @@ class Yahns::Sigevent # :nodoc:
5
5
  attr_reader :to_io
6
6
  def initialize
7
7
  @to_io, @wr = Kgio::Pipe.new
8
+ @to_io.close_on_exec = @wr.close_on_exec = true
8
9
  end
9
10
 
10
11
  def kgio_wait_readable
@@ -3,8 +3,24 @@
3
3
  # License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
4
4
  # this is only meant for Yahns::Server
5
5
  module Yahns::SocketHelper # :nodoc:
6
+
7
+ # Linux got SO_REUSEPORT in 3.9, BSDs have had it for ages
8
+ def so_reuseport
9
+ if defined?(Socket::SO_REUSEPORT)
10
+ Socket::SO_REUSEPORT
11
+ elsif RUBY_PLATFORM =~ /linux/
12
+ if RUBY_PLATFORM =~ /(?:alpha|mips|parisc|sparc)/
13
+ 0x0200 # untested
14
+ else
15
+ 15 # only tested on x86_64 and i686
16
+ end
17
+ else
18
+ nil
19
+ end
20
+ end
21
+
6
22
  def set_server_sockopt(sock, opt)
7
- opt = {backlog: 1024}.merge!(opt) if opt
23
+ opt = {backlog: 1024}.merge!(opt || {})
8
24
  sock.close_on_exec = true
9
25
 
10
26
  TCPSocket === sock and sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, 1)
@@ -26,6 +42,7 @@ module Yahns::SocketHelper # :nodoc:
26
42
  rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
27
43
  sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
28
44
  @logger.info("#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}")
45
+ rescue # TODO: get this fixed in rbx
29
46
  end
30
47
 
31
48
  # creates a new server, socket. address may be a HOST:PORT or
@@ -53,14 +70,14 @@ module Yahns::SocketHelper # :nodoc:
53
70
  end
54
71
  old_umask = File.umask(opt[:umask] || 0)
55
72
  begin
56
- Kgio::UNIXServer.new(address)
73
+ Yahns::UNIXServer.new(address)
57
74
  ensure
58
75
  File.umask(old_umask)
59
76
  end
60
77
  elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
61
- new_ipv6_server($1, $2.to_i, opt)
78
+ new_tcp_server($1, $2.to_i, opt.merge(ipv6: true))
62
79
  elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
63
- Kgio::TCPServer.new($1, $2.to_i)
80
+ new_tcp_server($1, $2.to_i, opt)
64
81
  else
65
82
  raise ArgumentError, "Don't know how to bind: #{address}"
66
83
  end
@@ -68,14 +85,23 @@ module Yahns::SocketHelper # :nodoc:
68
85
  sock
69
86
  end
70
87
 
71
- def new_ipv6_server(addr, port, opt)
72
- opt.key?(:ipv6only) or return Kgio::TCPServer.new(addr, port)
73
- sock = Socket.new(:AF_INET6, :SOCK_STREAM, 0)
74
- sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
88
+ def new_tcp_server(addr, port, opt)
89
+ sock = Socket.new(opt[:ipv6] ? :INET6 : :INET, :STREAM, 0)
90
+ if opt.key?(:ipv6only)
91
+ sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
92
+ end
75
93
  sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
94
+
95
+ begin
96
+ sock.setsockopt(:SOL_SOCKET, so_reuseport, 1)
97
+ rescue => e
98
+ name = sock_name(sock)
99
+ @logger.warn("failed to set SO_REUSEPORT on #{name}: #{e.message}")
100
+ end if opt[:reuseport]
101
+
76
102
  sock.bind(Socket.pack_sockaddr_in(port, addr))
77
103
  sock.autoclose = false
78
- Kgio::TCPServer.for_fd(sock.fileno)
104
+ Yahns::TCPServer.for_fd(sock.fileno)
79
105
  end
80
106
 
81
107
  # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
@@ -110,9 +136,9 @@ module Yahns::SocketHelper # :nodoc:
110
136
  sock.autoclose = false
111
137
  begin
112
138
  Socket.unpack_sockaddr_in(sock.getsockname)
113
- Kgio::TCPServer.for_fd(sock.fileno)
139
+ Yahns::TCPServer.for_fd(sock.fileno)
114
140
  rescue ArgumentError
115
- Kgio::UNIXServer.for_fd(sock.fileno)
141
+ Yahns::UNIXServer.for_fd(sock.fileno)
116
142
  end
117
143
  end
118
144
  end
@@ -6,6 +6,9 @@ require_relative 'wbuf_common'
6
6
  class Yahns::StreamFile # :nodoc:
7
7
  include Yahns::WbufCommon
8
8
 
9
+ # do not use this in your app (or any of our API)
10
+ NeedClose = Class.new(File) # :nodoc:
11
+
9
12
  def initialize(body, persist, offset, count)
10
13
  if body.respond_to?(:to_io)
11
14
  @tmpio = body.to_io
@@ -15,7 +18,16 @@ class Yahns::StreamFile # :nodoc:
15
18
  @tmpio = IO.for_fd($1.to_i)
16
19
  @tmpio.autoclose = false
17
20
  else
18
- @tmpio = File.open(path)
21
+ retried = false
22
+ begin
23
+ @tmpio = NeedClose.open(path)
24
+ rescue Errno::EMFILE, Errno::ENFILE
25
+ raise if retried
26
+ retried = true
27
+ Thread.current[:yahns_fdmap].desperate_expire_for(nil, 5)
28
+ sleep(1)
29
+ retry
30
+ end
19
31
  end
20
32
  end
21
33
  @sf_offset = offset
@@ -26,9 +38,7 @@ class Yahns::StreamFile # :nodoc:
26
38
 
27
39
  # called by last wbuf_flush
28
40
  def wbuf_close(client)
29
- if File === @tmpio && @tmpio != @body
30
- @tmpio.close
31
- end
41
+ @tmpio.close if NeedClose === @tmpio
32
42
  wbuf_close_common(client)
33
43
  end
34
44
  end
@@ -45,7 +45,7 @@ class Yahns::StreamInput # :nodoc:
45
45
  to_read = length - @rbuf.size
46
46
  rv.replace(@rbuf.slice!(0, @rbuf.size))
47
47
  until to_read == 0 || eof? || (rv.size > 0 && @chunked)
48
- @client.kgio_read(to_read, @buf) or eof!
48
+ @client.yahns_read(to_read, @buf) or eof!
49
49
  filter_body(@rbuf, @buf)
50
50
  rv << @rbuf
51
51
  to_read -= @rbuf.size
@@ -63,6 +63,10 @@ class Yahns::StreamInput # :nodoc:
63
63
  @client ? @client.class.client_body_buffer_size : nil
64
64
  end
65
65
 
66
+ def __tlsbuf
67
+ Thread.current[:yahns_rbuf]
68
+ end
69
+
66
70
  # :call-seq:
67
71
  # ios.gets => string or nil
68
72
  #
@@ -80,12 +84,13 @@ class Yahns::StreamInput # :nodoc:
80
84
  end
81
85
  re = /\A(.*?#{Regexp.escape(sep)})/
82
86
  rsize = __rsize or return
87
+ tlsbuf = __tlsbuf
83
88
  begin
84
89
  @rbuf.sub!(re, '') and return $1
85
90
  return @rbuf.empty? ? nil : @rbuf.slice!(0, @rbuf.size) if eof?
86
- @client.kgio_read(rsize, @buf) or eof!
87
- filter_body(once = '', @buf)
88
- @rbuf << once
91
+ @client.yahns_read(rsize, @buf) or eof!
92
+ filter_body(tlsbuf, @buf)
93
+ @rbuf << tlsbuf
89
94
  end while true
90
95
  end
91
96
 
@@ -105,9 +110,10 @@ class Yahns::StreamInput # :nodoc:
105
110
  def eof?
106
111
  if @parser.body_eof?
107
112
  rsize = __rsize
113
+ tlsbuf = __tlsbuf
108
114
  while @chunked && ! @parser.parse
109
- once = @client.kgio_read(rsize) or eof!
110
- @buf << once
115
+ @client.yahns_read(rsize, tlsbuf) or eof!
116
+ @buf << tlsbuf
111
117
  end
112
118
  @client = nil
113
119
  true
@@ -126,7 +132,7 @@ class Yahns::StreamInput # :nodoc:
126
132
  dst.replace(@rbuf)
127
133
  rsize = __rsize or return
128
134
  until eof?
129
- @client.kgio_read(rsize, @buf) or eof!
135
+ @client.yahns_read(rsize, @buf) or eof!
130
136
  filter_body(@rbuf, @buf)
131
137
  dst << @rbuf
132
138
  end
@@ -0,0 +1,7 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> et. al.
3
+ # License: GPLv3 or later (see COPYING for details)
4
+ require_relative 'acceptor'
5
+ class Yahns::TCPServer < Kgio::TCPServer # :nodoc:
6
+ include Yahns::Acceptor
7
+ end
data/lib/yahns/tmpio.rb CHANGED
@@ -11,11 +11,18 @@ class Yahns::TmpIO < File # :nodoc:
11
11
  # creates and returns a new File object. The File is unlinked
12
12
  # immediately, switched to binary mode, and userspace output
13
13
  # buffering is disabled
14
- def self.new
15
- fp = begin
16
- super("#{Dir.tmpdir}/#{rand}", RDWR|CREAT|EXCL, 0600)
14
+ def self.new(tmpdir = Dir.tmpdir)
15
+ retried = false
16
+ begin
17
+ fp = super("#{tmpdir}/#{rand}", RDWR|CREAT|EXCL, 0600)
17
18
  rescue Errno::EEXIST
18
19
  retry
20
+ rescue Errno::EMFILE, Errno::ENFILE
21
+ raise if retried
22
+ retried = true
23
+ Thread.current[:yahns_fdmap].desperate_expire_for(nil, 5)
24
+ sleep(1)
25
+ retry
19
26
  end
20
27
  unlink(fp.path)
21
28
  fp.binmode
@@ -0,0 +1,7 @@
1
+ # -*- encoding: binary -*-
2
+ # Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> et. al.
3
+ # License: GPLv3 or later (see COPYING for details)
4
+ require_relative 'acceptor'
5
+ class Yahns::UNIXServer < Kgio::UNIXServer # :nodoc:
6
+ include Yahns::Acceptor
7
+ end
data/lib/yahns/wbuf.rb CHANGED
@@ -6,14 +6,25 @@ require_relative 'wbuf_common'
6
6
  class Yahns::Wbuf # :nodoc:
7
7
  include Yahns::WbufCommon
8
8
 
9
- def initialize(body, persist)
10
- @tmpio = Yahns::TmpIO.new
9
+ def initialize(body, persist, tmpdir)
10
+ @tmpio = Yahns::TmpIO.new(tmpdir)
11
11
  @sf_offset = @sf_count = 0
12
12
  @wbuf_persist = persist # whether or not we keep the connection alive
13
13
  @body = body
14
+ @bypass = false
14
15
  end
15
16
 
16
17
  def wbuf_write(client, buf)
18
+ # try to bypass the VFS layer if we're all caught up
19
+ case rv = client.kgio_trywrite(buf)
20
+ when String
21
+ buf = rv # retry in loop
22
+ when nil
23
+ return # yay! hopefully we don't have to buffer again
24
+ when :wait_writable, :wait_readable
25
+ @bypass = false # ugh, continue to buffering to file
26
+ end while @bypass
27
+
17
28
  @sf_count += @tmpio.write(buf)
18
29
  case rv = client.trysendfile(@tmpio, @sf_offset, @sf_count)
19
30
  when Integer
@@ -25,6 +36,12 @@ class Yahns::Wbuf # :nodoc:
25
36
  raise "BUG: #{rv.nil ? "EOF" : rv.inspect} on tmpio " \
26
37
  "sf_offset=#@sf_offset sf_count=#@sf_count"
27
38
  end while @sf_count > 0
39
+
40
+ # we're all caught up, try to prevent dirty data from getting flushed
41
+ # to disk if we can help it.
42
+ @tmpio.truncate(@sf_offset = 0)
43
+ @tmpio.rewind
44
+ @bypass = true
28
45
  nil
29
46
  end
30
47
 
@@ -14,8 +14,16 @@ module Yahns::WbufCommon # :nodoc:
14
14
  @sf_offset += rv # keep going otherwise
15
15
  when :wait_writable, :wait_readable
16
16
  return rv
17
+ when nil
18
+ # response got truncated, drop the connection
19
+ # this may happens when using Rack::File or similar, we can't
20
+ # keep the connection alive because we already sent our Content-Length
21
+ # header the client would be confused.
22
+ @wbuf_persist = false
23
+ return wbuf_close(client)
17
24
  else
18
- raise "BUG: #{rv.nil? ? "EOF" : rv.inspect} on tmpio=#{@tmpio.inspect} " \
25
+ raise "BUG: rv=#{rv.inspect} " \
26
+ "on tmpio=#{@tmpio.inspect} " \
19
27
  "sf_offset=#@sf_offset sf_count=#@sf_count"
20
28
  end while true
21
29
  end
@@ -23,8 +31,7 @@ module Yahns::WbufCommon # :nodoc:
23
31
  def wbuf_close_common(client)
24
32
  @body.close if @body.respond_to?(:close)
25
33
  if @wbuf_persist.respond_to?(:call) # hijack
26
- @wbuf_persist.call(client)
27
- :ignore
34
+ client.response_hijacked(@wbuf_persist) # :ignore
28
35
  else
29
36
  @wbuf_persist # true or false or Yahns::StreamFile
30
37
  end