yahns 1.12.5 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Documentation/yahns-rackup.pod +0 -10
  3. data/Documentation/yahns_config.pod +3 -0
  4. data/GIT-VERSION-FILE +1 -1
  5. data/GIT-VERSION-GEN +1 -1
  6. data/NEWS +80 -0
  7. data/examples/init.sh +34 -9
  8. data/examples/logrotate.conf +5 -0
  9. data/examples/yahns.socket +17 -0
  10. data/examples/yahns@.service +50 -0
  11. data/examples/yahns_rack_basic.conf.rb +0 -6
  12. data/extras/autoindex.rb +3 -2
  13. data/extras/exec_cgi.rb +1 -0
  14. data/extras/try_gzip_static.rb +19 -5
  15. data/lib/yahns/chunk_body.rb +27 -0
  16. data/lib/yahns/fdmap.rb +7 -4
  17. data/lib/yahns/http_client.rb +39 -10
  18. data/lib/yahns/http_response.rb +41 -22
  19. data/lib/yahns/openssl_client.rb +7 -3
  20. data/lib/yahns/proxy_http_response.rb +132 -159
  21. data/lib/yahns/proxy_pass.rb +6 -170
  22. data/lib/yahns/queue_epoll.rb +1 -0
  23. data/lib/yahns/queue_kqueue.rb +1 -0
  24. data/lib/yahns/req_res.rb +164 -0
  25. data/lib/yahns/server.rb +2 -1
  26. data/lib/yahns/server_mp.rb +1 -1
  27. data/lib/yahns/version.rb +1 -1
  28. data/lib/yahns/wbuf.rb +5 -6
  29. data/lib/yahns/wbuf_common.rb +5 -10
  30. data/lib/yahns/wbuf_lite.rb +111 -0
  31. data/man/yahns-rackup.1 +29 -29
  32. data/man/yahns_config.5 +47 -35
  33. data/test/helper.rb +12 -0
  34. data/test/test_auto_chunk.rb +56 -0
  35. data/test/test_extras_exec_cgi.rb +1 -3
  36. data/test/test_extras_try_gzip_static.rb +30 -16
  37. data/test/test_output_buffering.rb +5 -1
  38. data/test/test_proxy_pass.rb +2 -2
  39. data/test/test_proxy_pass_no_buffering.rb +170 -0
  40. data/test/test_reopen_logs.rb +5 -1
  41. data/test/test_response.rb +42 -0
  42. data/test/test_server.rb +35 -0
  43. data/test/test_ssl.rb +0 -6
  44. data/test/test_tmpio.rb +4 -0
  45. data/test/test_wbuf.rb +11 -4
  46. metadata +10 -4
  47. data/lib/yahns/sendfile_compat.rb +0 -24
data/man/yahns_config.5 CHANGED
@@ -1,4 +1,4 @@
1
- .\" Automatically generated by Pod::Man 2.25 (Pod::Simple 3.16)
1
+ .\" Automatically generated by Pod::Man 2.28 (Pod::Simple 3.28)
2
2
  .\"
3
3
  .\" Standard preamble:
4
4
  .\" ========================================================================
