boourns-unicorn 4.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 (155) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +29 -0
  3. data/.gitignore +24 -0
  4. data/.mailmap +26 -0
  5. data/.wrongdoc.yml +10 -0
  6. data/Application_Timeouts +77 -0
  7. data/CONTRIBUTORS +35 -0
  8. data/COPYING +674 -0
  9. data/DESIGN +97 -0
  10. data/Documentation/.gitignore +5 -0
  11. data/Documentation/GNUmakefile +30 -0
  12. data/Documentation/unicorn.1.txt +174 -0
  13. data/Documentation/unicorn_rails.1.txt +175 -0
  14. data/FAQ +53 -0
  15. data/GIT-VERSION-GEN +40 -0
  16. data/GNUmakefile +267 -0
  17. data/HACKING +134 -0
  18. data/ISSUES +36 -0
  19. data/KNOWN_ISSUES +79 -0
  20. data/LICENSE +64 -0
  21. data/Links +56 -0
  22. data/PHILOSOPHY +145 -0
  23. data/README +149 -0
  24. data/Rakefile +97 -0
  25. data/SIGNALS +114 -0
  26. data/Sandbox +96 -0
  27. data/TODO +5 -0
  28. data/TUNING +98 -0
  29. data/bin/unicorn +121 -0
  30. data/bin/unicorn_rails +209 -0
  31. data/examples/big_app_gc.rb +2 -0
  32. data/examples/echo.ru +27 -0
  33. data/examples/git.ru +13 -0
  34. data/examples/init.sh +74 -0
  35. data/examples/logger_mp_safe.rb +25 -0
  36. data/examples/logrotate.conf +29 -0
  37. data/examples/nginx.conf +156 -0
  38. data/examples/unicorn.conf.minimal.rb +13 -0
  39. data/examples/unicorn.conf.rb +94 -0
  40. data/ext/unicorn_http/CFLAGS +13 -0
  41. data/ext/unicorn_http/c_util.h +124 -0
  42. data/ext/unicorn_http/common_field_optimization.h +111 -0
  43. data/ext/unicorn_http/ext_help.h +86 -0
  44. data/ext/unicorn_http/extconf.rb +10 -0
  45. data/ext/unicorn_http/global_variables.h +97 -0
  46. data/ext/unicorn_http/httpdate.c +82 -0
  47. data/ext/unicorn_http/unicorn_http.rl +1036 -0
  48. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  49. data/lib/unicorn.rb +107 -0
  50. data/lib/unicorn/app/exec_cgi.rb +154 -0
  51. data/lib/unicorn/app/inetd.rb +109 -0
  52. data/lib/unicorn/app/old_rails.rb +35 -0
  53. data/lib/unicorn/app/old_rails/static.rb +59 -0
  54. data/lib/unicorn/cgi_wrapper.rb +147 -0
  55. data/lib/unicorn/configurator.rb +630 -0
  56. data/lib/unicorn/const.rb +40 -0
  57. data/lib/unicorn/http_request.rb +83 -0
  58. data/lib/unicorn/http_response.rb +45 -0
  59. data/lib/unicorn/http_server.rb +755 -0
  60. data/lib/unicorn/launcher.rb +62 -0
  61. data/lib/unicorn/oob_gc.rb +71 -0
  62. data/lib/unicorn/preread_input.rb +33 -0
  63. data/lib/unicorn/socket_helper.rb +208 -0
  64. data/lib/unicorn/ssl_client.rb +11 -0
  65. data/lib/unicorn/ssl_configurator.rb +104 -0
  66. data/lib/unicorn/ssl_server.rb +42 -0
  67. data/lib/unicorn/stream_input.rb +149 -0
  68. data/lib/unicorn/tee_input.rb +126 -0
  69. data/lib/unicorn/tmpio.rb +29 -0
  70. data/lib/unicorn/util.rb +69 -0
  71. data/lib/unicorn/worker.rb +88 -0
  72. data/local.mk.sample +59 -0
  73. data/script/isolate_for_tests +32 -0
  74. data/setup.rb +1586 -0
  75. data/t/.gitignore +5 -0
  76. data/t/GNUmakefile +82 -0
  77. data/t/README +42 -0
  78. data/t/bin/content-md5-put +36 -0
  79. data/t/bin/sha1sum.rb +17 -0
  80. data/t/bin/unused_listen +40 -0
  81. data/t/bin/utee +12 -0
  82. data/t/broken-app.ru +12 -0
  83. data/t/detach.ru +11 -0
  84. data/t/env.ru +3 -0
  85. data/t/heartbeat-timeout.ru +12 -0
  86. data/t/listener_names.ru +4 -0
  87. data/t/my-tap-lib.sh +201 -0
  88. data/t/oob_gc.ru +21 -0
  89. data/t/oob_gc_path.ru +21 -0
  90. data/t/pid.ru +3 -0
  91. data/t/preread_input.ru +17 -0
  92. data/t/rack-input-tests.ru +21 -0
  93. data/t/sslgen.sh +71 -0
  94. data/t/t0000-http-basic.sh +50 -0
  95. data/t/t0001-reload-bad-config.sh +53 -0
  96. data/t/t0002-config-conflict.sh +49 -0
  97. data/t/t0002-parser-error.sh +94 -0
  98. data/t/t0003-working_directory.sh +51 -0
  99. data/t/t0004-heartbeat-timeout.sh +69 -0
  100. data/t/t0004-working_directory_broken.sh +24 -0
  101. data/t/t0005-working_directory_app.rb.sh +37 -0
  102. data/t/t0006-reopen-logs.sh +83 -0
  103. data/t/t0006.ru +13 -0
  104. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  105. data/t/t0008-back_out_of_upgrade.sh +110 -0
  106. data/t/t0009-broken-app.sh +56 -0
  107. data/t/t0009-winch_ttin.sh +59 -0
  108. data/t/t0010-reap-logging.sh +55 -0
  109. data/t/t0011-active-unix-socket.sh +79 -0
  110. data/t/t0012-reload-empty-config.sh +85 -0
  111. data/t/t0013-rewindable-input-false.sh +24 -0
  112. data/t/t0013.ru +12 -0
  113. data/t/t0014-rewindable-input-true.sh +24 -0
  114. data/t/t0014.ru +12 -0
  115. data/t/t0015-configurator-internals.sh +25 -0
  116. data/t/t0016-trust-x-forwarded-false.sh +30 -0
  117. data/t/t0017-trust-x-forwarded-true.sh +30 -0
  118. data/t/t0018-write-on-close.sh +23 -0
  119. data/t/t0019-max_header_len.sh +49 -0
  120. data/t/t0020-at_exit-handler.sh +49 -0
  121. data/t/t0021-process_detach.sh +29 -0
  122. data/t/t0022-listener_names-preload_app.sh +32 -0
  123. data/t/t0100-rack-input-tests.sh +124 -0
  124. data/t/t0116-client_body_buffer_size.sh +80 -0
  125. data/t/t0116.ru +16 -0
  126. data/t/t0600-https-server-basic.sh +48 -0
  127. data/t/t9000-preread-input.sh +48 -0
  128. data/t/t9001-oob_gc.sh +47 -0
  129. data/t/t9002-oob_gc-path.sh +75 -0
  130. data/t/test-lib.sh +113 -0
  131. data/t/write-on-close.ru +11 -0
  132. data/test/aggregate.rb +15 -0
  133. data/test/benchmark/README +50 -0
  134. data/test/benchmark/dd.ru +18 -0
  135. data/test/benchmark/stack.ru +8 -0
  136. data/test/exec/README +5 -0
  137. data/test/exec/test_exec.rb +1041 -0
  138. data/test/test_helper.rb +300 -0
  139. data/test/unit/test_configurator.rb +158 -0
  140. data/test/unit/test_droplet.rb +28 -0
  141. data/test/unit/test_http_parser.rb +860 -0
  142. data/test/unit/test_http_parser_ng.rb +716 -0
  143. data/test/unit/test_http_parser_xftrust.rb +38 -0
  144. data/test/unit/test_request.rb +197 -0
  145. data/test/unit/test_response.rb +99 -0
  146. data/test/unit/test_server.rb +289 -0
  147. data/test/unit/test_signals.rb +207 -0
  148. data/test/unit/test_sni_hostnames.rb +47 -0
  149. data/test/unit/test_socket_helper.rb +192 -0
  150. data/test/unit/test_stream_input.rb +204 -0
  151. data/test/unit/test_tee_input.rb +296 -0
  152. data/test/unit/test_upload.rb +306 -0
  153. data/test/unit/test_util.rb +99 -0
  154. data/unicorn.gemspec +44 -0
  155. metadata +333 -0
