yahns 0.0.0TP1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/COPYING +674 -0
- data/GIT-VERSION-GEN +41 -0
- data/GNUmakefile +90 -0
- data/README +127 -0
- data/Rakefile +60 -0
- data/bin/yahns +32 -0
- data/examples/README +3 -0
- data/examples/init.sh +76 -0
- data/examples/logger_mp_safe.rb +28 -0
- data/examples/logrotate.conf +32 -0
- data/examples/yahns_multi.conf.rb +89 -0
- data/examples/yahns_rack_basic.conf.rb +27 -0
- data/lib/yahns.rb +73 -0
- data/lib/yahns/acceptor.rb +28 -0
- data/lib/yahns/client_expire.rb +40 -0
- data/lib/yahns/client_expire_portable.rb +39 -0
- data/lib/yahns/config.rb +344 -0
- data/lib/yahns/daemon.rb +51 -0
- data/lib/yahns/fdmap.rb +90 -0
- data/lib/yahns/http_client.rb +198 -0
- data/lib/yahns/http_context.rb +65 -0
- data/lib/yahns/http_response.rb +184 -0
- data/lib/yahns/log.rb +73 -0
- data/lib/yahns/queue.rb +7 -0
- data/lib/yahns/queue_egg.rb +23 -0
- data/lib/yahns/queue_epoll.rb +57 -0
- data/lib/yahns/rack.rb +80 -0
- data/lib/yahns/server.rb +336 -0
- data/lib/yahns/server_mp.rb +181 -0
- data/lib/yahns/sigevent.rb +7 -0
- data/lib/yahns/sigevent_efd.rb +18 -0
- data/lib/yahns/sigevent_pipe.rb +29 -0
- data/lib/yahns/socket_helper.rb +117 -0
- data/lib/yahns/stream_file.rb +34 -0
- data/lib/yahns/stream_input.rb +150 -0
- data/lib/yahns/tee_input.rb +114 -0
- data/lib/yahns/tmpio.rb +27 -0
- data/lib/yahns/wbuf.rb +36 -0
- data/lib/yahns/wbuf_common.rb +32 -0
- data/lib/yahns/worker.rb +58 -0
- data/test/covshow.rb +29 -0
- data/test/helper.rb +115 -0
- data/test/server_helper.rb +65 -0
- data/test/test_bin.rb +97 -0
- data/test/test_client_expire.rb +132 -0
- data/test/test_config.rb +56 -0
- data/test/test_fdmap.rb +19 -0
- data/test/test_output_buffering.rb +291 -0
- data/test/test_queue.rb +59 -0
- data/test/test_rack.rb +28 -0
- data/test/test_serve_static.rb +42 -0
- data/test/test_server.rb +415 -0
- data/test/test_stream_file.rb +30 -0
- data/test/test_wbuf.rb +136 -0
- data/yahns.gemspec +19 -0
- metadata +165 -0
data/test/test_bin.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require_relative 'server_helper'
|
4
|
+
class TestBin < Testcase
|
5
|
+
parallelize_me!
|
6
|
+
include ServerHelper
|
7
|
+
alias teardown server_helper_teardown
|
8
|
+
|
9
|
+
def setup
|
10
|
+
server_helper_setup
|
11
|
+
@cmd = %W(ruby -I lib bin/yahns)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_bin_daemon_noworker_inherit
|
15
|
+
bin_daemon(false, true)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_bin_daemon_worker_inherit
|
19
|
+
bin_daemon(true, true)
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_bin_daemon_noworker_bind
|
23
|
+
bin_daemon(false, false)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_bin_daemon_worker_bind
|
27
|
+
bin_daemon(true, false)
|
28
|
+
end
|
29
|
+
|
30
|
+
def bin_daemon(worker, inherit)
|
31
|
+
@srv.close unless inherit
|
32
|
+
@pid = tmpfile(%w(test_bin_daemon .pid))
|
33
|
+
@ru = tmpfile(%w(test_bin_daemon .ru))
|
34
|
+
@ru.write("require 'rack/lobster'; run Rack::Lobster.new\n")
|
35
|
+
cfg = tmpfile(%w(test_bin_daemon_conf .rb))
|
36
|
+
cfg.puts "pid '#{@pid.path}'"
|
37
|
+
cfg.puts "stderr_path '#{@err.path}'"
|
38
|
+
cfg.puts "worker_processes 1" if worker
|
39
|
+
cfg.puts "app(:rack, '#{@ru.path}', preload: false) do"
|
40
|
+
cfg.puts " listen ENV['YAHNS_TEST_LISTEN']"
|
41
|
+
cfg.puts "end"
|
42
|
+
@cmd.concat(%W(-D -c #{cfg.path}))
|
43
|
+
addr = IO.pipe
|
44
|
+
pid = fork do
|
45
|
+
if inherit
|
46
|
+
@cmd << { @srv.fileno => @srv }
|
47
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
48
|
+
else
|
49
|
+
@srv = TCPServer.new(ENV["TEST_HOST"] || "127.0.0.1", 0)
|
50
|
+
end
|
51
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
52
|
+
listen = ENV["YAHNS_TEST_LISTEN"] = "#{host}:#{port}"
|
53
|
+
addr[1].write(listen)
|
54
|
+
addr[1].close
|
55
|
+
addr[0].close
|
56
|
+
exec(*@cmd)
|
57
|
+
end
|
58
|
+
addr[1].close
|
59
|
+
listen = Timeout.timeout(10) { addr[0].read }
|
60
|
+
addr[0].close
|
61
|
+
host, port = listen.split(/:/, 2)
|
62
|
+
port = port.to_i
|
63
|
+
assert_operator port, :>, 0
|
64
|
+
|
65
|
+
unless inherit
|
66
|
+
# daemon_pipe guarantees socket will be usable after this:
|
67
|
+
Timeout.timeout(10) do # Ruby startup is slow!
|
68
|
+
_, status = Process.waitpid2(pid)
|
69
|
+
assert status.success?, status.inspect
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
Net::HTTP.start(host, port) do |http|
|
74
|
+
req = Net::HTTP::Get.new("/")
|
75
|
+
res = http.request(req)
|
76
|
+
assert_equal 200, res.code.to_i
|
77
|
+
assert_equal "keep-alive", res["Connection"]
|
78
|
+
end
|
79
|
+
rescue => e
|
80
|
+
warn "#{e.message} (#{e.class})"
|
81
|
+
e.backtrace.each { |l| warn "#{l}" }
|
82
|
+
raise
|
83
|
+
ensure
|
84
|
+
cfg.close! if cfg
|
85
|
+
pid = File.read(@pid.path)
|
86
|
+
pid = pid.to_i
|
87
|
+
assert_operator pid, :>, 0
|
88
|
+
Process.kill(:QUIT, pid)
|
89
|
+
if inherit
|
90
|
+
_, status = Timeout.timeout(10) { Process.waitpid2(pid) }
|
91
|
+
assert status.success?, status.inspect
|
92
|
+
else
|
93
|
+
poke_until_dead pid
|
94
|
+
end
|
95
|
+
@pid.close! if @pid
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require_relative 'server_helper'
|
4
|
+
|
5
|
+
class TestClientExpire < Testcase
|
6
|
+
parallelize_me!
|
7
|
+
include ServerHelper
|
8
|
+
alias setup server_helper_setup
|
9
|
+
alias teardown server_helper_teardown
|
10
|
+
|
11
|
+
def test_client_expire_negative
|
12
|
+
err = @err
|
13
|
+
cfg = Yahns::Config.new
|
14
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
15
|
+
cfg.instance_eval do
|
16
|
+
GTL.synchronize do
|
17
|
+
ru = lambda { |e| h = { "Content-Length" => "0" }; [ 200, h, [] ] }
|
18
|
+
app(:rack, ru) do
|
19
|
+
listen "#{host}:#{port}", sndbuf: 2048, rcvbuf: 2048
|
20
|
+
end
|
21
|
+
client_expire_threshold(-10)
|
22
|
+
end
|
23
|
+
logger(Logger.new(err.path))
|
24
|
+
end
|
25
|
+
srv = Yahns::Server.new(cfg)
|
26
|
+
pid = fork do
|
27
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
28
|
+
srv.start.join
|
29
|
+
end
|
30
|
+
Net::HTTP.start(host, port) { |h|
|
31
|
+
res = h.get("/")
|
32
|
+
assert_empty res.body
|
33
|
+
}
|
34
|
+
ensure
|
35
|
+
quit_wait(pid)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_client_expire
|
39
|
+
nr = 32
|
40
|
+
err = @err
|
41
|
+
cfg = Yahns::Config.new
|
42
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
43
|
+
cfg.instance_eval do
|
44
|
+
GTL.synchronize do
|
45
|
+
h = { "Content-Length" => "0" }
|
46
|
+
app(:rack, lambda { |e| [ 200, h, [] ]}) do
|
47
|
+
listen "#{host}:#{port}", sndbuf: 2048, rcvbuf: 2048
|
48
|
+
client_timeout 1
|
49
|
+
end
|
50
|
+
client_expire_threshold(32)
|
51
|
+
end
|
52
|
+
logger(Logger.new(err.path))
|
53
|
+
end
|
54
|
+
srv = Yahns::Server.new(cfg)
|
55
|
+
pid = fork do
|
56
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
57
|
+
srv.start.join
|
58
|
+
end
|
59
|
+
f = TCPSocket.new(host, port)
|
60
|
+
s = TCPSocket.new(host, port)
|
61
|
+
req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
62
|
+
s.write(req)
|
63
|
+
str = Timeout.timeout(20) { s.readpartial(666) }
|
64
|
+
assert_match(%r{keep-alive}, str)
|
65
|
+
sleep 2
|
66
|
+
abe = tmpfile(%w(abe .err))
|
67
|
+
ab_res = `ab -c #{nr} -n 10000 -k http://#{host}:#{port}/ 2>#{abe.path}`
|
68
|
+
assert $?.success?, $?.inspect << abe.read
|
69
|
+
abe.close!
|
70
|
+
assert_match(/Complete requests:\s+10000\n/, ab_res)
|
71
|
+
|
72
|
+
[ f, s ].each do |io|
|
73
|
+
assert_raises(Errno::EPIPE,Errno::ECONNRESET) do
|
74
|
+
req.each_byte { |b| io.write(b.chr) }
|
75
|
+
end
|
76
|
+
io.close
|
77
|
+
end
|
78
|
+
rescue => e
|
79
|
+
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
80
|
+
raise
|
81
|
+
ensure
|
82
|
+
quit_wait(pid)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_client_expire_desperate
|
86
|
+
skip "disabled since FD counts vary heavily in an MT process"
|
87
|
+
err = @err
|
88
|
+
cfg = Yahns::Config.new
|
89
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
90
|
+
cfg.instance_eval do
|
91
|
+
GTL.synchronize do
|
92
|
+
h = { "Content-Length" => "0" }
|
93
|
+
app(:rack, lambda { |e| [ 200, h, [] ]}) do
|
94
|
+
listen "#{host}:#{port}", sndbuf: 2048, rcvbuf: 2048
|
95
|
+
client_timeout 1
|
96
|
+
end
|
97
|
+
client_expire_threshold 1.0
|
98
|
+
end
|
99
|
+
logger(Logger.new(err.path))
|
100
|
+
end
|
101
|
+
srv = Yahns::Server.new(cfg)
|
102
|
+
pid = fork do
|
103
|
+
Process.setrlimit :NOFILE, 512, 1024
|
104
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
105
|
+
srv.start.join
|
106
|
+
end
|
107
|
+
f = TCPSocket.new(host, port)
|
108
|
+
s = TCPSocket.new(host, port)
|
109
|
+
req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
110
|
+
s.write(req)
|
111
|
+
str = Timeout.timeout(20) { s.readpartial(666) }
|
112
|
+
assert_match(%r{keep-alive}, str)
|
113
|
+
sleep 3
|
114
|
+
system "ab -c 1000 -n 10000 -k http://#{host}:#{port}/ 2>&1"
|
115
|
+
|
116
|
+
[ f, s ].each do |io|
|
117
|
+
assert_raises(Errno::EPIPE,Errno::ECONNRESET) do
|
118
|
+
req.each_byte { |b| io.write(b.chr) }
|
119
|
+
end
|
120
|
+
io.close
|
121
|
+
end
|
122
|
+
errs = File.readlines(err.path).grep(/ERROR/)
|
123
|
+
File.truncate(err.path, 0) # avoid error on teardown
|
124
|
+
re = %r{consider raising open file limits} # - accept, consider raising open file limits}
|
125
|
+
assert_equal errs.grep(re), errs
|
126
|
+
rescue => e
|
127
|
+
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
128
|
+
raise
|
129
|
+
ensure
|
130
|
+
quit_wait(pid)
|
131
|
+
end
|
132
|
+
end
|
data/test/test_config.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require_relative 'helper'
|
4
|
+
require 'rack/lobster'
|
5
|
+
|
6
|
+
class TestConfig < Testcase
|
7
|
+
parallelize_me!
|
8
|
+
|
9
|
+
def test_initialize
|
10
|
+
cfg = Yahns::Config.new
|
11
|
+
assert_instance_of Yahns::Config, cfg
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_multi_conf_example
|
15
|
+
tmpdir = Dir.mktmpdir
|
16
|
+
|
17
|
+
# modify the example config file for testing
|
18
|
+
path = "examples/yahns_multi.conf.rb"
|
19
|
+
cfgs = File.read(path)
|
20
|
+
cfgs.gsub!(%r{/path/to/}, "#{tmpdir}/")
|
21
|
+
conf = File.open("#{tmpdir}/yahns_multi.conf.rb", "w")
|
22
|
+
conf.sync = true
|
23
|
+
conf.write(cfgs)
|
24
|
+
File.open("#{tmpdir}/another.ru", "w") do |fp|
|
25
|
+
fp.puts("run Rack::Lobster.new\n")
|
26
|
+
end
|
27
|
+
FileUtils.mkpath("#{tmpdir}/another")
|
28
|
+
|
29
|
+
cfg = GTL.synchronize { Yahns::Config.new(conf.path) }
|
30
|
+
assert_instance_of Yahns::Config, cfg
|
31
|
+
ensure
|
32
|
+
FileUtils.rm_rf(tmpdir) if tmpdir
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_rack_basic_conf_example
|
36
|
+
tmpdir = Dir.mktmpdir
|
37
|
+
|
38
|
+
# modify the example config file for testing
|
39
|
+
path = "examples/yahns_rack_basic.conf.rb"
|
40
|
+
cfgs = File.read(path)
|
41
|
+
cfgs.gsub!(%r{/path/to/}, "#{tmpdir}/")
|
42
|
+
Dir.mkdir("#{tmpdir}/my_app")
|
43
|
+
Dir.mkdir("#{tmpdir}/my_logs")
|
44
|
+
Dir.mkdir("#{tmpdir}/my_pids")
|
45
|
+
conf = File.open("#{tmpdir}/yahns_rack_basic.conf.rb", "w")
|
46
|
+
conf.sync = true
|
47
|
+
conf.write(cfgs)
|
48
|
+
File.open("#{tmpdir}/my_app/config.ru", "w") do |fp|
|
49
|
+
fp.puts("run Rack::Lobster.new\n")
|
50
|
+
end
|
51
|
+
cfg = GTL.synchronize { Yahns::Config.new(conf.path) }
|
52
|
+
assert_instance_of Yahns::Config, cfg
|
53
|
+
ensure
|
54
|
+
FileUtils.rm_rf(tmpdir) if tmpdir
|
55
|
+
end
|
56
|
+
end
|
data/test/test_fdmap.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require_relative 'helper'
|
4
|
+
|
5
|
+
class TestFdmap < Testcase
|
6
|
+
def test_fdmap_negative
|
7
|
+
fdmap = Yahns::Fdmap.new(Logger.new($stderr), -5)
|
8
|
+
nr = fdmap.instance_variable_get :@client_expire_threshold
|
9
|
+
assert_operator nr, :>, 0
|
10
|
+
assert_equal nr, Process.getrlimit(:NOFILE)[0] - 5
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_fdmap_float
|
14
|
+
fdmap = Yahns::Fdmap.new(Logger.new($stderr), 0.5)
|
15
|
+
nr = fdmap.instance_variable_get :@client_expire_threshold
|
16
|
+
assert_operator nr, :>, 0
|
17
|
+
assert_equal nr, Process.getrlimit(:NOFILE)[0]/2
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
# Copyright (C) 2013, Eric Wong <normalperson@yhbt.net> and all contributors
|
2
|
+
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
|
3
|
+
require_relative 'server_helper'
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'rack/file'
|
6
|
+
|
7
|
+
class TestOutputBuffering < Testcase
|
8
|
+
parallelize_me!
|
9
|
+
include ServerHelper
|
10
|
+
alias setup server_helper_setup
|
11
|
+
alias teardown server_helper_teardown
|
12
|
+
|
13
|
+
GPLv3 = File.read("COPYING")
|
14
|
+
RAND = IO.binread("/dev/urandom", 666) * 119
|
15
|
+
dig = Digest::MD5.new
|
16
|
+
NR = 1337
|
17
|
+
MD5 = Thread.new do
|
18
|
+
NR.times { dig << RAND }
|
19
|
+
dig.hexdigest
|
20
|
+
end
|
21
|
+
|
22
|
+
class BigBody
|
23
|
+
def each
|
24
|
+
NR.times { yield RAND }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_output_buffer_false_curl
|
29
|
+
output_buffer(false, :curl)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_output_buffer_false_http09
|
33
|
+
output_buffer(false, :http09)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_output_buffer_true_curl
|
37
|
+
output_buffer(true, :curl)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_output_buffer_true_http09
|
41
|
+
output_buffer(true, :http09)
|
42
|
+
end
|
43
|
+
|
44
|
+
def output_buffer(btype, check_type, delay = 4)
|
45
|
+
err = @err
|
46
|
+
cfg = Yahns::Config.new
|
47
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
48
|
+
len = (RAND.size * NR).to_s
|
49
|
+
cfg.instance_eval do
|
50
|
+
ru = lambda do |e|
|
51
|
+
[ 200, {'Content-Length'=>len}, BigBody.new ]
|
52
|
+
end
|
53
|
+
GTL.synchronize do
|
54
|
+
app(:rack, ru) do
|
55
|
+
listen "#{host}:#{port}"
|
56
|
+
output_buffering btype
|
57
|
+
end
|
58
|
+
end
|
59
|
+
logger(Logger.new(err.path))
|
60
|
+
end
|
61
|
+
srv = Yahns::Server.new(cfg)
|
62
|
+
pid = fork do
|
63
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
64
|
+
srv.start.join
|
65
|
+
end
|
66
|
+
|
67
|
+
case check_type
|
68
|
+
when :curl
|
69
|
+
# curl is faster for piping gigantic wads of data than Net::HTTP
|
70
|
+
sh_sleep = delay ? "sleep #{delay} && " : ""
|
71
|
+
md5 = `curl -sSf http://#{host}:#{port}/ | (#{sh_sleep} md5sum)`
|
72
|
+
assert $?.success?, $?.inspect
|
73
|
+
(md5 =~ /([a-f0-9]{32})/i) or raise "bad md5: #{md5.inspect}"
|
74
|
+
md5 = $1
|
75
|
+
assert_equal MD5.value, md5
|
76
|
+
when :http09
|
77
|
+
# HTTP/0.9
|
78
|
+
c = TCPSocket.new(host, port)
|
79
|
+
c.write("GET /\r\n\r\n")
|
80
|
+
md5in = IO.pipe
|
81
|
+
md5out = IO.pipe
|
82
|
+
sleep(delay) if delay
|
83
|
+
md5pid = Process.spawn("md5sum", :in => md5in[0], :out => md5out[1])
|
84
|
+
md5in[0].close
|
85
|
+
md5out[1].close
|
86
|
+
assert_equal(NR * RAND.size, IO.copy_stream(c, md5in[1]))
|
87
|
+
c.shutdown
|
88
|
+
c.close
|
89
|
+
md5in[1].close
|
90
|
+
_, status = Timeout.timeout(10) { Process.waitpid2(md5pid) }
|
91
|
+
assert status.success?, status.inspect
|
92
|
+
md5 = md5out[0].read
|
93
|
+
(md5 =~ /([a-f0-9]{32})/i) or raise "bad md5: #{md5.inspect}"
|
94
|
+
md5 = $1
|
95
|
+
assert_equal MD5.value, md5
|
96
|
+
md5out[0].close
|
97
|
+
else
|
98
|
+
raise "TESTBUG"
|
99
|
+
end
|
100
|
+
rescue => e
|
101
|
+
Yahns::Log.exception(Logger.new($stderr), "test", e)
|
102
|
+
raise
|
103
|
+
ensure
|
104
|
+
quit_wait(pid)
|
105
|
+
end
|
106
|
+
|
107
|
+
class BigHeader
|
108
|
+
A = "A" * 65536
|
109
|
+
def initialize(h)
|
110
|
+
@h = h
|
111
|
+
end
|
112
|
+
def each
|
113
|
+
NR.times do |n|
|
114
|
+
yield("X-#{n}", A)
|
115
|
+
end
|
116
|
+
@h.each { |k,v| yield(k,v) }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_big_header
|
121
|
+
err = @err
|
122
|
+
cfg = Yahns::Config.new
|
123
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
124
|
+
cfg.instance_eval do
|
125
|
+
ru = lambda do |e|
|
126
|
+
case e["PATH_INFO"]
|
127
|
+
when "/COPYING"
|
128
|
+
Rack::File.new(Dir.pwd).call(e)
|
129
|
+
gplv3 = File.open("COPYING")
|
130
|
+
def gplv3.each
|
131
|
+
raise "SHOULD NOT BE CALLED"
|
132
|
+
end
|
133
|
+
size = gplv3.stat.size
|
134
|
+
len = size.to_s
|
135
|
+
ranges = Rack::Utils.byte_ranges(e, size)
|
136
|
+
status = 200
|
137
|
+
h = { "Content-Type" => "text/plain", "Content-Length" => len }
|
138
|
+
if ranges && ranges.size == 1
|
139
|
+
status = 206
|
140
|
+
range = ranges[0]
|
141
|
+
h["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{size}"
|
142
|
+
size = range.end - range.begin + 1
|
143
|
+
len.replace(size.to_s)
|
144
|
+
end
|
145
|
+
[ status , BigHeader.new(h), gplv3 ]
|
146
|
+
when "/"
|
147
|
+
h = { "Content-Type" => "text/plain", "Content-Length" => "4" }
|
148
|
+
[ 200, BigHeader.new(h), ["BIG\n"] ]
|
149
|
+
else
|
150
|
+
raise "WTF"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
GTL.synchronize do
|
154
|
+
app(:rack, ru) do
|
155
|
+
listen "#{host}:#{port}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
logger(Logger.new(err.path))
|
159
|
+
end
|
160
|
+
srv = Yahns::Server.new(cfg)
|
161
|
+
pid = fork do
|
162
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
163
|
+
srv.start.join
|
164
|
+
end
|
165
|
+
threads = []
|
166
|
+
|
167
|
+
# start with just a big header
|
168
|
+
threads << Thread.new do
|
169
|
+
c = TCPSocket.new(host, port)
|
170
|
+
c.write "GET / HTTP/1.0\r\n\r\n"
|
171
|
+
begin
|
172
|
+
sleep 1
|
173
|
+
end while c.nread == 0
|
174
|
+
nr = 0
|
175
|
+
last = nil
|
176
|
+
c.each_line do |line|
|
177
|
+
case line
|
178
|
+
when %r{\AX-} then nr += 1
|
179
|
+
else
|
180
|
+
last = line
|
181
|
+
end
|
182
|
+
end
|
183
|
+
assert_equal NR, nr
|
184
|
+
assert_equal "BIG\n", last
|
185
|
+
c.close
|
186
|
+
end
|
187
|
+
|
188
|
+
threads << Thread.new do
|
189
|
+
c = TCPSocket.new(host, port)
|
190
|
+
c.write "GET /COPYING HTTP/1.0\r\n\r\n"
|
191
|
+
begin
|
192
|
+
sleep 1
|
193
|
+
end while c.nread == 0
|
194
|
+
nr = 0
|
195
|
+
c.each_line do |line|
|
196
|
+
case line
|
197
|
+
when %r{\AX-} then nr += 1
|
198
|
+
else
|
199
|
+
break if line == "\r\n"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
assert_equal NR, nr
|
203
|
+
assert_equal GPLv3, c.read
|
204
|
+
c.close
|
205
|
+
end
|
206
|
+
|
207
|
+
threads << Thread.new do
|
208
|
+
c = TCPSocket.new(host, port)
|
209
|
+
c.write "GET /COPYING HTTP/1.0\r\nRange: bytes=5-46\r\n\r\n"
|
210
|
+
begin
|
211
|
+
sleep 1
|
212
|
+
end while c.nread == 0
|
213
|
+
nr = 0
|
214
|
+
c.each_line do |line|
|
215
|
+
case line
|
216
|
+
when %r{\AX-} then nr += 1
|
217
|
+
else
|
218
|
+
break if line == "\r\n"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
assert_equal NR, nr
|
222
|
+
assert_equal GPLv3[5..46], c.read
|
223
|
+
c.close
|
224
|
+
end
|
225
|
+
threads.each do |t|
|
226
|
+
assert_equal t, t.join(30)
|
227
|
+
assert_nil t.value
|
228
|
+
end
|
229
|
+
ensure
|
230
|
+
quit_wait(pid)
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_client_timeout
|
234
|
+
err = @err
|
235
|
+
apperr = tmpfile(%w(app .err))
|
236
|
+
cfg = Yahns::Config.new
|
237
|
+
size = RAND.size * NR
|
238
|
+
host, port = @srv.addr[3], @srv.addr[1]
|
239
|
+
cfg.instance_eval do
|
240
|
+
ru = lambda do |e|
|
241
|
+
if e["PATH_INFO"] == "/bh"
|
242
|
+
h = { "Content-Type" => "text/plain", "Content-Length" => "4" }
|
243
|
+
[ 200, BigHeader.new(h), ["BIG\n"] ]
|
244
|
+
else
|
245
|
+
[ 200, {'Content-Length' => size.to_s }, BigBody.new ]
|
246
|
+
end
|
247
|
+
end
|
248
|
+
GTL.synchronize do
|
249
|
+
app(:rack, ru) do
|
250
|
+
listen "#{host}:#{port}"
|
251
|
+
output_buffering false
|
252
|
+
client_timeout 3
|
253
|
+
logger(Logger.new(apperr.path))
|
254
|
+
end
|
255
|
+
end
|
256
|
+
logger(Logger.new(err.path))
|
257
|
+
end
|
258
|
+
srv = Yahns::Server.new(cfg)
|
259
|
+
pid = fork do
|
260
|
+
ENV["YAHNS_FD"] = @srv.fileno.to_s
|
261
|
+
srv.start.join
|
262
|
+
end
|
263
|
+
threads = []
|
264
|
+
threads << Thread.new do
|
265
|
+
c = TCPSocket.new(host, port)
|
266
|
+
c.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
267
|
+
sleep(5) # wait for timeout
|
268
|
+
assert_operator c.nread, :>, 0
|
269
|
+
c
|
270
|
+
end
|
271
|
+
|
272
|
+
threads << Thread.new do
|
273
|
+
c = TCPSocket.new(host, port)
|
274
|
+
c.write("GET /bh HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
275
|
+
sleep(5) # wait for timeout
|
276
|
+
assert_operator c.nread, :>, 0
|
277
|
+
c
|
278
|
+
end
|
279
|
+
threads.each { |t| t.join(10) }
|
280
|
+
assert_operator size, :>, threads[0].value.read.size
|
281
|
+
assert_operator size, :>, threads[1].value.read.size
|
282
|
+
msg = File.readlines(apperr.path)
|
283
|
+
msg = msg.grep(/timeout on :wait_writable after 3s$/)
|
284
|
+
assert_equal 2, msg.size
|
285
|
+
threads.each { |t| t.value.close }
|
286
|
+
ensure
|
287
|
+
apperr.close! if apperr
|
288
|
+
quit_wait(pid)
|
289
|
+
end
|
290
|
+
end if `which curl 2>/dev/null`.strip =~ /curl/ &&
|
291
|
+
`which md5sum 2>/dev/null`.strip =~ /md5sum/
|