unicorn 0.2.3 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/.document +1 -1
  2. data/.gitignore +1 -0
  3. data/CHANGELOG +1 -0
  4. data/DESIGN +4 -0
  5. data/GNUmakefile +30 -6
  6. data/Manifest +62 -3
  7. data/README +52 -42
  8. data/SIGNALS +17 -17
  9. data/TODO +27 -5
  10. data/bin/unicorn +15 -13
  11. data/bin/unicorn_rails +59 -22
  12. data/ext/unicorn/http11/http11.c +25 -104
  13. data/ext/unicorn/http11/http11_parser.c +24 -23
  14. data/ext/unicorn/http11/http11_parser.h +1 -3
  15. data/ext/unicorn/http11/http11_parser.rl +2 -1
  16. data/lib/unicorn.rb +58 -44
  17. data/lib/unicorn/app/old_rails.rb +23 -0
  18. data/lib/unicorn/app/old_rails/static.rb +58 -0
  19. data/lib/unicorn/cgi_wrapper.rb +151 -0
  20. data/lib/unicorn/configurator.rb +71 -31
  21. data/lib/unicorn/const.rb +9 -34
  22. data/lib/unicorn/http_request.rb +63 -66
  23. data/lib/unicorn/http_response.rb +6 -1
  24. data/lib/unicorn/socket.rb +15 -2
  25. data/test/benchmark/README +55 -0
  26. data/test/benchmark/big_request.rb +35 -0
  27. data/test/benchmark/dd.ru +18 -0
  28. data/test/benchmark/request.rb +47 -0
  29. data/test/benchmark/response.rb +29 -0
  30. data/test/exec/test_exec.rb +41 -157
  31. data/test/rails/app-1.2.3/.gitignore +2 -0
  32. data/test/rails/app-1.2.3/app/controllers/application.rb +4 -0
  33. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +34 -0
  34. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +2 -0
  35. data/test/rails/app-1.2.3/config/boot.rb +9 -0
  36. data/test/rails/app-1.2.3/config/database.yml +12 -0
  37. data/test/rails/app-1.2.3/config/environment.rb +10 -0
  38. data/test/rails/app-1.2.3/config/environments/development.rb +7 -0
  39. data/test/rails/app-1.2.3/config/environments/production.rb +3 -0
  40. data/test/rails/app-1.2.3/config/routes.rb +4 -0
  41. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  42. data/test/rails/app-1.2.3/public/404.html +1 -0
  43. data/test/rails/app-1.2.3/public/500.html +1 -0
  44. data/test/rails/app-2.0.2/.gitignore +2 -0
  45. data/test/rails/app-2.0.2/app/controllers/application.rb +2 -0
  46. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +34 -0
  47. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +2 -0
  48. data/test/rails/app-2.0.2/config/boot.rb +9 -0
  49. data/test/rails/app-2.0.2/config/database.yml +12 -0
  50. data/test/rails/app-2.0.2/config/environment.rb +14 -0
  51. data/test/rails/app-2.0.2/config/environments/development.rb +6 -0
  52. data/test/rails/app-2.0.2/config/environments/production.rb +3 -0
  53. data/test/rails/app-2.0.2/config/routes.rb +4 -0
  54. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  55. data/test/rails/app-2.0.2/public/404.html +1 -0
  56. data/test/rails/app-2.0.2/public/500.html +1 -0
  57. data/test/rails/app-2.2.2/.gitignore +2 -0
  58. data/test/rails/app-2.2.2/app/controllers/application.rb +2 -0
  59. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +34 -0
  60. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +2 -0
  61. data/test/rails/app-2.2.2/config/boot.rb +109 -0
  62. data/test/rails/app-2.2.2/config/database.yml +12 -0
  63. data/test/rails/app-2.2.2/config/environment.rb +14 -0
  64. data/test/rails/app-2.2.2/config/environments/development.rb +5 -0
  65. data/test/rails/app-2.2.2/config/environments/production.rb +3 -0
  66. data/test/rails/app-2.2.2/config/routes.rb +4 -0
  67. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  68. data/test/rails/app-2.2.2/public/404.html +1 -0
  69. data/test/rails/app-2.2.2/public/500.html +1 -0
  70. data/test/rails/app-2.3.2.1/.gitignore +2 -0
  71. data/test/rails/app-2.3.2.1/app/controllers/application_controller.rb +3 -0
  72. data/test/rails/app-2.3.2.1/app/controllers/foo_controller.rb +34 -0
  73. data/test/rails/app-2.3.2.1/app/helpers/application_helper.rb +2 -0
  74. data/test/rails/app-2.3.2.1/config/boot.rb +107 -0
  75. data/test/rails/app-2.3.2.1/config/database.yml +12 -0
  76. data/test/rails/app-2.3.2.1/config/environment.rb +14 -0
  77. data/test/rails/app-2.3.2.1/config/environments/development.rb +5 -0
  78. data/test/rails/app-2.3.2.1/config/environments/production.rb +4 -0
  79. data/test/rails/app-2.3.2.1/config/routes.rb +4 -0
  80. data/test/rails/app-2.3.2.1/db/.gitignore +0 -0
  81. data/test/rails/app-2.3.2.1/public/404.html +1 -0
  82. data/test/rails/app-2.3.2.1/public/500.html +1 -0
  83. data/test/rails/test_rails.rb +243 -0
  84. data/test/test_helper.rb +149 -2
  85. data/test/unit/test_configurator.rb +46 -0
  86. data/test/unit/test_http_parser.rb +77 -36
  87. data/test/unit/test_request.rb +2 -0
  88. data/test/unit/test_response.rb +20 -4
  89. data/test/unit/test_server.rb +30 -1
  90. data/test/unit/test_socket_helper.rb +159 -0
  91. data/unicorn.gemspec +5 -5
  92. metadata +68 -5
  93. data/test/benchmark/previous.rb +0 -11
  94. data/test/benchmark/simple.rb +0 -11
  95. 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
- nread = parser.execute(req, http, 0)
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
- assert parser.nread == 0, "Number read after reset should be 0"
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
- nread = parser.execute(req, should_be_good, 0)
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
- # nread = parser.execute(req, nasty_pound_header, 0)
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
- nread = parser.execute(req, sorta_safe, 0)
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
- error = false
82
- begin
83
- nread = parser.execute(req, bad_http, 0)
84
- rescue => details
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
- assert error, "failed to throw exception"
89
- assert !parser.finished?, "Parser shouldn't be finished"
90
- assert parser.error?, "Parser SHOULD have error"
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, 0)
138
+ ok = parser.execute(req, get)
99
139
  end
100
- assert parser.finished?
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, 0)
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, 0)
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, 0)
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, 0)
194
+ parser.execute({}, get)
154
195
  parser.reset
155
196
  end
156
197
  end
@@ -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)
@@ -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
- resp = out.read
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.rewind
42
- assert_match(/.* #{HTTP_STATUS_CODES[code]}$/, io.readline.chomp, "wrong default reason phrase")
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
- end
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
@@ -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