yahns 1.12.3 → 1.12.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: be964a6d1a0a80744bd419b896f4aa83cdadbd06
4
- data.tar.gz: 907c5f15f7d6c2d49609326b1a8175d56a09cad7
3
+ metadata.gz: 9dc252e71729c27f38d5ef8bb6f82274d80408e5
4
+ data.tar.gz: ba8aa4cbf09b75919f065ba64924f1f6e76603d8
5
5
  SHA512:
6
- metadata.gz: 7bb344b77483d6fbb091b721fcc7b7d6f789738aa8552f7c7b85e856ca15b2edd2dd12b299b5af80a5d1484bded048c31599c5c187433bbf37987910a6effd47
7
- data.tar.gz: 0d1226f4bddf82eb366f71d020cafaedf0b7a0b63eed07792588d902f14aaf26cebee084d8e07379455bf91c1653823887ad35234d20abb09c6581aaab7818b1
6
+ metadata.gz: d768bd39c63353df5d086f6a5c1dfcb7c5e4725b4198ac2bf273a2c43865b3f804a1c4cd0d029bc8b95dc0eb4ecde6803e49a56eeba936df50da11a5458896ed
7
+ data.tar.gz: e0a962d0514d0c0fd19ab83b3109c0b75ed07e551900c87307c0fba8300b346b1bedd9bb84036ede20ce0bc55fff509c1306e53f5c0074e0aa2e0e58ca8da10b
@@ -159,6 +159,16 @@ The RACK_ENV variable is set by the aforementioned -E switch.
159
159
  If RACK_ENV is already set, it will be used unless -E is used.
160
160
  See rackup documentation for more details.
161
161
 
162
+ =head1 CAVEATS
163
+
164
+ yahns is strict about buggy, non-compliant Rack applications.
165
+ Some existing servers work fine without "Content-Length" or
166
+ "Transfer-Encoding: chunked" response headers enforced by Rack::Lint.
167
+ Forgetting these headers with yahns causes clients to stall as they
168
+ assume more data is coming. Loading the Rack::ContentLength and/or
169
+ Rack::Chunked middlewares will set the necessary response headers
170
+ and fix your app.
171
+
162
172
  =head1 CONTACT
163
173
 
164
174
  All feedback welcome via plain-text mail to L<mailto:yahns-public@yhbt.net>
@@ -5,7 +5,7 @@
5
5
  CONSTANT = "Yahns::VERSION"
6
6
  RVF = "lib/yahns/version.rb"
7
7
  GVF = "GIT-VERSION-FILE"
8
- DEF_VER = "v1.12.3"
8
+ DEF_VER = "v1.12.4"
9
9
  vn = DEF_VER.dup
10
10
 
11
11
  # First see if there is a version file (included in release tarballs),
@@ -30,6 +30,12 @@
30
30
  worker_threads 50
31
31
  end
32
32
 
33
+ # note: Rack requires responses set "Content-Length" or use
34
+ # "Transfer-Encoding: chunked". Some Rack servers tolerate
35
+ # the lack of these, yahns does not. Thus you should load
36
+ # Rack::Chunked and/or Rack::ContentLength middleware in your
37
+ # config.ru to ensure clients know when your application
38
+ # responses terminate.
33
39
  app(:rack, "config.ru", preload: false) do
34
40
  listen 80
35
41
 
@@ -12,6 +12,14 @@
12
12
  # (delfater: ensure that parent body is always closed)
13
13
  # Otherwise you will get zombies from HEAD requests which accept compressed
14
14
  # responses.
15
+ #
16
+ # Usage in config.ru using cgit as an example:
17
+ #
18
+ # use Rack::Chunked
19
+ # # other Rack middlewares can go here...
20
+ #
21
+ # run ExecCgi.new('/path/to/cgit.cgi') # cgit: https://git.zx2c4.com/cgit/
22
+ #
15
23
  class ExecCgi
16
24
  class MyIO < Kgio::Pipe
17
25
  attr_writer :my_pid
