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.
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