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,147 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
require 'test/test_helper'
|
4
|
+
require 'tempfile'
|
5
|
+
|
6
|
+
class TestSocketHelper < Test::Unit::TestCase
|
7
|
+
include Unicorn::SocketHelper
|
8
|
+
attr_reader :logger
|
9
|
+
GET_SLASH = "GET / HTTP/1.0\r\n\r\n".freeze
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@log_tmp = Tempfile.new 'logger'
|
13
|
+
@logger = Logger.new(@log_tmp.path)
|
14
|
+
@test_addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
15
|
+
GC.disable
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
GC.enable
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_bind_listen_tcp
|
23
|
+
port = unused_port @test_addr
|
24
|
+
@tcp_listener_name = "#@test_addr:#{port}"
|
25
|
+
@tcp_listener = bind_listen(@tcp_listener_name)
|
26
|
+
assert TCPServer === @tcp_listener
|
27
|
+
assert_equal @tcp_listener_name, sock_name(@tcp_listener)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_bind_listen_options
|
31
|
+
port = unused_port @test_addr
|
32
|
+
tcp_listener_name = "#@test_addr:#{port}"
|
33
|
+
tmp = Tempfile.new 'unix.sock'
|
34
|
+
unix_listener_name = tmp.path
|
35
|
+
File.unlink(tmp.path)
|
36
|
+
[ { :backlog => 5 }, { :sndbuf => 4096 }, { :rcvbuf => 4096 },
|
37
|
+
{ :backlog => 16, :rcvbuf => 4096, :sndbuf => 4096 }
|
38
|
+
].each do |opts|
|
39
|
+
assert_nothing_raised do
|
40
|
+
tcp_listener = bind_listen(tcp_listener_name, opts)
|
41
|
+
assert TCPServer === tcp_listener
|
42
|
+
tcp_listener.close
|
43
|
+
unix_listener = bind_listen(unix_listener_name, opts)
|
44
|
+
assert UNIXServer === unix_listener
|
45
|
+
unix_listener.close
|
46
|
+
end
|
47
|
+
end
|
48
|
+
#system('cat', @log_tmp.path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_bind_listen_unix
|
52
|
+
old_umask = File.umask(0777)
|
53
|
+
tmp = Tempfile.new 'unix.sock'
|
54
|
+
@unix_listener_path = tmp.path
|
55
|
+
File.unlink(@unix_listener_path)
|
56
|
+
@unix_listener = bind_listen(@unix_listener_path)
|
57
|
+
assert UNIXServer === @unix_listener
|
58
|
+
assert_equal @unix_listener_path, sock_name(@unix_listener)
|
59
|
+
assert File.readable?(@unix_listener_path), "not readable"
|
60
|
+
assert File.writable?(@unix_listener_path), "not writable"
|
61
|
+
assert_equal 0777, File.umask
|
62
|
+
ensure
|
63
|
+
File.umask(old_umask)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_bind_listen_unix_umask
|
67
|
+
old_umask = File.umask(0777)
|
68
|
+
tmp = Tempfile.new 'unix.sock'
|
69
|
+
@unix_listener_path = tmp.path
|
70
|
+
File.unlink(@unix_listener_path)
|
71
|
+
@unix_listener = bind_listen(@unix_listener_path, :umask => 077)
|
72
|
+
assert UNIXServer === @unix_listener
|
73
|
+
assert_equal @unix_listener_path, sock_name(@unix_listener)
|
74
|
+
assert_equal 0140700, File.stat(@unix_listener_path).mode
|
75
|
+
assert_equal 0777, File.umask
|
76
|
+
ensure
|
77
|
+
File.umask(old_umask)
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_bind_listen_unix_idempotent
|
81
|
+
test_bind_listen_unix
|
82
|
+
a = bind_listen(@unix_listener)
|
83
|
+
assert_equal a.fileno, @unix_listener.fileno
|
84
|
+
unix_server = server_cast(@unix_listener)
|
85
|
+
assert UNIXServer === unix_server
|
86
|
+
a = bind_listen(unix_server)
|
87
|
+
assert_equal a.fileno, unix_server.fileno
|
88
|
+
assert_equal a.fileno, @unix_listener.fileno
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_bind_listen_tcp_idempotent
|
92
|
+
test_bind_listen_tcp
|
93
|
+
a = bind_listen(@tcp_listener)
|
94
|
+
assert_equal a.fileno, @tcp_listener.fileno
|
95
|
+
tcp_server = server_cast(@tcp_listener)
|
96
|
+
assert TCPServer === tcp_server
|
97
|
+
a = bind_listen(tcp_server)
|
98
|
+
assert_equal a.fileno, tcp_server.fileno
|
99
|
+
assert_equal a.fileno, @tcp_listener.fileno
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_bind_listen_unix_rebind
|
103
|
+
test_bind_listen_unix
|
104
|
+
new_listener = bind_listen(@unix_listener_path)
|
105
|
+
assert UNIXServer === new_listener
|
106
|
+
assert new_listener.fileno != @unix_listener.fileno
|
107
|
+
assert_equal sock_name(new_listener), sock_name(@unix_listener)
|
108
|
+
assert_equal @unix_listener_path, sock_name(new_listener)
|
109
|
+
pid = fork do
|
110
|
+
client = server_cast(new_listener).accept
|
111
|
+
client.syswrite('abcde')
|
112
|
+
exit 0
|
113
|
+
end
|
114
|
+
s = UNIXSocket.new(@unix_listener_path)
|
115
|
+
IO.select([s])
|
116
|
+
assert_equal 'abcde', s.sysread(5)
|
117
|
+
pid, status = Process.waitpid2(pid)
|
118
|
+
assert status.success?
|
119
|
+
end
|
120
|
+
|
121
|
+
def test_server_cast
|
122
|
+
assert_nothing_raised do
|
123
|
+
test_bind_listen_unix
|
124
|
+
test_bind_listen_tcp
|
125
|
+
end
|
126
|
+
unix_listener_socket = Socket.for_fd(@unix_listener.fileno)
|
127
|
+
assert Socket === unix_listener_socket
|
128
|
+
@unix_server = server_cast(unix_listener_socket)
|
129
|
+
assert_equal @unix_listener.fileno, @unix_server.fileno
|
130
|
+
assert UNIXServer === @unix_server
|
131
|
+
assert File.socket?(@unix_server.path)
|
132
|
+
assert_equal @unix_listener_path, sock_name(@unix_server)
|
133
|
+
|
134
|
+
tcp_listener_socket = Socket.for_fd(@tcp_listener.fileno)
|
135
|
+
assert Socket === tcp_listener_socket
|
136
|
+
@tcp_server = server_cast(tcp_listener_socket)
|
137
|
+
assert_equal @tcp_listener.fileno, @tcp_server.fileno
|
138
|
+
assert TCPServer === @tcp_server
|
139
|
+
assert_equal @tcp_listener_name, sock_name(@tcp_server)
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_sock_name
|
143
|
+
test_server_cast
|
144
|
+
sock_name(@unix_server)
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'digest/sha1'
|
5
|
+
require 'unicorn'
|
6
|
+
|
7
|
+
class TestTeeInput < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@rs = $/
|
11
|
+
@env = {}
|
12
|
+
@rd, @wr = IO.pipe
|
13
|
+
@rd.sync = @wr.sync = true
|
14
|
+
@start_pid = $$
|
15
|
+
end
|
16
|
+
|
17
|
+
def teardown
|
18
|
+
return if $$ != @start_pid
|
19
|
+
$/ = @rs
|
20
|
+
@rd.close rescue nil
|
21
|
+
@wr.close rescue nil
|
22
|
+
begin
|
23
|
+
Process.wait
|
24
|
+
rescue Errno::ECHILD
|
25
|
+
break
|
26
|
+
end while true
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_gets_long
|
30
|
+
init_parser("hello", 5 + (4096 * 4 * 3) + "#$/foo#$/".size)
|
31
|
+
ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
|
32
|
+
status = line = nil
|
33
|
+
pid = fork {
|
34
|
+
@rd.close
|
35
|
+
3.times { @wr.write("ffff" * 4096) }
|
36
|
+
@wr.write "#$/foo#$/"
|
37
|
+
@wr.close
|
38
|
+
}
|
39
|
+
@wr.close
|
40
|
+
assert_nothing_raised { line = ti.gets }
|
41
|
+
assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
|
42
|
+
assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
|
43
|
+
assert_nothing_raised { line = ti.gets }
|
44
|
+
assert_equal "foo#$/", line
|
45
|
+
assert_nil ti.gets
|
46
|
+
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
47
|
+
assert status.success?
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_gets_short
|
51
|
+
init_parser("hello", 5 + "#$/foo".size)
|
52
|
+
ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
|
53
|
+
status = line = nil
|
54
|
+
pid = fork {
|
55
|
+
@rd.close
|
56
|
+
@wr.write "#$/foo"
|
57
|
+
@wr.close
|
58
|
+
}
|
59
|
+
@wr.close
|
60
|
+
assert_nothing_raised { line = ti.gets }
|
61
|
+
assert_equal("hello#$/", line)
|
62
|
+
assert_nothing_raised { line = ti.gets }
|
63
|
+
assert_equal "foo", line
|
64
|
+
assert_nil ti.gets
|
65
|
+
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
66
|
+
assert status.success?
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_small_body
|
70
|
+
init_parser('hello')
|
71
|
+
ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
|
72
|
+
assert_equal 0, @parser.content_length
|
73
|
+
assert @parser.body_eof?
|
74
|
+
assert_equal StringIO, ti.tmp.class
|
75
|
+
assert_equal 0, ti.tmp.pos
|
76
|
+
assert_equal 5, ti.size
|
77
|
+
assert_equal 'hello', ti.read
|
78
|
+
assert_equal '', ti.read
|
79
|
+
assert_nil ti.read(4096)
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_read_with_buffer
|
83
|
+
init_parser('hello')
|
84
|
+
ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
|
85
|
+
buf = ''
|
86
|
+
rv = ti.read(4, buf)
|
87
|
+
assert_equal 'hell', rv
|
88
|
+
assert_equal 'hell', buf
|
89
|
+
assert_equal rv.object_id, buf.object_id
|
90
|
+
assert_equal 'o', ti.read
|
91
|
+
assert_equal nil, ti.read(5, buf)
|
92
|
+
assert_equal 0, ti.rewind
|
93
|
+
assert_equal 'hello', ti.read(5, buf)
|
94
|
+
assert_equal 'hello', buf
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_big_body
|
98
|
+
init_parser('.' * Unicorn::Const::MAX_BODY << 'a')
|
99
|
+
ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
|
100
|
+
assert_equal 0, @parser.content_length
|
101
|
+
assert @parser.body_eof?
|
102
|
+
assert_kind_of File, ti.tmp
|
103
|
+
assert_equal 0, ti.tmp.pos
|
104
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_read_in_full_if_content_length
|
108
|
+
a, b = 300, 3
|
109
|
+
init_parser('.' * b, 300)
|
110
|
+
assert_equal 300, @parser.content_length
|
111
|
+
ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
|
112
|
+
pid = fork {
|
113
|
+
@wr.write('.' * 197)
|
114
|
+
sleep 1 # still a *potential* race here that would make the test moot...
|
115
|
+
@wr.write('.' * 100)
|
116
|
+
}
|
117
|
+
assert_equal a, ti.read(a).size
|
118
|
+
_, status = Process.waitpid2(pid)
|
119
|
+
assert status.success?
|
120
|
+
@wr.close
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_big_body_multi
|
124
|
+
init_parser('.', Unicorn::Const::MAX_BODY + 1)
|
125
|
+
ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
|
126
|
+
assert_equal Unicorn::Const::MAX_BODY, @parser.content_length
|
127
|
+
assert ! @parser.body_eof?
|
128
|
+
assert_kind_of File, ti.tmp
|
129
|
+
assert_equal 0, ti.tmp.pos
|
130
|
+
assert_equal 1, ti.tmp.size
|
131
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
132
|
+
nr = Unicorn::Const::MAX_BODY / 4
|
133
|
+
pid = fork {
|
134
|
+
@rd.close
|
135
|
+
nr.times { @wr.write('....') }
|
136
|
+
@wr.close
|
137
|
+
}
|
138
|
+
@wr.close
|
139
|
+
assert_equal '.', ti.read(1)
|
140
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
141
|
+
nr.times {
|
142
|
+
assert_equal '....', ti.read(4)
|
143
|
+
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
144
|
+
}
|
145
|
+
assert_nil ti.read(1)
|
146
|
+
status = nil
|
147
|
+
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
148
|
+
assert status.success?
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_chunked
|
152
|
+
@parser = Unicorn::HttpParser.new
|
153
|
+
@buf = "POST / HTTP/1.1\r\n" \
|
154
|
+
"Host: localhost\r\n" \
|
155
|
+
"Transfer-Encoding: chunked\r\n" \
|
156
|
+
"\r\n"
|
157
|
+
assert_equal @env, @parser.headers(@env, @buf)
|
158
|
+
assert_equal "", @buf
|
159
|
+
|
160
|
+
pid = fork {
|
161
|
+
@rd.close
|
162
|
+
5.times { @wr.write("5\r\nabcde\r\n") }
|
163
|
+
@wr.write("0\r\n\r\n")
|
164
|
+
}
|
165
|
+
@wr.close
|
166
|
+
ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
|
167
|
+
assert_nil @parser.content_length
|
168
|
+
assert_nil ti.len
|
169
|
+
assert ! @parser.body_eof?
|
170
|
+
assert_equal 25, ti.size
|
171
|
+
assert @parser.body_eof?
|
172
|
+
assert_equal 25, ti.len
|
173
|
+
assert_equal 0, ti.tmp.pos
|
174
|
+
assert_nothing_raised { ti.rewind }
|
175
|
+
assert_equal 0, ti.tmp.pos
|
176
|
+
assert_equal 'abcdeabcdeabcdeabcde', ti.read(20)
|
177
|
+
assert_equal 20, ti.tmp.pos
|
178
|
+
assert_nothing_raised { ti.rewind }
|
179
|
+
assert_equal 0, ti.tmp.pos
|
180
|
+
assert_kind_of File, ti.tmp
|
181
|
+
status = nil
|
182
|
+
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
183
|
+
assert status.success?
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_chunked_ping_pong
|
187
|
+
@parser = Unicorn::HttpParser.new
|
188
|
+
@buf = "POST / HTTP/1.1\r\n" \
|
189
|
+
"Host: localhost\r\n" \
|
190
|
+
"Transfer-Encoding: chunked\r\n" \
|
191
|
+
"\r\n"
|
192
|
+
assert_equal @env, @parser.headers(@env, @buf)
|
193
|
+
assert_equal "", @buf
|
194
|
+
chunks = %w(aa bbb cccc dddd eeee)
|
195
|
+
rd, wr = IO.pipe
|
196
|
+
|
197
|
+
pid = fork {
|
198
|
+
chunks.each do |chunk|
|
199
|
+
rd.read(1) == "." and
|
200
|
+
@wr.write("#{'%x' % [ chunk.size]}\r\n#{chunk}\r\n")
|
201
|
+
end
|
202
|
+
@wr.write("0\r\n\r\n")
|
203
|
+
}
|
204
|
+
ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
|
205
|
+
assert_nil @parser.content_length
|
206
|
+
assert_nil ti.len
|
207
|
+
assert ! @parser.body_eof?
|
208
|
+
chunks.each do |chunk|
|
209
|
+
wr.write('.')
|
210
|
+
assert_equal chunk, ti.read(16384)
|
211
|
+
end
|
212
|
+
_, status = Process.waitpid2(pid)
|
213
|
+
assert status.success?
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_chunked_with_trailer
|
217
|
+
@parser = Unicorn::HttpParser.new
|
218
|
+
@buf = "POST / HTTP/1.1\r\n" \
|
219
|
+
"Host: localhost\r\n" \
|
220
|
+
"Trailer: Hello\r\n" \
|
221
|
+
"Transfer-Encoding: chunked\r\n" \
|
222
|
+
"\r\n"
|
223
|
+
assert_equal @env, @parser.headers(@env, @buf)
|
224
|
+
assert_equal "", @buf
|
225
|
+
|
226
|
+
pid = fork {
|
227
|
+
@rd.close
|
228
|
+
5.times { @wr.write("5\r\nabcde\r\n") }
|
229
|
+
@wr.write("0\r\n")
|
230
|
+
@wr.write("Hello: World\r\n\r\n")
|
231
|
+
}
|
232
|
+
@wr.close
|
233
|
+
ti = Unicorn::TeeInput.new(@rd, @env, @parser, @buf)
|
234
|
+
assert_nil @parser.content_length
|
235
|
+
assert_nil ti.len
|
236
|
+
assert ! @parser.body_eof?
|
237
|
+
assert_equal 25, ti.size
|
238
|
+
assert_equal "World", @env['HTTP_HELLO']
|
239
|
+
status = nil
|
240
|
+
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
241
|
+
assert status.success?
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
|
246
|
+
def init_parser(body, size = nil)
|
247
|
+
@parser = Unicorn::HttpParser.new
|
248
|
+
body = body.to_s.freeze
|
249
|
+
@buf = "POST / HTTP/1.1\r\n" \
|
250
|
+
"Host: localhost\r\n" \
|
251
|
+
"Content-Length: #{size || body.size}\r\n" \
|
252
|
+
"\r\n#{body}"
|
253
|
+
assert_equal @env, @parser.headers(@env, @buf)
|
254
|
+
assert_equal body, @buf
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
|
3
|
+
# Copyright (c) 2009 Eric Wong
|
4
|
+
require 'test/test_helper'
|
5
|
+
require 'digest/md5'
|
6
|
+
|
7
|
+
include Unicorn
|
8
|
+
|
9
|
+
class UploadTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
13
|
+
@port = unused_port
|
14
|
+
@hdr = {'Content-Type' => 'text/plain', 'Content-Length' => '0'}
|
15
|
+
@bs = 4096
|
16
|
+
@count = 256
|
17
|
+
@server = nil
|
18
|
+
|
19
|
+
# we want random binary data to test 1.9 encoding-aware IO craziness
|
20
|
+
@random = File.open('/dev/urandom','rb')
|
21
|
+
@sha1 = Digest::SHA1.new
|
22
|
+
@sha1_app = lambda do |env|
|
23
|
+
input = env['rack.input']
|
24
|
+
resp = {}
|
25
|
+
|
26
|
+
@sha1.reset
|
27
|
+
while buf = input.read(@bs)
|
28
|
+
@sha1.update(buf)
|
29
|
+
end
|
30
|
+
resp[:sha1] = @sha1.hexdigest
|
31
|
+
|
32
|
+
# rewind and read again
|
33
|
+
input.rewind
|
34
|
+
@sha1.reset
|
35
|
+
while buf = input.read(@bs)
|
36
|
+
@sha1.update(buf)
|
37
|
+
end
|
38
|
+
|
39
|
+
if resp[:sha1] == @sha1.hexdigest
|
40
|
+
resp[:sysread_read_byte_match] = true
|
41
|
+
end
|
42
|
+
|
43
|
+
if expect_size = env['HTTP_X_EXPECT_SIZE']
|
44
|
+
if expect_size.to_i == input.size
|
45
|
+
resp[:expect_size_match] = true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
resp[:size] = input.size
|
49
|
+
resp[:content_md5] = env['HTTP_CONTENT_MD5']
|
50
|
+
|
51
|
+
[ 200, @hdr.merge({'X-Resp' => resp.inspect}), [] ]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def teardown
|
56
|
+
redirect_test_io { @server.stop(true) } if @server
|
57
|
+
@random.close
|
58
|
+
reset_sig_handlers
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_put
|
62
|
+
start_server(@sha1_app)
|
63
|
+
sock = TCPSocket.new(@addr, @port)
|
64
|
+
sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
|
65
|
+
@count.times do |i|
|
66
|
+
buf = @random.sysread(@bs)
|
67
|
+
@sha1.update(buf)
|
68
|
+
sock.syswrite(buf)
|
69
|
+
end
|
70
|
+
read = sock.read.split(/\r\n/)
|
71
|
+
assert_equal "HTTP/1.1 200 OK", read[0]
|
72
|
+
resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
|
73
|
+
assert_equal length, resp[:size]
|
74
|
+
assert_equal @sha1.hexdigest, resp[:sha1]
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_put_content_md5
|
78
|
+
md5 = Digest::MD5.new
|
79
|
+
start_server(@sha1_app)
|
80
|
+
sock = TCPSocket.new(@addr, @port)
|
81
|
+
sock.syswrite("PUT / HTTP/1.0\r\nTransfer-Encoding: chunked\r\n" \
|
82
|
+
"Trailer: Content-MD5\r\n\r\n")
|
83
|
+
@count.times do |i|
|
84
|
+
buf = @random.sysread(@bs)
|
85
|
+
@sha1.update(buf)
|
86
|
+
md5.update(buf)
|
87
|
+
sock.syswrite("#{'%x' % buf.size}\r\n")
|
88
|
+
sock.syswrite(buf << "\r\n")
|
89
|
+
end
|
90
|
+
sock.syswrite("0\r\n")
|
91
|
+
|
92
|
+
content_md5 = [ md5.digest! ].pack('m').strip.freeze
|
93
|
+
sock.syswrite("Content-MD5: #{content_md5}\r\n\r\n")
|
94
|
+
read = sock.read.split(/\r\n/)
|
95
|
+
assert_equal "HTTP/1.1 200 OK", read[0]
|
96
|
+
resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
|
97
|
+
assert_equal length, resp[:size]
|
98
|
+
assert_equal @sha1.hexdigest, resp[:sha1]
|
99
|
+
assert_equal content_md5, resp[:content_md5]
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_put_trickle_small
|
103
|
+
@count, @bs = 2, 128
|
104
|
+
start_server(@sha1_app)
|
105
|
+
assert_equal 256, length
|
106
|
+
sock = TCPSocket.new(@addr, @port)
|
107
|
+
hdr = "PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n"
|
108
|
+
@count.times do
|
109
|
+
buf = @random.sysread(@bs)
|
110
|
+
@sha1.update(buf)
|
111
|
+
hdr << buf
|
112
|
+
sock.syswrite(hdr)
|
113
|
+
hdr = ''
|
114
|
+
sleep 0.6
|
115
|
+
end
|
116
|
+
read = sock.read.split(/\r\n/)
|
117
|
+
assert_equal "HTTP/1.1 200 OK", read[0]
|
118
|
+
resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
|
119
|
+
assert_equal length, resp[:size]
|
120
|
+
assert_equal @sha1.hexdigest, resp[:sha1]
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_put_keepalive_truncates_small_overwrite
|
124
|
+
start_server(@sha1_app)
|
125
|
+
sock = TCPSocket.new(@addr, @port)
|
126
|
+
to_upload = length + 1
|
127
|
+
sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{to_upload}\r\n\r\n")
|
128
|
+
@count.times do
|
129
|
+
buf = @random.sysread(@bs)
|
130
|
+
@sha1.update(buf)
|
131
|
+
sock.syswrite(buf)
|
132
|
+
end
|
133
|
+
sock.syswrite('12345') # write 4 bytes more than we expected
|
134
|
+
@sha1.update('1')
|
135
|
+
|
136
|
+
buf = sock.readpartial(4096)
|
137
|
+
while buf !~ /\r\n\r\n/
|
138
|
+
buf << sock.readpartial(4096)
|
139
|
+
end
|
140
|
+
read = buf.split(/\r\n/)
|
141
|
+
assert_equal "HTTP/1.1 200 OK", read[0]
|
142
|
+
resp = eval(read.grep(/^X-Resp: /).first.sub!(/X-Resp: /, ''))
|
143
|
+
assert_equal to_upload, resp[:size]
|
144
|
+
assert_equal @sha1.hexdigest, resp[:sha1]
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_put_excessive_overwrite_closed
|
148
|
+
start_server(lambda { |env|
|
149
|
+
while env['rack.input'].read(65536); end
|
150
|
+
[ 200, @hdr, [] ]
|
151
|
+
})
|
152
|
+
sock = TCPSocket.new(@addr, @port)
|
153
|
+
buf = ' ' * @bs
|
154
|
+
sock.syswrite("PUT / HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n")
|
155
|
+
|
156
|
+
@count.times { sock.syswrite(buf) }
|
157
|
+
assert_raise(Errno::ECONNRESET, Errno::EPIPE) do
|
158
|
+
::Unicorn::Const::CHUNK_SIZE.times { sock.syswrite(buf) }
|
159
|
+
end
|
160
|
+
assert_equal "HTTP/1.1 200 OK\r\n", sock.gets
|
161
|
+
end
|
162
|
+
|
163
|
+
# Despite reading numerous articles and inspecting the 1.9.1-p0 C
|
164
|
+
# source, Eric Wong will never trust that we're always handling
|
165
|
+
# encoding-aware IO objects correctly. Thus this test uses shell
|
166
|
+
# utilities that should always operate on files/sockets on a
|
167
|
+
# byte-level.
|
168
|
+
def test_uncomfortable_with_onenine_encodings
|
169
|
+
# POSIX doesn't require all of these to be present on a system
|
170
|
+
which('curl') or return
|
171
|
+
which('sha1sum') or return
|
172
|
+
which('dd') or return
|
173
|
+
|
174
|
+
start_server(@sha1_app)
|
175
|
+
|
176
|
+
tmp = Tempfile.new('dd_dest')
|
177
|
+
assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
|
178
|
+
"bs=#{@bs}", "count=#{@count}"),
|
179
|
+
"dd #@random to #{tmp}")
|
180
|
+
sha1_re = %r!\b([a-f0-9]{40})\b!
|
181
|
+
sha1_out = `sha1sum #{tmp.path}`
|
182
|
+
assert $?.success?, 'sha1sum ran OK'
|
183
|
+
|
184
|
+
assert_match(sha1_re, sha1_out)
|
185
|
+
sha1 = sha1_re.match(sha1_out)[1]
|
186
|
+
resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
|
187
|
+
assert $?.success?, 'curl ran OK'
|
188
|
+
assert_match(%r!\b#{sha1}\b!, resp)
|
189
|
+
assert_match(/sysread_read_byte_match/, resp)
|
190
|
+
|
191
|
+
# small StringIO path
|
192
|
+
assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
|
193
|
+
"bs=1024", "count=1"),
|
194
|
+
"dd #@random to #{tmp}")
|
195
|
+
sha1_re = %r!\b([a-f0-9]{40})\b!
|
196
|
+
sha1_out = `sha1sum #{tmp.path}`
|
197
|
+
assert $?.success?, 'sha1sum ran OK'
|
198
|
+
|
199
|
+
assert_match(sha1_re, sha1_out)
|
200
|
+
sha1 = sha1_re.match(sha1_out)[1]
|
201
|
+
resp = `curl -isSfN -T#{tmp.path} http://#@addr:#@port/`
|
202
|
+
assert $?.success?, 'curl ran OK'
|
203
|
+
assert_match(%r!\b#{sha1}\b!, resp)
|
204
|
+
assert_match(/sysread_read_byte_match/, resp)
|
205
|
+
end
|
206
|
+
|
207
|
+
def test_chunked_upload_via_curl
|
208
|
+
# POSIX doesn't require all of these to be present on a system
|
209
|
+
which('curl') or return
|
210
|
+
which('sha1sum') or return
|
211
|
+
which('dd') or return
|
212
|
+
|
213
|
+
start_server(@sha1_app)
|
214
|
+
|
215
|
+
tmp = Tempfile.new('dd_dest')
|
216
|
+
assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
|
217
|
+
"bs=#{@bs}", "count=#{@count}"),
|
218
|
+
"dd #@random to #{tmp}")
|
219
|
+
sha1_re = %r!\b([a-f0-9]{40})\b!
|
220
|
+
sha1_out = `sha1sum #{tmp.path}`
|
221
|
+
assert $?.success?, 'sha1sum ran OK'
|
222
|
+
|
223
|
+
assert_match(sha1_re, sha1_out)
|
224
|
+
sha1 = sha1_re.match(sha1_out)[1]
|
225
|
+
cmd = "curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \
|
226
|
+
-isSf --no-buffer -T- " \
|
227
|
+
"http://#@addr:#@port/"
|
228
|
+
resp = Tempfile.new('resp')
|
229
|
+
resp.sync = true
|
230
|
+
|
231
|
+
rd, wr = IO.pipe
|
232
|
+
wr.sync = rd.sync = true
|
233
|
+
pid = fork {
|
234
|
+
STDIN.reopen(rd)
|
235
|
+
rd.close
|
236
|
+
wr.close
|
237
|
+
STDOUT.reopen(resp)
|
238
|
+
exec cmd
|
239
|
+
}
|
240
|
+
rd.close
|
241
|
+
|
242
|
+
tmp.rewind
|
243
|
+
@count.times { |i|
|
244
|
+
wr.write(tmp.read(@bs))
|
245
|
+
sleep(rand / 10) if 0 == i % 8
|
246
|
+
}
|
247
|
+
wr.close
|
248
|
+
pid, status = Process.waitpid2(pid)
|
249
|
+
|
250
|
+
resp.rewind
|
251
|
+
resp = resp.read
|
252
|
+
assert status.success?, 'curl ran OK'
|
253
|
+
assert_match(%r!\b#{sha1}\b!, resp)
|
254
|
+
assert_match(/sysread_read_byte_match/, resp)
|
255
|
+
assert_match(/expect_size_match/, resp)
|
256
|
+
end
|
257
|
+
|
258
|
+
def test_curl_chunked_small
|
259
|
+
# POSIX doesn't require all of these to be present on a system
|
260
|
+
which('curl') or return
|
261
|
+
which('sha1sum') or return
|
262
|
+
which('dd') or return
|
263
|
+
|
264
|
+
start_server(@sha1_app)
|
265
|
+
|
266
|
+
tmp = Tempfile.new('dd_dest')
|
267
|
+
# small StringIO path
|
268
|
+
assert(system("dd", "if=#{@random.path}", "of=#{tmp.path}",
|
269
|
+
"bs=1024", "count=1"),
|
270
|
+
"dd #@random to #{tmp}")
|
271
|
+
sha1_re = %r!\b([a-f0-9]{40})\b!
|
272
|
+
sha1_out = `sha1sum #{tmp.path}`
|
273
|
+
assert $?.success?, 'sha1sum ran OK'
|
274
|
+
|
275
|
+
assert_match(sha1_re, sha1_out)
|
276
|
+
sha1 = sha1_re.match(sha1_out)[1]
|
277
|
+
resp = `curl -H 'X-Expect-Size: #{tmp.size}' --tcp-nodelay \
|
278
|
+
-isSf --no-buffer -T- http://#@addr:#@port/ < #{tmp.path}`
|
279
|
+
assert $?.success?, 'curl ran OK'
|
280
|
+
assert_match(%r!\b#{sha1}\b!, resp)
|
281
|
+
assert_match(/sysread_read_byte_match/, resp)
|
282
|
+
assert_match(/expect_size_match/, resp)
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
|
287
|
+
def length
|
288
|
+
@bs * @count
|
289
|
+
end
|
290
|
+
|
291
|
+
def start_server(app)
|
292
|
+
redirect_test_io do
|
293
|
+
@server = HttpServer.new(app, :listeners => [ "#{@addr}:#{@port}" ] )
|
294
|
+
@server.start
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|