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.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/.CHANGELOG.old +25 -0
  3. data/.document +28 -0
  4. data/.gitattributes +5 -0
  5. data/.gitignore +25 -0
  6. data/.mailmap +26 -0
  7. data/.manifest +156 -0
  8. data/.olddoc.yml +18 -0
  9. data/Application_Timeouts +77 -0
  10. data/CONTRIBUTORS +35 -0
  11. data/COPYING +674 -0
  12. data/DESIGN +95 -0
  13. data/Documentation/.gitignore +5 -0
  14. data/Documentation/GNUmakefile +30 -0
  15. data/Documentation/unicorn.1.txt +187 -0
  16. data/Documentation/unicorn_rails.1.txt +175 -0
  17. data/FAQ +70 -0
  18. data/GIT-VERSION-FILE +1 -0
  19. data/GIT-VERSION-GEN +39 -0
  20. data/GNUmakefile +253 -0
  21. data/HACKING +120 -0
  22. data/ISSUES +90 -0
  23. data/KNOWN_ISSUES +79 -0
  24. data/LATEST +30 -0
  25. data/LICENSE +67 -0
  26. data/Links +56 -0
  27. data/NEWS +2465 -0
  28. data/PHILOSOPHY +139 -0
  29. data/README +138 -0
  30. data/Rakefile +16 -0
  31. data/SIGNALS +123 -0
  32. data/Sandbox +104 -0
  33. data/TODO +3 -0
  34. data/TUNING +119 -0
  35. data/archive/.gitignore +3 -0
  36. data/archive/slrnpull.conf +4 -0
  37. data/bin/unicorn +126 -0
  38. data/bin/unicorn_rails +209 -0
  39. data/examples/big_app_gc.rb +2 -0
  40. data/examples/echo.ru +27 -0
  41. data/examples/init.sh +102 -0
  42. data/examples/logger_mp_safe.rb +25 -0
  43. data/examples/logrotate.conf +44 -0
  44. data/examples/nginx.conf +155 -0
  45. data/examples/unicorn.conf.minimal.rb +13 -0
  46. data/examples/unicorn.conf.rb +110 -0
  47. data/examples/unicorn.socket +11 -0
  48. data/examples/unicorn@.service +33 -0
  49. data/ext/unicorn_http/CFLAGS +13 -0
  50. data/ext/unicorn_http/c_util.h +124 -0
  51. data/ext/unicorn_http/common_field_optimization.h +111 -0
  52. data/ext/unicorn_http/ext_help.h +62 -0
  53. data/ext/unicorn_http/extconf.rb +11 -0
  54. data/ext/unicorn_http/global_variables.h +97 -0
  55. data/ext/unicorn_http/httpdate.c +78 -0
  56. data/ext/unicorn_http/unicorn_http.c +4274 -0
  57. data/ext/unicorn_http/unicorn_http.rl +980 -0
  58. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  59. data/lib/unicorn/app/old_rails/static.rb +59 -0
  60. data/lib/unicorn/app/old_rails.rb +35 -0
  61. data/lib/unicorn/cgi_wrapper.rb +147 -0
  62. data/lib/unicorn/configurator.rb +664 -0
  63. data/lib/unicorn/const.rb +21 -0
  64. data/lib/unicorn/http_request.rb +122 -0
  65. data/lib/unicorn/http_response.rb +60 -0
  66. data/lib/unicorn/http_server.rb +824 -0
  67. data/lib/unicorn/launcher.rb +62 -0
  68. data/lib/unicorn/oob_gc.rb +82 -0
  69. data/lib/unicorn/preread_input.rb +33 -0
  70. data/lib/unicorn/socket_helper.rb +195 -0
  71. data/lib/unicorn/stream_input.rb +146 -0
  72. data/lib/unicorn/tee_input.rb +133 -0
  73. data/lib/unicorn/tmpio.rb +27 -0
  74. data/lib/unicorn/util.rb +90 -0
  75. data/lib/unicorn/version.rb +1 -0
  76. data/lib/unicorn/worker.rb +140 -0
  77. data/lib/unicorn.rb +123 -0
  78. data/man/man1/unicorn.1 +221 -0
  79. data/man/man1/unicorn_rails.1 +212 -0
  80. data/setup.rb +1586 -0
  81. data/t/.gitignore +4 -0
  82. data/t/GNUmakefile +74 -0
  83. data/t/README +42 -0
  84. data/t/bin/content-md5-put +36 -0
  85. data/t/bin/sha1sum.rb +17 -0
  86. data/t/bin/unused_listen +40 -0
  87. data/t/broken-app.ru +12 -0
  88. data/t/detach.ru +11 -0
  89. data/t/env.ru +3 -0
  90. data/t/fails-rack-lint.ru +5 -0
  91. data/t/heartbeat-timeout.ru +12 -0
  92. data/t/hijack.ru +43 -0
  93. data/t/listener_names.ru +4 -0
  94. data/t/my-tap-lib.sh +201 -0
  95. data/t/oob_gc.ru +20 -0
  96. data/t/oob_gc_path.ru +20 -0
  97. data/t/pid.ru +3 -0
  98. data/t/preread_input.ru +17 -0
  99. data/t/rack-input-tests.ru +21 -0
  100. data/t/t0000-http-basic.sh +50 -0
  101. data/t/t0001-reload-bad-config.sh +53 -0
  102. data/t/t0002-config-conflict.sh +49 -0
  103. data/t/t0002-parser-error.sh +94 -0
  104. data/t/t0003-working_directory.sh +51 -0
  105. data/t/t0004-heartbeat-timeout.sh +69 -0
  106. data/t/t0004-working_directory_broken.sh +24 -0
  107. data/t/t0005-working_directory_app.rb.sh +40 -0
  108. data/t/t0006-reopen-logs.sh +83 -0
  109. data/t/t0006.ru +13 -0
  110. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  111. data/t/t0008-back_out_of_upgrade.sh +110 -0
  112. data/t/t0009-broken-app.sh +56 -0
  113. data/t/t0009-winch_ttin.sh +59 -0
  114. data/t/t0010-reap-logging.sh +55 -0
  115. data/t/t0011-active-unix-socket.sh +79 -0
  116. data/t/t0012-reload-empty-config.sh +85 -0
  117. data/t/t0013-rewindable-input-false.sh +24 -0
  118. data/t/t0013.ru +12 -0
  119. data/t/t0014-rewindable-input-true.sh +24 -0
  120. data/t/t0014.ru +12 -0
  121. data/t/t0015-configurator-internals.sh +25 -0
  122. data/t/t0018-write-on-close.sh +23 -0
  123. data/t/t0019-max_header_len.sh +49 -0
  124. data/t/t0020-at_exit-handler.sh +49 -0
  125. data/t/t0021-process_detach.sh +29 -0
  126. data/t/t0022-listener_names-preload_app.sh +32 -0
  127. data/t/t0100-rack-input-tests.sh +124 -0
  128. data/t/t0116-client_body_buffer_size.sh +80 -0
  129. data/t/t0116.ru +16 -0
  130. data/t/t0200-rack-hijack.sh +30 -0
  131. data/t/t0300-no-default-middleware.sh +20 -0
  132. data/t/t9000-preread-input.sh +48 -0
  133. data/t/t9001-oob_gc.sh +47 -0
  134. data/t/t9002-oob_gc-path.sh +75 -0
  135. data/t/test-lib.sh +128 -0
  136. data/t/write-on-close.ru +11 -0
  137. data/test/aggregate.rb +15 -0
  138. data/test/benchmark/README +50 -0
  139. data/test/benchmark/dd.ru +18 -0
  140. data/test/benchmark/stack.ru +8 -0
  141. data/test/exec/README +5 -0
  142. data/test/exec/test_exec.rb +1099 -0
  143. data/test/test_helper.rb +298 -0
  144. data/test/unit/test_configurator.rb +175 -0
  145. data/test/unit/test_droplet.rb +28 -0
  146. data/test/unit/test_http_parser.rb +886 -0
  147. data/test/unit/test_http_parser_ng.rb +633 -0
  148. data/test/unit/test_request.rb +182 -0
  149. data/test/unit/test_response.rb +111 -0
  150. data/test/unit/test_server.rb +268 -0
  151. data/test/unit/test_signals.rb +188 -0
  152. data/test/unit/test_socket_helper.rb +197 -0
  153. data/test/unit/test_stream_input.rb +203 -0
  154. data/test/unit/test_tee_input.rb +304 -0
  155. data/test/unit/test_upload.rb +306 -0
  156. data/test/unit/test_util.rb +105 -0
  157. data/unicorn.gemspec +50 -0
  158. 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