unicorn 4.9.0 → 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/.gitattributes +5 -0
- data/.manifest +14 -15
- data/.olddoc.yml +16 -6
- data/Application_Timeouts +7 -7
- data/CONTRIBUTORS +6 -2
- data/DESIGN +2 -4
- data/Documentation/.gitignore +1 -3
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +17 -8
- data/GIT-VERSION-FILE +1 -1
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +121 -56
- data/HACKING +2 -10
- data/ISSUES +40 -43
- data/KNOWN_ISSUES +11 -11
- data/LATEST +16 -22
- data/LICENSE +2 -2
- data/Links +24 -25
- data/NEWS +771 -0
- data/PHILOSOPHY +0 -6
- data/README +46 -40
- data/SIGNALS +2 -2
- data/Sandbox +11 -10
- data/TODO +0 -2
- data/TUNING +30 -9
- data/archive/slrnpull.conf +1 -1
- data/bin/unicorn +4 -2
- data/bin/unicorn_rails +3 -3
- data/examples/big_app_gc.rb +1 -1
- data/examples/init.sh +36 -8
- data/examples/logrotate.conf +17 -2
- data/examples/nginx.conf +14 -14
- data/examples/unicorn.conf.minimal.rb +2 -2
- data/examples/unicorn.conf.rb +3 -6
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/c_util.h +5 -13
- data/ext/unicorn_http/common_field_optimization.h +22 -5
- data/ext/unicorn_http/epollexclusive.h +124 -0
- data/ext/unicorn_http/ext_help.h +0 -44
- data/ext/unicorn_http/extconf.rb +32 -5
- data/ext/unicorn_http/global_variables.h +2 -2
- data/ext/unicorn_http/httpdate.c +3 -2
- data/ext/unicorn_http/unicorn_http.c +926 -638
- data/ext/unicorn_http/unicorn_http.rl +159 -170
- data/ext/unicorn_http/unicorn_http_common.rl +1 -1
- data/lib/unicorn/configurator.rb +110 -44
- data/lib/unicorn/const.rb +2 -25
- data/lib/unicorn/http_request.rb +110 -31
- data/lib/unicorn/http_response.rb +17 -31
- data/lib/unicorn/http_server.rb +255 -179
- data/lib/unicorn/launcher.rb +1 -1
- data/lib/unicorn/oob_gc.rb +6 -6
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +58 -78
- data/lib/unicorn/stream_input.rb +8 -7
- data/lib/unicorn/tee_input.rb +8 -10
- data/lib/unicorn/tmpio.rb +8 -7
- data/lib/unicorn/util.rb +5 -4
- data/lib/unicorn/version.rb +1 -1
- data/lib/unicorn/worker.rb +36 -23
- data/lib/unicorn.rb +64 -46
- data/man/man1/unicorn.1 +123 -119
- data/man/man1/unicorn_rails.1 +106 -107
- data/t/GNUmakefile +3 -72
- data/t/README +4 -4
- data/t/t0011-active-unix-socket.sh +1 -1
- data/t/t0012-reload-empty-config.sh +2 -1
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/test-lib.sh +4 -3
- 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 +73 -19
- data/test/test_helper.rb +40 -31
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_droplet.rb +1 -1
- data/test/unit/test_http_parser.rb +46 -16
- data/test/unit/test_http_parser_ng.rb +97 -114
- data/test/unit/test_request.rb +10 -10
- data/test/unit/test_response.rb +28 -16
- data/test/unit/test_server.rb +86 -12
- data/test/unit/test_signals.rb +8 -8
- data/test/unit/test_socket_helper.rb +14 -10
- data/test/unit/test_upload.rb +9 -14
- data/test/unit/test_util.rb +31 -5
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +27 -19
- metadata +28 -45
- data/Documentation/GNUmakefile +0 -30
- data/Documentation/unicorn.1.txt +0 -185
- data/Documentation/unicorn_rails.1.txt +0 -175
- data/examples/git.ru +0 -13
- data/lib/unicorn/app/exec_cgi.rb +0 -154
- data/lib/unicorn/app/inetd.rb +0 -109
- data/lib/unicorn/ssl_client.rb +0 -11
- data/lib/unicorn/ssl_configurator.rb +0 -104
- data/lib/unicorn/ssl_server.rb +0 -42
- data/t/hijack.ru +0 -42
- data/t/t0016-trust-x-forwarded-false.sh +0 -30
- data/t/t0017-trust-x-forwarded-true.sh +0 -30
- data/t/t0200-rack-hijack.sh +0 -27
- data/test/unit/test_http_parser_xftrust.rb +0 -38
- data/test/unit/test_sni_hostnames.rb +0 -47
@@ -8,10 +8,29 @@ include Unicorn
|
|
8
8
|
class HttpParserNgTest < Test::Unit::TestCase
|
9
9
|
|
10
10
|
def setup
|
11
|
-
HttpParser.keepalive_requests = HttpParser::KEEPALIVE_REQUESTS_DEFAULT
|
12
11
|
@parser = HttpParser.new
|
13
12
|
end
|
14
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
|
+
|
28
|
+
def test_parser_max_len
|
29
|
+
assert_raises(RangeError) do
|
30
|
+
HttpParser.max_header_len = 0xffffffff + 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
15
34
|
def test_next_clear
|
16
35
|
r = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
17
36
|
@parser.buf << r
|
@@ -29,23 +48,15 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
29
48
|
assert_equal false, @parser.response_start_sent
|
30
49
|
end
|
31
50
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
assert HttpParser.keepalive_requests >= 0xffffffff
|
42
|
-
HttpParser.keepalive_requests = 1
|
43
|
-
assert_equal 1, HttpParser.keepalive_requests
|
44
|
-
HttpParser.keepalive_requests = 666
|
45
|
-
assert_equal 666, HttpParser.keepalive_requests
|
46
|
-
|
47
|
-
assert_raises(TypeError) { HttpParser.keepalive_requests = "666" }
|
48
|
-
assert_raises(TypeError) { HttpParser.keepalive_requests = [] }
|
51
|
+
def test_response_start_sent
|
52
|
+
assert_equal false, @parser.response_start_sent, "default is false"
|
53
|
+
@parser.response_start_sent = true
|
54
|
+
assert_equal true, @parser.response_start_sent
|
55
|
+
@parser.response_start_sent = false
|
56
|
+
assert_equal false, @parser.response_start_sent
|
57
|
+
@parser.response_start_sent = true
|
58
|
+
@parser.clear
|
59
|
+
assert_equal false, @parser.response_start_sent
|
49
60
|
end
|
50
61
|
|
51
62
|
def test_connection_TE
|
@@ -71,41 +82,11 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
71
82
|
"REQUEST_METHOD" => "GET",
|
72
83
|
"QUERY_STRING" => ""
|
73
84
|
}.freeze
|
74
|
-
|
85
|
+
100.times do |nr|
|
75
86
|
@parser.buf << req
|
76
87
|
assert_equal expect, @parser.parse
|
77
88
|
assert @parser.next?
|
78
89
|
end
|
79
|
-
@parser.buf << req
|
80
|
-
assert_equal expect, @parser.parse
|
81
|
-
assert ! @parser.next?
|
82
|
-
end
|
83
|
-
|
84
|
-
def test_fewer_keepalive_requests_with_next?
|
85
|
-
HttpParser.keepalive_requests = 5
|
86
|
-
@parser = HttpParser.new
|
87
|
-
req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
|
88
|
-
expect = {
|
89
|
-
"SERVER_NAME" => "example.com",
|
90
|
-
"HTTP_HOST" => "example.com",
|
91
|
-
"rack.url_scheme" => "http",
|
92
|
-
"REQUEST_PATH" => "/",
|
93
|
-
"SERVER_PROTOCOL" => "HTTP/1.1",
|
94
|
-
"PATH_INFO" => "/",
|
95
|
-
"HTTP_VERSION" => "HTTP/1.1",
|
96
|
-
"REQUEST_URI" => "/",
|
97
|
-
"SERVER_PORT" => "80",
|
98
|
-
"REQUEST_METHOD" => "GET",
|
99
|
-
"QUERY_STRING" => ""
|
100
|
-
}.freeze
|
101
|
-
5.times do |nr|
|
102
|
-
@parser.buf << req
|
103
|
-
assert_equal expect, @parser.parse
|
104
|
-
assert @parser.next?
|
105
|
-
end
|
106
|
-
@parser.buf << req
|
107
|
-
assert_equal expect, @parser.parse
|
108
|
-
assert ! @parser.next?
|
109
90
|
end
|
110
91
|
|
111
92
|
def test_default_keepalive_is_off
|
@@ -599,6 +580,73 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
599
580
|
end
|
600
581
|
end
|
601
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
|
+
|
602
650
|
def test_backtrace_is_empty
|
603
651
|
begin
|
604
652
|
@parser.headers({}, "AAADFSFDSFD\r\n\r\n")
|
@@ -663,69 +711,4 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
663
711
|
assert_equal expect, env2
|
664
712
|
assert_equal "", @parser.buf
|
665
713
|
end
|
666
|
-
|
667
|
-
def test_keepalive_requests_disabled
|
668
|
-
req = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".freeze
|
669
|
-
expect = {
|
670
|
-
"SERVER_NAME" => "example.com",
|
671
|
-
"HTTP_HOST" => "example.com",
|
672
|
-
"rack.url_scheme" => "http",
|
673
|
-
"REQUEST_PATH" => "/",
|
674
|
-
"SERVER_PROTOCOL" => "HTTP/1.1",
|
675
|
-
"PATH_INFO" => "/",
|
676
|
-
"HTTP_VERSION" => "HTTP/1.1",
|
677
|
-
"REQUEST_URI" => "/",
|
678
|
-
"SERVER_PORT" => "80",
|
679
|
-
"REQUEST_METHOD" => "GET",
|
680
|
-
"QUERY_STRING" => ""
|
681
|
-
}.freeze
|
682
|
-
HttpParser.keepalive_requests = 0
|
683
|
-
@parser = HttpParser.new
|
684
|
-
@parser.buf << req
|
685
|
-
assert_equal expect, @parser.parse
|
686
|
-
assert ! @parser.next?
|
687
|
-
end
|
688
|
-
|
689
|
-
def test_chunk_only
|
690
|
-
tmp = ""
|
691
|
-
assert_equal @parser, @parser.dechunk!
|
692
|
-
assert_nil @parser.filter_body(tmp, "6\r\n")
|
693
|
-
assert_equal "", tmp
|
694
|
-
assert_nil @parser.filter_body(tmp, "abcdef")
|
695
|
-
assert_equal "abcdef", tmp
|
696
|
-
assert_nil @parser.filter_body(tmp, "\r\n")
|
697
|
-
assert_equal "", tmp
|
698
|
-
src = "0\r\n\r\n"
|
699
|
-
assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
|
700
|
-
assert_equal "", tmp
|
701
|
-
end
|
702
|
-
|
703
|
-
def test_chunk_only_bad_align
|
704
|
-
tmp = ""
|
705
|
-
assert_equal @parser, @parser.dechunk!
|
706
|
-
assert_nil @parser.filter_body(tmp, "6\r\na")
|
707
|
-
assert_equal "a", tmp
|
708
|
-
assert_nil @parser.filter_body(tmp, "bcde")
|
709
|
-
assert_equal "bcde", tmp
|
710
|
-
assert_nil @parser.filter_body(tmp, "f\r")
|
711
|
-
assert_equal "f", tmp
|
712
|
-
src = "\n0\r\n\r\n"
|
713
|
-
assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
|
714
|
-
assert_equal "", tmp
|
715
|
-
end
|
716
|
-
|
717
|
-
def test_chunk_only_reset_ok
|
718
|
-
tmp = ""
|
719
|
-
assert_equal @parser, @parser.dechunk!
|
720
|
-
src = "1\r\na\r\n0\r\n\r\n"
|
721
|
-
assert_nil @parser.filter_body(tmp, src)
|
722
|
-
assert_equal "a", tmp
|
723
|
-
assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
|
724
|
-
|
725
|
-
assert_equal @parser, @parser.dechunk!
|
726
|
-
src = "0\r\n\r\n"
|
727
|
-
assert_equal src.object_id, @parser.filter_body(tmp, src).object_id
|
728
|
-
assert_equal "", tmp
|
729
|
-
assert_equal src, @parser.filter_body(tmp, src)
|
730
|
-
end
|
731
714
|
end
|
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_response.rb
CHANGED
@@ -33,12 +33,20 @@ class ResponseTest < Test::Unit::TestCase
|
|
33
33
|
assert out.length > 0, "output didn't have data"
|
34
34
|
end
|
35
35
|
|
36
|
+
# ref: <CAO47=rJa=zRcLn_Xm4v2cHPr6c0UswaFC_omYFEH+baSxHOWKQ@mail.gmail.com>
|
37
|
+
def test_response_header_broken_nil
|
38
|
+
out = StringIO.new
|
39
|
+
http_response_write(out, 200, {"Nil" => nil}, %w(hysterical raisin))
|
40
|
+
assert ! out.closed?
|
41
|
+
|
42
|
+
assert_match %r{^Nil: \r\n}sm, out.string, 'nil accepted'
|
43
|
+
end
|
44
|
+
|
36
45
|
def test_response_string_status
|
37
46
|
out = StringIO.new
|
38
47
|
http_response_write(out,'200', {}, [])
|
39
48
|
assert ! out.closed?
|
40
49
|
assert out.length > 0, "output didn't have data"
|
41
|
-
assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/).size
|
42
50
|
end
|
43
51
|
|
44
52
|
def test_response_200
|
@@ -71,18 +79,6 @@ class ResponseTest < Test::Unit::TestCase
|
|
71
79
|
out = StringIO.new
|
72
80
|
http_response_write(out,200, {"X-Whatever" => "stuff"}, [])
|
73
81
|
assert ! out.closed?
|
74
|
-
assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
|
75
|
-
end
|
76
|
-
|
77
|
-
def test_body_closed
|
78
|
-
expect_body = %w(1 2 3 4).join("\n")
|
79
|
-
body = StringIO.new(expect_body)
|
80
|
-
body.rewind
|
81
|
-
out = StringIO.new
|
82
|
-
http_response_write(out,200, {}, body)
|
83
|
-
assert ! out.closed?
|
84
|
-
assert body.closed?
|
85
|
-
assert_match(expect_body, out.string.split(/\r\n/).last)
|
86
82
|
end
|
87
83
|
|
88
84
|
def test_unknown_status_pass_through
|
@@ -91,9 +87,25 @@ class ResponseTest < Test::Unit::TestCase
|
|
91
87
|
assert ! out.closed?
|
92
88
|
headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
|
93
89
|
assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0])
|
94
|
-
status = headers.grep(/\AStatus:/i).first
|
95
|
-
assert status
|
96
|
-
assert_equal "Status: 666 I AM THE BEAST", status
|
97
90
|
end
|
98
91
|
|
92
|
+
def test_modified_rack_http_status_codes_late
|
93
|
+
r, w = IO.pipe
|
94
|
+
pid = fork do
|
95
|
+
r.close
|
96
|
+
# Users may want to globally override the status text associated
|
97
|
+
# with an HTTP status code in their app.
|
98
|
+
Rack::Utils::HTTP_STATUS_CODES[200] = "HI"
|
99
|
+
http_response_write(w, 200, {}, [])
|
100
|
+
w.close
|
101
|
+
end
|
102
|
+
w.close
|
103
|
+
assert_equal "HTTP/1.1 200 HI\r\n", r.gets
|
104
|
+
r.read # just drain the pipe
|
105
|
+
pid, status = Process.waitpid2(pid)
|
106
|
+
assert status.success?, status.inspect
|
107
|
+
ensure
|
108
|
+
r.close
|
109
|
+
w.close unless w.closed?
|
110
|
+
end
|
99
111
|
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
|
data/test/unit/test_signals.rb
CHANGED
@@ -52,7 +52,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
52
52
|
redirect_test_io { HttpServer.new(app, opts).start.join }
|
53
53
|
}
|
54
54
|
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
55
|
-
sock =
|
55
|
+
sock = tcp_socket('127.0.0.1', @port)
|
56
56
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
57
57
|
buf = sock.readpartial(4096)
|
58
58
|
assert_nil sock.close
|
@@ -79,7 +79,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
79
79
|
}
|
80
80
|
wr.close
|
81
81
|
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
82
|
-
sock =
|
82
|
+
sock = tcp_socket('127.0.0.1', @port)
|
83
83
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
84
84
|
buf = rd.readpartial(1)
|
85
85
|
wait_master_ready("test_stderr.#{pid}.log")
|
@@ -102,7 +102,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
102
102
|
}
|
103
103
|
t0 = Time.now
|
104
104
|
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
105
|
-
sock =
|
105
|
+
sock = tcp_socket('127.0.0.1', @port)
|
106
106
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
107
107
|
|
108
108
|
buf = nil
|
@@ -114,8 +114,8 @@ class SignalsTest < Test::Unit::TestCase
|
|
114
114
|
assert_nil buf
|
115
115
|
assert diff > 1.0, "diff was #{diff.inspect}"
|
116
116
|
assert diff < 60.0
|
117
|
-
|
118
|
-
|
117
|
+
ensure
|
118
|
+
Process.kill(:TERM, pid) rescue nil
|
119
119
|
end
|
120
120
|
|
121
121
|
def test_response_write
|
@@ -125,7 +125,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
125
125
|
}
|
126
126
|
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
127
127
|
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
128
|
-
sock =
|
128
|
+
sock = tcp_socket('127.0.0.1', @port)
|
129
129
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
130
130
|
buf = ''
|
131
131
|
header_len = pid = nil
|
@@ -163,13 +163,13 @@ class SignalsTest < Test::Unit::TestCase
|
|
163
163
|
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
164
164
|
|
165
165
|
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
166
|
-
sock =
|
166
|
+
sock = tcp_socket('127.0.0.1', @port)
|
167
167
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
168
168
|
pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
169
169
|
assert_nil sock.close
|
170
170
|
|
171
171
|
assert pid > 0, "pid not positive: #{pid.inspect}"
|
172
|
-
sock =
|
172
|
+
sock = tcp_socket('127.0.0.1', @port)
|
173
173
|
sock.syswrite("PUT / HTTP/1.0\r\n")
|
174
174
|
sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
|
175
175
|
1000.times { Process.kill(:HUP, pid) }
|