unicorn 5.3.1 → 6.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.manifest +10 -5
- data/.olddoc.yml +15 -7
- data/Application_Timeouts +4 -4
- data/CONTRIBUTORS +6 -2
- data/Documentation/.gitignore +1 -3
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +1 -1
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +117 -57
- data/HACKING +2 -9
- data/ISSUES +33 -32
- data/KNOWN_ISSUES +2 -2
- data/LATEST +16 -95
- data/LICENSE +2 -2
- data/Links +13 -11
- data/NEWS +239 -0
- data/README +27 -14
- data/SIGNALS +1 -1
- data/Sandbox +5 -5
- data/archive/slrnpull.conf +1 -1
- data/bin/unicorn +3 -1
- data/bin/unicorn_rails +2 -2
- data/examples/big_app_gc.rb +1 -1
- data/examples/logrotate.conf +3 -3
- data/examples/nginx.conf +4 -3
- data/examples/unicorn.conf.minimal.rb +2 -2
- data/examples/unicorn.conf.rb +2 -2
- data/examples/unicorn@.service +7 -0
- data/ext/unicorn_http/c_util.h +5 -13
- data/ext/unicorn_http/common_field_optimization.h +23 -6
- data/ext/unicorn_http/epollexclusive.h +124 -0
- data/ext/unicorn_http/ext_help.h +0 -24
- data/ext/unicorn_http/extconf.rb +32 -6
- data/ext/unicorn_http/global_variables.h +3 -3
- data/ext/unicorn_http/httpdate.c +3 -2
- data/ext/unicorn_http/unicorn_http.c +277 -237
- data/ext/unicorn_http/unicorn_http.rl +67 -27
- data/lib/unicorn/configurator.rb +26 -5
- data/lib/unicorn/http_request.rb +13 -3
- data/lib/unicorn/http_response.rb +3 -2
- data/lib/unicorn/http_server.rb +76 -51
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +5 -5
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +4 -3
- data/lib/unicorn/tmpio.rb +8 -2
- data/lib/unicorn/util.rb +3 -3
- data/lib/unicorn/version.rb +1 -1
- data/lib/unicorn/worker.rb +16 -2
- data/lib/unicorn.rb +25 -10
- data/man/man1/unicorn.1 +88 -85
- data/man/man1/unicorn_rails.1 +79 -81
- data/t/GNUmakefile +3 -72
- data/t/README +4 -4
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/test-lib.sh +2 -1
- data/test/benchmark/README +14 -4
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/test_exec.rb +20 -19
- data/test/test_helper.rb +38 -30
- data/test/unit/test_ccc.rb +5 -4
- data/test/unit/test_droplet.rb +1 -1
- data/test/unit/test_http_parser.rb +16 -0
- data/test/unit/test_http_parser_ng.rb +81 -0
- data/test/unit/test_request.rb +10 -10
- data/test/unit/test_server.rb +86 -12
- data/test/unit/test_signals.rb +8 -8
- data/test/unit/test_socket_helper.rb +5 -5
- data/test/unit/test_upload.rb +9 -14
- data/test/unit/test_util.rb +29 -3
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +8 -7
- metadata +19 -13
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -187
- data/Documentation/unicorn_rails.1.txt +0 -175
- data/t/hijack.ru +0 -43
- data/t/t0200-rack-hijack.sh +0 -30
data/test/exec/test_exec.rb
CHANGED
@@ -45,8 +45,9 @@ end
|
|
45
45
|
|
46
46
|
COMMON_TMP = Tempfile.new('unicorn_tmp') unless defined?(COMMON_TMP)
|
47
47
|
|
48
|
+
HEAVY_WORKERS = 2
|
48
49
|
HEAVY_CFG = <<-EOS
|
49
|
-
worker_processes
|
50
|
+
worker_processes #{HEAVY_WORKERS}
|
50
51
|
timeout 30
|
51
52
|
logger Logger.new('#{COMMON_TMP.path}')
|
52
53
|
before_fork do |server, worker|
|
@@ -193,8 +194,8 @@ EOF
|
|
193
194
|
assert_equal other.path, results.first
|
194
195
|
|
195
196
|
Process.kill(:QUIT, pid)
|
196
|
-
|
197
|
-
|
197
|
+
ensure
|
198
|
+
FileUtils.rmtree(other.path)
|
198
199
|
end
|
199
200
|
|
200
201
|
def test_working_directory
|
@@ -229,8 +230,8 @@ EOF
|
|
229
230
|
assert_equal other.path, results.first
|
230
231
|
|
231
232
|
Process.kill(:QUIT, pid)
|
232
|
-
|
233
|
-
|
233
|
+
ensure
|
234
|
+
FileUtils.rmtree(other.path)
|
234
235
|
end
|
235
236
|
|
236
237
|
def test_working_directory_controls_relative_paths
|
@@ -271,11 +272,10 @@ EOF
|
|
271
272
|
wait_master_ready("#{other.path}/stderr_log_here")
|
272
273
|
|
273
274
|
Process.kill(:QUIT, pid)
|
274
|
-
|
275
|
-
|
275
|
+
ensure
|
276
|
+
FileUtils.rmtree(other.path)
|
276
277
|
end
|
277
278
|
|
278
|
-
|
279
279
|
def test_exit_signals
|
280
280
|
%w(INT TERM QUIT).each do |sig|
|
281
281
|
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
@@ -574,7 +574,7 @@ EOF
|
|
574
574
|
assert_equal String, results[0].class
|
575
575
|
worker_pid = results[0].to_i
|
576
576
|
assert_not_equal pid, worker_pid
|
577
|
-
s =
|
577
|
+
s = unix_socket(tmp.path)
|
578
578
|
s.syswrite("GET / HTTP/1.0\r\n\r\n")
|
579
579
|
results = ''
|
580
580
|
loop { results << s.sysread(4096) } rescue nil
|
@@ -607,6 +607,7 @@ EOF
|
|
607
607
|
def test_weird_config_settings
|
608
608
|
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
609
609
|
ucfg = Tempfile.new('unicorn_test_config')
|
610
|
+
proc_total = HEAVY_WORKERS + 1 # + 1 for master
|
610
611
|
ucfg.syswrite(HEAVY_CFG)
|
611
612
|
pid = xfork do
|
612
613
|
redirect_test_io do
|
@@ -617,9 +618,9 @@ EOF
|
|
617
618
|
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
618
619
|
assert_equal String, results[0].class
|
619
620
|
wait_master_ready(COMMON_TMP.path)
|
620
|
-
wait_workers_ready(COMMON_TMP.path,
|
621
|
+
wait_workers_ready(COMMON_TMP.path, HEAVY_WORKERS)
|
621
622
|
bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
|
622
|
-
assert_equal
|
623
|
+
assert_equal HEAVY_WORKERS, bf.size
|
623
624
|
rotate = Tempfile.new('unicorn_rotate')
|
624
625
|
|
625
626
|
File.rename(COMMON_TMP.path, rotate.path)
|
@@ -631,20 +632,20 @@ EOF
|
|
631
632
|
tries = DEFAULT_TRIES
|
632
633
|
log = File.readlines(rotate.path)
|
633
634
|
while (tries -= 1) > 0 &&
|
634
|
-
log.grep(/reopening logs\.\.\./).size <
|
635
|
+
log.grep(/reopening logs\.\.\./).size < proc_total
|
635
636
|
sleep DEFAULT_RES
|
636
637
|
log = File.readlines(rotate.path)
|
637
638
|
end
|
638
|
-
assert_equal
|
639
|
+
assert_equal proc_total, log.grep(/reopening logs\.\.\./).size
|
639
640
|
assert_equal 0, log.grep(/done reopening logs/).size
|
640
641
|
|
641
642
|
tries = DEFAULT_TRIES
|
642
643
|
log = File.readlines(COMMON_TMP.path)
|
643
|
-
while (tries -= 1) > 0 && log.grep(/done reopening logs/).size <
|
644
|
+
while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < proc_total
|
644
645
|
sleep DEFAULT_RES
|
645
646
|
log = File.readlines(COMMON_TMP.path)
|
646
647
|
end
|
647
|
-
assert_equal
|
648
|
+
assert_equal proc_total, log.grep(/done reopening logs/).size
|
648
649
|
assert_equal 0, log.grep(/reopening logs\.\.\./).size
|
649
650
|
|
650
651
|
Process.kill(:QUIT, pid)
|
@@ -731,7 +732,7 @@ EOF
|
|
731
732
|
wait_for_file(sock_path)
|
732
733
|
assert File.socket?(sock_path)
|
733
734
|
|
734
|
-
sock =
|
735
|
+
sock = unix_socket(sock_path)
|
735
736
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
736
737
|
results = sock.sysread(4096)
|
737
738
|
|
@@ -741,7 +742,7 @@ EOF
|
|
741
742
|
wait_for_file(sock_path)
|
742
743
|
assert File.socket?(sock_path)
|
743
744
|
|
744
|
-
sock =
|
745
|
+
sock = unix_socket(sock_path)
|
745
746
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
746
747
|
results = sock.sysread(4096)
|
747
748
|
|
@@ -776,7 +777,7 @@ EOF
|
|
776
777
|
assert_equal pid, File.read(pid_file).to_i
|
777
778
|
assert File.socket?(sock_path), "socket created"
|
778
779
|
|
779
|
-
sock =
|
780
|
+
sock = unix_socket(sock_path)
|
780
781
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
781
782
|
results = sock.sysread(4096)
|
782
783
|
|
@@ -802,7 +803,7 @@ EOF
|
|
802
803
|
wait_for_file(new_sock_path)
|
803
804
|
assert File.socket?(new_sock_path), "socket exists"
|
804
805
|
@sockets.each do |path|
|
805
|
-
sock =
|
806
|
+
sock = unix_socket(path)
|
806
807
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
807
808
|
results = sock.sysread(4096)
|
808
809
|
assert_equal String, results.class
|
data/test/test_helper.rb
CHANGED
@@ -28,22 +28,43 @@ require 'tempfile'
|
|
28
28
|
require 'fileutils'
|
29
29
|
require 'logger'
|
30
30
|
require 'unicorn'
|
31
|
+
require 'io/nonblock'
|
31
32
|
|
32
33
|
if ENV['DEBUG']
|
33
34
|
require 'ruby-debug'
|
34
35
|
Debugger.start
|
35
36
|
end
|
36
37
|
|
38
|
+
unless RUBY_VERSION < '3.1'
|
39
|
+
warn "Unicorn was only tested against MRI up to 3.0.\n" \
|
40
|
+
"It might not properly work with #{RUBY_VERSION}"
|
41
|
+
end
|
42
|
+
|
37
43
|
def redirect_test_io
|
38
44
|
orig_err = STDERR.dup
|
39
45
|
orig_out = STDOUT.dup
|
40
|
-
|
41
|
-
|
46
|
+
rdr_pid = $$
|
47
|
+
new_out = File.open("test_stdout.#$$.log", "a")
|
48
|
+
new_err = File.open("test_stderr.#$$.log", "a")
|
49
|
+
new_out.sync = new_err.sync = true
|
50
|
+
|
51
|
+
if tail = ENV['TAIL'] # "tail -F" if GNU, "tail -f" otherwise
|
52
|
+
require 'shellwords'
|
53
|
+
cmd = tail.shellsplit
|
54
|
+
cmd << new_out.path
|
55
|
+
cmd << new_err.path
|
56
|
+
pid = Process.spawn(*cmd, { 1 => 2, :pgroup => true })
|
57
|
+
sleep 0.1 # wait for tail(1) to startup
|
58
|
+
end
|
59
|
+
STDERR.reopen(new_err)
|
60
|
+
STDOUT.reopen(new_out)
|
42
61
|
STDERR.sync = STDOUT.sync = true
|
43
62
|
|
44
63
|
at_exit do
|
45
|
-
|
46
|
-
|
64
|
+
if rdr_pid == $$
|
65
|
+
File.unlink(new_out.path) rescue nil
|
66
|
+
File.unlink(new_err.path) rescue nil
|
67
|
+
end
|
47
68
|
end
|
48
69
|
|
49
70
|
begin
|
@@ -51,6 +72,7 @@ def redirect_test_io
|
|
51
72
|
ensure
|
52
73
|
STDERR.reopen(orig_err)
|
53
74
|
STDOUT.reopen(orig_out)
|
75
|
+
Process.kill(:TERM, pid) if pid
|
54
76
|
end
|
55
77
|
end
|
56
78
|
|
@@ -265,34 +287,20 @@ def wait_for_death(pid)
|
|
265
287
|
raise "PID:#{pid} never died!"
|
266
288
|
end
|
267
289
|
|
268
|
-
# executes +cmd+ and chunks its STDOUT
|
269
|
-
def chunked_spawn(stdout, *cmd)
|
270
|
-
fork {
|
271
|
-
crd, cwr = IO.pipe
|
272
|
-
crd.binmode
|
273
|
-
cwr.binmode
|
274
|
-
crd.sync = cwr.sync = true
|
275
|
-
|
276
|
-
pid = fork {
|
277
|
-
STDOUT.reopen(cwr)
|
278
|
-
crd.close
|
279
|
-
cwr.close
|
280
|
-
exec(*cmd)
|
281
|
-
}
|
282
|
-
cwr.close
|
283
|
-
begin
|
284
|
-
buf = crd.readpartial(16384)
|
285
|
-
stdout.write("#{'%x' % buf.size}\r\n#{buf}")
|
286
|
-
rescue EOFError
|
287
|
-
stdout.write("0\r\n")
|
288
|
-
pid, status = Process.waitpid(pid)
|
289
|
-
exit status.exitstatus
|
290
|
-
end while true
|
291
|
-
}
|
292
|
-
end
|
293
|
-
|
294
290
|
def reset_sig_handlers
|
295
291
|
%w(WINCH QUIT INT TERM USR1 USR2 HUP TTIN TTOU CHLD).each do |sig|
|
296
292
|
trap(sig, "DEFAULT")
|
297
293
|
end
|
298
294
|
end
|
295
|
+
|
296
|
+
def tcp_socket(*args)
|
297
|
+
sock = TCPSocket.new(*args)
|
298
|
+
sock.nonblock = false
|
299
|
+
sock
|
300
|
+
end
|
301
|
+
|
302
|
+
def unix_socket(*args)
|
303
|
+
sock = UNIXSocket.new(*args)
|
304
|
+
sock.nonblock = false
|
305
|
+
sock
|
306
|
+
end
|
data/test/unit/test_ccc.rb
CHANGED
@@ -3,6 +3,7 @@ require 'unicorn'
|
|
3
3
|
require 'io/wait'
|
4
4
|
require 'tempfile'
|
5
5
|
require 'test/unit'
|
6
|
+
require './test/test_helper'
|
6
7
|
|
7
8
|
class TestCccTCPI < Test::Unit::TestCase
|
8
9
|
def test_ccc_tcpi
|
@@ -42,22 +43,22 @@ class TestCccTCPI < Test::Unit::TestCase
|
|
42
43
|
wr.close
|
43
44
|
|
44
45
|
# make sure the server is running, at least
|
45
|
-
client =
|
46
|
+
client = tcp_socket(host, port)
|
46
47
|
client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
47
|
-
assert client.
|
48
|
+
assert client.wait(10), 'never got response from server'
|
48
49
|
res = client.read
|
49
50
|
assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first response'
|
50
51
|
assert_match %r{\r\n\r\n\z}, res, 'got end of response, server is ready'
|
51
52
|
client.close
|
52
53
|
|
53
54
|
# start a slow request...
|
54
|
-
sleeper =
|
55
|
+
sleeper = tcp_socket(host, port)
|
55
56
|
sleeper.write("GET /sleep HTTP/1.1\r\nHost: example.com\r\n\r\n")
|
56
57
|
|
57
58
|
# and a bunch of aborted ones
|
58
59
|
nr = 100
|
59
60
|
nr.times do |i|
|
60
|
-
client =
|
61
|
+
client = tcp_socket(host, port)
|
61
62
|
client.write("GET /collections/#{rand(10000)} HTTP/1.1\r\n" \
|
62
63
|
"Host: example.com\r\n\r\n")
|
63
64
|
client.close
|
data/test/unit/test_droplet.rb
CHANGED
@@ -4,7 +4,7 @@ require 'unicorn'
|
|
4
4
|
class TestDroplet < Test::Unit::TestCase
|
5
5
|
def test_create_many_droplets
|
6
6
|
now = Time.now.to_i
|
7
|
-
|
7
|
+
(0..1024).each do |i|
|
8
8
|
droplet = Unicorn::Worker.new(i)
|
9
9
|
assert droplet.respond_to?(:tick)
|
10
10
|
assert_equal 0, droplet.tick
|
@@ -865,4 +865,20 @@ class HttpParserTest < Test::Unit::TestCase
|
|
865
865
|
rescue LoadError
|
866
866
|
# not all Ruby implementations have objspace
|
867
867
|
end
|
868
|
+
|
869
|
+
def test_dedupe
|
870
|
+
parser = HttpParser.new
|
871
|
+
# n.b. String#freeze optimization doesn't work under modern test-unit
|
872
|
+
exp = -'HTTP_HOST'
|
873
|
+
get = "GET / HTTP/1.1\r\nHost: example.com\r\nHavpbea-fhpxf: true\r\n\r\n"
|
874
|
+
assert parser.add_parse(get)
|
875
|
+
key = parser.env.keys.detect { |k| k == exp }
|
876
|
+
assert_same exp, key
|
877
|
+
|
878
|
+
if RUBY_VERSION.to_r >= 2.6 # 2.6.0-rc1+
|
879
|
+
exp = -'HTTP_HAVPBEA_FHPXF'
|
880
|
+
key = parser.env.keys.detect { |k| k == exp }
|
881
|
+
assert_same exp, key
|
882
|
+
end
|
883
|
+
end if RUBY_VERSION.to_r >= 2.5 && RUBY_ENGINE == 'ruby'
|
868
884
|
end
|
@@ -11,6 +11,20 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
11
11
|
@parser = HttpParser.new
|
12
12
|
end
|
13
13
|
|
14
|
+
# RFC 7230 allows gzip/deflate/compress Transfer-Encoding,
|
15
|
+
# but "chunked" must be last if used
|
16
|
+
def test_is_chunked
|
17
|
+
[ 'chunked,chunked', 'chunked,gzip', 'chunked,gzip,chunked' ].each do |x|
|
18
|
+
assert_raise(HttpParserError) { HttpParser.is_chunked?(x) }
|
19
|
+
end
|
20
|
+
[ 'gzip, chunked', 'gzip,chunked', 'gzip ,chunked' ].each do |x|
|
21
|
+
assert HttpParser.is_chunked?(x)
|
22
|
+
end
|
23
|
+
[ 'gzip', 'xhunked', 'xchunked' ].each do |x|
|
24
|
+
assert !HttpParser.is_chunked?(x)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
14
28
|
def test_parser_max_len
|
15
29
|
assert_raises(RangeError) do
|
16
30
|
HttpParser.max_header_len = 0xffffffff + 1
|
@@ -566,6 +580,73 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
566
580
|
end
|
567
581
|
end
|
568
582
|
|
583
|
+
def test_duplicate_content_length
|
584
|
+
str = "PUT / HTTP/1.1\r\n" \
|
585
|
+
"Content-Length: 1\r\n" \
|
586
|
+
"Content-Length: 9\r\n" \
|
587
|
+
"\r\n"
|
588
|
+
assert_raises(HttpParserError) { @parser.headers({}, str) }
|
589
|
+
end
|
590
|
+
|
591
|
+
def test_chunked_overrides_content_length
|
592
|
+
order = [ 'Transfer-Encoding: chunked', 'Content-Length: 666' ]
|
593
|
+
%w(a b).each do |x|
|
594
|
+
str = "PUT /#{x} HTTP/1.1\r\n" \
|
595
|
+
"#{order.join("\r\n")}" \
|
596
|
+
"\r\n\r\na\r\nhelloworld\r\n0\r\n\r\n"
|
597
|
+
order.reverse!
|
598
|
+
env = @parser.headers({}, str)
|
599
|
+
assert_nil @parser.content_length
|
600
|
+
assert_equal 'chunked', env['HTTP_TRANSFER_ENCODING']
|
601
|
+
assert_equal '666', env['CONTENT_LENGTH'],
|
602
|
+
'Content-Length logged so the app can log a possible client bug/attack'
|
603
|
+
@parser.filter_body(dst = '', str)
|
604
|
+
assert_equal 'helloworld', dst
|
605
|
+
@parser.parse # handle the non-existent trailer
|
606
|
+
assert @parser.next?
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
def test_chunked_order_good
|
611
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
612
|
+
"Transfer-Encoding: gzip\r\n" \
|
613
|
+
"Transfer-Encoding: chunked\r\n" \
|
614
|
+
"\r\n"
|
615
|
+
env = @parser.headers({}, str)
|
616
|
+
assert_equal 'gzip,chunked', env['HTTP_TRANSFER_ENCODING']
|
617
|
+
assert_nil @parser.content_length
|
618
|
+
|
619
|
+
@parser.clear
|
620
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
621
|
+
"Transfer-Encoding: gzip, chunked\r\n" \
|
622
|
+
"\r\n"
|
623
|
+
env = @parser.headers({}, str)
|
624
|
+
assert_equal 'gzip, chunked', env['HTTP_TRANSFER_ENCODING']
|
625
|
+
assert_nil @parser.content_length
|
626
|
+
end
|
627
|
+
|
628
|
+
def test_chunked_order_bad
|
629
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
630
|
+
"Transfer-Encoding: chunked\r\n" \
|
631
|
+
"Transfer-Encoding: gzip\r\n" \
|
632
|
+
"\r\n"
|
633
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
634
|
+
end
|
635
|
+
|
636
|
+
def test_double_chunked
|
637
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
638
|
+
"Transfer-Encoding: chunked\r\n" \
|
639
|
+
"Transfer-Encoding: chunked\r\n" \
|
640
|
+
"\r\n"
|
641
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
642
|
+
|
643
|
+
@parser.clear
|
644
|
+
str = "PUT /x HTTP/1.1\r\n" \
|
645
|
+
"Transfer-Encoding: chunked,chunked\r\n" \
|
646
|
+
"\r\n"
|
647
|
+
assert_raise(HttpParserError) { @parser.headers({}, str) }
|
648
|
+
end
|
649
|
+
|
569
650
|
def test_backtrace_is_empty
|
570
651
|
begin
|
571
652
|
@parser.headers({}, "AAADFSFDSFD\r\n\r\n")
|
data/test/unit/test_request.rb
CHANGED
@@ -34,7 +34,7 @@ class RequestTest < Test::Unit::TestCase
|
|
34
34
|
assert_equal '', env['REQUEST_PATH']
|
35
35
|
assert_equal '', env['PATH_INFO']
|
36
36
|
assert_equal '*', env['REQUEST_URI']
|
37
|
-
|
37
|
+
assert_kind_of Array, @lint.call(env)
|
38
38
|
end
|
39
39
|
|
40
40
|
def test_absolute_uri_with_query
|
@@ -44,7 +44,7 @@ class RequestTest < Test::Unit::TestCase
|
|
44
44
|
assert_equal '/x', env['REQUEST_PATH']
|
45
45
|
assert_equal '/x', env['PATH_INFO']
|
46
46
|
assert_equal 'y=z', env['QUERY_STRING']
|
47
|
-
|
47
|
+
assert_kind_of Array, @lint.call(env)
|
48
48
|
end
|
49
49
|
|
50
50
|
def test_absolute_uri_with_fragment
|
@@ -55,7 +55,7 @@ class RequestTest < Test::Unit::TestCase
|
|
55
55
|
assert_equal '/x', env['PATH_INFO']
|
56
56
|
assert_equal '', env['QUERY_STRING']
|
57
57
|
assert_equal 'frag', env['FRAGMENT']
|
58
|
-
|
58
|
+
assert_kind_of Array, @lint.call(env)
|
59
59
|
end
|
60
60
|
|
61
61
|
def test_absolute_uri_with_query_and_fragment
|
@@ -66,7 +66,7 @@ class RequestTest < Test::Unit::TestCase
|
|
66
66
|
assert_equal '/x', env['PATH_INFO']
|
67
67
|
assert_equal 'a=b', env['QUERY_STRING']
|
68
68
|
assert_equal 'frag', env['FRAGMENT']
|
69
|
-
|
69
|
+
assert_kind_of Array, @lint.call(env)
|
70
70
|
end
|
71
71
|
|
72
72
|
def test_absolute_uri_unsupported_schemes
|
@@ -83,7 +83,7 @@ class RequestTest < Test::Unit::TestCase
|
|
83
83
|
"Host: foo\r\n\r\n")
|
84
84
|
env = @request.read(client)
|
85
85
|
assert_equal "https", env['rack.url_scheme']
|
86
|
-
|
86
|
+
assert_kind_of Array, @lint.call(env)
|
87
87
|
end
|
88
88
|
|
89
89
|
def test_x_forwarded_proto_http
|
@@ -92,7 +92,7 @@ class RequestTest < Test::Unit::TestCase
|
|
92
92
|
"Host: foo\r\n\r\n")
|
93
93
|
env = @request.read(client)
|
94
94
|
assert_equal "http", env['rack.url_scheme']
|
95
|
-
|
95
|
+
assert_kind_of Array, @lint.call(env)
|
96
96
|
end
|
97
97
|
|
98
98
|
def test_x_forwarded_proto_invalid
|
@@ -101,7 +101,7 @@ class RequestTest < Test::Unit::TestCase
|
|
101
101
|
"Host: foo\r\n\r\n")
|
102
102
|
env = @request.read(client)
|
103
103
|
assert_equal "http", env['rack.url_scheme']
|
104
|
-
|
104
|
+
assert_kind_of Array, @lint.call(env)
|
105
105
|
end
|
106
106
|
|
107
107
|
def test_rack_lint_get
|
@@ -109,7 +109,7 @@ class RequestTest < Test::Unit::TestCase
|
|
109
109
|
env = @request.read(client)
|
110
110
|
assert_equal "http", env['rack.url_scheme']
|
111
111
|
assert_equal '127.0.0.1', env['REMOTE_ADDR']
|
112
|
-
|
112
|
+
assert_kind_of Array, @lint.call(env)
|
113
113
|
end
|
114
114
|
|
115
115
|
def test_no_content_stringio
|
@@ -143,7 +143,7 @@ class RequestTest < Test::Unit::TestCase
|
|
143
143
|
"abcde")
|
144
144
|
env = @request.read(client)
|
145
145
|
assert ! env.include?(:http_body)
|
146
|
-
|
146
|
+
assert_kind_of Array, @lint.call(env)
|
147
147
|
end
|
148
148
|
|
149
149
|
def test_rack_lint_big_put
|
@@ -177,6 +177,6 @@ class RequestTest < Test::Unit::TestCase
|
|
177
177
|
}
|
178
178
|
assert_nil env['rack.input'].read(bs)
|
179
179
|
env['rack.input'].rewind
|
180
|
-
|
180
|
+
assert_kind_of Array, @lint.call(env)
|
181
181
|
end
|
182
182
|
end
|
data/test/unit/test_server.rb
CHANGED
@@ -17,12 +17,40 @@ class TestHandler
|
|
17
17
|
while env['rack.input'].read(4096)
|
18
18
|
end
|
19
19
|
[200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
|
21
|
+
$stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
|
22
|
+
raise e
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
class TestEarlyHintsHandler
|
27
|
+
def call(env)
|
28
|
+
while env['rack.input'].read(4096)
|
29
|
+
end
|
30
|
+
env['rack.early_hints'].call(
|
31
|
+
"Link" => "</style.css>; rel=preload; as=style\n</script.js>; rel=preload"
|
32
|
+
)
|
33
|
+
[200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class TestRackAfterReply
|
38
|
+
def initialize
|
39
|
+
@called = false
|
40
|
+
end
|
41
|
+
|
42
|
+
def call(env)
|
43
|
+
while env['rack.input'].read(4096)
|
44
|
+
end
|
45
|
+
|
46
|
+
env["rack.after_reply"] << -> { @called = true }
|
47
|
+
|
48
|
+
[200, { 'Content-Type' => 'text/plain' }, ["after_reply_called: #{@called}"]]
|
49
|
+
rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
|
50
|
+
$stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
|
51
|
+
raise e
|
52
|
+
end
|
53
|
+
end
|
26
54
|
|
27
55
|
class WebServerTest < Test::Unit::TestCase
|
28
56
|
|
@@ -80,8 +108,54 @@ class WebServerTest < Test::Unit::TestCase
|
|
80
108
|
loader_pid = tmp.sysread(4096).to_i
|
81
109
|
assert_equal $$, loader_pid
|
82
110
|
assert worker_pid != loader_pid
|
83
|
-
|
84
|
-
|
111
|
+
ensure
|
112
|
+
tmp.close!
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_early_hints
|
116
|
+
teardown
|
117
|
+
redirect_test_io do
|
118
|
+
@server = HttpServer.new(TestEarlyHintsHandler.new,
|
119
|
+
:listeners => [ "127.0.0.1:#@port"],
|
120
|
+
:early_hints => true)
|
121
|
+
@server.start
|
122
|
+
end
|
123
|
+
|
124
|
+
sock = tcp_socket('127.0.0.1', @port)
|
125
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
126
|
+
|
127
|
+
responses = sock.read(4096)
|
128
|
+
assert_match %r{\AHTTP/1.[01] 103\b}, responses
|
129
|
+
assert_match %r{^Link: </style\.css>}, responses
|
130
|
+
assert_match %r{^Link: </script\.js>}, responses
|
131
|
+
|
132
|
+
assert_match %r{^HTTP/1.[01] 200\b}, responses
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_after_reply
|
136
|
+
teardown
|
137
|
+
|
138
|
+
redirect_test_io do
|
139
|
+
@server = HttpServer.new(TestRackAfterReply.new,
|
140
|
+
:listeners => [ "127.0.0.1:#@port"])
|
141
|
+
@server.start
|
142
|
+
end
|
143
|
+
|
144
|
+
sock = tcp_socket('127.0.0.1', @port)
|
145
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
146
|
+
|
147
|
+
responses = sock.read(4096)
|
148
|
+
assert_match %r{\AHTTP/1.[01] 200\b}, responses
|
149
|
+
assert_match %r{^after_reply_called: false}, responses
|
150
|
+
|
151
|
+
sock = tcp_socket('127.0.0.1', @port)
|
152
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
153
|
+
|
154
|
+
responses = sock.read(4096)
|
155
|
+
assert_match %r{\AHTTP/1.[01] 200\b}, responses
|
156
|
+
assert_match %r{^after_reply_called: true}, responses
|
157
|
+
|
158
|
+
sock.close
|
85
159
|
end
|
86
160
|
|
87
161
|
def test_broken_app
|
@@ -92,7 +166,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
92
166
|
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
|
93
167
|
@server.start
|
94
168
|
end
|
95
|
-
sock =
|
169
|
+
sock = tcp_socket('127.0.0.1', @port)
|
96
170
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
97
171
|
assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
|
98
172
|
assert_nil sock.close
|
@@ -105,7 +179,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
105
179
|
|
106
180
|
def test_client_shutdown_writes
|
107
181
|
bs = 15609315 * rand
|
108
|
-
sock =
|
182
|
+
sock = tcp_socket('127.0.0.1', @port)
|
109
183
|
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
110
184
|
sock.syswrite("Host: example.com\r\n")
|
111
185
|
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
@@ -132,7 +206,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
132
206
|
|
133
207
|
def test_client_shutdown_write_truncates
|
134
208
|
bs = 15609315 * rand
|
135
|
-
sock =
|
209
|
+
sock = tcp_socket('127.0.0.1', @port)
|
136
210
|
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
137
211
|
sock.syswrite("Host: example.com\r\n")
|
138
212
|
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
@@ -158,7 +232,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
158
232
|
|
159
233
|
def test_client_malformed_body
|
160
234
|
bs = 15653984
|
161
|
-
sock =
|
235
|
+
sock = tcp_socket('127.0.0.1', @port)
|
162
236
|
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
163
237
|
sock.syswrite("Host: example.com\r\n")
|
164
238
|
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
@@ -180,7 +254,7 @@ class WebServerTest < Test::Unit::TestCase
|
|
180
254
|
|
181
255
|
def do_test(string, chunk, close_after=nil, shutdown_delay=0)
|
182
256
|
# Do not use instance variables here, because it needs to be thread safe
|
183
|
-
socket =
|
257
|
+
socket = tcp_socket("127.0.0.1", @port);
|
184
258
|
request = StringIO.new(string)
|
185
259
|
chunks_out = 0
|
186
260
|
|
@@ -225,14 +299,14 @@ class WebServerTest < Test::Unit::TestCase
|
|
225
299
|
end
|
226
300
|
|
227
301
|
def test_bad_client_400
|
228
|
-
sock =
|
302
|
+
sock = tcp_socket('127.0.0.1', @port)
|
229
303
|
sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
|
230
304
|
assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
|
231
305
|
assert_nil sock.close
|
232
306
|
end
|
233
307
|
|
234
308
|
def test_http_0_9
|
235
|
-
sock =
|
309
|
+
sock = tcp_socket('127.0.0.1', @port)
|
236
310
|
sock.syswrite("GET /hello\r\n")
|
237
311
|
assert_match 'hello!\n', sock.sysread(4096)
|
238
312
|
assert_nil sock.close
|