unicorn-fotopedia 0.99.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +19 -0
  3. data/.gitignore +21 -0
  4. data/.mailmap +26 -0
  5. data/CONTRIBUTORS +32 -0
  6. data/COPYING +339 -0
  7. data/DESIGN +105 -0
  8. data/Documentation/.gitignore +5 -0
  9. data/Documentation/GNUmakefile +30 -0
  10. data/Documentation/unicorn.1.txt +171 -0
  11. data/Documentation/unicorn_rails.1.txt +172 -0
  12. data/FAQ +52 -0
  13. data/GIT-VERSION-GEN +40 -0
  14. data/GNUmakefile +292 -0
  15. data/HACKING +116 -0
  16. data/ISSUES +36 -0
  17. data/KNOWN_ISSUES +50 -0
  18. data/LICENSE +55 -0
  19. data/PHILOSOPHY +145 -0
  20. data/README +149 -0
  21. data/Rakefile +191 -0
  22. data/SIGNALS +109 -0
  23. data/Sandbox +78 -0
  24. data/TODO +5 -0
  25. data/TUNING +70 -0
  26. data/bin/unicorn +126 -0
  27. data/bin/unicorn_rails +203 -0
  28. data/examples/big_app_gc.rb +33 -0
  29. data/examples/echo.ru +27 -0
  30. data/examples/git.ru +13 -0
  31. data/examples/init.sh +58 -0
  32. data/examples/logger_mp_safe.rb +25 -0
  33. data/examples/nginx.conf +139 -0
  34. data/examples/unicorn.conf.rb +78 -0
  35. data/ext/unicorn_http/CFLAGS +13 -0
  36. data/ext/unicorn_http/c_util.h +124 -0
  37. data/ext/unicorn_http/common_field_optimization.h +111 -0
  38. data/ext/unicorn_http/ext_help.h +77 -0
  39. data/ext/unicorn_http/extconf.rb +14 -0
  40. data/ext/unicorn_http/global_variables.h +89 -0
  41. data/ext/unicorn_http/unicorn_http.rl +714 -0
  42. data/ext/unicorn_http/unicorn_http_common.rl +75 -0
  43. data/lib/unicorn.rb +847 -0
  44. data/lib/unicorn/app/exec_cgi.rb +150 -0
  45. data/lib/unicorn/app/inetd.rb +109 -0
  46. data/lib/unicorn/app/old_rails.rb +33 -0
  47. data/lib/unicorn/app/old_rails/static.rb +58 -0
  48. data/lib/unicorn/cgi_wrapper.rb +145 -0
  49. data/lib/unicorn/configurator.rb +421 -0
  50. data/lib/unicorn/const.rb +34 -0
  51. data/lib/unicorn/http_request.rb +72 -0
  52. data/lib/unicorn/http_response.rb +75 -0
  53. data/lib/unicorn/launcher.rb +65 -0
  54. data/lib/unicorn/oob_gc.rb +58 -0
  55. data/lib/unicorn/socket_helper.rb +152 -0
  56. data/lib/unicorn/tee_input.rb +217 -0
  57. data/lib/unicorn/util.rb +90 -0
  58. data/local.mk.sample +62 -0
  59. data/setup.rb +1586 -0
  60. data/t/.gitignore +2 -0
  61. data/t/GNUmakefile +67 -0
  62. data/t/README +42 -0
  63. data/t/bin/content-md5-put +36 -0
  64. data/t/bin/sha1sum.rb +23 -0
  65. data/t/bin/unused_listen +40 -0
  66. data/t/bin/utee +12 -0
  67. data/t/env.ru +3 -0
  68. data/t/my-tap-lib.sh +200 -0
  69. data/t/t0000-http-basic.sh +50 -0
  70. data/t/t0001-reload-bad-config.sh +52 -0
  71. data/t/t0002-config-conflict.sh +49 -0
  72. data/t/test-lib.sh +100 -0
  73. data/test/aggregate.rb +15 -0
  74. data/test/benchmark/README +50 -0
  75. data/test/benchmark/dd.ru +18 -0
  76. data/test/exec/README +5 -0
  77. data/test/exec/test_exec.rb +1038 -0
  78. data/test/rails/app-1.2.3/.gitignore +2 -0
  79. data/test/rails/app-1.2.3/Rakefile +7 -0
  80. data/test/rails/app-1.2.3/app/controllers/application.rb +6 -0
  81. data/test/rails/app-1.2.3/app/controllers/foo_controller.rb +36 -0
  82. data/test/rails/app-1.2.3/app/helpers/application_helper.rb +4 -0
  83. data/test/rails/app-1.2.3/config/boot.rb +11 -0
  84. data/test/rails/app-1.2.3/config/database.yml +12 -0
  85. data/test/rails/app-1.2.3/config/environment.rb +13 -0
  86. data/test/rails/app-1.2.3/config/environments/development.rb +9 -0
  87. data/test/rails/app-1.2.3/config/environments/production.rb +5 -0
  88. data/test/rails/app-1.2.3/config/routes.rb +6 -0
  89. data/test/rails/app-1.2.3/db/.gitignore +0 -0
  90. data/test/rails/app-1.2.3/public/404.html +1 -0
  91. data/test/rails/app-1.2.3/public/500.html +1 -0
  92. data/test/rails/app-2.0.2/.gitignore +2 -0
  93. data/test/rails/app-2.0.2/Rakefile +7 -0
  94. data/test/rails/app-2.0.2/app/controllers/application.rb +4 -0
  95. data/test/rails/app-2.0.2/app/controllers/foo_controller.rb +36 -0
  96. data/test/rails/app-2.0.2/app/helpers/application_helper.rb +4 -0
  97. data/test/rails/app-2.0.2/config/boot.rb +11 -0
  98. data/test/rails/app-2.0.2/config/database.yml +12 -0
  99. data/test/rails/app-2.0.2/config/environment.rb +17 -0
  100. data/test/rails/app-2.0.2/config/environments/development.rb +8 -0
  101. data/test/rails/app-2.0.2/config/environments/production.rb +5 -0
  102. data/test/rails/app-2.0.2/config/routes.rb +6 -0
  103. data/test/rails/app-2.0.2/db/.gitignore +0 -0
  104. data/test/rails/app-2.0.2/public/404.html +1 -0
  105. data/test/rails/app-2.0.2/public/500.html +1 -0
  106. data/test/rails/app-2.1.2/.gitignore +2 -0
  107. data/test/rails/app-2.1.2/Rakefile +7 -0
  108. data/test/rails/app-2.1.2/app/controllers/application.rb +4 -0
  109. data/test/rails/app-2.1.2/app/controllers/foo_controller.rb +36 -0
  110. data/test/rails/app-2.1.2/app/helpers/application_helper.rb +4 -0
  111. data/test/rails/app-2.1.2/config/boot.rb +111 -0
  112. data/test/rails/app-2.1.2/config/database.yml +12 -0
  113. data/test/rails/app-2.1.2/config/environment.rb +17 -0
  114. data/test/rails/app-2.1.2/config/environments/development.rb +7 -0
  115. data/test/rails/app-2.1.2/config/environments/production.rb +5 -0
  116. data/test/rails/app-2.1.2/config/routes.rb +6 -0
  117. data/test/rails/app-2.1.2/db/.gitignore +0 -0
  118. data/test/rails/app-2.1.2/public/404.html +1 -0
  119. data/test/rails/app-2.1.2/public/500.html +1 -0
  120. data/test/rails/app-2.2.2/.gitignore +2 -0
  121. data/test/rails/app-2.2.2/Rakefile +7 -0
  122. data/test/rails/app-2.2.2/app/controllers/application.rb +4 -0
  123. data/test/rails/app-2.2.2/app/controllers/foo_controller.rb +36 -0
  124. data/test/rails/app-2.2.2/app/helpers/application_helper.rb +4 -0
  125. data/test/rails/app-2.2.2/config/boot.rb +111 -0
  126. data/test/rails/app-2.2.2/config/database.yml +12 -0
  127. data/test/rails/app-2.2.2/config/environment.rb +17 -0
  128. data/test/rails/app-2.2.2/config/environments/development.rb +7 -0
  129. data/test/rails/app-2.2.2/config/environments/production.rb +5 -0
  130. data/test/rails/app-2.2.2/config/routes.rb +6 -0
  131. data/test/rails/app-2.2.2/db/.gitignore +0 -0
  132. data/test/rails/app-2.2.2/public/404.html +1 -0
  133. data/test/rails/app-2.2.2/public/500.html +1 -0
  134. data/test/rails/app-2.3.5/.gitignore +2 -0
  135. data/test/rails/app-2.3.5/Rakefile +7 -0
  136. data/test/rails/app-2.3.5/app/controllers/application_controller.rb +5 -0
  137. data/test/rails/app-2.3.5/app/controllers/foo_controller.rb +36 -0
  138. data/test/rails/app-2.3.5/app/helpers/application_helper.rb +4 -0
  139. data/test/rails/app-2.3.5/config/boot.rb +109 -0
  140. data/test/rails/app-2.3.5/config/database.yml +12 -0
  141. data/test/rails/app-2.3.5/config/environment.rb +17 -0
  142. data/test/rails/app-2.3.5/config/environments/development.rb +7 -0
  143. data/test/rails/app-2.3.5/config/environments/production.rb +6 -0
  144. data/test/rails/app-2.3.5/config/routes.rb +6 -0
  145. data/test/rails/app-2.3.5/db/.gitignore +0 -0
  146. data/test/rails/app-2.3.5/public/404.html +1 -0
  147. data/test/rails/app-2.3.5/public/500.html +1 -0
  148. data/test/rails/app-2.3.5/public/x.txt +1 -0
  149. data/test/rails/test_rails.rb +280 -0
  150. data/test/test_helper.rb +301 -0
  151. data/test/unit/test_configurator.rb +150 -0
  152. data/test/unit/test_http_parser.rb +555 -0
  153. data/test/unit/test_http_parser_ng.rb +443 -0
  154. data/test/unit/test_request.rb +184 -0
  155. data/test/unit/test_response.rb +110 -0
  156. data/test/unit/test_server.rb +291 -0
  157. data/test/unit/test_signals.rb +206 -0
  158. data/test/unit/test_socket_helper.rb +147 -0
  159. data/test/unit/test_tee_input.rb +257 -0
  160. data/test/unit/test_upload.rb +298 -0
  161. data/test/unit/test_util.rb +96 -0
  162. data/unicorn.gemspec +52 -0
  163. 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