unicorn-simon 0.0.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.
- 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 +156 -0
- data/.olddoc.yml +18 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +35 -0
- data/COPYING +674 -0
- data/DESIGN +95 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +187 -0
- data/Documentation/unicorn_rails.1.txt +175 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +253 -0
- data/HACKING +120 -0
- data/ISSUES +90 -0
- data/KNOWN_ISSUES +79 -0
- data/LATEST +30 -0
- data/LICENSE +67 -0
- data/Links +56 -0
- data/NEWS +2465 -0
- data/PHILOSOPHY +139 -0
- data/README +138 -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 +126 -0
- data/bin/unicorn_rails +209 -0
- data/examples/big_app_gc.rb +2 -0
- data/examples/echo.ru +27 -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 +155 -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 +33 -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 +62 -0
- data/ext/unicorn_http/extconf.rb +11 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +78 -0
- data/ext/unicorn_http/unicorn_http.c +4274 -0
- data/ext/unicorn_http/unicorn_http.rl +980 -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 +664 -0
- data/lib/unicorn/const.rb +21 -0
- data/lib/unicorn/http_request.rb +122 -0
- data/lib/unicorn/http_response.rb +60 -0
- data/lib/unicorn/http_server.rb +824 -0
- data/lib/unicorn/launcher.rb +62 -0
- data/lib/unicorn/oob_gc.rb +82 -0
- data/lib/unicorn/preread_input.rb +33 -0
- data/lib/unicorn/socket_helper.rb +195 -0
- data/lib/unicorn/stream_input.rb +146 -0
- data/lib/unicorn/tee_input.rb +133 -0
- data/lib/unicorn/tmpio.rb +27 -0
- data/lib/unicorn/util.rb +90 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +140 -0
- data/lib/unicorn.rb +123 -0
- data/man/man1/unicorn.1 +221 -0
- data/man/man1/unicorn_rails.1 +212 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +74 -0
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +17 -0
- data/t/bin/unused_listen +40 -0
- data/t/broken-app.ru +12 -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/hijack.ru +43 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +20 -0
- data/t/oob_gc_path.ru +20 -0
- data/t/pid.ru +3 -0
- data/t/preread_input.ru +17 -0
- data/t/rack-input-tests.ru +21 -0
- data/t/t0000-http-basic.sh +50 -0
- data/t/t0001-reload-bad-config.sh +53 -0
- data/t/t0002-config-conflict.sh +49 -0
- data/t/t0002-parser-error.sh +94 -0
- data/t/t0003-working_directory.sh +51 -0
- data/t/t0004-heartbeat-timeout.sh +69 -0
- data/t/t0004-working_directory_broken.sh +24 -0
- data/t/t0005-working_directory_app.rb.sh +40 -0
- data/t/t0006-reopen-logs.sh +83 -0
- data/t/t0006.ru +13 -0
- data/t/t0007-working_directory_no_embed_cli.sh +44 -0
- data/t/t0008-back_out_of_upgrade.sh +110 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0009-winch_ttin.sh +59 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0011-active-unix-socket.sh +79 -0
- data/t/t0012-reload-empty-config.sh +85 -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/t0018-write-on-close.sh +23 -0
- data/t/t0019-max_header_len.sh +49 -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/t0100-rack-input-tests.sh +124 -0
- data/t/t0116-client_body_buffer_size.sh +80 -0
- data/t/t0116.ru +16 -0
- data/t/t0200-rack-hijack.sh +30 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t9000-preread-input.sh +48 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +128 -0
- data/t/write-on-close.ru +11 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +50 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1099 -0
- data/test/test_helper.rb +298 -0
- data/test/unit/test_configurator.rb +175 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +886 -0
- data/test/unit/test_http_parser_ng.rb +633 -0
- data/test/unit/test_request.rb +182 -0
- data/test/unit/test_response.rb +111 -0
- data/test/unit/test_server.rb +268 -0
- data/test/unit/test_signals.rb +188 -0
- data/test/unit/test_socket_helper.rb +197 -0
- data/test/unit/test_stream_input.rb +203 -0
- data/test/unit/test_tee_input.rb +304 -0
- data/test/unit/test_upload.rb +306 -0
- data/test/unit/test_util.rb +105 -0
- data/unicorn.gemspec +50 -0
- metadata +310 -0
|
@@ -0,0 +1,182 @@
|
|
|
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
|
+
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
|
+
env = @request.read(client)
|
|
34
|
+
assert_equal '', env['REQUEST_PATH']
|
|
35
|
+
assert_equal '', env['PATH_INFO']
|
|
36
|
+
assert_equal '*', env['REQUEST_URI']
|
|
37
|
+
res = @lint.call(env)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_absolute_uri_with_query
|
|
41
|
+
client = MockRequest.new("GET http://e:3/x?y=z HTTP/1.1\r\n" \
|
|
42
|
+
"Host: foo\r\n\r\n")
|
|
43
|
+
env = @request.read(client)
|
|
44
|
+
assert_equal '/x', env['REQUEST_PATH']
|
|
45
|
+
assert_equal '/x', env['PATH_INFO']
|
|
46
|
+
assert_equal 'y=z', env['QUERY_STRING']
|
|
47
|
+
res = @lint.call(env)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_absolute_uri_with_fragment
|
|
51
|
+
client = MockRequest.new("GET http://e:3/x#frag HTTP/1.1\r\n" \
|
|
52
|
+
"Host: foo\r\n\r\n")
|
|
53
|
+
env = @request.read(client)
|
|
54
|
+
assert_equal '/x', env['REQUEST_PATH']
|
|
55
|
+
assert_equal '/x', env['PATH_INFO']
|
|
56
|
+
assert_equal '', env['QUERY_STRING']
|
|
57
|
+
assert_equal 'frag', env['FRAGMENT']
|
|
58
|
+
res = @lint.call(env)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_absolute_uri_with_query_and_fragment
|
|
62
|
+
client = MockRequest.new("GET http://e:3/x?a=b#frag HTTP/1.1\r\n" \
|
|
63
|
+
"Host: foo\r\n\r\n")
|
|
64
|
+
env = @request.read(client)
|
|
65
|
+
assert_equal '/x', env['REQUEST_PATH']
|
|
66
|
+
assert_equal '/x', env['PATH_INFO']
|
|
67
|
+
assert_equal 'a=b', env['QUERY_STRING']
|
|
68
|
+
assert_equal 'frag', env['FRAGMENT']
|
|
69
|
+
res = @lint.call(env)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_absolute_uri_unsupported_schemes
|
|
73
|
+
%w(ssh+http://e/ ftp://e/x http+ssh://e/x).each do |abs_uri|
|
|
74
|
+
client = MockRequest.new("GET #{abs_uri} HTTP/1.1\r\n" \
|
|
75
|
+
"Host: foo\r\n\r\n")
|
|
76
|
+
assert_raises(HttpParserError) { @request.read(client) }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def test_x_forwarded_proto_https
|
|
81
|
+
client = MockRequest.new("GET / HTTP/1.1\r\n" \
|
|
82
|
+
"X-Forwarded-Proto: https\r\n" \
|
|
83
|
+
"Host: foo\r\n\r\n")
|
|
84
|
+
env = @request.read(client)
|
|
85
|
+
assert_equal "https", env['rack.url_scheme']
|
|
86
|
+
res = @lint.call(env)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def test_x_forwarded_proto_http
|
|
90
|
+
client = MockRequest.new("GET / HTTP/1.1\r\n" \
|
|
91
|
+
"X-Forwarded-Proto: http\r\n" \
|
|
92
|
+
"Host: foo\r\n\r\n")
|
|
93
|
+
env = @request.read(client)
|
|
94
|
+
assert_equal "http", env['rack.url_scheme']
|
|
95
|
+
res = @lint.call(env)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def test_x_forwarded_proto_invalid
|
|
99
|
+
client = MockRequest.new("GET / HTTP/1.1\r\n" \
|
|
100
|
+
"X-Forwarded-Proto: ftp\r\n" \
|
|
101
|
+
"Host: foo\r\n\r\n")
|
|
102
|
+
env = @request.read(client)
|
|
103
|
+
assert_equal "http", env['rack.url_scheme']
|
|
104
|
+
res = @lint.call(env)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def test_rack_lint_get
|
|
108
|
+
client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
|
|
109
|
+
env = @request.read(client)
|
|
110
|
+
assert_equal "http", env['rack.url_scheme']
|
|
111
|
+
assert_equal '127.0.0.1', env['REMOTE_ADDR']
|
|
112
|
+
res = @lint.call(env)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_no_content_stringio
|
|
116
|
+
client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
|
|
117
|
+
env = @request.read(client)
|
|
118
|
+
assert_equal StringIO, env['rack.input'].class
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def test_zero_content_stringio
|
|
122
|
+
client = MockRequest.new("PUT / HTTP/1.1\r\n" \
|
|
123
|
+
"Content-Length: 0\r\n" \
|
|
124
|
+
"Host: foo\r\n\r\n")
|
|
125
|
+
env = @request.read(client)
|
|
126
|
+
assert_equal StringIO, env['rack.input'].class
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_real_content_not_stringio
|
|
130
|
+
client = MockRequest.new("PUT / HTTP/1.1\r\n" \
|
|
131
|
+
"Content-Length: 1\r\n" \
|
|
132
|
+
"Host: foo\r\n\r\n")
|
|
133
|
+
env = @request.read(client)
|
|
134
|
+
assert_equal Unicorn::TeeInput, env['rack.input'].class
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def test_rack_lint_put
|
|
138
|
+
client = MockRequest.new(
|
|
139
|
+
"PUT / HTTP/1.1\r\n" \
|
|
140
|
+
"Host: foo\r\n" \
|
|
141
|
+
"Content-Length: 5\r\n" \
|
|
142
|
+
"\r\n" \
|
|
143
|
+
"abcde")
|
|
144
|
+
env = @request.read(client)
|
|
145
|
+
assert ! env.include?(:http_body)
|
|
146
|
+
res = @lint.call(env)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def test_rack_lint_big_put
|
|
150
|
+
count = 100
|
|
151
|
+
bs = 0x10000
|
|
152
|
+
buf = (' ' * bs).freeze
|
|
153
|
+
length = bs * count
|
|
154
|
+
client = Tempfile.new('big_put')
|
|
155
|
+
def client.kgio_addr; '127.0.0.1'; end
|
|
156
|
+
def client.kgio_read(*args)
|
|
157
|
+
readpartial(*args)
|
|
158
|
+
rescue EOFError
|
|
159
|
+
end
|
|
160
|
+
def client.kgio_read!(*args)
|
|
161
|
+
readpartial(*args)
|
|
162
|
+
end
|
|
163
|
+
client.syswrite(
|
|
164
|
+
"PUT / HTTP/1.1\r\n" \
|
|
165
|
+
"Host: foo\r\n" \
|
|
166
|
+
"Content-Length: #{length}\r\n" \
|
|
167
|
+
"\r\n")
|
|
168
|
+
count.times { assert_equal bs, client.syswrite(buf) }
|
|
169
|
+
assert_equal 0, client.sysseek(0)
|
|
170
|
+
env = @request.read(client)
|
|
171
|
+
assert ! env.include?(:http_body)
|
|
172
|
+
assert_equal length, env['rack.input'].size
|
|
173
|
+
count.times {
|
|
174
|
+
tmp = env['rack.input'].read(bs)
|
|
175
|
+
tmp << env['rack.input'].read(bs - tmp.size) if tmp.size != bs
|
|
176
|
+
assert_equal buf, tmp
|
|
177
|
+
}
|
|
178
|
+
assert_nil env['rack.input'].read(bs)
|
|
179
|
+
env['rack.input'].rewind
|
|
180
|
+
res = @lint.call(env)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
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
|
+
# ref: <CAO47=rJa=zRcLn_Xm4v2cHPr6c0UswaFC_omYFEH+baSxHOWKQ@mail.gmail.com>
|
|
37
|
+
def test_response_header_broken_nil
|
|
38
|
+
out = StringIO.new
|
|
39
|
+
http_response_write(out, 200, {"Nil" => nil}, %w(hysterical raisin))
|
|
40
|
+
assert ! out.closed?
|
|
41
|
+
|
|
42
|
+
assert_match %r{^Nil: \r\n}sm, out.string, 'nil accepted'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_response_string_status
|
|
46
|
+
out = StringIO.new
|
|
47
|
+
http_response_write(out,'200', {}, [])
|
|
48
|
+
assert ! out.closed?
|
|
49
|
+
assert out.length > 0, "output didn't have data"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_response_200
|
|
53
|
+
io = StringIO.new
|
|
54
|
+
http_response_write(io, 200, {}, [])
|
|
55
|
+
assert ! io.closed?
|
|
56
|
+
assert io.length > 0, "output didn't have data"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_response_with_default_reason
|
|
60
|
+
code = 400
|
|
61
|
+
io = StringIO.new
|
|
62
|
+
http_response_write(io, code, {}, [])
|
|
63
|
+
assert ! io.closed?
|
|
64
|
+
lines = io.string.split(/\r\n/)
|
|
65
|
+
assert_match(/.* Bad Request$/, lines.first,
|
|
66
|
+
"wrong default reason phrase")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_rack_multivalue_headers
|
|
70
|
+
out = StringIO.new
|
|
71
|
+
http_response_write(out,200, {"X-Whatever" => "stuff\nbleh"}, [])
|
|
72
|
+
assert ! out.closed?
|
|
73
|
+
assert_match(/^X-Whatever: stuff\r\nX-Whatever: bleh\r\n/, out.string)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Even though Rack explicitly forbids "Status" in the header hash,
|
|
77
|
+
# some broken clients still rely on it
|
|
78
|
+
def test_status_header_added
|
|
79
|
+
out = StringIO.new
|
|
80
|
+
http_response_write(out,200, {"X-Whatever" => "stuff"}, [])
|
|
81
|
+
assert ! out.closed?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_unknown_status_pass_through
|
|
85
|
+
out = StringIO.new
|
|
86
|
+
http_response_write(out,"666 I AM THE BEAST", {}, [] )
|
|
87
|
+
assert ! out.closed?
|
|
88
|
+
headers = out.string.split(/\r\n\r\n/).first.split(/\r\n/)
|
|
89
|
+
assert %r{\AHTTP/\d\.\d 666 I AM THE BEAST\z}.match(headers[0])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_modified_rack_http_status_codes_late
|
|
93
|
+
r, w = IO.pipe
|
|
94
|
+
pid = fork do
|
|
95
|
+
r.close
|
|
96
|
+
# Users may want to globally override the status text associated
|
|
97
|
+
# with an HTTP status code in their app.
|
|
98
|
+
Rack::Utils::HTTP_STATUS_CODES[200] = "HI"
|
|
99
|
+
http_response_write(w, 200, {}, [])
|
|
100
|
+
w.close
|
|
101
|
+
end
|
|
102
|
+
w.close
|
|
103
|
+
assert_equal "HTTP/1.1 200 HI\r\n", r.gets
|
|
104
|
+
r.read # just drain the pipe
|
|
105
|
+
pid, status = Process.waitpid2(pid)
|
|
106
|
+
assert status.success?, status.inspect
|
|
107
|
+
ensure
|
|
108
|
+
r.close
|
|
109
|
+
w.close unless w.closed?
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,268 @@
|
|
|
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
|
+
|
|
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 = TCPSocket.new('127.0.0.1', @port)
|
|
96
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
|
97
|
+
assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
|
|
98
|
+
assert_nil sock.close
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_simple_server
|
|
102
|
+
results = hit(["http://localhost:#{@port}/test"])
|
|
103
|
+
assert_equal 'hello!\n', results[0], "Handler didn't really run"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def test_client_shutdown_writes
|
|
107
|
+
bs = 15609315 * rand
|
|
108
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
109
|
+
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
|
110
|
+
sock.syswrite("Host: example.com\r\n")
|
|
111
|
+
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
|
112
|
+
sock.syswrite("Trailer: X-Foo\r\n")
|
|
113
|
+
sock.syswrite("\r\n")
|
|
114
|
+
sock.syswrite("%x\r\n" % [ bs ])
|
|
115
|
+
sock.syswrite("F" * bs)
|
|
116
|
+
sock.syswrite("\r\n0\r\nX-")
|
|
117
|
+
"Foo: bar\r\n\r\n".each_byte do |x|
|
|
118
|
+
sock.syswrite x.chr
|
|
119
|
+
sleep 0.05
|
|
120
|
+
end
|
|
121
|
+
# we wrote the entire request before shutting down, server should
|
|
122
|
+
# continue to process our request and never hit EOFError on our sock
|
|
123
|
+
sock.shutdown(Socket::SHUT_WR)
|
|
124
|
+
buf = sock.read
|
|
125
|
+
assert_equal 'hello!\n', buf.split(/\r\n\r\n/).last
|
|
126
|
+
next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
|
|
127
|
+
assert_equal 'hello!\n', next_client
|
|
128
|
+
lines = File.readlines("test_stderr.#$$.log")
|
|
129
|
+
assert lines.grep(/^Unicorn::ClientShutdown: /).empty?
|
|
130
|
+
assert_nil sock.close
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def test_client_shutdown_write_truncates
|
|
134
|
+
bs = 15609315 * rand
|
|
135
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
136
|
+
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
|
137
|
+
sock.syswrite("Host: example.com\r\n")
|
|
138
|
+
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
|
139
|
+
sock.syswrite("Trailer: X-Foo\r\n")
|
|
140
|
+
sock.syswrite("\r\n")
|
|
141
|
+
sock.syswrite("%x\r\n" % [ bs ])
|
|
142
|
+
sock.syswrite("F" * (bs / 2.0))
|
|
143
|
+
|
|
144
|
+
# shutdown prematurely, this will force the server to abort
|
|
145
|
+
# processing on us even during app dispatch
|
|
146
|
+
sock.shutdown(Socket::SHUT_WR)
|
|
147
|
+
IO.select([sock], nil, nil, 60) or raise "Timed out"
|
|
148
|
+
buf = sock.read
|
|
149
|
+
assert_equal "", buf
|
|
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::ClientShutdown: bytes_read=\d+/)
|
|
154
|
+
assert_equal 1, lines.size
|
|
155
|
+
assert_match %r{\AUnicorn::ClientShutdown: bytes_read=\d+ true$}, lines[0]
|
|
156
|
+
assert_nil sock.close
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def test_client_malformed_body
|
|
160
|
+
bs = 15653984
|
|
161
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
162
|
+
sock.syswrite("PUT /hello HTTP/1.1\r\n")
|
|
163
|
+
sock.syswrite("Host: example.com\r\n")
|
|
164
|
+
sock.syswrite("Transfer-Encoding: chunked\r\n")
|
|
165
|
+
sock.syswrite("Trailer: X-Foo\r\n")
|
|
166
|
+
sock.syswrite("\r\n")
|
|
167
|
+
sock.syswrite("%x\r\n" % [ bs ])
|
|
168
|
+
sock.syswrite("F" * bs)
|
|
169
|
+
begin
|
|
170
|
+
File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
|
|
171
|
+
rescue
|
|
172
|
+
end
|
|
173
|
+
assert_nil sock.close
|
|
174
|
+
next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
|
|
175
|
+
assert_equal 'hello!\n', next_client
|
|
176
|
+
lines = File.readlines("test_stderr.#$$.log")
|
|
177
|
+
lines = lines.grep(/^Unicorn::HttpParserError: .* true$/)
|
|
178
|
+
assert_equal 1, lines.size
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def do_test(string, chunk, close_after=nil, shutdown_delay=0)
|
|
182
|
+
# Do not use instance variables here, because it needs to be thread safe
|
|
183
|
+
socket = TCPSocket.new("127.0.0.1", @port);
|
|
184
|
+
request = StringIO.new(string)
|
|
185
|
+
chunks_out = 0
|
|
186
|
+
|
|
187
|
+
while data = request.read(chunk)
|
|
188
|
+
chunks_out += socket.write(data)
|
|
189
|
+
socket.flush
|
|
190
|
+
sleep 0.2
|
|
191
|
+
if close_after and chunks_out > close_after
|
|
192
|
+
socket.close
|
|
193
|
+
sleep 1
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
sleep(shutdown_delay)
|
|
197
|
+
socket.write(" ") # Some platforms only raise the exception on attempted write
|
|
198
|
+
socket.flush
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def test_trickle_attack
|
|
202
|
+
do_test(@valid_request, 3)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def test_close_client
|
|
206
|
+
assert_raises IOError do
|
|
207
|
+
do_test(@valid_request, 10, 20)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def test_bad_client
|
|
212
|
+
redirect_test_io do
|
|
213
|
+
do_test("GET /test HTTP/BAD", 3)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def test_logger_set
|
|
218
|
+
assert_equal @server.logger, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def test_logger_changed
|
|
222
|
+
tmp = Logger.new($stdout)
|
|
223
|
+
@server.logger = tmp
|
|
224
|
+
assert_equal tmp, Unicorn::HttpRequest::DEFAULTS["rack.logger"]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def test_bad_client_400
|
|
228
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
229
|
+
sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
|
|
230
|
+
assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
|
|
231
|
+
assert_nil sock.close
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def test_http_0_9
|
|
235
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
|
236
|
+
sock.syswrite("GET /hello\r\n")
|
|
237
|
+
assert_match 'hello!\n', sock.sysread(4096)
|
|
238
|
+
assert_nil sock.close
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def test_header_is_too_long
|
|
242
|
+
redirect_test_io do
|
|
243
|
+
long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n"
|
|
244
|
+
assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do
|
|
245
|
+
do_test(long, long.length/2, 10)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def test_file_streamed_request
|
|
251
|
+
body = "a" * (Unicorn::Const::MAX_BODY * 2)
|
|
252
|
+
long = "PUT /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body
|
|
253
|
+
do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def test_file_streamed_request_bad_body
|
|
257
|
+
body = "a" * (Unicorn::Const::MAX_BODY * 2)
|
|
258
|
+
long = "GET /test HTTP/1.1\r\nContent-ength: #{body.length}\r\n\r\n" + body
|
|
259
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
|
260
|
+
Errno::EBADF) {
|
|
261
|
+
do_test(long, Unicorn::Const::CHUNK_SIZE * 2 - 400)
|
|
262
|
+
}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def test_listener_names
|
|
266
|
+
assert_equal [ "127.0.0.1:#@port" ], Unicorn.listener_names
|
|
267
|
+
end
|
|
268
|
+
end
|