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.
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