unicorn-maintained 6.2.0
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.
- checksums.yaml +7 -0
- data/.CHANGELOG.old +25 -0
- data/.document +28 -0
- data/.gitattributes +5 -0
- data/.gitignore +25 -0
- data/.mailmap +26 -0
- data/.manifest +149 -0
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +39 -0
- data/COPYING +674 -0
- data/DESIGN +99 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +317 -0
- data/HACKING +112 -0
- data/ISSUES +102 -0
- data/KNOWN_ISSUES +79 -0
- data/LATEST +1 -0
- data/LICENSE +67 -0
- data/Links +58 -0
- data/NEWS +1 -0
- data/PHILOSOPHY +139 -0
- data/README +156 -0
- data/Rakefile +16 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +3 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +128 -0
- data/bin/unicorn_rails +209 -0
- data/examples/big_app_gc.rb +2 -0
- data/examples/echo.ru +26 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +13 -0
- data/examples/unicorn.conf.rb +110 -0
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +116 -0
- data/ext/unicorn_http/common_field_optimization.h +128 -0
- data/ext/unicorn_http/epollexclusive.h +128 -0
- data/ext/unicorn_http/ext_help.h +38 -0
- data/ext/unicorn_http/extconf.rb +39 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +91 -0
- data/ext/unicorn_http/unicorn_http.c +4334 -0
- data/ext/unicorn_http/unicorn_http.rl +1040 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +59 -0
- data/lib/unicorn/app/old_rails.rb +35 -0
- data/lib/unicorn/cgi_wrapper.rb +147 -0
- data/lib/unicorn/configurator.rb +748 -0
- data/lib/unicorn/const.rb +21 -0
- data/lib/unicorn/http_request.rb +201 -0
- data/lib/unicorn/http_response.rb +93 -0
- data/lib/unicorn/http_server.rb +859 -0
- data/lib/unicorn/launcher.rb +62 -0
- data/lib/unicorn/oob_gc.rb +81 -0
- data/lib/unicorn/preread_input.rb +33 -0
- data/lib/unicorn/select_waiter.rb +6 -0
- data/lib/unicorn/socket_helper.rb +185 -0
- data/lib/unicorn/stream_input.rb +151 -0
- data/lib/unicorn/tee_input.rb +131 -0
- data/lib/unicorn/tmpio.rb +33 -0
- data/lib/unicorn/util.rb +90 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +165 -0
- data/lib/unicorn.rb +136 -0
- data/man/man1/unicorn.1 +222 -0
- data/man/man1/unicorn_rails.1 +207 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +5 -0
- data/t/README +49 -0
- data/t/active-unix-socket.t +117 -0
- data/t/bin/unused_listen +40 -0
- data/t/broken-app.ru +12 -0
- data/t/client_body_buffer_size.ru +14 -0
- data/t/client_body_buffer_size.t +80 -0
- data/t/detach.ru +11 -0
- data/t/env.ru +3 -0
- data/t/fails-rack-lint.ru +5 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/heartbeat-timeout.t +62 -0
- data/t/integration.ru +115 -0
- data/t/integration.t +356 -0
- data/t/lib.perl +258 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +17 -0
- data/t/oob_gc_path.ru +17 -0
- data/t/pid.ru +3 -0
- data/t/preread_input.ru +22 -0
- data/t/reload-bad-config.t +54 -0
- data/t/reopen-logs.ru +13 -0
- data/t/reopen-logs.t +39 -0
- data/t/t0008-back_out_of_upgrade.sh +110 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0012-reload-empty-config.sh +86 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +12 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +12 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +13 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +125 -0
- data/t/winch_ttin.t +67 -0
- data/t/working_directory.t +94 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +60 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/ddstream.ru +50 -0
- data/test/benchmark/readinput.ru +40 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1029 -0
- data/test/test_helper.rb +306 -0
- data/test/unit/test_ccc.rb +91 -0
- data/test/unit/test_configurator.rb +175 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +884 -0
- data/test/unit/test_http_parser_ng.rb +714 -0
- data/test/unit/test_request.rb +169 -0
- data/test/unit/test_server.rb +244 -0
- data/test/unit/test_signals.rb +188 -0
- data/test/unit/test_socket_helper.rb +159 -0
- data/test/unit/test_stream_input.rb +210 -0
- data/test/unit/test_tee_input.rb +303 -0
- data/test/unit/test_util.rb +131 -0
- data/test/unit/test_waiter.rb +34 -0
- data/unicorn.gemspec +48 -0
- metadata +275 -0
|
@@ -0,0 +1,169 @@
|
|
|
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 GPLv2+ (GPLv3+ preferred)
|
|
6
|
+
|
|
7
|
+
require './test/test_helper'
|
|
8
|
+
|
|
9
|
+
include Unicorn
|
|
10
|
+
|
|
11
|
+
class RequestTest < Test::Unit::TestCase
|
|
12
|
+
|
|
13
|
+
MockRequest = Class.new(StringIO)
|
|
14
|
+
|
|
15
|
+
AI = Addrinfo.new(Socket.sockaddr_un('/unicorn/sucks'))
|
|
16
|
+
|
|
17
|
+
def setup
|
|
18
|
+
@request = HttpRequest.new
|
|
19
|
+
@app = lambda do |env|
|
|
20
|
+
[ 200, { 'content-length' => '0', 'content-type' => 'text/plain' }, [] ]
|
|
21
|
+
end
|
|
22
|
+
@lint = Rack::Lint.new(@app)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_options
|
|
26
|
+
client = MockRequest.new("OPTIONS * HTTP/1.1\r\n" \
|
|
27
|
+
"Host: foo\r\n\r\n")
|
|
28
|
+
env = @request.read_headers(client, AI)
|
|
29
|
+
assert_equal '', env['REQUEST_PATH']
|
|
30
|
+
assert_equal '', env['PATH_INFO']
|
|
31
|
+
assert_equal '*', env['REQUEST_URI']
|
|
32
|
+
assert_kind_of Array, @lint.call(env)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def test_absolute_uri_with_query
|
|
36
|
+
client = MockRequest.new("GET http://e:3/x?y=z HTTP/1.1\r\n" \
|
|
37
|
+
"Host: foo\r\n\r\n")
|
|
38
|
+
env = @request.read_headers(client, AI)
|
|
39
|
+
assert_equal '/x', env['REQUEST_PATH']
|
|
40
|
+
assert_equal '/x', env['PATH_INFO']
|
|
41
|
+
assert_equal 'y=z', env['QUERY_STRING']
|
|
42
|
+
assert_kind_of Array, @lint.call(env)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_absolute_uri_with_fragment
|
|
46
|
+
client = MockRequest.new("GET http://e:3/x#frag HTTP/1.1\r\n" \
|
|
47
|
+
"Host: foo\r\n\r\n")
|
|
48
|
+
env = @request.read_headers(client, AI)
|
|
49
|
+
assert_equal '/x', env['REQUEST_PATH']
|
|
50
|
+
assert_equal '/x', env['PATH_INFO']
|
|
51
|
+
assert_equal '', env['QUERY_STRING']
|
|
52
|
+
assert_equal 'frag', env['FRAGMENT']
|
|
53
|
+
assert_kind_of Array, @lint.call(env)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_absolute_uri_with_query_and_fragment
|
|
57
|
+
client = MockRequest.new("GET http://e:3/x?a=b#frag HTTP/1.1\r\n" \
|
|
58
|
+
"Host: foo\r\n\r\n")
|
|
59
|
+
env = @request.read_headers(client, AI)
|
|
60
|
+
assert_equal '/x', env['REQUEST_PATH']
|
|
61
|
+
assert_equal '/x', env['PATH_INFO']
|
|
62
|
+
assert_equal 'a=b', env['QUERY_STRING']
|
|
63
|
+
assert_equal 'frag', env['FRAGMENT']
|
|
64
|
+
assert_kind_of Array, @lint.call(env)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def test_absolute_uri_unsupported_schemes
|
|
68
|
+
%w(ssh+http://e/ ftp://e/x http+ssh://e/x).each do |abs_uri|
|
|
69
|
+
client = MockRequest.new("GET #{abs_uri} HTTP/1.1\r\n" \
|
|
70
|
+
"Host: foo\r\n\r\n")
|
|
71
|
+
assert_raises(HttpParserError) { @request.read_headers(client, AI) }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_x_forwarded_proto_https
|
|
76
|
+
client = MockRequest.new("GET / HTTP/1.1\r\n" \
|
|
77
|
+
"X-Forwarded-Proto: https\r\n" \
|
|
78
|
+
"Host: foo\r\n\r\n")
|
|
79
|
+
env = @request.read_headers(client, AI)
|
|
80
|
+
assert_equal "https", env['rack.url_scheme']
|
|
81
|
+
assert_kind_of Array, @lint.call(env)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_x_forwarded_proto_http
|
|
85
|
+
client = MockRequest.new("GET / HTTP/1.1\r\n" \
|
|
86
|
+
"X-Forwarded-Proto: http\r\n" \
|
|
87
|
+
"Host: foo\r\n\r\n")
|
|
88
|
+
env = @request.read_headers(client, AI)
|
|
89
|
+
assert_equal "http", env['rack.url_scheme']
|
|
90
|
+
assert_kind_of Array, @lint.call(env)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def test_x_forwarded_proto_invalid
|
|
94
|
+
client = MockRequest.new("GET / HTTP/1.1\r\n" \
|
|
95
|
+
"X-Forwarded-Proto: ftp\r\n" \
|
|
96
|
+
"Host: foo\r\n\r\n")
|
|
97
|
+
env = @request.read_headers(client, AI)
|
|
98
|
+
assert_equal "http", env['rack.url_scheme']
|
|
99
|
+
assert_kind_of Array, @lint.call(env)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_rack_lint_get
|
|
103
|
+
client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
|
|
104
|
+
env = @request.read_headers(client, AI)
|
|
105
|
+
assert_equal "http", env['rack.url_scheme']
|
|
106
|
+
assert_equal '127.0.0.1', env['REMOTE_ADDR']
|
|
107
|
+
assert_kind_of Array, @lint.call(env)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_no_content_stringio
|
|
111
|
+
client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
|
|
112
|
+
env = @request.read_headers(client, AI)
|
|
113
|
+
assert_equal StringIO, env['rack.input'].class
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_zero_content_stringio
|
|
117
|
+
client = MockRequest.new("PUT / HTTP/1.1\r\n" \
|
|
118
|
+
"Content-Length: 0\r\n" \
|
|
119
|
+
"Host: foo\r\n\r\n")
|
|
120
|
+
env = @request.read_headers(client, AI)
|
|
121
|
+
assert_equal StringIO, env['rack.input'].class
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_real_content_not_stringio
|
|
125
|
+
client = MockRequest.new("PUT / HTTP/1.1\r\n" \
|
|
126
|
+
"Content-Length: 1\r\n" \
|
|
127
|
+
"Host: foo\r\n\r\n")
|
|
128
|
+
env = @request.read_headers(client, AI)
|
|
129
|
+
assert_equal Unicorn::TeeInput, env['rack.input'].class
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_rack_lint_put
|
|
133
|
+
client = MockRequest.new(
|
|
134
|
+
"PUT / HTTP/1.1\r\n" \
|
|
135
|
+
"Host: foo\r\n" \
|
|
136
|
+
"Content-Length: 5\r\n" \
|
|
137
|
+
"\r\n" \
|
|
138
|
+
"abcde")
|
|
139
|
+
env = @request.read_headers(client, AI)
|
|
140
|
+
assert ! env.include?(:http_body)
|
|
141
|
+
assert_kind_of Array, @lint.call(env)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def test_rack_lint_big_put
|
|
145
|
+
count = 100
|
|
146
|
+
bs = 0x10000
|
|
147
|
+
buf = (' ' * bs).freeze
|
|
148
|
+
length = bs * count
|
|
149
|
+
client = Tempfile.new('big_put')
|
|
150
|
+
client.syswrite(
|
|
151
|
+
"PUT / HTTP/1.1\r\n" \
|
|
152
|
+
"Host: foo\r\n" \
|
|
153
|
+
"Content-Length: #{length}\r\n" \
|
|
154
|
+
"\r\n")
|
|
155
|
+
count.times { assert_equal bs, client.syswrite(buf) }
|
|
156
|
+
assert_equal 0, client.sysseek(0)
|
|
157
|
+
env = @request.read_headers(client, AI)
|
|
158
|
+
assert ! env.include?(:http_body)
|
|
159
|
+
assert_equal length, env['rack.input'].size
|
|
160
|
+
count.times {
|
|
161
|
+
tmp = env['rack.input'].read(bs)
|
|
162
|
+
tmp << env['rack.input'].read(bs - tmp.size) if tmp.size != bs
|
|
163
|
+
assert_equal buf, tmp
|
|
164
|
+
}
|
|
165
|
+
assert_nil env['rack.input'].read(bs)
|
|
166
|
+
env['rack.input'].rewind
|
|
167
|
+
assert_kind_of Array, @lint.call(env)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
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 GPLv2+ (GPLv3+ preferred)
|
|
6
|
+
#
|
|
7
|
+
# Additional work donated by contributors. See git history
|
|
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
|
+
class TestRackAfterReply
|
|
27
|
+
def initialize
|
|
28
|
+
@called = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def call(env)
|
|
32
|
+
while env['rack.input'].read(4096)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
env["rack.after_reply"] << -> { @called = true }
|
|
36
|
+
|
|
37
|
+
[200, { 'content-type' => 'text/plain' }, ["after_reply_called: #{@called}"]]
|
|
38
|
+
rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
|
|
39
|
+
$stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
|
|
40
|
+
raise e
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class WebServerTest < Test::Unit::TestCase
|
|
45
|
+
|
|
46
|
+
def setup
|
|
47
|
+
@valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n"
|
|
48
|
+
@port = unused_port
|
|
49
|
+
@tester = TestHandler.new
|
|
50
|
+
redirect_test_io do
|
|
51
|
+
@server = HttpServer.new(@tester, :listeners => [ "127.0.0.1:#{@port}" ] )
|
|
52
|
+
@server.start
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def teardown
|
|
57
|
+
redirect_test_io do
|
|
58
|
+
wait_workers_ready("test_stderr.#$$.log", 1)
|
|
59
|
+
File.truncate("test_stderr.#$$.log", 0)
|
|
60
|
+
@server.stop(false)
|
|
61
|
+
end
|
|
62
|
+
reset_sig_handlers
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def test_preload_app_config
|
|
66
|
+
teardown
|
|
67
|
+
tmp = Tempfile.new('test_preload_app_config')
|
|
68
|
+
ObjectSpace.undefine_finalizer(tmp)
|
|
69
|
+
app = lambda { ||
|
|
70
|
+
tmp.sysseek(0)
|
|
71
|
+
tmp.truncate(0)
|
|
72
|
+
tmp.syswrite($$)
|
|
73
|
+
lambda { |env| [ 200, { 'content-type' => 'text/plain' }, [ "#$$\n" ] ] }
|
|
74
|
+
}
|
|
75
|
+
redirect_test_io do
|
|
76
|
+
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
|
|
77
|
+
@server.start
|
|
78
|
+
end
|
|
79
|
+
results = hit(["http://localhost:#@port/"])
|
|
80
|
+
worker_pid = results[0].to_i
|
|
81
|
+
assert worker_pid != 0
|
|
82
|
+
tmp.sysseek(0)
|
|
83
|
+
loader_pid = tmp.sysread(4096).to_i
|
|
84
|
+
assert loader_pid != 0
|
|
85
|
+
assert_equal worker_pid, loader_pid
|
|
86
|
+
teardown
|
|
87
|
+
|
|
88
|
+
redirect_test_io do
|
|
89
|
+
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"],
|
|
90
|
+
:preload_app => true)
|
|
91
|
+
@server.start
|
|
92
|
+
end
|
|
93
|
+
results = hit(["http://localhost:#@port/"])
|
|
94
|
+
worker_pid = results[0].to_i
|
|
95
|
+
assert worker_pid != 0
|
|
96
|
+
tmp.sysseek(0)
|
|
97
|
+
loader_pid = tmp.sysread(4096).to_i
|
|
98
|
+
assert_equal $$, loader_pid
|
|
99
|
+
assert worker_pid != loader_pid
|
|
100
|
+
ensure
|
|
101
|
+
tmp.close!
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_after_reply
|
|
105
|
+
teardown
|
|
106
|
+
|
|
107
|
+
redirect_test_io do
|
|
108
|
+
@server = HttpServer.new(TestRackAfterReply.new,
|
|
109
|
+
:listeners => [ "127.0.0.1:#@port"])
|
|
110
|
+
@server.start
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
114
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
115
|
+
|
|
116
|
+
responses = sock.read(4096)
|
|
117
|
+
assert_match %r{\AHTTP/1.[01] 200\b}, responses
|
|
118
|
+
assert_match %r{^after_reply_called: false}, responses
|
|
119
|
+
|
|
120
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
121
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
122
|
+
|
|
123
|
+
responses = sock.read(4096)
|
|
124
|
+
assert_match %r{\AHTTP/1.[01] 200\b}, responses
|
|
125
|
+
assert_match %r{^after_reply_called: true}, responses
|
|
126
|
+
|
|
127
|
+
sock.close
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def test_simple_server
|
|
131
|
+
results = hit(["http://localhost:#{@port}/test"])
|
|
132
|
+
assert_equal 'hello!\n', results[0], "Handler didn't really run"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_client_malformed_body
|
|
136
|
+
bs = 15653984
|
|
137
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
138
|
+
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
|
139
|
+
sock.syswrite("Host: example.com\r\n")
|
|
140
|
+
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
|
141
|
+
sock.syswrite("Trailer: X-Foo\r\n")
|
|
142
|
+
sock.syswrite("\r\n")
|
|
143
|
+
sock.syswrite("%x\r\n" % [ bs ])
|
|
144
|
+
sock.syswrite("F" * bs)
|
|
145
|
+
begin
|
|
146
|
+
File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
|
|
147
|
+
rescue
|
|
148
|
+
end
|
|
149
|
+
assert_nil sock.close
|
|
150
|
+
next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
|
|
151
|
+
assert_equal 'hello!\n', next_client
|
|
152
|
+
lines = File.readlines("test_stderr.#$$.log")
|
|
153
|
+
lines = lines.grep(/^Unicorn::HttpParserError: .* true$/)
|
|
154
|
+
assert_equal 1, lines.size
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def do_test(string, chunk, close_after=nil, shutdown_delay=0)
|
|
158
|
+
# Do not use instance variables here, because it needs to be thread safe
|
|
159
|
+
socket = tcp_socket("127.0.0.1", @port);
|
|
160
|
+
request = StringIO.new(string)
|
|
161
|
+
chunks_out = 0
|
|
162
|
+
|
|
163
|
+
while data = request.read(chunk)
|
|
164
|
+
chunks_out += socket.write(data)
|
|
165
|
+
socket.flush
|
|
166
|
+
sleep 0.2
|
|
167
|
+
if close_after and chunks_out > close_after
|
|
168
|
+
socket.close
|
|
169
|
+
sleep 1
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
sleep(shutdown_delay)
|
|
173
|
+
socket.write(" ") # Some platforms only raise the exception on attempted write
|
|
174
|
+
socket.flush
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def test_trickle_attack
|
|
178
|
+
do_test(@valid_request, 3)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def test_close_client
|
|
182
|
+
assert_raises IOError do
|
|
183
|
+
do_test(@valid_request, 10, 20)
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def test_bad_client
|
|
188
|
+
redirect_test_io do
|
|
189
|
+
do_test("GET /test HTTP/BAD", 3)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def test_logger_set
|
|
194
|
+
assert_equal @server.logger, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def test_logger_changed
|
|
198
|
+
tmp = Logger.new($stdout)
|
|
199
|
+
@server.logger = tmp
|
|
200
|
+
assert_equal tmp, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def test_bad_client_400
|
|
204
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
205
|
+
sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
|
|
206
|
+
assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
|
|
207
|
+
assert_nil sock.close
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def test_http_0_9
|
|
211
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
212
|
+
sock.syswrite("GET /hello\r\n")
|
|
213
|
+
assert_match 'hello!\n', sock.sysread(4096)
|
|
214
|
+
assert_nil sock.close
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def test_header_is_too_long
|
|
218
|
+
redirect_test_io do
|
|
219
|
+
long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
|
|
220
|
+
assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do
|
|
221
|
+
do_test(long, long.length/2, 10)
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def test_file_streamed_request
|
|
227
|
+
body = "a" * (Unicorn::Const::MAX_BODY * 2)
|
|
228
|
+
long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
|
|
229
|
+
do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def test_file_streamed_request_bad_body
|
|
233
|
+
body = "a" * (Unicorn::Const::MAX_BODY * 2)
|
|
234
|
+
long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
|
|
235
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
|
236
|
+
Errno::EBADF) {
|
|
237
|
+
do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
|
|
238
|
+
}
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def test_listener_names
|
|
242
|
+
assert_equal [ "127.0.0.1:#@port" ], Unicorn.listener_names
|
|
243
|
+
end
|
|
244
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
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 GPLv2+ (GPLv3+ preferred)
|
|
6
|
+
#
|
|
7
|
+
# Ensure we stay sane in the face of signals being sent to us
|
|
8
|
+
|
|
9
|
+
require './test/test_helper'
|
|
10
|
+
|
|
11
|
+
include Unicorn
|
|
12
|
+
|
|
13
|
+
class Dd
|
|
14
|
+
def initialize(bs, count)
|
|
15
|
+
@count = count
|
|
16
|
+
@buf = ' ' * bs
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def each(&block)
|
|
20
|
+
@count.times { yield @buf }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class SignalsTest < Test::Unit::TestCase
|
|
25
|
+
|
|
26
|
+
def setup
|
|
27
|
+
@bs = 1 * 1024 * 1024
|
|
28
|
+
@count = 100
|
|
29
|
+
@port = unused_port
|
|
30
|
+
@sock = Tempfile.new('unicorn.sock')
|
|
31
|
+
@tmp = Tempfile.new('unicorn.write')
|
|
32
|
+
@tmp.sync = true
|
|
33
|
+
File.unlink(@sock.path)
|
|
34
|
+
File.unlink(@tmp.path)
|
|
35
|
+
@server_opts = {
|
|
36
|
+
:listeners => [ "127.0.0.1:#@port", @sock.path ],
|
|
37
|
+
:after_fork => lambda { |server,worker|
|
|
38
|
+
trap(:HUP) { @tmp.syswrite('.') }
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
@server = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def teardown
|
|
45
|
+
reset_sig_handlers
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def test_worker_dies_on_dead_master
|
|
49
|
+
pid = fork {
|
|
50
|
+
app = lambda { |env| [ 200, {'x-pid' => "#$$" }, [] ] }
|
|
51
|
+
opts = @server_opts.merge(:timeout => 3)
|
|
52
|
+
redirect_test_io { HttpServer.new(app, opts).start.join }
|
|
53
|
+
}
|
|
54
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
|
55
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
56
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
57
|
+
buf = sock.readpartial(4096)
|
|
58
|
+
assert_nil sock.close
|
|
59
|
+
buf =~ /\bx-pid: (\d+)\b/ or raise Exception
|
|
60
|
+
child = $1.to_i
|
|
61
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
|
62
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
|
63
|
+
Process.kill(:KILL, pid)
|
|
64
|
+
Process.waitpid(pid)
|
|
65
|
+
File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
|
|
66
|
+
t0 = Time.now
|
|
67
|
+
assert child
|
|
68
|
+
assert t0
|
|
69
|
+
assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
|
|
70
|
+
assert((Time.now - t0) < 60)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_sleepy_kill
|
|
74
|
+
rd, wr = IO.pipe
|
|
75
|
+
pid = fork {
|
|
76
|
+
rd.close
|
|
77
|
+
app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
|
|
78
|
+
redirect_test_io { HttpServer.new(app, @server_opts).start.join }
|
|
79
|
+
}
|
|
80
|
+
wr.close
|
|
81
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
|
82
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
83
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
84
|
+
buf = rd.readpartial(1)
|
|
85
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
|
86
|
+
Process.kill(:INT, pid)
|
|
87
|
+
Process.waitpid(pid)
|
|
88
|
+
assert_equal '.', buf
|
|
89
|
+
buf = nil
|
|
90
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
|
91
|
+
Errno::EBADF) do
|
|
92
|
+
buf = sock.sysread(4096)
|
|
93
|
+
end
|
|
94
|
+
assert_nil buf
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_timeout_slow_response
|
|
98
|
+
pid = fork {
|
|
99
|
+
app = lambda { |env| sleep }
|
|
100
|
+
opts = @server_opts.merge(:timeout => 3)
|
|
101
|
+
redirect_test_io { HttpServer.new(app, opts).start.join }
|
|
102
|
+
}
|
|
103
|
+
t0 = Time.now
|
|
104
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
|
105
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
106
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
107
|
+
|
|
108
|
+
buf = nil
|
|
109
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
|
110
|
+
Errno::EBADF) do
|
|
111
|
+
buf = sock.sysread(4096)
|
|
112
|
+
end
|
|
113
|
+
diff = Time.now - t0
|
|
114
|
+
assert_nil buf
|
|
115
|
+
assert diff > 1.0, "diff was #{diff.inspect}"
|
|
116
|
+
assert diff < 60.0
|
|
117
|
+
ensure
|
|
118
|
+
Process.kill(:TERM, pid) rescue nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_response_write
|
|
122
|
+
app = lambda { |env|
|
|
123
|
+
[ 200, { 'content-type' => 'text/plain', 'x-pid' => Process.pid.to_s },
|
|
124
|
+
Dd.new(@bs, @count) ]
|
|
125
|
+
}
|
|
126
|
+
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
|
127
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
|
128
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
129
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
130
|
+
buf = ''
|
|
131
|
+
header_len = pid = nil
|
|
132
|
+
buf = sock.sysread(16384, buf)
|
|
133
|
+
pid = buf[/\r\nx-pid: (\d+)\r\n/, 1].to_i
|
|
134
|
+
header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
|
|
135
|
+
assert pid > 0, "pid not positive: #{pid.inspect}"
|
|
136
|
+
read = buf.size
|
|
137
|
+
size_before = @tmp.stat.size
|
|
138
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
|
139
|
+
Errno::EBADF) do
|
|
140
|
+
loop do
|
|
141
|
+
3.times { Process.kill(:HUP, pid) }
|
|
142
|
+
sock.sysread(16384, buf)
|
|
143
|
+
read += buf.size
|
|
144
|
+
3.times { Process.kill(:HUP, pid) }
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
redirect_test_io { @server.stop(true) }
|
|
149
|
+
# can't check for == since pending signals get merged
|
|
150
|
+
assert size_before < @tmp.stat.size
|
|
151
|
+
got = read - header_len
|
|
152
|
+
expect = @bs * @count
|
|
153
|
+
assert_equal(expect, got, "expect=#{expect} got=#{got}")
|
|
154
|
+
assert_nil sock.close
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def test_request_read
|
|
158
|
+
app = lambda { |env|
|
|
159
|
+
while env['rack.input'].read(4096)
|
|
160
|
+
end
|
|
161
|
+
[ 200, {'content-type'=>'text/plain', 'x-pid'=>Process.pid.to_s}, [] ]
|
|
162
|
+
}
|
|
163
|
+
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
|
164
|
+
|
|
165
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
|
166
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
167
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
168
|
+
pid = sock.sysread(4096)[/\r\nx-pid: (\d+)\r\n/, 1].to_i
|
|
169
|
+
assert_nil sock.close
|
|
170
|
+
|
|
171
|
+
assert pid > 0, "pid not positive: #{pid.inspect}"
|
|
172
|
+
sock = tcp_socket('127.0.0.1', @port)
|
|
173
|
+
sock.syswrite("PUT / HTTP/1.0\r\n")
|
|
174
|
+
sock.syswrite("Content-Length: #{@bs * @count}\r\n\r\n")
|
|
175
|
+
1000.times { Process.kill(:HUP, pid) }
|
|
176
|
+
size_before = @tmp.stat.size
|
|
177
|
+
killer = fork { loop { Process.kill(:HUP, pid); sleep(0.01) } }
|
|
178
|
+
buf = ' ' * @bs
|
|
179
|
+
@count.times { sock.syswrite(buf) }
|
|
180
|
+
Process.kill(:KILL, killer)
|
|
181
|
+
Process.waitpid2(killer)
|
|
182
|
+
redirect_test_io { @server.stop(true) }
|
|
183
|
+
# can't check for == since pending signals get merged
|
|
184
|
+
assert size_before < @tmp.stat.size
|
|
185
|
+
assert_equal pid, sock.sysread(4096)[/\r\nx-pid: (\d+)\r\n/, 1].to_i
|
|
186
|
+
assert_nil sock.close
|
|
187
|
+
end
|
|
188
|
+
end
|