yahns 1.12.3 → 1.12.4

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