unicorn 0.2.3 → 0.4.1
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.
- data/.document +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG +1 -0
- data/DESIGN +4 -0
- data/GNUmakefile +30 -6
- data/Manifest +62 -3
- data/README +52 -42
- data/SIGNALS +17 -17
- data/TODO +27 -5
- data/bin/unicorn +15 -13
- data/bin/unicorn_rails +59 -22
- data/ext/unicorn/http11/http11.c +25 -104
- data/ext/unicorn/http11/http11_parser.c +24 -23
- data/ext/unicorn/http11/http11_parser.h +1 -3
- data/ext/unicorn/http11/http11_parser.rl +2 -1
- data/lib/unicorn.rb +58 -44
- data/lib/unicorn/app/old_rails.rb +23 -0
- data/lib/unicorn/app/old_rails/static.rb +58 -0
- data/lib/unicorn/cgi_wrapper.rb +151 -0
- data/lib/unicorn/configurator.rb +71 -31
- data/lib/unicorn/const.rb +9 -34
- data/lib/unicorn/http_request.rb +63 -66
- data/lib/unicorn/http_response.rb +6 -1
- data/lib/unicorn/socket.rb +15 -2
- data/test/benchmark/README +55 -0
- data/test/benchmark/big_request.rb +35 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/request.rb +47 -0
- data/test/benchmark/response.rb +29 -0
- data/test/exec/test_exec.rb +41 -157
- data/test/rails/app-1.2.3/.gitignore +2 -0
- data/test/rails/app-1.2.3/app/controllers/application.rb +4 -0
- data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-1.2.3/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-1.2.3/config/boot.rb +9 -0
- data/test/rails/app-1.2.3/config/database.yml +12 -0
- data/test/rails/app-1.2.3/config/environment.rb +10 -0
- data/test/rails/app-1.2.3/config/environments/development.rb +7 -0
- data/test/rails/app-1.2.3/config/environments/production.rb +3 -0
- data/test/rails/app-1.2.3/config/routes.rb +4 -0
- data/test/rails/app-1.2.3/db/.gitignore +0 -0
- data/test/rails/app-1.2.3/public/404.html +1 -0
- data/test/rails/app-1.2.3/public/500.html +1 -0
- data/test/rails/app-2.0.2/.gitignore +2 -0
- data/test/rails/app-2.0.2/app/controllers/application.rb +2 -0
- data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.0.2/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.0.2/config/boot.rb +9 -0
- data/test/rails/app-2.0.2/config/database.yml +12 -0
- data/test/rails/app-2.0.2/config/environment.rb +14 -0
- data/test/rails/app-2.0.2/config/environments/development.rb +6 -0
- data/test/rails/app-2.0.2/config/environments/production.rb +3 -0
- data/test/rails/app-2.0.2/config/routes.rb +4 -0
- data/test/rails/app-2.0.2/db/.gitignore +0 -0
- data/test/rails/app-2.0.2/public/404.html +1 -0
- data/test/rails/app-2.0.2/public/500.html +1 -0
- data/test/rails/app-2.2.2/.gitignore +2 -0
- data/test/rails/app-2.2.2/app/controllers/application.rb +2 -0
- data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.2.2/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.2.2/config/boot.rb +109 -0
- data/test/rails/app-2.2.2/config/database.yml +12 -0
- data/test/rails/app-2.2.2/config/environment.rb +14 -0
- data/test/rails/app-2.2.2/config/environments/development.rb +5 -0
- data/test/rails/app-2.2.2/config/environments/production.rb +3 -0
- data/test/rails/app-2.2.2/config/routes.rb +4 -0
- data/test/rails/app-2.2.2/db/.gitignore +0 -0
- data/test/rails/app-2.2.2/public/404.html +1 -0
- data/test/rails/app-2.2.2/public/500.html +1 -0
- data/test/rails/app-2.3.2.1/.gitignore +2 -0
- data/test/rails/app-2.3.2.1/app/controllers/application_controller.rb +3 -0
- data/test/rails/app-2.3.2.1/app/controllers/foo_controller.rb +34 -0
- data/test/rails/app-2.3.2.1/app/helpers/application_helper.rb +2 -0
- data/test/rails/app-2.3.2.1/config/boot.rb +107 -0
- data/test/rails/app-2.3.2.1/config/database.yml +12 -0
- data/test/rails/app-2.3.2.1/config/environment.rb +14 -0
- data/test/rails/app-2.3.2.1/config/environments/development.rb +5 -0
- data/test/rails/app-2.3.2.1/config/environments/production.rb +4 -0
- data/test/rails/app-2.3.2.1/config/routes.rb +4 -0
- data/test/rails/app-2.3.2.1/db/.gitignore +0 -0
- data/test/rails/app-2.3.2.1/public/404.html +1 -0
- data/test/rails/app-2.3.2.1/public/500.html +1 -0
- data/test/rails/test_rails.rb +243 -0
- data/test/test_helper.rb +149 -2
- data/test/unit/test_configurator.rb +46 -0
- data/test/unit/test_http_parser.rb +77 -36
- data/test/unit/test_request.rb +2 -0
- data/test/unit/test_response.rb +20 -4
- data/test/unit/test_server.rb +30 -1
- data/test/unit/test_socket_helper.rb +159 -0
- data/unicorn.gemspec +5 -5
- metadata +68 -5
- data/test/benchmark/previous.rb +0 -11
- data/test/benchmark/simple.rb +0 -11
- data/test/benchmark/utils.rb +0 -82
|
@@ -8,6 +8,28 @@ class TestConfigurator < Test::Unit::TestCase
|
|
|
8
8
|
assert_nothing_raised { Unicorn::Configurator.new {} }
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
def test_expand_addr
|
|
12
|
+
meth = Unicorn::Configurator.new.method(:expand_addr)
|
|
13
|
+
|
|
14
|
+
assert_equal "/var/run/unicorn.sock", meth.call("/var/run/unicorn.sock")
|
|
15
|
+
assert_equal "#{Dir.pwd}/foo/bar.sock", meth.call("unix:foo/bar.sock")
|
|
16
|
+
|
|
17
|
+
path = meth.call("~/foo/bar.sock")
|
|
18
|
+
assert_equal "/", path[0..0]
|
|
19
|
+
assert_match %r{/foo/bar\.sock\z}, path
|
|
20
|
+
|
|
21
|
+
path = meth.call("~root/foo/bar.sock")
|
|
22
|
+
assert_equal "/", path[0..0]
|
|
23
|
+
assert_match %r{/foo/bar\.sock\z}, path
|
|
24
|
+
|
|
25
|
+
assert_equal "1.2.3.4:2007", meth.call('1.2.3.4:2007')
|
|
26
|
+
assert_equal "0.0.0.0:2007", meth.call('0.0.0.0:2007')
|
|
27
|
+
assert_equal "0.0.0.0:2007", meth.call(':2007')
|
|
28
|
+
assert_equal "0.0.0.0:2007", meth.call('*:2007')
|
|
29
|
+
assert_match %r{\A\d+\.\d+\.\d+\.\d+:2007\z}, meth.call('1:2007')
|
|
30
|
+
assert_match %r{\A\d+\.\d+\.\d+\.\d+:2007\z}, meth.call('2:2007')
|
|
31
|
+
end
|
|
32
|
+
|
|
11
33
|
def test_config_invalid
|
|
12
34
|
tmp = Tempfile.new('unicorn_config')
|
|
13
35
|
tmp.syswrite(%q(asdfasdf "hello-world"))
|
|
@@ -45,4 +67,28 @@ class TestConfigurator < Test::Unit::TestCase
|
|
|
45
67
|
assert_nil @logger
|
|
46
68
|
end
|
|
47
69
|
|
|
70
|
+
def test_listen_options
|
|
71
|
+
tmp = Tempfile.new('unicorn_config')
|
|
72
|
+
expect = { :sndbuf => 1, :rcvbuf => 2, :backlog => 10 }.freeze
|
|
73
|
+
listener = "127.0.0.1:12345"
|
|
74
|
+
tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
|
|
75
|
+
cfg = nil
|
|
76
|
+
assert_nothing_raised do
|
|
77
|
+
cfg = Unicorn::Configurator.new(:config_file => tmp.path)
|
|
78
|
+
end
|
|
79
|
+
assert_nothing_raised { cfg.commit!(self) }
|
|
80
|
+
assert(listener_opts = instance_variable_get("@listener_opts"))
|
|
81
|
+
assert_equal expect, listener_opts[listener]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_listen_option_bad
|
|
85
|
+
tmp = Tempfile.new('unicorn_config')
|
|
86
|
+
expect = { :sndbuf => "five" }
|
|
87
|
+
listener = "127.0.0.1:12345"
|
|
88
|
+
tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
|
|
89
|
+
assert_raises(ArgumentError) do
|
|
90
|
+
Unicorn::Configurator.new(:config_file => tmp.path)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
48
94
|
end
|
|
@@ -14,33 +14,40 @@ class HttpParserTest < Test::Unit::TestCase
|
|
|
14
14
|
parser = HttpParser.new
|
|
15
15
|
req = {}
|
|
16
16
|
http = "GET / HTTP/1.1\r\n\r\n"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
assert nread == http.length, "Failed to parse the full HTTP request"
|
|
20
|
-
assert parser.finished?, "Parser didn't finish"
|
|
21
|
-
assert !parser.error?, "Parser had error"
|
|
22
|
-
assert nread == parser.nread, "Number read returned from execute does not match"
|
|
17
|
+
assert parser.execute(req, http)
|
|
23
18
|
|
|
24
19
|
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
|
25
20
|
assert_equal '/', req['REQUEST_PATH']
|
|
26
21
|
assert_equal 'HTTP/1.1', req['HTTP_VERSION']
|
|
27
22
|
assert_equal '/', req['REQUEST_URI']
|
|
28
|
-
assert_equal 'GET', req['REQUEST_METHOD']
|
|
23
|
+
assert_equal 'GET', req['REQUEST_METHOD']
|
|
29
24
|
assert_nil req['FRAGMENT']
|
|
30
25
|
assert_nil req['QUERY_STRING']
|
|
31
26
|
|
|
32
27
|
parser.reset
|
|
33
|
-
|
|
28
|
+
req.clear
|
|
29
|
+
|
|
30
|
+
assert ! parser.execute(req, "G")
|
|
31
|
+
assert req.empty?
|
|
32
|
+
|
|
33
|
+
# try parsing again to ensure we were reset correctly
|
|
34
|
+
http = "GET /hello-world HTTP/1.1\r\n\r\n"
|
|
35
|
+
assert parser.execute(req, http)
|
|
36
|
+
|
|
37
|
+
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
|
38
|
+
assert_equal '/hello-world', req['REQUEST_PATH']
|
|
39
|
+
assert_equal 'HTTP/1.1', req['HTTP_VERSION']
|
|
40
|
+
assert_equal '/hello-world', req['REQUEST_URI']
|
|
41
|
+
assert_equal 'GET', req['REQUEST_METHOD']
|
|
42
|
+
assert_nil req['FRAGMENT']
|
|
43
|
+
assert_nil req['QUERY_STRING']
|
|
34
44
|
end
|
|
35
|
-
|
|
45
|
+
|
|
36
46
|
def test_parse_strange_headers
|
|
37
47
|
parser = HttpParser.new
|
|
38
48
|
req = {}
|
|
39
49
|
should_be_good = "GET / HTTP/1.1\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
|
|
40
|
-
|
|
41
|
-
assert_equal should_be_good.length, nread
|
|
42
|
-
assert parser.finished?
|
|
43
|
-
assert !parser.error?
|
|
50
|
+
assert parser.execute(req, should_be_good)
|
|
44
51
|
|
|
45
52
|
# ref: http://thread.gmane.org/gmane.comp.lang.ruby.Unicorn.devel/37/focus=45
|
|
46
53
|
# (note we got 'pen' mixed up with 'pound' in that thread,
|
|
@@ -49,10 +56,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
|
49
56
|
# nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"
|
|
50
57
|
# parser = HttpParser.new
|
|
51
58
|
# req = {}
|
|
52
|
-
#
|
|
53
|
-
# assert_equal nasty_pound_header.length, nread
|
|
54
|
-
# assert parser.finished?
|
|
55
|
-
# assert !parser.error?
|
|
59
|
+
# assert parser.execute(req, nasty_pound_header, 0)
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
def test_parse_ie6_urls
|
|
@@ -66,10 +70,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
|
66
70
|
parser = HttpParser.new
|
|
67
71
|
req = {}
|
|
68
72
|
sorta_safe = %(GET #{path} HTTP/1.1\r\n\r\n)
|
|
69
|
-
|
|
70
|
-
assert_equal sorta_safe.length, nread
|
|
71
|
-
assert parser.finished?
|
|
72
|
-
assert !parser.error?
|
|
73
|
+
assert parser.execute(req, sorta_safe)
|
|
73
74
|
end
|
|
74
75
|
end
|
|
75
76
|
|
|
@@ -78,28 +79,68 @@ class HttpParserTest < Test::Unit::TestCase
|
|
|
78
79
|
req = {}
|
|
79
80
|
bad_http = "GET / SsUTF/1.1"
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
error = true
|
|
86
|
-
end
|
|
82
|
+
assert_raises(HttpParserError) { parser.execute(req, bad_http) }
|
|
83
|
+
parser.reset
|
|
84
|
+
assert(parser.execute({}, "GET / HTTP/1.0\r\n\r\n"))
|
|
85
|
+
end
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
def test_piecemeal
|
|
88
|
+
parser = HttpParser.new
|
|
89
|
+
req = {}
|
|
90
|
+
http = "GET"
|
|
91
|
+
assert ! parser.execute(req, http)
|
|
92
|
+
assert_raises(HttpParserError) { parser.execute(req, http) }
|
|
93
|
+
assert ! parser.execute(req, http << " / HTTP/1.0")
|
|
94
|
+
assert_equal '/', req['REQUEST_PATH']
|
|
95
|
+
assert_equal '/', req['REQUEST_URI']
|
|
96
|
+
assert_equal 'GET', req['REQUEST_METHOD']
|
|
97
|
+
assert ! parser.execute(req, http << "\r\n")
|
|
98
|
+
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
|
99
|
+
assert ! parser.execute(req, http << "\r")
|
|
100
|
+
assert parser.execute(req, http << "\n")
|
|
101
|
+
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
|
102
|
+
assert_nil req['FRAGMENT']
|
|
103
|
+
assert_nil req['QUERY_STRING']
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_put_body_oneshot
|
|
107
|
+
parser = HttpParser.new
|
|
108
|
+
req = {}
|
|
109
|
+
http = "PUT / HTTP/1.0\r\nContent-Length: 5\r\n\r\nabcde"
|
|
110
|
+
assert parser.execute(req, http)
|
|
111
|
+
assert_equal '/', req['REQUEST_PATH']
|
|
112
|
+
assert_equal '/', req['REQUEST_URI']
|
|
113
|
+
assert_equal 'PUT', req['REQUEST_METHOD']
|
|
114
|
+
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
|
115
|
+
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
|
116
|
+
assert_equal "abcde", req[:http_body]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_put_body_later
|
|
120
|
+
parser = HttpParser.new
|
|
121
|
+
req = {}
|
|
122
|
+
http = "PUT /l HTTP/1.0\r\nContent-Length: 5\r\n\r\n"
|
|
123
|
+
assert parser.execute(req, http)
|
|
124
|
+
assert_equal '/l', req['REQUEST_PATH']
|
|
125
|
+
assert_equal '/l', req['REQUEST_URI']
|
|
126
|
+
assert_equal 'PUT', req['REQUEST_METHOD']
|
|
127
|
+
assert_equal 'HTTP/1.0', req['HTTP_VERSION']
|
|
128
|
+
assert_equal 'HTTP/1.1', req['SERVER_PROTOCOL']
|
|
129
|
+
assert_equal "", req[:http_body]
|
|
91
130
|
end
|
|
92
131
|
|
|
93
132
|
def test_fragment_in_uri
|
|
94
133
|
parser = HttpParser.new
|
|
95
134
|
req = {}
|
|
96
135
|
get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
|
|
136
|
+
ok = false
|
|
97
137
|
assert_nothing_raised do
|
|
98
|
-
parser.execute(req, get
|
|
138
|
+
ok = parser.execute(req, get)
|
|
99
139
|
end
|
|
100
|
-
assert
|
|
140
|
+
assert ok
|
|
101
141
|
assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
|
|
102
142
|
assert_equal 'posts-17408', req['FRAGMENT']
|
|
143
|
+
assert_equal 'page=1', req['QUERY_STRING']
|
|
103
144
|
end
|
|
104
145
|
|
|
105
146
|
# lame random garbage maker
|
|
@@ -124,7 +165,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
|
124
165
|
10.times do |c|
|
|
125
166
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
|
|
126
167
|
assert_raises Unicorn::HttpParserError do
|
|
127
|
-
parser.execute({}, get
|
|
168
|
+
parser.execute({}, get)
|
|
128
169
|
parser.reset
|
|
129
170
|
end
|
|
130
171
|
end
|
|
@@ -133,7 +174,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
|
133
174
|
10.times do |c|
|
|
134
175
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
|
135
176
|
assert_raises Unicorn::HttpParserError do
|
|
136
|
-
parser.execute({}, get
|
|
177
|
+
parser.execute({}, get)
|
|
137
178
|
parser.reset
|
|
138
179
|
end
|
|
139
180
|
end
|
|
@@ -142,7 +183,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
|
142
183
|
get = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
|
|
143
184
|
get << "X-Test: test\r\n" * (80 * 1024)
|
|
144
185
|
assert_raises Unicorn::HttpParserError do
|
|
145
|
-
parser.execute({}, get
|
|
186
|
+
parser.execute({}, get)
|
|
146
187
|
parser.reset
|
|
147
188
|
end
|
|
148
189
|
|
|
@@ -150,7 +191,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
|
150
191
|
10.times do |c|
|
|
151
192
|
get = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
|
152
193
|
assert_raises Unicorn::HttpParserError do
|
|
153
|
-
parser.execute({}, get
|
|
194
|
+
parser.execute({}, get)
|
|
154
195
|
parser.reset
|
|
155
196
|
end
|
|
156
197
|
end
|
data/test/unit/test_request.rb
CHANGED
|
@@ -50,6 +50,7 @@ class RequestTest < Test::Unit::TestCase
|
|
|
50
50
|
"abcde")
|
|
51
51
|
res = env = nil
|
|
52
52
|
assert_nothing_raised { env = @request.read(client) }
|
|
53
|
+
assert ! env.include?(:http_body)
|
|
53
54
|
assert_nothing_raised { res = @lint.call(env) }
|
|
54
55
|
end
|
|
55
56
|
|
|
@@ -71,6 +72,7 @@ class RequestTest < Test::Unit::TestCase
|
|
|
71
72
|
assert_equal 0, client.sysseek(0)
|
|
72
73
|
res = env = nil
|
|
73
74
|
assert_nothing_raised { env = @request.read(client) }
|
|
75
|
+
assert ! env.include?(:http_body)
|
|
74
76
|
assert_equal length, env['rack.input'].size
|
|
75
77
|
count.times { assert_equal buf, env['rack.input'].read(bs) }
|
|
76
78
|
assert_nil env['rack.input'].read(bs)
|
data/test/unit/test_response.rb
CHANGED
|
@@ -13,6 +13,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
|
13
13
|
def test_response_headers
|
|
14
14
|
out = StringIO.new
|
|
15
15
|
HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, ["cool"]])
|
|
16
|
+
assert out.closed?
|
|
16
17
|
|
|
17
18
|
assert out.length > 0, "output didn't have data"
|
|
18
19
|
end
|
|
@@ -22,7 +23,8 @@ class ResponseTest < Test::Unit::TestCase
|
|
|
22
23
|
$, = "\f\v"
|
|
23
24
|
out = StringIO.new
|
|
24
25
|
HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, ["cool"]])
|
|
25
|
-
|
|
26
|
+
assert out.closed?
|
|
27
|
+
resp = out.string
|
|
26
28
|
assert ! resp.include?("\f\v"), "output didn't use $, ($OFS)"
|
|
27
29
|
ensure
|
|
28
30
|
$, = old_ofs
|
|
@@ -31,6 +33,7 @@ class ResponseTest < Test::Unit::TestCase
|
|
|
31
33
|
def test_response_200
|
|
32
34
|
io = StringIO.new
|
|
33
35
|
HttpResponse.write(io, [200, {}, []])
|
|
36
|
+
assert io.closed?
|
|
34
37
|
assert io.length > 0, "output didn't have data"
|
|
35
38
|
end
|
|
36
39
|
|
|
@@ -38,15 +41,28 @@ class ResponseTest < Test::Unit::TestCase
|
|
|
38
41
|
code = 400
|
|
39
42
|
io = StringIO.new
|
|
40
43
|
HttpResponse.write(io, [code, {}, []])
|
|
41
|
-
io.
|
|
42
|
-
|
|
44
|
+
assert io.closed?
|
|
45
|
+
lines = io.string.split(/\r\n/)
|
|
46
|
+
assert_match(/.* #{HTTP_STATUS_CODES[code]}$/, lines.first,
|
|
47
|
+
"wrong default reason phrase")
|
|
43
48
|
end
|
|
44
49
|
|
|
45
50
|
def test_rack_multivalue_headers
|
|
46
51
|
out = StringIO.new
|
|
47
52
|
HttpResponse.write(out,[200, {"X-Whatever" => "stuff\nbleh"}, []])
|
|
53
|
+
assert out.closed?
|
|
48
54
|
assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
|
|
49
55
|
end
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
def test_body_closed
|
|
58
|
+
expect_body = %w(1 2 3 4).join("\n")
|
|
59
|
+
body = StringIO.new(expect_body)
|
|
60
|
+
body.rewind
|
|
61
|
+
out = StringIO.new
|
|
62
|
+
HttpResponse.write(out,[200, {}, body])
|
|
63
|
+
assert out.closed?
|
|
64
|
+
assert body.closed?
|
|
65
|
+
assert_match(expect_body, out.string.split(/\r\n/).last)
|
|
66
|
+
end
|
|
52
67
|
|
|
68
|
+
end
|
data/test/unit/test_server.rb
CHANGED
|
@@ -25,8 +25,8 @@ class WebServerTest < Test::Unit::TestCase
|
|
|
25
25
|
@tester = TestHandler.new
|
|
26
26
|
redirect_test_io do
|
|
27
27
|
@server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
|
|
28
|
+
@server.start
|
|
28
29
|
end
|
|
29
|
-
@server.start
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def teardown
|
|
@@ -35,6 +35,25 @@ class WebServerTest < Test::Unit::TestCase
|
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
def test_broken_app
|
|
39
|
+
teardown
|
|
40
|
+
port = unused_port
|
|
41
|
+
app = lambda { |env| raise RuntimeError, "hello" }
|
|
42
|
+
# [200, {}, []] }
|
|
43
|
+
redirect_test_io do
|
|
44
|
+
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#{port}"] )
|
|
45
|
+
@server.start
|
|
46
|
+
end
|
|
47
|
+
sock = nil
|
|
48
|
+
assert_nothing_raised do
|
|
49
|
+
sock = TCPSocket.new('127.0.0.1', port)
|
|
50
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
|
|
54
|
+
assert_nothing_raised { sock.close }
|
|
55
|
+
end
|
|
56
|
+
|
|
38
57
|
def test_simple_server
|
|
39
58
|
results = hit(["http://localhost:#{@port}/test"])
|
|
40
59
|
assert_equal 'hello!\n', results[0], "Handler didn't really run"
|
|
@@ -77,6 +96,16 @@ class WebServerTest < Test::Unit::TestCase
|
|
|
77
96
|
end
|
|
78
97
|
end
|
|
79
98
|
|
|
99
|
+
def test_bad_client_400
|
|
100
|
+
sock = nil
|
|
101
|
+
assert_nothing_raised do
|
|
102
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
103
|
+
sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
|
|
104
|
+
end
|
|
105
|
+
assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
|
|
106
|
+
assert_nothing_raised { sock.close }
|
|
107
|
+
end
|
|
108
|
+
|
|
80
109
|
def test_header_is_too_long
|
|
81
110
|
redirect_test_io do
|
|
82
111
|
long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
require 'test/test_helper'
|
|
2
|
+
require 'tempfile'
|
|
3
|
+
|
|
4
|
+
class TestSocketHelper < Test::Unit::TestCase
|
|
5
|
+
include Unicorn::SocketHelper
|
|
6
|
+
attr_reader :logger
|
|
7
|
+
GET_SLASH = "GET / HTTP/1.0\r\n\r\n".freeze
|
|
8
|
+
|
|
9
|
+
def setup
|
|
10
|
+
@log_tmp = Tempfile.new 'logger'
|
|
11
|
+
@logger = Logger.new(@log_tmp.path)
|
|
12
|
+
@test_addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_bind_listen_tcp
|
|
16
|
+
port = unused_port @test_addr
|
|
17
|
+
@tcp_listener_name = "#@test_addr:#{port}"
|
|
18
|
+
@tcp_listener = bind_listen(@tcp_listener_name)
|
|
19
|
+
assert Socket === @tcp_listener
|
|
20
|
+
assert_equal @tcp_listener_name, sock_name(@tcp_listener)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def test_bind_listen_options
|
|
24
|
+
port = unused_port @test_addr
|
|
25
|
+
tcp_listener_name = "#@test_addr:#{port}"
|
|
26
|
+
tmp = Tempfile.new 'unix.sock'
|
|
27
|
+
unix_listener_name = tmp.path
|
|
28
|
+
File.unlink(tmp.path)
|
|
29
|
+
[ { :backlog => 5 }, { :sndbuf => 4096 }, { :rcvbuf => 4096 },
|
|
30
|
+
{ :backlog => 16, :rcvbuf => 4096, :sndbuf => 4096 }
|
|
31
|
+
].each do |opts|
|
|
32
|
+
assert_nothing_raised do
|
|
33
|
+
tcp_listener = bind_listen(tcp_listener_name, opts)
|
|
34
|
+
assert Socket === tcp_listener
|
|
35
|
+
tcp_listener.close
|
|
36
|
+
unix_listener = bind_listen(unix_listener_name, opts)
|
|
37
|
+
assert Socket === unix_listener
|
|
38
|
+
unix_listener.close
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
#system('cat', @log_tmp.path)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_bind_listen_unix
|
|
45
|
+
tmp = Tempfile.new 'unix.sock'
|
|
46
|
+
@unix_listener_path = tmp.path
|
|
47
|
+
File.unlink(@unix_listener_path)
|
|
48
|
+
@unix_listener = bind_listen(@unix_listener_path)
|
|
49
|
+
assert Socket === @unix_listener
|
|
50
|
+
assert_equal @unix_listener_path, sock_name(@unix_listener)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def test_bind_listen_unix_idempotent
|
|
54
|
+
test_bind_listen_unix
|
|
55
|
+
a = bind_listen(@unix_listener)
|
|
56
|
+
assert_equal a.fileno, @unix_listener.fileno
|
|
57
|
+
unix_server = server_cast(@unix_listener)
|
|
58
|
+
a = bind_listen(unix_server)
|
|
59
|
+
assert_equal a.fileno, unix_server.fileno
|
|
60
|
+
assert_equal a.fileno, @unix_listener.fileno
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def test_bind_listen_tcp_idempotent
|
|
64
|
+
test_bind_listen_tcp
|
|
65
|
+
a = bind_listen(@tcp_listener)
|
|
66
|
+
assert_equal a.fileno, @tcp_listener.fileno
|
|
67
|
+
tcp_server = server_cast(@tcp_listener)
|
|
68
|
+
a = bind_listen(tcp_server)
|
|
69
|
+
assert_equal a.fileno, tcp_server.fileno
|
|
70
|
+
assert_equal a.fileno, @tcp_listener.fileno
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_bind_listen_unix_rebind
|
|
74
|
+
test_bind_listen_unix
|
|
75
|
+
new_listener = bind_listen(@unix_listener_path)
|
|
76
|
+
assert Socket === new_listener
|
|
77
|
+
assert new_listener.fileno != @unix_listener.fileno
|
|
78
|
+
assert_equal sock_name(new_listener), sock_name(@unix_listener)
|
|
79
|
+
assert_equal @unix_listener_path, sock_name(new_listener)
|
|
80
|
+
pid = fork do
|
|
81
|
+
client = server_cast(new_listener).accept
|
|
82
|
+
client.syswrite('abcde')
|
|
83
|
+
exit 0
|
|
84
|
+
end
|
|
85
|
+
s = UNIXSocket.new(@unix_listener_path)
|
|
86
|
+
IO.select([s])
|
|
87
|
+
assert_equal 'abcde', s.sysread(5)
|
|
88
|
+
pid, status = Process.waitpid2(pid)
|
|
89
|
+
assert status.success?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_server_cast
|
|
93
|
+
assert_nothing_raised do
|
|
94
|
+
test_bind_listen_unix
|
|
95
|
+
test_bind_listen_tcp
|
|
96
|
+
end
|
|
97
|
+
@unix_server = server_cast(@unix_listener)
|
|
98
|
+
assert_equal @unix_listener.fileno, @unix_server.fileno
|
|
99
|
+
assert UNIXServer === @unix_server
|
|
100
|
+
assert File.socket?(@unix_server.path)
|
|
101
|
+
assert_equal @unix_listener_path, sock_name(@unix_server)
|
|
102
|
+
|
|
103
|
+
@tcp_server = server_cast(@tcp_listener)
|
|
104
|
+
assert_equal @tcp_listener.fileno, @tcp_server.fileno
|
|
105
|
+
assert TCPServer === @tcp_server
|
|
106
|
+
assert_equal @tcp_listener_name, sock_name(@tcp_server)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def test_sock_name
|
|
110
|
+
test_server_cast
|
|
111
|
+
sock_name(@unix_server)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_tcp_unicorn_peeraddr
|
|
115
|
+
test_bind_listen_tcp
|
|
116
|
+
@tcp_server = server_cast(@tcp_listener)
|
|
117
|
+
tmp = Tempfile.new 'shared'
|
|
118
|
+
pid = fork do
|
|
119
|
+
client = @tcp_server.accept
|
|
120
|
+
IO.select([client])
|
|
121
|
+
assert_equal GET_SLASH, client.sysread(GET_SLASH.size)
|
|
122
|
+
tmp.syswrite "#{client.unicorn_peeraddr}"
|
|
123
|
+
exit 0
|
|
124
|
+
end
|
|
125
|
+
host, port = sock_name(@tcp_server).split(/:/)
|
|
126
|
+
client = TCPSocket.new(host, port.to_i)
|
|
127
|
+
client.syswrite(GET_SLASH)
|
|
128
|
+
|
|
129
|
+
pid, status = Process.waitpid2(pid)
|
|
130
|
+
assert_nothing_raised { client.close }
|
|
131
|
+
assert status.success?
|
|
132
|
+
tmp.sysseek 0
|
|
133
|
+
assert_equal @test_addr, tmp.sysread(4096)
|
|
134
|
+
tmp.sysseek 0
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def test_unix_unicorn_peeraddr
|
|
138
|
+
test_bind_listen_unix
|
|
139
|
+
@unix_server = server_cast(@unix_listener)
|
|
140
|
+
tmp = Tempfile.new 'shared'
|
|
141
|
+
pid = fork do
|
|
142
|
+
client = @unix_server.accept
|
|
143
|
+
IO.select([client])
|
|
144
|
+
assert_equal GET_SLASH, client.sysread(4096)
|
|
145
|
+
tmp.syswrite "#{client.unicorn_peeraddr}"
|
|
146
|
+
exit 0
|
|
147
|
+
end
|
|
148
|
+
client = UNIXSocket.new(@unix_listener_path)
|
|
149
|
+
client.syswrite(GET_SLASH)
|
|
150
|
+
|
|
151
|
+
pid, status = Process.waitpid2(pid)
|
|
152
|
+
assert_nothing_raised { client.close }
|
|
153
|
+
assert status.success?
|
|
154
|
+
tmp.sysseek 0
|
|
155
|
+
assert_equal '127.0.0.1', tmp.sysread(4096)
|
|
156
|
+
tmp.sysseek 0
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
end
|