@@ -0,0 +1,38 @@
1
+ # -*- encoding: binary -*-
2
+ require 'test/test_helper'
3
+
4
+ include Unicorn
5
+
6
+ class HttpParserXFTrustTest < Test::Unit::TestCase
7
+ def setup
8
+ assert HttpParser.trust_x_forwarded?
9
+ end
10
+
11
+ def test_xf_trust_false_xfp
12
+ HttpParser.trust_x_forwarded = false
13
+ parser = HttpParser.new
14
+ parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n" \
15
+ "X-Forwarded-Proto: https\r\n\r\n"
16
+ env = parser.parse
17
+ assert_kind_of Hash, env
18
+ assert_equal 'foo', env['SERVER_NAME']
19
+ assert_equal '80', env['SERVER_PORT']
20
+ assert_equal 'http', env['rack.url_scheme']
21
+ end
22
+
23
+ def test_xf_trust_false_xfs
24
+ HttpParser.trust_x_forwarded = false
25
+ parser = HttpParser.new
26
+ parser.buf << "GET / HTTP/1.1\r\nHost: foo:\r\n" \
27
+ "X-Forwarded-SSL: on\r\n\r\n"
28
+ env = parser.parse
29
+ assert_kind_of Hash, env
30
+ assert_equal 'foo', env['SERVER_NAME']
31
+ assert_equal '80', env['SERVER_PORT']
32
+ assert_equal 'http', env['rack.url_scheme']
33
+ end
34
+
35
+ def teardown
36
+ HttpParser.trust_x_forwarded = true
37
+ end
38
+ end
@@ -0,0 +1,197 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2009 Eric Wong
4
+ # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5
+ # the GPLv3
6
+
7
+ require 'test/test_helper'
8
+
9
+ include Unicorn
10
+
11
+ class RequestTest < Test::Unit::TestCase
12
+
13
+ class MockRequest < StringIO
14
+ alias_method :readpartial, :sysread
15
+ alias_method :kgio_read!, :sysread
16
+ alias_method :read_nonblock, :sysread
17
+ def kgio_addr
18
+ '127.0.0.1'
19
+ end
20
+ end
21
+
22
+ def setup
23
+ @request = HttpRequest.new
24
+ @app = lambda do |env|
25
+ [ 200, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
26
+ end
27
+ @lint = Rack::Lint.new(@app)
28
+ end
29
+
30
+ def test_options
31
+ client = MockRequest.new("OPTIONS * HTTP/1.1\r\n" \
32
+ "Host: foo\r\n\r\n")
33
+ res = env = nil
34
+ assert_nothing_raised { env = @request.read(client) }
35
+ assert_equal '', env['REQUEST_PATH']
36
+ assert_equal '', env['PATH_INFO']
37
+ assert_equal '*', env['REQUEST_URI']
38
+ assert_nothing_raised { res = @lint.call(env) }
39
+ end
40
+
41
+ def test_absolute_uri_with_query
42
+ client = MockRequest.new("GET http://e:3/x?y=z HTTP/1.1\r\n" \
43
+ "Host: foo\r\n\r\n")
44
+ res = env = nil
45
+ assert_nothing_raised { env = @request.read(client) }
46
+ assert_equal '/x', env['REQUEST_PATH']
47
+ assert_equal '/x', env['PATH_INFO']
48
+ assert_equal 'y=z', env['QUERY_STRING']
49
+ assert_nothing_raised { res = @lint.call(env) }
50
+ end
51
+
52
+ def test_absolute_uri_with_fragment
53
+ client = MockRequest.new("GET http://e:3/x#frag HTTP/1.1\r\n" \
54
+ "Host: foo\r\n\r\n")
55
+ res = env = nil
56
+ assert_nothing_raised { env = @request.read(client) }
57
+ assert_equal '/x', env['REQUEST_PATH']
58
+ assert_equal '/x', env['PATH_INFO']
59
+ assert_equal '', env['QUERY_STRING']
60
+ assert_equal 'frag', env['FRAGMENT']
61
+ assert_nothing_raised { res = @lint.call(env) }
62
+ end
63
+
64
+ def test_absolute_uri_with_query_and_fragment
65
+ client = MockRequest.new("GET http://e:3/x?a=b#frag HTTP/1.1\r\n" \
66
+ "Host: foo\r\n\r\n")
67
+ res = env = nil
68
+ assert_nothing_raised { env = @request.read(client) }
69
+ assert_equal '/x', env['REQUEST_PATH']
70
+ assert_equal '/x', env['PATH_INFO']
71
+ assert_equal 'a=b', env['QUERY_STRING']
72
+ assert_equal 'frag', env['FRAGMENT']
73
+ assert_nothing_raised { res = @lint.call(env) }
74
+ end
75
+
76
+ def test_absolute_uri_unsupported_schemes
77
+ %w(ssh+http://e/ ftp://e/x http+ssh://e/x).each do |abs_uri|
78
+ client = MockRequest.new("GET #{abs_uri} HTTP/1.1\r\n" \
79
+ "Host: foo\r\n\r\n")
80
+ assert_raises(HttpParserError) { @request.read(client) }
81
+ end
82
+ end
83
+
84
+ def test_x_forwarded_proto_https
85
+ res = env = nil
86
+ client = MockRequest.new("GET / HTTP/1.1\r\n" \
87
+ "X-Forwarded-Proto: https\r\n" \
88
+ "Host: foo\r\n\r\n")
89
+ assert_nothing_raised { env = @request.read(client) }
90
+ assert_equal "https", env['rack.url_scheme']
91
+ assert_nothing_raised { res = @lint.call(env) }
92
+ end
93
+
94
+ def test_x_forwarded_proto_http
95
+ res = env = nil
96
+ client = MockRequest.new("GET / HTTP/1.1\r\n" \
97
+ "X-Forwarded-Proto: http\r\n" \
98
+ "Host: foo\r\n\r\n")
99
+ assert_nothing_raised { env = @request.read(client) }
100
+ assert_equal "http", env['rack.url_scheme']
101
+ assert_nothing_raised { res = @lint.call(env) }
102
+ end
103
+
104
+ def test_x_forwarded_proto_invalid
105
+ res = env = nil
106
+ client = MockRequest.new("GET / HTTP/1.1\r\n" \
107
+ "X-Forwarded-Proto: ftp\r\n" \
108
+ "Host: foo\r\n\r\n")
109
+ assert_nothing_raised { env = @request.read(client) }
110
+ assert_equal "http", env['rack.url_scheme']
111
+ assert_nothing_raised { res = @lint.call(env) }
112
+ end
113
+
114
+ def test_rack_lint_get
115
+ client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
116
+ res = env = nil
117
+ assert_nothing_raised { env = @request.read(client) }
118
+ assert_equal "http", env['rack.url_scheme']
119
+ assert_equal '127.0.0.1', env['REMOTE_ADDR']
120
+ assert_nothing_raised { res = @lint.call(env) }
121
+ end
122
+
123
+ def test_no_content_stringio
124
+ client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
125
+ env = nil
126
+ assert_nothing_raised { env = @request.read(client) }
127
+ assert_equal StringIO, env['rack.input'].class
128
+ end
129
+
130
+ def test_zero_content_stringio
131
+ client = MockRequest.new("PUT / HTTP/1.1\r\n" \
132
+ "Content-Length: 0\r\n" \
133
+ "Host: foo\r\n\r\n")
134
+ env = nil
135
+ assert_nothing_raised { env = @request.read(client) }
136
+ assert_equal StringIO, env['rack.input'].class
137
+ end
138
+
139
+ def test_real_content_not_stringio
140
+ client = MockRequest.new("PUT / HTTP/1.1\r\n" \
141
+ "Content-Length: 1\r\n" \
142
+ "Host: foo\r\n\r\n")
143
+ env = nil
144
+ assert_nothing_raised { env = @request.read(client) }
145
+ assert_equal Unicorn::TeeInput, env['rack.input'].class
146
+ end
147
+
148
+ def test_rack_lint_put
149
+ client = MockRequest.new(
150
+ "PUT / HTTP/1.1\r\n" \
151
+ "Host: foo\r\n" \
152
+ "Content-Length: 5\r\n" \
153
+ "\r\n" \
154
+ "abcde")
155
+ res = env = nil
156
+ assert_nothing_raised { env = @request.read(client) }
157
+ assert ! env.include?(:http_body)
158
+ assert_nothing_raised { res = @lint.call(env) }
159
+ end
160
+
161
+ def test_rack_lint_big_put
162
+ count = 100
163
+ bs = 0x10000
164
+ buf = (' ' * bs).freeze
165
+ length = bs * count
166
+ client = Tempfile.new('big_put')
167
+ def client.kgio_addr; '127.0.0.1'; end
168
+ def client.kgio_read(*args)
169
+ readpartial(*args)
170
+ rescue EOFError
171
+ end
172
+ def client.kgio_read!(*args)
173
+ readpartial(*args)
174
+ end
175
+ client.syswrite(
176
+ "PUT / HTTP/1.1\r\n" \
177
+ "Host: foo\r\n" \
178
+ "Content-Length: #{length}\r\n" \
179
+ "\r\n")
180
+ count.times { assert_equal bs, client.syswrite(buf) }
181
+ assert_equal 0, client.sysseek(0)
182
+ res = env = nil
183
+ assert_nothing_raised { env = @request.read(client) }
184
+ assert ! env.include?(:http_body)
185
+ assert_equal length, env['rack.input'].size
186
+ count.times {
187
+ tmp = env['rack.input'].read(bs)
188
+ tmp << env['rack.input'].read(bs - tmp.size) if tmp.size != bs
189
+ assert_equal buf, tmp
190
+ }
191
+ assert_nil env['rack.input'].read(bs)
192
+ assert_nothing_raised { env['rack.input'].rewind }
193
+ assert_nothing_raised { res = @lint.call(env) }
194
+ end
195
+
196
+ end
197
+
@@ -0,0 +1,99 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2005 Zed A. Shaw
4
+ # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5
+ # the GPLv3
6
+ #
7
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
8
+ # for more information.
9
+
10
+ require 'test/test_helper'
11
+ require 'time'
12
+
13
+ include Unicorn
14
+
15
+ class ResponseTest < Test::Unit::TestCase
16
+ include Unicorn::HttpResponse
17
+
18
+ def test_httpdate
19
+ before = Time.now.to_i - 1
20
+ str = httpdate
21
+ assert_kind_of(String, str)
22
+ middle = Time.parse(str).to_i
23
+ after = Time.now.to_i
24
+ assert before <= middle
25
+ assert middle <= after
26
+ end
27
+
28
+ def test_response_headers
29
+ out = StringIO.new
30
+ http_response_write(out, 200, {"X-Whatever" => "stuff"}, ["cool"])
31
+ assert ! out.closed?
32
+
33
+ assert out.length > 0, "output didn't have data"
34
+ end
35
+
36
+ def test_response_string_status
37
+ out = StringIO.new
38
+ http_response_write(out,'200', {}, [])
39
+ assert ! out.closed?
40
+ assert out.length > 0, "output didn't have data"
41
+ assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/).size
42
+ end
43
+
44
+ def test_response_200
45
+ io = StringIO.new
46
+ http_response_write(io, 200, {}, [])
47
+ assert ! io.closed?
48
+ assert io.length > 0, "output didn't have data"
49
+ end
50
+
51
+ def test_response_with_default_reason
52
+ code = 400
53
+ io = StringIO.new
54
+ http_response_write(io, code, {}, [])
55
+ assert ! io.closed?
56
+ lines = io.string.split(/\r\n/)
57
+ assert_match(/.* Bad Request$/, lines.first,
58
+ "wrong default reason phrase")
59
+ end
60
+
61
+ def test_rack_multivalue_headers
62
+ out = StringIO.new
63
+ http_response_write(out,200, {"X-Whatever" => "stuff\nbleh"}, [])
64
+ assert ! out.closed?
65
+ assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
66
+ end
67
+
68
+ # Even though Rack explicitly forbids "Status" in the header hash,
69
+ # some broken clients still rely on it
70
+ def test_status_header_added
71
+ out = StringIO.new
72
+ http_response_write(out,200, {"X-Whatever" => "stuff"}, [])
73
+ 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
+ end
87
+
88
+ def test_unknown_status_pass_through
89
+ out = StringIO.new
90
+ http_response_write(out,"666 I AM THE BEAST", {}, [] )
91
+ assert ! out.closed?
92
+ headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
93
+ 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
+ end
98
+
99
+ end
@@ -0,0 +1,289 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # Copyright (c) 2005 Zed A. Shaw
4
+ # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or
5
+ # the GPLv3
6
+ #
7
+ # Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
8
+ # for more information.
9
+
10
+ require 'test/test_helper'
11
+
12
+ include Unicorn
13
+
14
+ class TestHandler
15
+
16
+ def call(env)
17
+ while env['rack.input'].read(4096)
18
+ end
19
+ [200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
20
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
21
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
22
+ raise e
23
+ end
24
+ end
25
+
26
+
27
+ class WebServerTest < Test::Unit::TestCase
28
+
29
+ def setup
30
+ @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
31
+ @port = unused_port
32
+ @tester = TestHandler.new
33
+ redirect_test_io do
34
+ @server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
35
+ @server.start
36
+ end
37
+ end
38
+
39
+ def teardown
40
+ redirect_test_io do
41
+ wait_workers_ready("test_stderr.#$$.log", 1)
42
+ File.truncate("test_stderr.#$$.log", 0)
43
+ @server.stop(false)
44
+ end
45
+ reset_sig_handlers
46
+ end
47
+
48
+ def test_preload_app_config
49
+ teardown
50
+ tmp = Tempfile.new('test_preload_app_config')
51
+ ObjectSpace.undefine_finalizer(tmp)
52
+ app = lambda { ||
53
+ tmp.sysseek(0)
54
+ tmp.truncate(0)
55
+ tmp.syswrite($$)
56
+ lambda { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "#$$\n" ] ] }
57
+ }
58
+ redirect_test_io do
59
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
60
+ @server.start
61
+ end
62
+ results = hit(["http://localhost:#@port/"])
63
+ worker_pid = results[0].to_i
64
+ assert worker_pid != 0
65
+ tmp.sysseek(0)
66
+ loader_pid = tmp.sysread(4096).to_i
67
+ assert loader_pid != 0
68
+ assert_equal worker_pid, loader_pid
69
+ teardown
70
+
71
+ redirect_test_io do
72
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"],
73
+ :preload_app => true)
74
+ @server.start
75
+ end
76
+ results = hit(["http://localhost:#@port/"])
77
+ worker_pid = results[0].to_i
78
+ assert worker_pid != 0
79
+ tmp.sysseek(0)
80
+ loader_pid = tmp.sysread(4096).to_i
81
+ assert_equal $$, loader_pid
82
+ assert worker_pid != loader_pid
83
+ ensure
84
+ tmp.close!
85
+ end
86
+
87
+ def test_broken_app
88
+ teardown
89
+ app = lambda { |env| raise RuntimeError, "hello" }
90
+ # [200, {}, []] }
91
+ redirect_test_io do
92
+ @server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
93
+ @server.start
94
+ end
95
+ sock = nil
96
+ assert_nothing_raised do
97
+ sock = TCPSocket.new('127.0.0.1', @port)
98
+ sock.syswrite("GET / HTTP/1.0\r\n\r\n")
99
+ end
100
+
101
+ assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
102
+ assert_nothing_raised { sock.close }
103
+ end
104
+
105
+ def test_simple_server
106
+ results = hit(["http://localhost:#{@port}/test"])
107
+ assert_equal 'hello!\n', results[0], "Handler didn't really run"
108
+ end
109
+
110
+ def test_client_shutdown_writes
111
+ sock = nil
112
+ buf = nil
113
+ bs = 15609315 * rand
114
+ assert_nothing_raised do
115
+ sock = TCPSocket.new('127.0.0.1', @port)
116
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
117
+ sock.syswrite("Host: example.com\r\n")
118
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
119
+ sock.syswrite("Trailer: X-Foo\r\n")
120
+ sock.syswrite("\r\n")
121
+ sock.syswrite("%x\r\n" % [ bs ])
122
+ sock.syswrite("F" * bs)
123
+ sock.syswrite("\r\n0\r\nX-")
124
+ "Foo: bar\r\n\r\n".each_byte do |x|
125
+ sock.syswrite x.chr
126
+ sleep 0.05
127
+ end
128
+ # we wrote the entire request before shutting down, server should
129
+ # continue to process our request and never hit EOFError on our sock
130
+ sock.shutdown(Socket::SHUT_WR)
131
+ buf = sock.read
132
+ end
133
+ assert_equal 'hello!\n', buf.split(/\r\n\r\n/).last
134
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
135
+ assert_equal 'hello!\n', next_client
136
+ lines = File.readlines("test_stderr.#$$.log")
137
+ assert lines.grep(/^Unicorn::ClientShutdown: /).empty?
138
+ assert_nothing_raised { sock.close }
139
+ end
140
+
141
+ def test_client_shutdown_write_truncates
142
+ sock = nil
143
+ buf = nil
144
+ bs = 15609315 * rand
145
+ assert_nothing_raised do
146
+ sock = TCPSocket.new('127.0.0.1', @port)
147
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
148
+ sock.syswrite("Host: example.com\r\n")
149
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
150
+ sock.syswrite("Trailer: X-Foo\r\n")
151
+ sock.syswrite("\r\n")
152
+ sock.syswrite("%x\r\n" % [ bs ])
153
+ sock.syswrite("F" * (bs / 2.0))
154
+
155
+ # shutdown prematurely, this will force the server to abort
156
+ # processing on us even during app dispatch
157
+ sock.shutdown(Socket::SHUT_WR)
158
+ IO.select([sock], nil, nil, 60) or raise "Timed out"
159
+ buf = sock.read
160
+ end
161
+ assert_equal "", buf
162
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
163
+ assert_equal 'hello!\n', next_client
164
+ lines = File.readlines("test_stderr.#$$.log")
165
+ lines = lines.grep(/^Unicorn::ClientShutdown: bytes_read=\d+/)
166
+ assert_equal 1, lines.size
167
+ assert_match %r{\AUnicorn::ClientShutdown: bytes_read=\d+ true$}, lines[0]
168
+ assert_nothing_raised { sock.close }
169
+ end
170
+
171
+ def test_client_malformed_body
172
+ sock = nil
173
+ bs = 15653984
174
+ assert_nothing_raised do
175
+ sock = TCPSocket.new('127.0.0.1', @port)
176
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
177
+ sock.syswrite("Host: example.com\r\n")
178
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
179
+ sock.syswrite("Trailer: X-Foo\r\n")
180
+ sock.syswrite("\r\n")
181
+ sock.syswrite("%x\r\n" % [ bs ])
182
+ sock.syswrite("F" * bs)
183
+ end
184
+ begin
185
+ File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
186
+ rescue
187
+ end
188
+ assert_nothing_raised { sock.close }
189
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
190
+ assert_equal 'hello!\n', next_client
191
+ lines = File.readlines("test_stderr.#$$.log")
192
+ lines = lines.grep(/^Unicorn::HttpParserError: .* true$/)
193
+ assert_equal 1, lines.size
194
+ end
195
+
196
+ def do_test(string, chunk, close_after=nil, shutdown_delay=0)
197
+ # Do not use instance variables here, because it needs to be thread safe
198
+ socket = TCPSocket.new("127.0.0.1", @port);
199
+ request = StringIO.new(string)
200
+ chunks_out = 0
201
+
202
+ while data = request.read(chunk)
203
+ chunks_out += socket.write(data)
204
+ socket.flush
205
+ sleep 0.2
206
+ if close_after and chunks_out > close_after
207
+ socket.close
208
+ sleep 1
209
+ end
210
+ end
211
+ sleep(shutdown_delay)
212
+ socket.write(" ") # Some platforms only raise the exception on attempted write
213
+ socket.flush
214
+ end
215
+
216
+ def test_trickle_attack
217
+ do_test(@valid_request, 3)
218
+ end
219
+
220
+ def test_close_client
221
+ assert_raises IOError do
222
+ do_test(@valid_request, 10, 20)
223
+ end
224
+ end
225
+
226
+ def test_bad_client
227
+ redirect_test_io do
228
+ do_test("GET /test HTTP/BAD", 3)
229
+ end
230
+ end
231
+
232
+ def test_logger_set
233
+ assert_equal @server.logger, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
234
+ end
235
+
236
+ def test_logger_changed
237
+ tmp = Logger.new($stdout)
238
+ @server.logger = tmp
239
+ assert_equal tmp, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
240
+ end
241
+
242
+ def test_bad_client_400
243
+ sock = nil
244
+ assert_nothing_raised do
245
+ sock = TCPSocket.new('127.0.0.1', @port)
246
+ sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
247
+ end
248
+ assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
249
+ assert_nothing_raised { sock.close }
250
+ end
251
+
252
+ def test_http_0_9
253
+ sock = nil
254
+ assert_nothing_raised do
255
+ sock = TCPSocket.new('127.0.0.1', @port)
256
+ sock.syswrite("GET /hello\r\n")
257
+ end
258
+ assert_match 'hello!\n', sock.sysread(4096)
259
+ assert_nothing_raised { sock.close }
260
+ end
261
+
262
+ def test_header_is_too_long
263
+ redirect_test_io do
264
+ long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
265
+ assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do
266
+ do_test(long, long.length/2, 10)
267
+ end
268
+ end
269
+ end
270
+
271
+ def test_file_streamed_request
272
+ body = "a" * (Unicorn::Const::MAX_BODY * 2)
273
+ long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
274
+ do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
275
+ end
276
+
277
+ def test_file_streamed_request_bad_body
278
+ body = "a" * (Unicorn::Const::MAX_BODY * 2)
279
+ long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
280
+ assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
281
+ Errno::EBADF) {
282
+ do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
283
+ }
284
+ end
285
+
286
+ def test_listener_names
287
+ assert_equal [ "127.0.0.1:#@port" ], Unicorn.listener_names
288
+ end
289
+ end