@@ -43,7 +43,12 @@ def proxy_err_response(code, req_res, exc, wbuf)
43
43
  Rack::Utils::HTTP_STATUS_CODES[code]}\r\n\r\n") rescue nil
44
44
 
45
45
  shutdown rescue nil
46
- req_res.shutdown rescue nil
46
+ @input = @input.close if @input
47
+
48
+ # this is safe ONLY because we are in an :ignore state after
49
+ # Fdmap#forget when we got hijacked:
50
+ close
51
+
47
52
  nil # signal close of req_res from yahns_step in yahns/proxy_pass.rb
48
53
  ensure
49
54
  wbuf.wbuf_abort if wbuf
@@ -56,6 +61,7 @@ def wait_on_upstream(req_res, alive, wbuf)
56
61
  :wait_readable # self remains in :ignore, wait on upstream
57
62
  end
58
63
 
64
+ # start streaming the response once upstream is done sending headers to us.
59
65
  # returns :wait_readable if we need to read more from req_res
60
66
  # returns :ignore if we yield control to the client(self)
61
67
  # returns nil if completely done
@@ -101,7 +107,6 @@ def proxy_response_start(res, tip, kcar, req_res)
101
107
  # but the backend does not terminate properly
102
108
  if alive && ! term && (env['HTTP_VERSION'] == 'HTTP/1.1'.freeze)
103
109
  res << "Transfer-Encoding: chunked\r\n".freeze
104
- alive = true
105
110
  end
106
111
  res << (alive ? "Connection: keep-alive\r\n\r\n".freeze
107
112
  : "Connection: close\r\n\r\n".freeze)
@@ -168,6 +173,7 @@ def proxy_response_start(res, tip, kcar, req_res)
168
173
  tmp = chunk_out(tmp) if alive
169
174
  wbuf = proxy_write(wbuf, tmp, alive)
170
175
  when nil
176
+ wbuf = proxy_write(wbuf, "0\r\n\r\n".freeze, true) if alive
171
177
  req_res.shutdown
172
178
  break
173
179
  when :wait_readable
@@ -177,16 +183,15 @@ def proxy_response_start(res, tip, kcar, req_res)
177
183
  end
178
184
  end
179
185
 
180
- return proxy_busy_mod_done(alive) unless wbuf
181
- req_res.resbuf = wbuf
182
- proxy_busy_mod_blocked(wbuf, wbuf.busy)
186
+ # all done reading response from upstream, req_res will be discarded
187
+ # when we return nil:
188
+ wbuf ? proxy_busy_mod_blocked(wbuf, wbuf.busy) : proxy_busy_mod_done(alive)
183
189
  rescue => e
184
190
  proxy_err_response(502, req_res, e, wbuf)
185
191
  end
186
192
 
187
193
  def proxy_response_finish(kcar, wbuf, req_res)
188
194
  rbuf = Thread.current[:yahns_rbuf]
189
- alive = wbuf.wbuf_persist
190
195
  if len = kcar.body_bytes_left # known Content-Length
191
196
 
192
197
  case tmp = req_res.kgio_tryread(0x2000, rbuf)
@@ -232,6 +237,7 @@ def proxy_response_finish(kcar, wbuf, req_res)
232
237
 
233
238
  else # no Content-Length or Transfer-Encoding: chunked, wait on EOF!
234
239
 
240
+ alive = wbuf.wbuf_persist
235
241
  case tmp = req_res.kgio_tryread(0x2000, rbuf)
236
242
  when String
237
243
  tmp = chunk_out(tmp) if alive
@@ -247,7 +253,7 @@ def proxy_response_finish(kcar, wbuf, req_res)
247
253
  end
248
254
 
249
255
  busy = wbuf.busy and return proxy_busy_mod_blocked(wbuf, busy)
250
- proxy_busy_mod_done(alive) # returns nil
256
+ proxy_busy_mod_done(wbuf.wbuf_persist) # returns nil to close req_res
251
257
  end
252
258
 
253
259
  def proxy_wait_next(qflags)
