yahns 0.0.1 → 0.0.2

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