@@ -38,6 +38,8 @@
38
38
  . ds PI \(*p
39
39
  . ds L" ``
40
40
  . ds R" ''
41
+ . ds C`
42
+ . ds C'
41
43
  'br\}
42
44
  .\"
43
45
  .\" Escape single quotes in literal strings from groff's Unicode transform.
@@ -48,17 +50,24 @@
48
50
  .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
49
51
  .\" entries marked with X<> in POD. Of course, you'll have to process the
50
52
  .\" output yourself in some meaningful fashion.
51
- .ie \nF \{\
52
- . de IX
53
- . tm Index:\\$1\t\\n%\t"\\$2"
53
+ .\"
54
+ .\" Avoid warning from groff about undefined register 'F'.
55
+ .de IX
54
56
  ..
55
- . nr % 0
56
- . rr F
57
- .\}
58
- .el \{\
59
- . de IX
57
+ .nr rF 0
58
+ .if \n(.g .if rF .nr rF 1
59
+ .if (\n(rF:(\n(.g==0)) \{
60
+ . if \nF \{
61
+ . de IX
62
+ . tm Index:\\$1\t\\n%\t"\\$2"
60
63
  ..
64
+ . if !\nF==2 \{
65
+ . nr % 0
66
+ . nr F 2
67
+ . \}
68
+ . \}
61
69
  .\}
70
+ .rr rF
62
71
  .\"
63
72
  .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
64
73
  .\" Fear. Run. Save yourself. No user-serviceable parts.
@@ -124,7 +133,7 @@
124
133
  .\" ========================================================================
125
134
  .\"
126
135
  .IX Title "YAHNS_CONFIG 5"
127
- .TH YAHNS_CONFIG 5 "1994-10-02" "yahns 1.12.4.8.gd5d0" "yahns user manual"
136
+ .TH YAHNS_CONFIG 5 "1994-10-02" "yahns 1.12.5.48.g013d" "yahns user manual"
128
137
  .\" For nroff, turn off justification. Always turn off hyphenation; it makes
129
138
  .\" way too many mistakes in technical documents.
130
139
  .if n .ad l
@@ -152,7 +161,7 @@ An app with the same :TYPE and \s-1APP_ARGUMENTS\s0 may be defined multiple
152
161
  times with different &BLOCK contents (with different listeners and
153
162
  buffering settings).
154
163
  .Sp
155
- See the \*(L"APP-LEVEL \s-1DIRECTIVES\s0\*(R" section for details on what goes into
164
+ See the \*(L"APP-LEVEL \s-1DIRECTIVES\*(R"\s0 section for details on what goes into
156
165
  the &BLOCK.
157
166
  .Sp
158
167
  This directive may be specified multiple times for different or
@@ -161,7 +170,7 @@ identical applications.
161
170
  If the \*(L"working_directory\*(R" directive is used, all app directives may
162
171
  only be specified after setting \*(L"working_directory\*(R".
163
172
  .Sp
164
- For Rack \s-1HTTP\s0 applications, see \*(L"\s-1RACK\s0 \s-1APP\s0 \s-1ARGUMENTS\s0\*(R" for more
173
+ For Rack \s-1HTTP\s0 applications, see \*(L"\s-1RACK APP ARGUMENTS\*(R"\s0 for more
165
174
  information.
166
175
  .IP "before_exec &BLOCK" 4
167
176
  .IX Item "before_exec &BLOCK"
@@ -227,7 +236,7 @@ Default: none
227
236
  .IX Item "queue [NAME] &BLOCK"
228
237
  As a top-level directive, this configures or defines a queue.
229
238
  If no \s-1NAME\s0 is specified, a default queue (named :default) is
230
- assumed. See the \*(L"QUEUE-LEVEL \s-1DIRECTIVES\s0\*(R" section for details.
239
+ assumed. See the \*(L"QUEUE-LEVEL \s-1DIRECTIVES\*(R"\s0 section for details.
231
240
  .Sp
232
241
  A &BLOCK must be given if used as a top-level directive. This
233
242
  behaves slightly differently inside an app &BLOCK. This may also
@@ -297,8 +306,8 @@ Default: 7
297
306
  .IX Subsection "APP-LEVEL DIRECTIVES"
298
307
  .IP "atfork_prepare, atfork_parent, atfork_child" 4
299
308
  .IX Item "atfork_prepare, atfork_parent, atfork_child"
300
- These are identical to the methods defined in \s-1WORKER_PROCESSES\-LEVEL\s0
301
- \&\s-1DIRECTIVES\s0, however they are available inside the app blocks for
309
+ These are identical to the methods defined in \s-1WORKER_PROCESSES\-LEVEL
310
+ DIRECTIVES,\s0 however they are available inside the app blocks for
302
311
  convenience in case it is easier to organize per-app hooks.
303
312
  .Sp
304
313
  Default: (none)
@@ -315,7 +324,7 @@ This only affects clients connecting over Unix domain sockets and
315
324
  \&\s-1TCP\s0 via loopback (127.*.*.*). It is unlikely to detect disconnects
316
325
  if the client is on a remote host (even on a fast \s-1LAN\s0).
317
326
  .Sp
318
- This has no effect for (rare) \s-1HTTP/0\s0.9 clients.
327
+ This has no effect for (rare) \s-1HTTP/0.9\s0 clients.
319
328
  .Sp
320
329
  Default: false
321
330
  .IP "client_body_buffer_size \s-1INTEGER\s0" 4
@@ -340,7 +349,7 @@ Default: 4000 bytes
340
349
  .IP "client_max_body_size {INTEGER|nil}" 4
341
350
  .IX Item "client_max_body_size {INTEGER|nil}"
342
351
  This controls the maximum request body size before a client is
343
- rejected with an \s-1HTTP\s0 413 error.
352
+ rejected with an \s-1HTTP 413\s0 error.
344
353
  .Sp
345
354
  Setting this to nil will allow unlimited-sized inputs.
346
355
  .Sp
@@ -367,7 +376,7 @@ Default: 15 (seconds)
367
376
  .IP "errors {IO|PATHNAME}" 4
368
377
  .IX Item "errors {IO|PATHNAME}"
369
378
  For Rack applications, this controls the env[\*(L"rack.errors\*(R"]
370
- destination. If given a \s-1PATHNAME\s0, it will be a writable file
379
+ destination. If given a \s-1PATHNAME,\s0 it will be a writable file
371
380
  opened with O_APPEND without userspace buffering, making it suitable
372
381
  for concurrent appends by multiple processes and threads, as well as
373
382
  making it eligible for reopening via \s-1SIGUSR1\s0 after log rotation.
@@ -421,7 +430,7 @@ Default: Dir.tmpdir (usually from \s-1TMPDIR\s0 env or /tmp)
421
430
  .IX Item "listen ADDRESS [, OPTIONS]"
422
431
  Adds an \s-1ADDRESS\s0 to the existing listener set. May be specified more
423
432
  than once. \s-1ADDRESS\s0 may be an Integer port number for a \s-1TCP\s0 port, an
424
- \&\*(L"\s-1IP_ADDRESS:PORT\s0\*(R" for \s-1TCP\s0 listeners or a pathname for \s-1UNIX\s0 domain sockets.
433
+ \&\*(L"\s-1IP_ADDRESS:PORT\*(R"\s0 for \s-1TCP\s0 listeners or a pathname for \s-1UNIX\s0 domain sockets.
425
434
  .Sp
426
435
  .Vb 2
427
436
  \& # listen to port 3000 on all TCP interfaces
@@ -476,7 +485,7 @@ listener is required if this is true.
476
485
  .Sp
477
486
  Enabling this option for the IPv6\-only listener and having a
478
487
  separate IPv4 listener is recommended if you wish to support IPv6
479
- on the same \s-1TCP\s0 port. Otherwise, the value of env[\*(L"\s-1REMOTE_ADDR\s0\*(R"]
488
+ on the same \s-1TCP\s0 port. Otherwise, the value of env[\*(L"\s-1REMOTE_ADDR\*(R"\s0]
480
489
  will appear as an ugly IPv4\-mapped\-IPv6 address for IPv4 clients
481
490
  (e.g \*(L":ffff:10.0.0.1\*(R" instead of just \*(L"10.0.0.1\*(R").
482
491
  .Sp
@@ -502,9 +511,9 @@ This enables multiple, independently-started yahns instances to
502
511
  bind to the same port (as long as all the processes enable this).
503
512
  .Sp
504
513
  This option must be used when yahns first binds the listen socket.
505
- It cannot be enabled when a socket is inherited via \s-1SIGUSR2\s0
506
- (but it will remain on if inherited), and it cannot be enabled
507
- directly via \s-1SIGHUP\s0.
514
+ It cannot be enabled when a socket is inherited via \s-1SIGUSR2
515
+ \&\s0(but it will remain on if inherited), and it cannot be enabled
516
+ directly via \s-1SIGHUP.\s0
508
517
  .Sp
509
518
  Note: there is a chance of connections being dropped if
510
519
  one of the yahns instances is stopped while using this.
@@ -520,7 +529,7 @@ To enable \s-1TLS\s0 connections, you must configure this yourself.
520
529
  See documentation for OpenSSL::SSL::SSLContext
521
530
  for more information:
522
531
  .Sp
523
- http://docs.ruby\-lang.org/en/trunk/OpenSSL/SSL/SSLContext.html <http://docs.ruby-lang.org/en/trunk/OpenSSL/SSL/SSLContext.html>
532
+ <http://docs.ruby\-lang.org/en/trunk/OpenSSL/SSL/SSLContext.html>
524
533
  .Sp
525
534
  Default: none
526
535
  .Sp
@@ -545,6 +554,9 @@ An example which seems to work is:
545
554
  \& # but disable client certificate verification as it is rare:
546
555
  \& ssl_ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
547
556
  \&
557
+ \& # Built\-in session cache (only works if worker_processes is nil or 1)
558
+ \& ssl_ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_SERVER
559
+ \&
548
560
  \& app(:rack, "/path/to/my/app/config.ru") do
549
561
  \& listen 443, ssl_ctx: ssl_ctx
550
562
  \& end
@@ -624,7 +636,7 @@ client_expire_threshold is hit and a client hits its client_timeout.
624
636
  Default: true
625
637
  .IP "user \s-1USER\s0 [,GROUP]" 4
626
638
  .IX Item "user USER [,GROUP]"
627
- Runs application process(es) as the specified \s-1USER\s0 and \s-1GROUP\s0.
639
+ Runs application process(es) as the specified \s-1USER\s0 and \s-1GROUP.\s0
628
640
  .Sp
629
641
  If using worker_processes, this only affects the workers and the
630
642
  master stays running as the user who started it. This switch will
@@ -641,7 +653,7 @@ existing queue.
641
653
  If given a &BLOCK\-only, this will create an anonymous queue for this
642
654
  application context only. If given a &BLOCK, this takes the same
643
655
  parameters (worker_threads and max_events) as described in
644
- \&\*(L"QUEUE-LEVEL \s-1DIRECTIVES\s0\*(R".
656
+ \&\*(L"QUEUE-LEVEL \s-1DIRECTIVES\*(R"\s0.
645
657
  .Sp
646
658
  \&\s-1NAME\s0 and &BLOCK may not be combined inside an app &BLOCK.
647
659
  .Sp
@@ -665,16 +677,16 @@ model instead of a single process.
665
677
  .Sp
666
678
  If an optional &BLOCK is given, it may be used to configure
667
679
  \&\fIpthread_atfork\fR\|(3)\-style hooks.
668
- See \*(L"\s-1WORKER_PROCESSES\-LEVEL\s0 \s-1DIRECTIVES\s0\*(R" for details.
680
+ See \*(L"\s-1WORKER_PROCESSES\-LEVEL DIRECTIVES\*(R"\s0 for details.
669
681
  .Sp
670
682
  Using worker_processes is strongly recommended if your application
671
683
  relies on using a \s-1SIGCHLD\s0 handler for reaping forked processes.
672
684
  Without worker_processes, yahns must reserve \s-1SIGCHLD\s0 for rolling
673
685
  back \s-1SIGUSR2\s0 upgrades, leading to conflicts if the appplication
674
- expects to handle \s-1SIGCHLD\s0.
686
+ expects to handle \s-1SIGCHLD.\s0
675
687
  .Sp
676
688
  Default: nil (single process, no master/worker separation)
677
- .SS "\s-1WORKER_PROCESSES\-LEVEL\s0 \s-1DIRECTIVES\s0"
689
+ .SS "\s-1WORKER_PROCESSES\-LEVEL DIRECTIVES\s0"
678
690
  .IX Subsection "WORKER_PROCESSES-LEVEL DIRECTIVES"
679
691
  Note: all of the atfork_* hooks described here are available inside the
680
692
  \&\*(L"app\*(R" blocks, too.
@@ -709,11 +721,11 @@ The only supported keyword argument is:
709
721
  .IP "preload: \s-1BOOLEAN\s0" 4
710
722
  .IX Item "preload: BOOLEAN"
711
723
  preload: only makes sense if worker_processes are configured.
712
- Preloading an app allows memory to be saved under a copy-on-write \s-1GC\s0,
724
+ Preloading an app allows memory to be saved under a copy-on-write \s-1GC,\s0
713
725
  but will often require atfork_* hooks to be registered when configuring
714
726
  worker_processes. preload: defaults to false for maximum out-of-the-box
715
727
  compatibility.
716
- .SS "\s-1RACK\s0 \s-1APP\s0 \s-1EXAMPLE\s0"
728
+ .SS "\s-1RACK APP EXAMPLE\s0"
717
729
  .IX Subsection "RACK APP EXAMPLE"
718
730
  .Vb 3
719
731
  \& app(:rack, "/path/to/config.ru", preload: false) do
@@ -729,13 +741,13 @@ See the examples/ directory in the git source tree.
729
741
  .Ve
730
742
  .SH "CONTACT"
731
743
  .IX Header "CONTACT"
732
- All feedback welcome via plain-text mail to mailto:yahns\-public@yhbt.net <mailto:yahns-public@yhbt.net>
744
+ All feedback welcome via plain-text mail to <mailto:yahns\-public@yhbt.net>
733
745
  No subscription is necessary to post to the mailing list.
734
- List archives are available at http://yhbt.net/yahns\-public/ <http://yhbt.net/yahns-public/>
746
+ List archives are available at <http://yhbt.net/yahns\-public/>
735
747
  .SH "COPYRIGHT"
736
748
  .IX Header "COPYRIGHT"
737
- Copyright (C) 2013\-2016 all contributors mailto:yahns\-public@yhbt.net <mailto:yahns-public@yhbt.net>
738
- License: \s-1GPL\-3\s0.0+ http://www.gnu.org/licenses/gpl\-3.0.txt <http://www.gnu.org/licenses/gpl-3.0.txt>
749
+ Copyright (C) 2013\-2016 all contributors <mailto:yahns\-public@yhbt.net>
750
+ License: \s-1GPL\-3.0+ \s0<http://www.gnu.org/licenses/gpl\-3.0.txt>
739
751
  .SH "SEE ALSO"
740
752
  .IX Header "SEE ALSO"
741
753
  \&\fIyahns\fR\|(1)
data/test/helper.rb CHANGED
@@ -147,6 +147,18 @@ class DieIfUsed
147
147
  end
148
148
  end
149
149
 
150
+ # tricky to test output buffering behavior across different OSes
151
+ def skip_skb_mem
152
+ return if ENV['YAHNS_TEST_FORCE']
153
+ skip "linux-only test" unless RUBY_PLATFORM =~ /linux/
154
+ [ [ '/proc/sys/net/ipv4/tcp_rmem', "4096 87380 6291456\n" ],
155
+ [ '/proc/sys/net/ipv4/tcp_wmem', "4096 16384 4194304\n" ]
156
+ ].each do |file, expect|
157
+ val = IO.read(file)
158
+ val == expect or skip "#{file} had: #{val}expected: #{expect}"
159
+ end
160
+ end
161
+
150
162
  require 'yahns'
151
163
 
152
164
  # needed for parallel (MT) tests)
@@ -0,0 +1,56 @@
1
+ # Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
2
+ # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
3
+ # frozen_string_literal: true
4
+ require_relative 'server_helper'
5
+
6
+ class TestAutoChunk < Testcase
7
+ ENV["N"].to_i > 1 and parallelize_me!
8
+ include ServerHelper
9
+ alias setup server_helper_setup
10
+ alias teardown server_helper_teardown
11
+
12
+ def test_auto_head
13
+ err = @err
14
+ cfg = Yahns::Config.new
15
+ host, port = @srv.addr[3], @srv.addr[1]
16
+ cfg.instance_eval do
17
+ GTL.synchronize do
18
+ app = Rack::Builder.new do
19
+ use Rack::ContentType, "text/plain"
20
+ run(lambda do |env|
21
+ [ 200, {}, %w(a b c) ]
22
+ end)
23
+ end
24
+ app(:rack, app) { listen "#{host}:#{port}" }
25
+ end
26
+ logger(Logger.new(err.path))
27
+ end
28
+ pid = mkserver(cfg)
29
+ s = TCPSocket.new(host, port)
30
+ s.write("GET / HTTP/1.0\r\n\r\n")
31
+ assert s.wait(30), "IO wait failed"
32
+ buf = s.read
33
+ assert_match %r{\r\n\r\nabc\z}, buf
34
+ s.close
35
+
36
+ s = TCPSocket.new(host, port)
37
+ s.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
38
+ buf = ''.dup
39
+ Timeout.timeout(30) do
40
+ until buf =~ /\r\n\r\n1\r\na\r\n1\r\nb\r\n1\r\nc\r\n0\r\n\r\n\z/
41
+ buf << s.readpartial(16384)
42
+ end
43
+ end
44
+ assert_match(%r{^Transfer-Encoding: chunked\r\n}, buf)
45
+ s.close
46
+
47
+ Net::HTTP.start(host, port) do |http|
48
+ req = Net::HTTP::Get.new("/")
49
+ res = http.request(req)
50
+ assert_equal 200, res.code.to_i
51
+ assert_equal 'abc', res.body
52
+ end
53
+ ensure
54
+ quit_wait(pid)
55
+ end
56
+ end
@@ -169,10 +169,8 @@ class TestExtrasExecCGI < Testcase
169
169
  assert_match %r{200 OK}, head
170
170
  assert_match %r{\A\d+\n\z}, body
171
171
  exec_pid = body.to_i
172
- assert_raises(Errno::EAGAIN, IO::WaitReadable) { c.read_nonblock(666) }
173
172
  poke_until_dead exec_pid
174
- # still alive?
175
- assert_raises(Errno::EAGAIN, IO::WaitReadable) { c.read_nonblock(666) }
173
+ assert_raises(EOFError) { c.read_nonblock(666) }
176
174
  else
177
175
  raise "BUG in test, bad rtype"
178
176
  end
@@ -34,8 +34,22 @@ class TestExtrasTryGzipStatic < Testcase
34
34
  end
35
35
  end
36
36
 
37
+ Net::HTTP.start(host, port) do |http|
38
+ uri = "/COPYING/foo" + ('-' * 4096)
39
+ begin
40
+ res = http.request(Net::HTTP::Get.new(uri))
41
+ end while res.code.to_i == 414 && uri.chop!
42
+ res = http.request(Net::HTTP::Get.new("/COPYING/foo"))
43
+ assert_equal 404, res.code.to_i
44
+ lines = File.readlines(err.path)
45
+ File.truncate(err.path, 0)
46
+ assert_operator lines.size, :<, 3, lines.map! { |s| s[0,64] }.inspect
47
+ end
48
+
37
49
  begin # setup
38
50
  gpl = "#{tmpdir}/COPYING"
51
+ File.symlink gpl, "#{tmpdir}/COPYING.abssymlink"
52
+ File.symlink "COPYING", "#{tmpdir}/COPYING.relsymlink"
39
53
  gplgz = "#{tmpdir}/COPYING.gz"
40
54
  FileUtils.cp("COPYING", gpl)
41
55
  _, status = Process.waitpid2(fork do
@@ -76,16 +90,19 @@ class TestExtrasTryGzipStatic < Testcase
76
90
  when "HEAD" then assert_nil body
77
91
  end
78
92
 
79
- req = "#{m} /COPYING HTTP/1.0\r\nAccept-Encoding: gzip"
80
- body = check.call(req) do |head|
81
- assert_match %r{^Content-Encoding: gzip\b}, head
82
- assert_match %r{^Content-Type: text/plain\b}, head
83
- assert_match %r{^Content-Length: #{gz_st.size}\b}, head
84
- end
85
- case m
86
- when "GET"
87
- assert_equal GPL_TEXT, Zlib::GzipReader.new(StringIO.new(body)).read
88
- when "HEAD" then assert_nil body
93
+ %w(COPYING COPYING.abssymlink COPYING.relsymlink).each do |path|
94
+ req = "#{m} /#{path} HTTP/1.0\r\nAccept-Encoding: gzip"
95
+ body = check.call(req) do |head|
96
+ assert_match %r{^Content-Encoding: gzip\b}, head
97
+ assert_match %r{^Content-Type: text/plain\b}, head
98
+ assert_match %r{^Content-Length: #{gz_st.size}\b}, head
99
+ end
100
+ case m
101
+ when "GET"
102
+ body =StringIO.new(body)
103
+ assert_equal GPL_TEXT, Zlib::GzipReader.new(body).read
104
+ when "HEAD" then assert_nil body
105
+ end
89
106
  end
90
107
  end
91
108
  end
@@ -108,11 +125,10 @@ class TestExtrasTryGzipStatic < Testcase
108
125
 
109
126
  req = "#{m} /COPYING HTTP/1.0\r\n" \
110
127
  "Range: bytes=66666666-\r\nAccept-Encoding: gzip"
111
- body = check.call(req) do |head|
128
+ check.call(req) do |head|
112
129
  assert_match %r{^Content-Range: bytes \*/#{st.size}\r\n}, head
113
130
  assert_match %r{\AHTTP/1\.1 416 }, head
114
131
  end
115
- assert_nil body
116
132
  end
117
133
  end
118
134
 
@@ -164,15 +180,13 @@ class TestExtrasTryGzipStatic < Testcase
164
180
  Timeout.timeout(30) do # 404
165
181
  %w(GET HEAD).each do |m|
166
182
  req = "#{m} /cp-ing HTTP/1.0\r\nAccept-Encoding: gzip"
167
- body = check.call(req) do |head|
183
+ check.call(req) do |head|
168
184
  assert_match %r{HTTP/1\.1 404 }, head
169
185
  end
170
- assert_nil body
171
186
  end
172
- body = check.call("FOO /COPYING HTTP/1.0") do |head|
187
+ check.call("FOO /COPYING HTTP/1.0") do |head|
173
188
  assert_match %r{HTTP/1\.1 405 }, head
174
189
  end
175
- assert_nil body
176
190
  end
177
191
 
178
192
  Net::HTTP.start(host, port) do |http|
@@ -126,7 +126,10 @@ class TestOutputBuffering < Testcase
126
126
  end
127
127
  size = gplv3.size
128
128
  len = size.to_s
129
- ranges = Rack::Utils.byte_ranges(e, size)
129
+
130
+ ranges = Rack::Utils.respond_to?(:get_byte_ranges) ?
131
+ Rack::Utils.get_byte_ranges(e['HTTP_RANGE'], size) :
132
+ Rack::Utils.byte_ranges(e, size)
130
133
  status = 200
131
134
  h = { "Content-Type" => "text/plain", "Content-Length" => len }
132
135
  if ranges && ranges.size == 1
@@ -216,6 +219,7 @@ class TestOutputBuffering < Testcase
216
219
 
217
220
  def test_client_timeout
218
221
  err = @err
222
+ skip_skb_mem
219
223
  apperr = tmpfile(%w(app .err))
220
224
  cfg = Yahns::Config.new
221
225
  size = RAND.size * NR
@@ -485,7 +485,7 @@ class TestProxyPass < Testcase
485
485
  assert_equal exp, res
486
486
  errs = File.readlines(@err.path).grep(/\bERROR\b/)
487
487
  assert_equal 1, errs.size
488
- assert_match(/premature upstream EOF/, errs[0])
488
+ assert_match(/upstream EOF/, errs[0])
489
489
  @err.truncate(0)
490
490
 
491
491
  # truncated headers or no response at all...
@@ -501,7 +501,7 @@ class TestProxyPass < Testcase
501
501
  s.close
502
502
  errs = File.readlines(@err.path).grep(/\bERROR\b/)
503
503
  assert_equal 1, errs.size
504
- assert_match(/premature upstream EOF/, errs[0])
504
+ assert_match(/upstream EOF/, errs[0])
505
505
  @err.truncate(0)
506
506
  end
507
507
  end
@@ -0,0 +1,170 @@
1
+ # Copyright (C) 2015-2016 all contributors <yahns-public@yhbt.net>
2
+ # License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
3
+ # frozen_string_literal: true
4
+ require_relative 'server_helper'
5
+ begin
6
+ require 'kcar'
7
+ rescue LoadError
8
+ end
9
+ require 'digest/md5'
10
+ class TestProxyPassNoBuffering < Testcase
11
+ ENV["N"].to_i > 1 and parallelize_me!
12
+ include ServerHelper
13
+ STR4 = 'abcd' * (256 * 1024)
14
+ NCHUNK = 50
15
+ class ProxiedApp
16
+ def call(env)
17
+ case env['REQUEST_METHOD']
18
+ when 'GET'
19
+ case env['PATH_INFO']
20
+ when '/giant-body'
21
+ h = [ %W(content-type text/pain) ]
22
+
23
+ # HTTP/1.0 is not Rack-compliant, so no Rack::Lint for us :)
24
+ if env['HTTP_VERSION'] == 'HTTP/1.1'
25
+ h << %W(content-length #{NCHUNK * STR4.size})
26
+ end
27
+
28
+ body = Object.new
29
+ def body.each
30
+ NCHUNK.times { yield STR4 }
31
+ end
32
+ [ 200, h, body ]
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def setup
39
+ @srv2 = TCPServer.new(ENV["TEST_HOST"] || "127.0.0.1", 0)
40
+ server_helper_setup
41
+ skip "kcar missing yahns/proxy_pass" unless defined?(Kcar)
42
+ require 'yahns/proxy_pass'
43
+ @tmpdir = yahns_mktmpdir
44
+ end
45
+
46
+ def teardown
47
+ @srv2.close if defined?(@srv2) && !@srv2.closed?
48
+ server_helper_teardown
49
+ FileUtils.rm_rf(@tmpdir) if defined?(@tmpdir)
50
+ end
51
+
52
+ def check_headers(io)
53
+ l = io.gets
54
+ assert_match %r{\AHTTP/1\.[01] 200\b}, l
55
+ begin
56
+ l = io.gets
57
+ end until l == "\r\n"
58
+ end
59
+
60
+ def test_proxy_pass_no_buffering
61
+ to_close = []
62
+ err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
63
+ host2, port2 = @srv2.addr[3], @srv2.addr[1]
64
+ pxp = Yahns::ProxyPass.new("http://#{host2}:#{port2}",
65
+ proxy_buffering: false)
66
+ tmpdir = @tmpdir
67
+ pid = mkserver(cfg) do
68
+ ObjectSpace.each_object(Yahns::TmpIO) { |io| io.close unless io.closed? }
69
+ @srv2.close
70
+ cfg.instance_eval do
71
+ app(:rack, pxp) do
72
+ listen "#{host}:#{port}"
73
+ output_buffering true, tmpdir: tmpdir
74
+ end
75
+ stderr_path err.path
76
+ end
77
+ end
78
+
79
+ pid2 = mkserver(cfg, @srv2) do
80
+ ObjectSpace.each_object(Yahns::TmpIO) { |io| io.close unless io.closed? }
81
+ @srv.close
82
+ cfg.instance_eval do
83
+ app(:rack, ProxiedApp.new) do
84
+ output_buffering false
85
+ listen "#{host2}:#{port2}"
86
+ end
87
+ stderr_path err.path
88
+ end
89
+ end
90
+ %w(1.0 1.1).each do |ver|
91
+ s = TCPSocket.new(host, port)
92
+ to_close << s
93
+ req = "GET /giant-body HTTP/#{ver}\r\nHost: example.com\r\n".dup
94
+ req << "Connection: close\r\n" if ver == '1.1'
95
+ req << "\r\n"
96
+ s.write(req)
97
+ bufs = []
98
+ sleep 1
99
+ 10.times do
100
+ sleep 0.1
101
+ # ensure no files get created
102
+ if RUBY_PLATFORM =~ /\blinux\b/ && `which lsof 2>/dev/null`.size >= 4
103
+ qtmpdir = Regexp.quote("#@tmpdir/")
104
+ deleted1 = `lsof -p #{pid}`.split("\n")
105
+ deleted1 = deleted1.grep(/\bREG\b.*#{qtmpdir}.* \(deleted\)/)
106
+ deleted2 = `lsof -p #{pid2}`.split("\n")
107
+ deleted2 = deleted2.grep(/\bREG\b.*#{qtmpdir}.* \(deleted\)/)
108
+ [ deleted1, deleted2 ].each do |ary|
109
+ ary.delete_if { |x| x =~ /\.(?:err|out|rb|ru) \(deleted\)/ }
110
+ end
111
+ assert_equal 0, deleted1.size, "pid1=#{deleted1.inspect}"
112
+ assert_equal 0, deleted2.size, "pid2=#{deleted2.inspect}"
113
+ bufs.push(deleted1[0])
114
+ end
115
+ end
116
+ before = bufs.size
117
+ bufs.uniq!
118
+ assert bufs.size < before, 'unlinked buffer should not grow'
119
+ buf = ''.dup
120
+ slow = Digest::MD5.new
121
+ ft = Thread.new do
122
+ fast = Digest::MD5.new
123
+ f = TCPSocket.new(host2, port2)
124
+ f.write(req)
125
+ b2 = ''.dup
126
+ check_headers(f)
127
+ nf = 0
128
+ begin
129
+ f.readpartial(1024 * 1024, b2)
130
+ nf += b2.bytesize
131
+ fast.update(b2)
132
+ rescue EOFError
133
+ f = f.close
134
+ end while f
135
+ b2.clear
136
+ [ nf, fast.hexdigest ]
137
+ end
138
+ Thread.abort_on_exception = true
139
+ check_headers(s)
140
+ n = 0
141
+ begin
142
+ s.readpartial(1024 * 1024, buf)
143
+ slow.update(buf)
144
+ n += buf.bytesize
145
+ sleep 0.01
146
+ rescue EOFError
147
+ s = s.close
148
+ end while s
149
+ ft.join(5)
150
+ assert_equal [n, slow.hexdigest ], ft.value
151
+
152
+ fast = Digest::MD5.new
153
+ f = TCPSocket.new(host, port)
154
+ f.write(req)
155
+ check_headers(f)
156
+ begin
157
+ f.readpartial(1024 * 1024, buf)
158
+ fast.update(buf)
159
+ rescue EOFError
160
+ f = f.close
161
+ end while f
162
+ buf.clear
163
+ assert_equal slow.hexdigest, fast.hexdigest
164
+ end
165
+ ensure
166
+ to_close.each { |io| io.close unless io.closed? }
167
+ quit_wait(pid)
168
+ quit_wait(pid2)
169
+ end
170
+ end
@@ -2,7 +2,11 @@
2
2
  # License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
3
3
  # frozen_string_literal: true
4
4
  require_relative 'server_helper'
5
- require 'rack/commonlogger'
5
+ require 'rack'
6
+ # trigger autoload, since rack 1.x => 2.x renames:
7
+ # 'rack/commonlogger' => 'rack/common_logger'
8
+ # so we can't require directly
9
+ Rack::CommonLogger.class
6
10
 
7
11
  class TestReopenLogs < Testcase
8
12
  ENV["N"].to_i > 1 and parallelize_me!
@@ -9,6 +9,48 @@ class TestResponse < Testcase
9
9
  alias setup server_helper_setup
10
10
  alias teardown server_helper_teardown
11
11
 
12
+ def test_auto_head
13
+ err = @err
14
+ cfg = Yahns::Config.new
15
+ host, port = @srv.addr[3], @srv.addr[1]
16
+ str = "HELLO WORLD\n"
17
+ cfg.instance_eval do
18
+ GTL.synchronize do
19
+ app = Rack::Builder.new do
20
+ use Rack::ContentLength
21
+ use Rack::ContentType, "text/plain"
22
+ run(lambda do |env|
23
+ case env['PATH_INFO']
24
+ when '/'; return [ 200, {}, [ str ] ]
25
+ when '/304'; return [ 304, {}, [ str ] ]
26
+ else
27
+ abort 'unsupported'
28
+ end
29
+ end)
30
+ end
31
+ app(:rack, app) { listen "#{host}:#{port}" }
32
+ end
33
+ logger(Logger.new(err.path))
34
+ end
35
+ pid = mkserver(cfg)
36
+ s = TCPSocket.new(host, port)
37
+ s.write("HEAD / HTTP/1.0\r\n\r\n")
38
+ assert s.wait(30), "IO wait failed"
39
+ buf = s.read
40
+ assert_match %r{\r\n\r\n\z}, buf
41
+ s.close
42
+
43
+ s = TCPSocket.new(host, port)
44
+ s.write("GET /304 HTTP/1.0\r\n\r\n")
45
+ assert s.wait(30), "IO wait failed"
46
+ buf = s.read
47
+ assert_match %r{\r\n\r\n\z}, buf
48
+ assert_match %r{\b304\b}, buf
49
+ s.close
50
+ ensure
51
+ quit_wait(pid)
52
+ end
53
+
12
54
  def test_response_time_empty_body
13
55
  err = @err
14
56
  cfg = Yahns::Config.new