yahns 0.0.0TP1
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 +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/
|