unicorn 5.5.2 → 5.7.0
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 +4 -4
- data/.olddoc.yml +12 -7
- data/Documentation/unicorn.1 +4 -4
- data/Documentation/unicorn_rails.1 +4 -4
- data/FAQ +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +104 -60
- data/HACKING +1 -1
- data/ISSUES +16 -13
- data/KNOWN_ISSUES +2 -2
- data/Links +5 -5
- data/README +13 -6
- data/SIGNALS +1 -1
- data/Sandbox +2 -2
- data/archive/slrnpull.conf +1 -1
- data/examples/big_app_gc.rb +1 -1
- data/examples/logrotate.conf +2 -2
- data/examples/nginx.conf +1 -1
- data/examples/unicorn.conf.minimal.rb +2 -2
- data/examples/unicorn.conf.rb +2 -2
- data/ext/unicorn_http/extconf.rb +5 -0
- data/ext/unicorn_http/unicorn_http.rl +43 -5
- data/lib/unicorn.rb +1 -1
- data/lib/unicorn/configurator.rb +13 -3
- data/lib/unicorn/http_request.rb +11 -0
- data/lib/unicorn/http_server.rb +32 -4
- data/lib/unicorn/oob_gc.rb +2 -2
- data/t/GNUmakefile +3 -72
- data/test/exec/test_exec.rb +9 -7
- data/test/test_helper.rb +22 -30
- data/test/unit/test_http_parser_ng.rb +81 -0
- data/test/unit/test_server.rb +30 -0
- data/test/unit/test_upload.rb +4 -9
- data/unicorn.gemspec +8 -7
- metadata +7 -6
data/test/exec/test_exec.rb
CHANGED
@@ -45,8 +45,9 @@ def call(env)
|
|
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|
|
@@ -606,6 +607,7 @@ def test_unicorn_config_listen_augments_cli
|
|
606
607
|
def test_weird_config_settings
|
607
608
|
File.open("config.ru", "wb") { |fp| fp.syswrite(HI) }
|
608
609
|
ucfg = Tempfile.new('unicorn_test_config')
|
610
|
+
proc_total = HEAVY_WORKERS + 1 # + 1 for master
|
609
611
|
ucfg.syswrite(HEAVY_CFG)
|
610
612
|
pid = xfork do
|
611
613
|
redirect_test_io do
|
@@ -616,9 +618,9 @@ def test_weird_config_settings
|
|
616
618
|
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
617
619
|
assert_equal String, results[0].class
|
618
620
|
wait_master_ready(COMMON_TMP.path)
|
619
|
-
wait_workers_ready(COMMON_TMP.path,
|
621
|
+
wait_workers_ready(COMMON_TMP.path, HEAVY_WORKERS)
|
620
622
|
bf = File.readlines(COMMON_TMP.path).grep(/\bbefore_fork: worker=/)
|
621
|
-
assert_equal
|
623
|
+
assert_equal HEAVY_WORKERS, bf.size
|
622
624
|
rotate = Tempfile.new('unicorn_rotate')
|
623
625
|
|
624
626
|
File.rename(COMMON_TMP.path, rotate.path)
|
@@ -630,20 +632,20 @@ def test_weird_config_settings
|
|
630
632
|
tries = DEFAULT_TRIES
|
631
633
|
log = File.readlines(rotate.path)
|
632
634
|
while (tries -= 1) > 0 &&
|
633
|
-
log.grep(/reopening logs\.\.\./).size <
|
635
|
+
log.grep(/reopening logs\.\.\./).size < proc_total
|
634
636
|
sleep DEFAULT_RES
|
635
637
|
log = File.readlines(rotate.path)
|
636
638
|
end
|
637
|
-
assert_equal
|
639
|
+
assert_equal proc_total, log.grep(/reopening logs\.\.\./).size
|
638
640
|
assert_equal 0, log.grep(/done reopening logs/).size
|
639
641
|
|
640
642
|
tries = DEFAULT_TRIES
|
641
643
|
log = File.readlines(COMMON_TMP.path)
|
642
|
-
while (tries -= 1) > 0 && log.grep(/done reopening logs/).size <
|
644
|
+
while (tries -= 1) > 0 && log.grep(/done reopening logs/).size < proc_total
|
643
645
|
sleep DEFAULT_RES
|
644
646
|
log = File.readlines(COMMON_TMP.path)
|
645
647
|
end
|
646
|
-
assert_equal
|
648
|
+
assert_equal proc_total, log.grep(/done reopening logs/).size
|
647
649
|
assert_equal 0, log.grep(/reopening logs\.\.\./).size
|
648
650
|
|
649
651
|
Process.kill(:QUIT, pid)
|
data/test/test_helper.rb
CHANGED
@@ -34,16 +34,33 @@
|
|
34
34
|
Debugger.start
|
35
35
|
end
|
36
36
|
|
37
|
+
unless RUBY_VERSION < '3.1'
|
38
|
+
warn "Unicorn was only tested against MRI up to 3.0.\n" \
|
39
|
+
"It might not properly work with #{RUBY_VERSION}"
|
40
|
+
end
|
41
|
+
|
37
42
|
def redirect_test_io
|
38
43
|
orig_err = STDERR.dup
|
39
44
|
orig_out = STDOUT.dup
|
40
|
-
|
41
|
-
|
45
|
+
new_out = File.open("test_stdout.#$$.log", "a")
|
46
|
+
new_err = File.open("test_stderr.#$$.log", "a")
|
47
|
+
new_out.sync = new_err.sync = true
|
48
|
+
|
49
|
+
if tail = ENV['TAIL'] # "tail -F" if GNU, "tail -f" otherwise
|
50
|
+
require 'shellwords'
|
51
|
+
cmd = tail.shellsplit
|
52
|
+
cmd << new_out.path
|
53
|
+
cmd << new_err.path
|
54
|
+
pid = Process.spawn(*cmd, { 1 => 2, :pgroup => true })
|
55
|
+
sleep 0.1 # wait for tail(1) to startup
|
56
|
+
end
|
57
|
+
STDERR.reopen(new_err)
|
58
|
+
STDOUT.reopen(new_out)
|
42
59
|
STDERR.sync = STDOUT.sync = true
|
43
60
|
|
44
61
|
at_exit do
|
45
|
-
File.unlink(
|
46
|
-
File.unlink(
|
62
|
+
File.unlink(new_out.path) rescue nil
|
63
|
+
File.unlink(new_err.path) rescue nil
|
47
64
|
end
|
48
65
|
|
49
66
|
begin
|
@@ -51,6 +68,7 @@ def redirect_test_io
|
|
51
68
|
ensure
|
52
69
|
STDERR.reopen(orig_err)
|
53
70
|
STDOUT.reopen(orig_out)
|
71
|
+
Process.kill(:TERM, pid) if pid
|
54
72
|
end
|
55
73
|
end
|
56
74
|
|
@@ -265,32 +283,6 @@ def wait_for_death(pid)
|
|
265
283
|
raise "PID:#{pid} never died!"
|
266
284
|
end
|
267
285
|
|
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
286
|
def reset_sig_handlers
|
295
287
|
%w(WINCH QUIT INT TERM USR1 USR2 HUP TTIN TTOU CHLD).each do |sig|
|
296
288
|
trap(sig, "DEFAULT")
|
@@ -11,6 +11,20 @@ def setup
|
|
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 @@ def test_invalid_content_length
|
|
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_server.rb
CHANGED
@@ -23,6 +23,16 @@ def call(env)
|
|
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
|
26
36
|
|
27
37
|
class WebServerTest < Test::Unit::TestCase
|
28
38
|
|
@@ -84,6 +94,26 @@ def test_preload_app_config
|
|
84
94
|
tmp.close!
|
85
95
|
end
|
86
96
|
|
97
|
+
def test_early_hints
|
98
|
+
teardown
|
99
|
+
redirect_test_io do
|
100
|
+
@server = HttpServer.new(TestEarlyHintsHandler.new,
|
101
|
+
:listeners => [ "127.0.0.1:#@port"],
|
102
|
+
:early_hints => true)
|
103
|
+
@server.start
|
104
|
+
end
|
105
|
+
|
106
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
107
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
108
|
+
|
109
|
+
responses = sock.read(4096)
|
110
|
+
assert_match %r{\AHTTP/1.[01] 103\b}, responses
|
111
|
+
assert_match %r{^Link: </style\.css>}, responses
|
112
|
+
assert_match %r{^Link: </script\.js>}, responses
|
113
|
+
|
114
|
+
assert_match %r{^HTTP/1.[01] 200\b}, responses
|
115
|
+
end
|
116
|
+
|
87
117
|
def test_broken_app
|
88
118
|
teardown
|
89
119
|
app = lambda { |env| raise RuntimeError, "hello" }
|
data/test/unit/test_upload.rb
CHANGED
@@ -236,15 +236,10 @@ def test_chunked_upload_via_curl
|
|
236
236
|
resp = Tempfile.new('resp')
|
237
237
|
resp.sync = true
|
238
238
|
|
239
|
-
rd, wr = IO.pipe
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
rd.close
|
244
|
-
wr.close
|
245
|
-
STDOUT.reopen(resp)
|
246
|
-
exec cmd
|
247
|
-
}
|
239
|
+
rd, wr = IO.pipe.each do |io|
|
240
|
+
io.sync = io.close_on_exec = true
|
241
|
+
end
|
242
|
+
pid = spawn(*cmd, { 0 => rd, 1 => resp })
|
248
243
|
rd.close
|
249
244
|
|
250
245
|
tmp.rewind
|
data/unicorn.gemspec
CHANGED
@@ -11,24 +11,25 @@
|
|
11
11
|
|
12
12
|
Gem::Specification.new do |s|
|
13
13
|
s.name = %q{unicorn}
|
14
|
-
s.version = (ENV['VERSION'] || '5.
|
14
|
+
s.version = (ENV['VERSION'] || '5.7.0').dup
|
15
15
|
s.authors = ['unicorn hackers']
|
16
16
|
s.summary = 'Rack HTTP server for fast clients and Unix'
|
17
17
|
s.description = File.read('README').split("\n\n")[1]
|
18
|
-
s.email = %q{unicorn-public@
|
18
|
+
s.email = %q{unicorn-public@yhbt.net}
|
19
19
|
s.executables = %w(unicorn unicorn_rails)
|
20
20
|
s.extensions = %w(ext/unicorn_http/extconf.rb)
|
21
21
|
s.extra_rdoc_files = IO.readlines('.document').map!(&:chomp!).keep_if do |f|
|
22
22
|
File.exist?(f)
|
23
23
|
end
|
24
24
|
s.files = manifest
|
25
|
-
s.homepage = 'https://
|
25
|
+
s.homepage = 'https://yhbt.net/unicorn/'
|
26
26
|
s.test_files = test_files
|
27
27
|
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
|
28
|
+
# 1.9.3 is the minumum supported version. We don't specify
|
29
|
+
# a maximum version to make it easier to test pre-releases,
|
30
|
+
# but we do warn users if they install unicorn on an untested
|
31
|
+
# version in extconf.rb
|
32
|
+
s.required_ruby_version = ">= 1.9.3"
|
32
33
|
|
33
34
|
# We do not have a hard dependency on rack, it's possible to load
|
34
35
|
# things which respond to #call. HTTP status lines in responses
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- unicorn hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -72,7 +72,7 @@ description: |-
|
|
72
72
|
advantage of features in Unix/Unix-like kernels. Slow clients should
|
73
73
|
only be served by placing a reverse proxy capable of fully buffering
|
74
74
|
both the the request and response in between unicorn and slow clients.
|
75
|
-
email: unicorn-public@
|
75
|
+
email: unicorn-public@yhbt.net
|
76
76
|
executables:
|
77
77
|
- unicorn
|
78
78
|
- unicorn_rails
|
@@ -271,7 +271,7 @@ files:
|
|
271
271
|
- unicorn.gemspec
|
272
272
|
- unicorn_1
|
273
273
|
- unicorn_rails_1
|
274
|
-
homepage: https://
|
274
|
+
homepage: https://yhbt.net/unicorn/
|
275
275
|
licenses:
|
276
276
|
- GPL-2.0+
|
277
277
|
- Ruby-1.8
|
@@ -282,9 +282,9 @@ require_paths:
|
|
282
282
|
- lib
|
283
283
|
required_ruby_version: !ruby/object:Gem::Requirement
|
284
284
|
requirements:
|
285
|
-
- - "
|
285
|
+
- - ">="
|
286
286
|
- !ruby/object:Gem::Version
|
287
|
-
version:
|
287
|
+
version: 1.9.3
|
288
288
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
289
289
|
requirements:
|
290
290
|
- - ">="
|
@@ -301,4 +301,5 @@ test_files:
|
|
301
301
|
- test/unit/test_http_parser_ng.rb
|
302
302
|
- test/unit/test_request.rb
|
303
303
|
- test/unit/test_server.rb
|
304
|
+
- test/unit/test_upload.rb
|
304
305
|
- test/unit/test_util.rb
|