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'
6 class TestServer < Testcase
7 ENV["N"].to_i > 1 and parallelize_me!
10 alias setup server_helper_setup
11 alias teardown server_helper_teardown
13 def test_single_process
15 cfg = Yahns::Config.new
16 host, port = @srv.addr[3], @srv.addr[1]
18 ru = lambda { |_| [ 200, {'Content-Length'=>'2'}, ['HI'] ] }
19 GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
20 logger(Logger.new(err.path))
23 run_client(host, port) { |res| assert_equal "HI", res.body }
24 c = get_tcp_client(host, port)
27 r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
30 Timeout.timeout(10) do
31 until buf =~ /HI.+HI/m
32 buf << c.readpartial(4096)
39 Timeout.timeout(10) do
41 buf << c.readpartial(4096)
44 c.write("/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
45 Timeout.timeout(10) do
46 until buf =~ /HI.+HI/m
47 buf << c.readpartial(4096)
50 Process.kill(:QUIT, pid)
51 "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".each_byte do |x|
55 buf = Timeout.timeout(30) { c.read }
56 assert_match(/Connection: close/, buf)
57 _, status = Timeout.timeout(10) { Process.waitpid2(pid) }
58 assert status.success?, status.inspect
62 def test_input_body_true; input_body(true); end
63 def test_input_body_false; input_body(false); end
64 def test_input_body_lazy; input_body(:lazy); end
68 cfg = Yahns::Config.new
69 host, port = @srv.addr[3], @srv.addr[1]
71 ru = lambda {|e|[ 200, {'Content-Length'=>'2'},[e["rack.input"].read]]}
74 listen "#{host}:#{port}"
78 logger(Logger.new(err.path))
81 c = get_tcp_client(host, port)
82 buf = "PUT / HTTP/1.0\r\nContent-Length: 2\r\n\r\nHI"
84 IO.select([c], nil, nil, 5)
86 head, body = rv.split(/\r\n\r\n/)
87 assert_match(%r{^Content-Length: 2\r\n}, head)
88 assert_equal "HI", body, "#{rv.inspect} - #{btype.inspect}"
92 buf = "PUT / HTTP/1.1\r\nContent-Length: 2\r\n\r\nHI"
93 c = get_tcp_client(host, port)
96 Timeout.timeout(10) do
97 until buf =~ /HI.+HI/m
98 buf << c.readpartial(4096)
101 assert buf.gsub!(/Date:[^\r\n]+\r\n/, ""), "kill differing Date"
102 rv = buf.sub!(/\A(HTTP.+?\r\n\r\nHI)/m, "")
105 assert_equal first, buf
108 buf = "PUT / HTTP/1.1\r\nContent-Length: 5\r\n\r\nHIBYE"
109 (buf + buf).each_byte do |b|
111 sleep(0.01) if b.chr == ":"
115 Timeout.timeout(10) do
116 until buf =~ /HIBYE.+HIBYE/m
117 buf << c.readpartial(4096)
120 assert buf.gsub!(/Date:[^\r\n]+\r\n/, ""), "kill differing Date"
121 rv = buf.sub!(/\A(HTTP.+?\r\n\r\nHIBYE)/m, "")
124 assert_equal first, buf
130 def test_trailer_true; trailer(true); end
131 def test_trailer_false; trailer(false); end
132 def test_trailer_lazy; trailer(:lazy); end
133 def test_slow_trailer_true; trailer(true, 0.02); end
134 def test_slow_trailer_false; trailer(false, 0.02); end
135 def test_slow_trailer_lazy; trailer(:lazy, 0.02); end
137 def trailer(btype, delay = false)
139 cfg = Yahns::Config.new
140 host, port = @srv.addr[3], @srv.addr[1]
143 body = e["rack.input"].read
144 s = e["HTTP_XBT"] + "\n" + body
145 [ 200, {'Content-Length'=>s.size.to_s}, [ s ] ]
149 listen "#{host}:#{port}"
150 input_buffering btype
153 logger(Logger.new(err.path))
156 c = get_tcp_client(host, port)
157 buf = "PUT / HTTP/1.0\r\nTrailer:xbt\r\nTransfer-Encoding: chunked\r\n\r\n"
160 sleep(delay) if delay
161 c.write(sprintf("%x\r\n", xbt.size))
162 sleep(delay) if delay
164 sleep(delay) if delay
166 sleep(delay) if delay
167 c.write("0\r\nXBT: ")
168 sleep(delay) if delay
169 c.write("#{xbt}\r\n\r\n")
170 IO.select([c], nil, nil, 5000) or raise "timed out"
172 _, body = rv.split(/\r\n\r\n/)
173 a, b = body.split(/\n/)
181 def test_check_client_connection
182 tmpdir = yahns_mktmpdir
183 sock = "#{tmpdir}/sock"
184 unix_srv = UNIXServer.new(sock)
187 cfg = Yahns::Config.new
194 a.instance_variable_set(:@bpipe, bpipe)
195 a.instance_variable_set(:@msgs, msgs)
198 yield @bpipe[0].read(msg.size)
202 # we should not get here if check_client_connection worked
207 [ 200, {'Content-Length'=>'2'}, a ]
212 check_client_connection true
213 # needed to avoid concurrency with check_client_connection
214 queue { worker_threads 1 }
215 output_buffering false
218 logger(Logger.new(err.path))
220 srv = Yahns::Server.new(cfg)
222 # ensure we set worker_threads correctly
223 eggs = srv.instance_variable_get(:@config).qeggs
224 assert_equal 1, eggs.size
225 assert_equal 1, eggs.first[1].instance_variable_get(:@worker_threads)
229 ENV["YAHNS_FD"] = unix_srv.fileno.to_s
230 unix_srv.autoclose = false
234 a = UNIXSocket.new(sock)
235 b = UNIXSocket.new(sock)
236 a.write("GET /sleep HTTP/1.0\r\n\r\n")
237 r = IO.select([a], nil, nil, 4)
238 assert r, "nothing ready"
239 assert_equal a, r[0][0]
241 assert_equal "HTTP/1.1", buf
243 # hope the kernel sees this before it sees the bpipe ping-ponging below
244 b.write("GET /cccfail HTTP/1.0\r\n\r\n")
248 # ping-pong a bit to stall the server
251 Timeout.timeout(10) { buf << a.readpartial(10) until buf =~ /#{msg}/ }
254 assert_equal msgs.join, buf.split(/\r\n\r\n/)[1]
256 # do things still work?
257 c = UNIXSocket.new(sock)
258 c.write "GET /\r\n\r\n"
259 assert_equal "HI", c.read
265 warn e.backtrace.join("\n")
269 FileUtils.rm_rf(tmpdir)
273 pid, host, port = new_mp_server
275 run_client(host, port) do |res|
276 wpid ||= res.body.to_i
281 assert_raises(Errno::ESRCH) { Process.kill(:KILL, wpid) }
282 assert_raises(Errno::ECHILD) { Process.waitpid2(wpid) }
286 def test_mp_worker_die
287 pid, host, port = new_mp_server
289 run_client(host, port) do |res|
290 wpid1 ||= res.body.to_i
292 Process.kill(:QUIT, wpid1)
293 poke_until_dead(wpid1)
294 run_client(host, port) do |res|
295 wpid2 ||= res.body.to_i
297 refute_equal wpid2, wpid1
300 assert_raises(Errno::ESRCH) { Process.kill(:KILL, wpid2) } if wpid2
303 def test_mp_dead_parent
304 pid, host, port = new_mp_server(1)
306 run_client(host, port) do |res|
307 wpid ||= res.body.to_i
309 Process.kill(:KILL, pid)
310 _, status = Process.waitpid2(pid)
311 assert status.signaled?, status.inspect
312 poke_until_dead(wpid)
315 def run_client(host, port)
316 c = get_tcp_client(host, port)
317 Net::HTTP.start(host, port) do |http|
318 res = http.request(Net::HTTP::Get.new("/"))
319 assert_equal 200, res.code.to_i
320 assert_equal "keep-alive", res["Connection"]
322 res = http.request(Net::HTTP::Get.new("/"))
323 assert_equal 200, res.code.to_i
324 assert_equal "keep-alive", res["Connection"]
327 c.write "GET / HTTP/1.0\r\n\r\n"
328 res = Timeout.timeout(10) { c.read }
329 head, _ = res.split(/\r\n\r\n/)
330 head = head.split(/\r\n/)
331 assert_equal "HTTP/1.1 200 OK", head[0]
332 assert_equal "Connection: close", head[-1]
336 def new_mp_server(nr = 2)
337 ru = @ru = tmpfile(%w(config .ru))
338 @ru.puts('a = $$.to_s')
339 @ru.puts('run lambda { |_| [ 200, {"Content-Length"=>a.size.to_s},[a]]}')
341 cfg = Yahns::Config.new
342 host, port = @srv.addr[3], @srv.addr[1]
345 GTL.synchronize { app(:rack, ru.path) { listen "#{host}:#{port}" } }
346 logger(Logger.new(File.open(err.path, "a")))
352 def test_nonpersistent
354 cfg = Yahns::Config.new
355 host, port = @srv.addr[3], @srv.addr[1]
357 ru = lambda { |_| [ 200, {'Content-Length'=>'2'}, ['HI'] ] }
360 listen "#{host}:#{port}"
361 persistent_connections false
364 logger(Logger.new(err.path))
367 c = get_tcp_client(host, port)
368 c.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
369 buf = Timeout.timeout(10) { c.read }
370 assert_match(/Connection: close/, buf)
377 err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
380 [ 200, {'Content-Length'=>b.size.to_s}, [b] ]
383 GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
390 c = get_tcp_client(host, port)
391 c.write("GET /\r\n\r\n")
392 body = Timeout.timeout(10) { c.read }
394 assert_match(/\A\d+\z/, body)
398 orig_worker_pid = read_pid.call.to_i
399 assert_equal 1, Process.kill(0, orig_worker_pid)
401 Process.kill(:TTOU, pid)
402 poke_until_dead(orig_worker_pid)
404 Process.kill(:TTIN, pid)
405 second_worker_pid = read_pid.call.to_i
407 # PID recycling is rare, hope it doesn't fail here
408 refute_equal orig_worker_pid, second_worker_pid
415 out = tmpfile(%w(mp_hooks .out))
416 cfg = Yahns::Config.new
417 host, port = @srv.addr[3], @srv.addr[1]
419 ru = lambda {|_|x="#$$";[200,{'Content-Length'=>x.size.to_s },[x]]}
422 listen "#{host}:#{port}"
423 persistent_connections false
424 atfork_child { warn "INFO hihi from app.atfork_child" }
426 worker_processes(1) do
427 atfork_child { puts "af #$$ worker is running" }
428 atfork_prepare { puts "af #$$ parent about to spawn" }
429 atfork_parent { puts "af #$$ parent done spawning" }
435 master_pid = pid = mkserver(cfg)
436 c = get_tcp_client(host, port)
437 c.write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
438 buf = Timeout.timeout(10) { c.read }
440 head, body = buf.split(/\r\n\r\n/)
441 assert_match(/200 OK/, head)
442 assert_match(/\A\d+\z/, body)
443 worker_pid = body.to_i
445 # ensure atfork_parent has run
446 quit_wait(master_pid)
449 lines = out.readlines.map!(&:chomp!)
452 assert_match %r{INFO hihi from app\.atfork_child}, File.read(err.path)
454 assert_equal 3, lines.size, lines.join("\n")
455 assert_equal("af #{pid} parent about to spawn", lines.shift)
457 # child/parent ordering is not guaranteed
458 assert_equal 1, lines.grep(/\Aaf #{pid} parent done spawning\z/).size
459 assert_equal 1, lines.grep(/\Aaf #{worker_pid} worker is running\z/).size
461 quit_wait(master_pid)
464 def test_mp_hooks_worker_nr
466 out = tmpfile(%w(mp_hooks .out))
467 cfg = Yahns::Config.new
468 host, port = @srv.addr[3], @srv.addr[1]
470 ru = lambda {|_|x="#$$";[200,{'Content-Length'=>x.size.to_s },[x]]}
473 listen "#{host}:#{port}"
474 persistent_connections false
475 atfork_child { |nr| warn "INFO hihi.#{nr} from app.atfork_child" }
477 worker_processes(1) do
478 atfork_child { |nr| puts "af.#{nr} #$$ worker is running" }
479 atfork_prepare { |nr| puts "af.#{nr} #$$ parent about to spawn" }
480 atfork_parent { |nr| puts "af.#{nr} #$$ parent done spawning" }
487 c = get_tcp_client(host, port)
488 c.write("GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
489 buf = Timeout.timeout(10) { c.read }
491 head, body = buf.split(/\r\n\r\n/)
492 assert_match(/200 OK/, head)
493 assert_match(/\A\d+\z/, body)
494 worker_pid = body.to_i
495 lines = out.readlines.map!(&:chomp!)
498 assert_match %r{INFO hihi\.0 from app\.atfork_child}, File.read(err.path)
499 assert_equal 3, lines.size
500 assert_equal("af.0 #{pid} parent about to spawn", lines.shift)
502 # child/parent ordering is not guaranteed
504 lines.grep(/\Aaf\.0 #{pid} parent done spawning\z/).size
506 lines.grep(/\Aaf\.0 #{worker_pid} worker is running\z/).size
511 def test_pidfile_usr2
512 tmpdir = yahns_mktmpdir
513 pidf = "#{tmpdir}/pid"
514 old = "#{pidf}.oldbin"
516 cfg = Yahns::Config.new
517 host, port = @srv.addr[3], @srv.addr[1]
520 app(:rack, lambda { |_| [ 200, {}, [] ] }) { listen "#{host}:#{port}" }
525 pid = mkserver(cfg) do
526 Yahns::START[0] = "sh"
527 Yahns::START[:argv] = [ '-c', "echo $$ > #{pidf}; sleep 10" ]
530 # ensure server is running
531 c = get_tcp_client(host, port)
532 c.write("GET / HTTP/1.0\r\n\r\n")
533 buf = Timeout.timeout(10) { c.read }
534 assert_match(/Connection: close/, buf)
537 assert_equal pid, File.read(pidf).to_i
538 before = File.stat(pidf)
541 Process.kill(:USR2, pid)
542 Timeout.timeout(10) { sleep(0.01) until File.exist?(old) }
543 after = File.stat(old)
544 assert_equal after.ino, before.ino
545 Timeout.timeout(10) { sleep(0.01) until File.exist?(pidf) }
546 new = File.read(pidf).to_i
547 refute_equal pid, new
549 # abort the upgrade (just wait for it to finish)
550 Process.kill(:TERM, new)
553 # ensure reversion is OK
554 Timeout.timeout(10) { sleep(0.01) while File.exist?(old) }
555 after = File.stat(pidf)
556 assert_equal before.ino, after.ino
557 assert_equal before.mtime, after.mtime
558 assert_equal pid, File.read(pidf).to_i
560 lines = File.readlines(err.path).grep(/ERROR/)
561 assert_equal 1, lines.size
562 assert_match(/reaped/, lines[0], lines)
563 File.truncate(err.path, 0)
566 FileUtils.rm_rf(tmpdir)
569 module MockSwitchUser
570 def self.included(cls)
571 cls.__send__(:remove_method, :switch_user)
572 cls.__send__(:alias_method, :switch_user, :mock_switch_user)
575 def mock_switch_user(user, group = nil)
576 $yahns_user = [ $$, user, group ]
580 def test_user_no_workers
581 refute defined?($yahns_user), "$yahns_user global should be undefined"
583 cfg = Yahns::Config.new
584 host, port = @srv.addr[3], @srv.addr[1]
587 b = $yahns_user.inspect
588 [ 200, {'Content-Length'=>b.size.to_s }, [b] ]
590 GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
594 pid = mkserver(cfg) { Yahns::Server.__send__(:include, MockSwitchUser) }
595 expect = [ pid, "nobody", nil ].inspect
596 run_client(host, port) { |res| assert_equal expect, res.body }
597 refute defined?($yahns_user), "$yahns_user global should be undefined"
602 def test_user_workers
603 refute defined?($yahns_user), "$yahns_user global should be undefined"
605 cfg = Yahns::Config.new
606 host, port = @srv.addr[3], @srv.addr[1]
609 b = $yahns_user.inspect
610 [ 200, {'Content-Length'=>b.size.to_s, 'X-Pid' => "#$$" }, [b] ]
612 GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
617 pid = mkserver(cfg) { Yahns::Server.__send__(:include, MockSwitchUser) }
618 run_client(host, port) do |res|
619 worker_pid = res["X-Pid"].to_i
620 assert_operator worker_pid, :>, 0
621 refute_equal pid, worker_pid
622 refute_equal $$, worker_pid
623 expect = [ worker_pid, "nobody", nil ].inspect
624 assert_equal expect, res.body
626 refute defined?($yahns_user), "$yahns_user global should be undefined"
631 def test_working_directory
632 err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
634 [ 200, {'Content-Length'=>Dir.pwd.size.to_s }, [Dir.pwd] ]
636 yahns_mktmpdir do |tmpdir|
638 pid = mkserver(cfg) do
639 $LOAD_PATH << File.expand_path("lib")
641 working_directory tmpdir
642 app(:rack, ru) { listen "#{host}:#{port}" }
646 refute_equal Dir.pwd, tmpdir
647 Net::HTTP.start(host, port) do |http|
648 assert_equal tmpdir, http.request(Net::HTTP::Get.new("/")).body
657 tmpdir = yahns_mktmpdir
658 sock = "#{tmpdir}/sock"
659 err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
660 re = tmpfile(%w(rack .errors))
662 e["rack.errors"].write "INFO HIHI\n"
663 [ 200, {'Content-Length'=>'2' }, %w(OK) ]
668 listen "#{host}:#{port}"
671 app(:rack, ru) { listen sock }
676 Net::HTTP.start(host, port) do |http|
677 assert_equal "OK", http.request(Net::HTTP::Get.new("/")).body
679 assert_equal "INFO HIHI\n", re.read
681 c = UNIXSocket.new(sock)
682 c.write "GET /\r\n\r\n"
683 assert_equal c, c.wait(30)
684 assert_equal "OK", c.read
686 assert_match %r{INFO HIHI}, File.read(err.path)
690 FileUtils.rm_rf(tmpdir)
693 def test_persistent_shutdown_timeout; _persistent_shutdown(nil); end
694 def test_persistent_shutdown_timeout_mp; _persistent_shutdown(1); end
696 def _persistent_shutdown(nr_workers)
697 err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
698 pid = mkserver(cfg) do
699 ru = lambda { |e| [ 200, {'Content-Length'=>'2'}, %w(OK) ] }
701 app(:rack, ru) { listen "#{host}:#{port}" }
704 worker_processes(nr_workers) if nr_workers
707 c = get_tcp_client(host, port)
708 c.write "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
709 assert_equal c, c.wait(30)
712 Timeout.timeout(30) do
714 buf << c.readpartial(666)
717 refute_match %r{Connection: close}, buf
718 assert_nil c.wait(0.001), "connection should still be alive"
719 Process.kill(:QUIT, pid)
720 _, status = Timeout.timeout(5) { Process.waitpid2(pid) }
721 assert status.success?, status.inspect
722 assert_nil c.read(666)
725 def test_slow_shutdown_timeout; _slow_shutdown(nil); end
726 def test_slow_shutdown_timeout_mp; _slow_shutdown(1); end
728 def _slow_shutdown(nr_workers)
729 err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
730 pid = mkserver(cfg) do
731 ru = lambda { |e| [ 200, {'Content-Length'=>'2'}, %w(OK) ] }
733 app(:rack, ru) { listen "#{host}:#{port}" }
735 worker_processes(nr_workers) if nr_workers
738 c = get_tcp_client(host, port)
740 100000.times { Thread.pass }
741 Process.kill(:QUIT, pid)
742 "ET / HTTP/1.1\r\nHost: example.com\r\n\r\n".each_byte do |x|
747 assert_equal c, c.wait(30)
750 Timeout.timeout(30) do
752 buf << c.readpartial(666)
756 _, status = Timeout.timeout(5) { Process.waitpid2(pid) }
757 assert status.success?, status.inspect
761 err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
762 ru = lambda { |e| [ 200, {'Content-Length'=>'2' }, %w(OK) ] }
763 tmp = tmpfile(%w(exec .pid))
764 x = "echo $$ >> #{tmp.path}"
765 pid = mkserver(cfg) do
767 app(:rack, ru) { listen "#{host}:#{port}" }
768 before_exec do |exec_cmd|
769 exec_cmd.replace(%W(/bin/sh -c #{x}))
775 # did we start properly?
776 Net::HTTP.start(host, port) do |http|
777 assert_equal "OK", http.request(Net::HTTP::Get.new("/")).body
780 Process.kill(:USR2, pid)
781 Timeout.timeout(30) { sleep(0.01) until tmp.size > 0 }
783 assert_match %r{\A\d+}, buf
785 poke_until_dead exec_pid
787 # ensure it recovered
788 Net::HTTP.start(host, port) do |http|
789 assert_equal "OK", http.request(Net::HTTP::Get.new("/")).body
791 assert_match %r{reaped}, err.read
798 def test_app_controls_close
799 err, cfg, host, port = @err, Yahns::Config.new, @srv.addr[3], @srv.addr[1]
800 pid = mkserver(cfg) do
803 h = { 'Content-Length' => '2' }
804 if env["PATH_INFO"] =~ %r{\A/(.+)}
809 app(:rack, ru) { listen "#{host}:#{port}" }
813 c = get_tcp_client(host, port)
816 c.write "GET /keep-alive HTTP/1.1\r\nHost: example.com\r\n\r\n"
818 Timeout.timeout(30) do
819 buf << c.readpartial(4096) until buf =~ /HI\z/
821 assert_match %r{^Connection: keep-alive}, buf
822 assert_raises(Errno::EAGAIN,IO::WaitReadable) { c.read_nonblock(666) }
824 # we allow whatever in the response, but don't send it
825 c.write "GET /whatever HTTP/1.1\r\nHost: example.com\r\n\r\n"
827 Timeout.timeout(30) do
828 buf << c.readpartial(4096) until buf =~ /HI\z/
830 assert_match %r{^Connection: keep-alive}, buf
831 assert_raises(Errno::EAGAIN,IO::WaitReadable) { c.read_nonblock(666) }
833 c.write "GET /close HTTP/1.1\r\nHost: example.com\r\n\r\n"
835 Timeout.timeout(30) do
836 buf << c.readpartial(4096) until buf =~ /HI\z/
838 assert_match %r{^Connection: close}, buf
839 assert_equal c, IO.select([c], nil, nil, 30)[0][0]
840 assert_raises(EOFError) { c.readpartial(666) }
846 def test_inherit_too_many
848 s2 = TCPServer.new(ENV["TEST_HOST"] || "127.0.0.1", 0)
849 cfg = Yahns::Config.new
850 host, port = @srv.addr[3], @srv.addr[1]
852 ru = lambda { |_| [ 200, {'Content-Length'=>'2'}, ['HI'] ] }
853 GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
854 logger(Logger.new(err.path))
856 pid = mkserver(cfg, @srv) do
858 ENV["YAHNS_FD"] = "#{@srv.fileno},#{s2.fileno}"
860 run_client(host, port) { |res| assert_equal "HI", res.body }
864 c.write "HTTP/1.0 666 OK\r\n\r\nGO AWAY"
869 s2host, s2port = s2.addr[3], s2.addr[1]
870 Net::HTTP.start(s2host, s2port) do |http|
871 res = http.request(Net::HTTP::Get.new("/"))
872 assert_equal 666, res.code.to_i
873 assert_equal "GO AWAY", res.body
875 assert_equal :OK, th.value
876 tmpc = TCPSocket.new(s2host, s2port)
878 assert_nil IO.select([a2], nil, nil, 0.05)
880 assert_nil a2.read(1)
887 def test_inherit_tcp_nodelay_set
889 cfg = Yahns::Config.new
890 host, port = @srv.addr[3], @srv.addr[1]
891 @srv.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, 0)
892 assert_equal 0, @srv.getsockopt(:IPPROTO_TCP, :TCP_NODELAY).int
894 ru = lambda { |_| [ 200, { 'Content-Length' => '2' } , [ 'HI' ] ] }
895 GTL.synchronize { app(:rack, ru) { listen "#{host}:#{port}" } }
896 logger(Logger.new(err.path))
898 pid = mkserver(cfg, @srv) { ENV["YAHNS_FD"] = "#{@srv.fileno}" }
899 run_client(host, port) { |res| assert_equal "HI", res.body }
901 # TCP socket option is shared at file level, not FD level:
902 assert_equal 1, @srv.getsockopt(:IPPROTO_TCP, :TCP_NODELAY).int