unicorn-fotopedia 0.99.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/.CHANGELOG.old +25 -0
- data/.document +19 -0
- data/.gitignore +21 -0
- data/.mailmap +26 -0
- data/CONTRIBUTORS +32 -0
- data/COPYING +339 -0
- data/DESIGN +105 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +171 -0
- data/Documentation/unicorn_rails.1.txt +172 -0
- data/FAQ +52 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +292 -0
- data/HACKING +116 -0
- data/ISSUES +36 -0
- data/KNOWN_ISSUES +50 -0
- data/LICENSE +55 -0
- data/PHILOSOPHY +145 -0
- data/README +149 -0
- data/Rakefile +191 -0
- data/SIGNALS +109 -0
- data/Sandbox +78 -0
- data/TODO +5 -0
- data/TUNING +70 -0
- data/bin/unicorn +126 -0
- data/bin/unicorn_rails +203 -0
- data/examples/big_app_gc.rb +33 -0
- data/examples/echo.ru +27 -0
- data/examples/git.ru +13 -0
- data/examples/init.sh +58 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/nginx.conf +139 -0
- data/examples/unicorn.conf.rb +78 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +124 -0
- data/ext/unicorn_http/common_field_optimization.h +111 -0
- data/ext/unicorn_http/ext_help.h +77 -0
- data/ext/unicorn_http/extconf.rb +14 -0
- data/ext/unicorn_http/global_variables.h +89 -0
- data/ext/unicorn_http/unicorn_http.rl +714 -0
- data/ext/unicorn_http/unicorn_http_common.rl +75 -0
- data/lib/unicorn.rb +847 -0
- data/lib/unicorn/app/exec_cgi.rb +150 -0
- data/lib/unicorn/app/inetd.rb +109 -0
- data/lib/unicorn/app/old_rails.rb +33 -0
- data/lib/unicorn/app/old_rails/static.rb +58 -0
- data/lib/unicorn/cgi_wrapper.rb +145 -0
- data/lib/unicorn/configurator.rb +421 -0
- data/lib/unicorn/const.rb +34 -0
- data/lib/unicorn/http_request.rb +72 -0
- data/lib/unicorn/http_response.rb +75 -0
- data/lib/unicorn/launcher.rb +65 -0
- data/lib/unicorn/oob_gc.rb +58 -0
- data/lib/unicorn/socket_helper.rb +152 -0
- data/lib/unicorn/tee_input.rb +217 -0
- data/lib/unicorn/util.rb +90 -0
- data/local.mk.sample +62 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +2 -0
- data/t/GNUmakefile +67 -0
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +23 -0
- data/t/bin/unused_listen +40 -0
- data/t/bin/utee +12 -0
- data/t/env.ru +3 -0
- data/t/my-tap-lib.sh +200 -0
- data/t/t0000-http-basic.sh +50 -0
- data/t/t0001-reload-bad-config.sh +52 -0
- data/t/t0002-config-conflict.sh +49 -0
- data/t/test-lib.sh +100 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +50 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1038 -0
- data/test/rails/app-1.2.3/.gitignore +2 -0
- data/test/rails/app-1.2.3/Rakefile +7 -0
- data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
- data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-1.2.3/config/boot.rb +11 -0
- data/test/rails/app-1.2.3/config/database.yml +12 -0
- data/test/rails/app-1.2.3/config/environment.rb +13 -0
- data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
- data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
- data/test/rails/app-1.2.3/config/routes.rb +6 -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/Rakefile +7 -0
- data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
- data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.0.2/config/boot.rb +11 -0
- data/test/rails/app-2.0.2/config/database.yml +12 -0
- data/test/rails/app-2.0.2/config/environment.rb +17 -0
- data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
- data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
- data/test/rails/app-2.0.2/config/routes.rb +6 -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.1.2/.gitignore +2 -0
- data/test/rails/app-2.1.2/Rakefile +7 -0
- data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
- data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.1.2/config/boot.rb +111 -0
- data/test/rails/app-2.1.2/config/database.yml +12 -0
- data/test/rails/app-2.1.2/config/environment.rb +17 -0
- data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
- data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
- data/test/rails/app-2.1.2/config/routes.rb +6 -0
- data/test/rails/app-2.1.2/db/.gitignore +0 -0
- data/test/rails/app-2.1.2/public/404.html +1 -0
- data/test/rails/app-2.1.2/public/500.html +1 -0
- data/test/rails/app-2.2.2/.gitignore +2 -0
- data/test/rails/app-2.2.2/Rakefile +7 -0
- data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
- data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.2.2/config/boot.rb +111 -0
- data/test/rails/app-2.2.2/config/database.yml +12 -0
- data/test/rails/app-2.2.2/config/environment.rb +17 -0
- data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
- data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
- data/test/rails/app-2.2.2/config/routes.rb +6 -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.5/.gitignore +2 -0
- data/test/rails/app-2.3.5/Rakefile +7 -0
- data/test/rails/app-2.3.5/app/controllers/application_controller.rb +5 -0
- data/test/rails/app-2.3.5/app/controllers/foo_controller.rb +36 -0
- data/test/rails/app-2.3.5/app/helpers/application_helper.rb +4 -0
- data/test/rails/app-2.3.5/config/boot.rb +109 -0
- data/test/rails/app-2.3.5/config/database.yml +12 -0
- data/test/rails/app-2.3.5/config/environment.rb +17 -0
- data/test/rails/app-2.3.5/config/environments/development.rb +7 -0
- data/test/rails/app-2.3.5/config/environments/production.rb +6 -0
- data/test/rails/app-2.3.5/config/routes.rb +6 -0
- data/test/rails/app-2.3.5/db/.gitignore +0 -0
- data/test/rails/app-2.3.5/public/404.html +1 -0
- data/test/rails/app-2.3.5/public/500.html +1 -0
- data/test/rails/app-2.3.5/public/x.txt +1 -0
- data/test/rails/test_rails.rb +280 -0
- data/test/test_helper.rb +301 -0
- data/test/unit/test_configurator.rb +150 -0
- data/test/unit/test_http_parser.rb +555 -0
- data/test/unit/test_http_parser_ng.rb +443 -0
- data/test/unit/test_request.rb +184 -0
- data/test/unit/test_response.rb +110 -0
- data/test/unit/test_server.rb +291 -0
- data/test/unit/test_signals.rb +206 -0
- data/test/unit/test_socket_helper.rb +147 -0
- data/test/unit/test_tee_input.rb +257 -0
- data/test/unit/test_upload.rb +298 -0
- data/test/unit/test_util.rb +96 -0
- data/unicorn.gemspec +52 -0
- metadata +283 -0
@@ -0,0 +1,110 @@
|
|
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.
|
5
|
+
#
|
6
|
+
# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
|
7
|
+
# for more information.
|
8
|
+
|
9
|
+
require 'test/test_helper'
|
10
|
+
|
11
|
+
include Unicorn
|
12
|
+
|
13
|
+
class ResponseTest < Test::Unit::TestCase
|
14
|
+
|
15
|
+
def test_response_headers
|
16
|
+
out = StringIO.new
|
17
|
+
HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, ["cool"]])
|
18
|
+
assert out.closed?
|
19
|
+
|
20
|
+
assert out.length > 0, "output didn't have data"
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_response_string_status
|
24
|
+
out = StringIO.new
|
25
|
+
HttpResponse.write(out,['200', {}, []])
|
26
|
+
assert out.closed?
|
27
|
+
assert out.length > 0, "output didn't have data"
|
28
|
+
assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/).size
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_response_OFS_set
|
32
|
+
old_ofs = $,
|
33
|
+
$, = "\f\v"
|
34
|
+
out = StringIO.new
|
35
|
+
HttpResponse.write(out,[200, {"X-k" => "cd","X-y" => "z"}, ["cool"]])
|
36
|
+
assert out.closed?
|
37
|
+
resp = out.string
|
38
|
+
assert ! resp.include?("\f\v"), "output didn't use $, ($OFS)"
|
39
|
+
ensure
|
40
|
+
$, = old_ofs
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_response_200
|
44
|
+
io = StringIO.new
|
45
|
+
HttpResponse.write(io, [200, {}, []])
|
46
|
+
assert io.closed?
|
47
|
+
assert io.length > 0, "output didn't have data"
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_response_with_default_reason
|
51
|
+
code = 400
|
52
|
+
io = StringIO.new
|
53
|
+
HttpResponse.write(io, [code, {}, []])
|
54
|
+
assert io.closed?
|
55
|
+
lines = io.string.split(/\r\n/)
|
56
|
+
assert_match(/.* Bad Request$/, lines.first,
|
57
|
+
"wrong default reason phrase")
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_rack_multivalue_headers
|
61
|
+
out = StringIO.new
|
62
|
+
HttpResponse.write(out,[200, {"X-Whatever" => "stuff\nbleh"}, []])
|
63
|
+
assert out.closed?
|
64
|
+
assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Even though Rack explicitly forbids "Status" in the header hash,
|
68
|
+
# some broken clients still rely on it
|
69
|
+
def test_status_header_added
|
70
|
+
out = StringIO.new
|
71
|
+
HttpResponse.write(out,[200, {"X-Whatever" => "stuff"}, []])
|
72
|
+
assert out.closed?
|
73
|
+
assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
|
74
|
+
end
|
75
|
+
|
76
|
+
# we always favor the code returned by the application, since "Status"
|
77
|
+
# in the header hash is not allowed by Rack (but not every app is
|
78
|
+
# fully Rack-compliant).
|
79
|
+
def test_status_header_ignores_app_hash
|
80
|
+
out = StringIO.new
|
81
|
+
header_hash = {"X-Whatever" => "stuff", 'StaTus' => "666" }
|
82
|
+
HttpResponse.write(out,[200, header_hash, []])
|
83
|
+
assert out.closed?
|
84
|
+
assert_equal 1, out.string.split(/\r\n/).grep(/^Status: 200 OK/i).size
|
85
|
+
assert_equal 1, out.string.split(/\r\n/).grep(/^Status:/i).size
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_body_closed
|
89
|
+
expect_body = %w(1 2 3 4).join("\n")
|
90
|
+
body = StringIO.new(expect_body)
|
91
|
+
body.rewind
|
92
|
+
out = StringIO.new
|
93
|
+
HttpResponse.write(out,[200, {}, body])
|
94
|
+
assert out.closed?
|
95
|
+
assert body.closed?
|
96
|
+
assert_match(expect_body, out.string.split(/\r\n/).last)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_unknown_status_pass_through
|
100
|
+
out = StringIO.new
|
101
|
+
HttpResponse.write(out,["666 I AM THE BEAST", {}, [] ])
|
102
|
+
assert out.closed?
|
103
|
+
headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
|
104
|
+
assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0])
|
105
|
+
status = headers.grep(/\AStatus:/i).first
|
106
|
+
assert status
|
107
|
+
assert_equal "Status: 666 I AM THE BEAST", status
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -0,0 +1,291 @@
|
|
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.
|
5
|
+
#
|
6
|
+
# Additional work donated by contributors. See http://mongrel.rubyforge.org/attributions.html
|
7
|
+
# for more information.
|
8
|
+
|
9
|
+
require 'test/test_helper'
|
10
|
+
|
11
|
+
include Unicorn
|
12
|
+
|
13
|
+
class TestHandler
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
while env['rack.input'].read(4096)
|
17
|
+
end
|
18
|
+
[200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
|
19
|
+
rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
|
20
|
+
$stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
|
21
|
+
raise e
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
class WebServerTest < Test::Unit::TestCase
|
27
|
+
|
28
|
+
def setup
|
29
|
+
@valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
|
30
|
+
@port = unused_port
|
31
|
+
@tester = TestHandler.new
|
32
|
+
redirect_test_io do
|
33
|
+
@server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
|
34
|
+
@server.start
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def teardown
|
39
|
+
redirect_test_io do
|
40
|
+
wait_workers_ready("test_stderr.#$$.log", 1)
|
41
|
+
File.truncate("test_stderr.#$$.log", 0)
|
42
|
+
@server.stop(true)
|
43
|
+
end
|
44
|
+
reset_sig_handlers
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_preload_app_config
|
48
|
+
teardown
|
49
|
+
tmp = Tempfile.new('test_preload_app_config')
|
50
|
+
ObjectSpace.undefine_finalizer(tmp)
|
51
|
+
app = lambda { ||
|
52
|
+
tmp.sysseek(0)
|
53
|
+
tmp.truncate(0)
|
54
|
+
tmp.syswrite($$)
|
55
|
+
lambda { |env| [ 200, { 'Content-Type' => 'text/plain' }, [ "#$$\n" ] ] }
|
56
|
+
}
|
57
|
+
redirect_test_io do
|
58
|
+
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
|
59
|
+
@server.start
|
60
|
+
end
|
61
|
+
results = hit(["http://localhost:#@port/"])
|
62
|
+
worker_pid = results[0].to_i
|
63
|
+
assert worker_pid != 0
|
64
|
+
tmp.sysseek(0)
|
65
|
+
loader_pid = tmp.sysread(4096).to_i
|
66
|
+
assert loader_pid != 0
|
67
|
+
assert_equal worker_pid, loader_pid
|
68
|
+
teardown
|
69
|
+
|
70
|
+
redirect_test_io do
|
71
|
+
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"],
|
72
|
+
:preload_app => true)
|
73
|
+
@server.start
|
74
|
+
end
|
75
|
+
results = hit(["http://localhost:#@port/"])
|
76
|
+
worker_pid = results[0].to_i
|
77
|
+
assert worker_pid != 0
|
78
|
+
tmp.sysseek(0)
|
79
|
+
loader_pid = tmp.sysread(4096).to_i
|
80
|
+
assert_equal $$, loader_pid
|
81
|
+
assert worker_pid != loader_pid
|
82
|
+
ensure
|
83
|
+
tmp.close!
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_broken_app
|
87
|
+
teardown
|
88
|
+
app = lambda { |env| raise RuntimeError, "hello" }
|
89
|
+
# [200, {}, []] }
|
90
|
+
redirect_test_io do
|
91
|
+
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
|
92
|
+
@server.start
|
93
|
+
end
|
94
|
+
sock = nil
|
95
|
+
assert_nothing_raised do
|
96
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
97
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
98
|
+
end
|
99
|
+
|
100
|
+
assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
|
101
|
+
assert_nothing_raised { sock.close }
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_simple_server
|
105
|
+
results = hit(["http://localhost:#{@port}/test"])
|
106
|
+
assert_equal 'hello!\n', results[0], "Handler didn't really run"
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_client_shutdown_writes
|
110
|
+
sock = nil
|
111
|
+
buf = nil
|
112
|
+
bs = 15609315 * rand
|
113
|
+
assert_nothing_raised do
|
114
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
115
|
+
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
116
|
+
sock.syswrite("Host: example.com\r\n")
|
117
|
+
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
118
|
+
sock.syswrite("Trailer: X-Foo\r\n")
|
119
|
+
sock.syswrite("\r\n")
|
120
|
+
sock.syswrite("%x\r\n" % [ bs ])
|
121
|
+
sock.syswrite("F" * bs)
|
122
|
+
sock.syswrite("\r\n0\r\nX-")
|
123
|
+
"Foo: bar\r\n\r\n".each_byte do |x|
|
124
|
+
sock.syswrite x.chr
|
125
|
+
sleep 0.05
|
126
|
+
end
|
127
|
+
# we wrote the entire request before shutting down, server should
|
128
|
+
# continue to process our request and never hit EOFError on our sock
|
129
|
+
sock.shutdown(Socket::SHUT_WR)
|
130
|
+
buf = sock.read
|
131
|
+
end
|
132
|
+
assert_equal 'hello!\n', buf.split(/\r\n\r\n/).last
|
133
|
+
next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
|
134
|
+
assert_equal 'hello!\n', next_client
|
135
|
+
lines = File.readlines("test_stderr.#$$.log")
|
136
|
+
assert lines.grep(/^Unicorn::ClientShutdown: /).empty?
|
137
|
+
assert_nothing_raised { sock.close }
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_client_shutdown_write_truncates
|
141
|
+
sock = nil
|
142
|
+
buf = nil
|
143
|
+
bs = 15609315 * rand
|
144
|
+
assert_nothing_raised do
|
145
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
146
|
+
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
147
|
+
sock.syswrite("Host: example.com\r\n")
|
148
|
+
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
149
|
+
sock.syswrite("Trailer: X-Foo\r\n")
|
150
|
+
sock.syswrite("\r\n")
|
151
|
+
sock.syswrite("%x\r\n" % [ bs ])
|
152
|
+
sock.syswrite("F" * (bs / 2.0))
|
153
|
+
|
154
|
+
# shutdown prematurely, this will force the server to abort
|
155
|
+
# processing on us even during app dispatch
|
156
|
+
sock.shutdown(Socket::SHUT_WR)
|
157
|
+
IO.select([sock], nil, nil, 60) or raise "Timed out"
|
158
|
+
buf = sock.read
|
159
|
+
end
|
160
|
+
assert_equal "", buf
|
161
|
+
next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
|
162
|
+
assert_equal 'hello!\n', next_client
|
163
|
+
lines = File.readlines("test_stderr.#$$.log")
|
164
|
+
lines = lines.grep(/^Unicorn::ClientShutdown: bytes_read=\d+/)
|
165
|
+
assert_equal 1, lines.size
|
166
|
+
assert_match %r{\AUnicorn::ClientShutdown: bytes_read=\d+ true$}, lines[0]
|
167
|
+
assert_nothing_raised { sock.close }
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_client_malformed_body
|
171
|
+
sock = nil
|
172
|
+
buf = 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
|
+
|
290
|
+
end
|
291
|
+
|
@@ -0,0 +1,206 @@
|
|
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.
|
5
|
+
#
|
6
|
+
# Ensure we stay sane in the face of signals being sent to us
|
7
|
+
|
8
|
+
require 'test/test_helper'
|
9
|
+
|
10
|
+
include Unicorn
|
11
|
+
|
12
|
+
class Dd
|
13
|
+
def initialize(bs, count)
|
14
|
+
@count = count
|
15
|
+
@buf = ' ' * bs
|
16
|
+
end
|
17
|
+
|
18
|
+
def each(&block)
|
19
|
+
@count.times { yield @buf }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class SignalsTest < Test::Unit::TestCase
|
24
|
+
|
25
|
+
def setup
|
26
|
+
@bs = 1 * 1024 * 1024
|
27
|
+
@count = 100
|
28
|
+
@port = unused_port
|
29
|
+
@sock = Tempfile.new('unicorn.sock')
|
30
|
+
@tmp = Tempfile.new('unicorn.write')
|
31
|
+
@tmp.sync = true
|
32
|
+
File.unlink(@sock.path)
|
33
|
+
File.unlink(@tmp.path)
|
34
|
+
@server_opts = {
|
35
|
+
:listeners => [ "127.0.0.1:#@port", @sock.path ],
|
36
|
+
:after_fork => lambda { |server,worker|
|
37
|
+
trap(:HUP) { @tmp.syswrite('.') }
|
38
|
+
},
|
39
|
+
}
|
40
|
+
@server = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def teardown
|
44
|
+
reset_sig_handlers
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_worker_dies_on_dead_master
|
48
|
+
pid = fork {
|
49
|
+
app = lambda { |env| [ 200, {'X-Pid' => "#$$" }, [] ] }
|
50
|
+
opts = @server_opts.merge(:timeout => 3)
|
51
|
+
redirect_test_io { HttpServer.new(app, opts).start.join }
|
52
|
+
}
|
53
|
+
child = sock = buf = t0 = nil
|
54
|
+
assert_nothing_raised do
|
55
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
56
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
57
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
58
|
+
buf = sock.readpartial(4096)
|
59
|
+
sock.close
|
60
|
+
buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
|
61
|
+
child = $1.to_i
|
62
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
63
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
64
|
+
Process.kill(:KILL, pid)
|
65
|
+
Process.waitpid(pid)
|
66
|
+
File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
|
67
|
+
t0 = Time.now
|
68
|
+
end
|
69
|
+
assert child
|
70
|
+
assert t0
|
71
|
+
assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
|
72
|
+
assert((Time.now - t0) < 60)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_sleepy_kill
|
76
|
+
rd, wr = IO.pipe
|
77
|
+
pid = fork {
|
78
|
+
rd.close
|
79
|
+
app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
|
80
|
+
redirect_test_io { HttpServer.new(app, @server_opts).start.join }
|
81
|
+
}
|
82
|
+
sock = buf = nil
|
83
|
+
wr.close
|
84
|
+
assert_nothing_raised do
|
85
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
86
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
87
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
88
|
+
buf = rd.readpartial(1)
|
89
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
90
|
+
Process.kill(:INT, pid)
|
91
|
+
Process.waitpid(pid)
|
92
|
+
end
|
93
|
+
assert_equal '.', buf
|
94
|
+
buf = nil
|
95
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
96
|
+
Errno::EBADF) do
|
97
|
+
buf = sock.sysread(4096)
|
98
|
+
end
|
99
|
+
assert_nil buf
|
100
|
+
ensure
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_timeout_slow_response
|
104
|
+
pid = fork {
|
105
|
+
app = lambda { |env| sleep }
|
106
|
+
opts = @server_opts.merge(:timeout => 3)
|
107
|
+
redirect_test_io { HttpServer.new(app, opts).start.join }
|
108
|
+
}
|
109
|
+
t0 = Time.now
|
110
|
+
sock = nil
|
111
|
+
assert_nothing_raised do
|
112
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
113
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
114
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
115
|
+
end
|
116
|
+
|
117
|
+
buf = nil
|
118
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
119
|
+
Errno::EBADF) do
|
120
|
+
buf = sock.sysread(4096)
|
121
|
+
end
|
122
|
+
diff = Time.now - t0
|
123
|
+
assert_nil buf
|
124
|
+
assert diff > 1.0, "diff was #{diff.inspect}"
|
125
|
+
assert diff < 60.0
|
126
|
+
ensure
|
127
|
+
Process.kill(:QUIT, pid) rescue nil
|
128
|
+
end
|
129
|
+
|
130
|
+
def test_response_write
|
131
|
+
app = lambda { |env|
|
132
|
+
[ 200, { 'Content-Type' => 'text/plain', 'X-Pid' => Process.pid.to_s },
|
133
|
+
Dd.new(@bs, @count) ]
|
134
|
+
}
|
135
|
+
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
136
|
+
sock = nil
|
137
|
+
assert_nothing_raised do
|
138
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
139
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
140
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
141
|
+
end
|
142
|
+
buf = ''
|
143
|
+
header_len = pid = nil
|
144
|
+
assert_nothing_raised do
|
145
|
+
buf = sock.sysread(16384, buf)
|
146
|
+
pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
147
|
+
header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
|
148
|
+
end
|
149
|
+
assert pid > 0, "pid not positive: #{pid.inspect}"
|
150
|
+
read = buf.size
|
151
|
+
size_before = @tmp.stat.size
|
152
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
153
|
+
Errno::EBADF) do
|
154
|
+
loop do
|
155
|
+
3.times { Process.kill(:HUP, pid) }
|
156
|
+
sock.sysread(16384, buf)
|
157
|
+
read += buf.size
|
158
|
+
3.times { Process.kill(:HUP, pid) }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
redirect_test_io { @server.stop(true) }
|
163
|
+
# can't check for == since pending signals get merged
|
164
|
+
assert size_before < @tmp.stat.size
|
165
|
+
got = read - header_len
|
166
|
+
expect = @bs * @count
|
167
|
+
assert_equal(expect, got, "expect=#{expect} got=#{got}")
|
168
|
+
assert_nothing_raised { sock.close }
|
169
|
+
end
|
170
|
+
|
171
|
+
def test_request_read
|
172
|
+
app = lambda { |env|
|
173
|
+
while env['rack.input'].read(4096)
|
174
|
+
end
|
175
|
+
[ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ]
|
176
|
+
}
|
177
|
+
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
178
|
+
pid = nil
|
179
|
+
|
180
|
+
assert_nothing_raised do
|
181
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
182
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
183
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
184
|
+
pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
185
|
+
sock.close
|
186
|
+
end
|
187
|
+
|
188
|
+
assert pid > 0, "pid not positive: #{pid.inspect}"
|
189
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
190
|
+
sock.syswrite("PUT / HTTP/1.0\r\n")
|
191
|
+
sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
|
192
|
+
1000.times { Process.kill(:HUP, pid) }
|
193
|
+
size_before = @tmp.stat.size
|
194
|
+
killer = fork { loop { Process.kill(:HUP, pid); sleep(0.0001) } }
|
195
|
+
buf = ' ' * @bs
|
196
|
+
@count.times { sock.syswrite(buf) }
|
197
|
+
Process.kill(:KILL, killer)
|
198
|
+
Process.waitpid2(killer)
|
199
|
+
redirect_test_io { @server.stop(true) }
|
200
|
+
# can't check for == since pending signals get merged
|
201
|
+
assert size_before < @tmp.stat.size
|
202
|
+
assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
203
|
+
sock.close
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|