@@ -288,23 +294,23 @@ def proxy_busy_mod_done(alive)
288
294
  when :close then close
289
295
  end
290
296
 
291
- nil # close the req_res, too
297
+ nil # signal close for ReqRes#yahns_step
292
298
  end
293
299
 
294
300
  def proxy_busy_mod_blocked(wbuf, busy)
295
- q = Thread.current[:yahns_queue]
296
301
  # we are completely done reading and buffering the upstream response,
297
302
  # but have not completely written the response to the client,
298
303
  # yield control to the client socket:
299
304
  @state = wbuf
300
- case busy
301
- when :wait_readable then q.queue_mod(self, Yahns::Queue::QEV_RD)
302
- when :wait_writable then q.queue_mod(self, Yahns::Queue::QEV_WR)
303
- else
304
- abort "BUG: invalid wbuf.busy: #{busy.inspect}"
305
- end
306
- # no touching self after queue_mod
307
- :ignore
305
+ proxy_wait_next(case busy
306
+ when :wait_readable then Yahns::Queue::QEV_RD
307
+ when :wait_writable then Yahns::Queue::QEV_WR
308
+ else
309
+ abort "BUG: invalid wbuf.busy: #{busy.inspect}"
310
+ end)
311
+ # no touching self after proxy_wait_next, we may be running
312
+ # HttpClient#yahns_step in a different thread at this point
313
+ nil # signal close for ReqRes#yahns_step
308
314
  end
309
315
 
310
316
  # n.b.: we can use String#size for optimized dispatch under YARV instead
@@ -63,7 +63,8 @@ def yahns_step # yahns event loop entry point
63
63
 
64
64
  when Yahns::WbufCommon # streaming/buffering the response body
65
65
 
66
- return c.proxy_response_finish(req, resbuf, self)
66
+ # we assign wbuf for rescue below:
67
+ return c.proxy_response_finish(req, wbuf = resbuf, self)
67
68
 
68
69
  end while true # case @resbuf
69
70
 
@@ -79,7 +80,7 @@ def yahns_step # yahns event loop entry point
79
80
  when Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE
80
81
  e.set_backtrace([])
81
82
  end
82
- c.proxy_err_response(502, self, e, nil)
83
+ c.proxy_err_response(502, self, e, wbuf)
83
84
  end
84
85
 
85
86
  # returns :wait_readable if complete, :wait_writable if not
@@ -38,6 +38,7 @@ def wbuf_flush(client)
38
38
  end while @sf_count > 0
39
39
  wbuf_close(client)
40
40
  rescue
41
+ @wbuf_persist = false # ensure a hijack response is not called
41
42
  wbuf_close(client)
42
43
  raise
43
44
  end
@@ -586,6 +586,21 @@ def check_eof_body(host, port)
586
586
  assert_match %r{\AHTTP/1\.1 200 OK\r\n}, res
587
587
  assert_match %r{\r\n\r\neof-body-slow\z}, res
588
588
  s.close
589
+
590
+ # we auto-chunk on 1.1 requests and 1.0 backends
591
+ %w(eof-body-slow eof-body-fast).each do |x|
592
+ s = TCPSocket.new(host, port)
593
+ s.write("GET /#{x} HTTP/1.1\r\nHost: example.com\r\n\r\n")
594
+ res = ''.dup
595
+ res << s.readpartial(512) until res =~ /0\r\n\r\n\z/
596
+ s.close
597
+ head, body = res.split("\r\n\r\n", 2)
598
+ head = head.split("\r\n")
599
+ assert_equal 'HTTP/1.1 200 OK', head[0]
600
+ assert head.include?('Connection: keep-alive')
601
+ assert head.include?('Transfer-Encoding: chunked')
602
+ assert_match %r{\Ad\r\n#{x}\r\n0\r\n\r\n\z}, body
603
+ end
589
604
  end
590
605
  end
591
606
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yahns
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.12.3
4
+ version: 1.12.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - yahns hackers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-04-08 00:00:00.000000000 Z
11
+ date: 2016-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: kgio