unicorn 4.4.0 → 4.5.0pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/GIT-VERSION-GEN +1 -1
- data/Rakefile +2 -2
- data/examples/unicorn.conf.rb +8 -0
- data/ext/unicorn_http/unicorn_http.rl +3 -1
- data/lib/unicorn/configurator.rb +24 -0
- data/lib/unicorn/const.rb +9 -5
- data/lib/unicorn/http_request.rb +19 -0
- data/lib/unicorn/http_response.rb +4 -2
- data/lib/unicorn/http_server.rb +21 -2
- data/test/exec/test_exec.rb +6 -1
- data/test/test_helper.rb +14 -17
- data/test/unit/test_configurator.rb +29 -12
- data/test/unit/test_http_parser.rb +2 -8
- data/test/unit/test_http_parser_ng.rb +5 -7
- data/test/unit/test_request.rb +24 -39
- data/test/unit/test_server.rb +49 -70
- data/test/unit/test_signals.rb +37 -56
- data/test/unit/test_socket_helper.rb +12 -17
- data/test/unit/test_stream_input.rb +4 -5
- data/test/unit/test_tee_input.rb +11 -13
- data/test/unit/test_upload.rb +1 -1
- metadata +13 -9
data/GIT-VERSION-GEN
CHANGED
data/Rakefile
CHANGED
@@ -49,7 +49,7 @@ task :fm_update do
|
|
49
49
|
require 'net/netrc'
|
50
50
|
require 'json'
|
51
51
|
version = ENV['VERSION'] or abort "VERSION= needed"
|
52
|
-
uri = URI.parse('
|
52
|
+
uri = URI.parse('https://freecode.com/projects/unicorn/releases.json')
|
53
53
|
rc = Net::Netrc.locate('unicorn-fm') or abort "~/.netrc not found"
|
54
54
|
api_token = rc.password
|
55
55
|
_, subject, body = `git cat-file tag v#{version}`.split(/\n\n/, 3)
|
@@ -71,7 +71,7 @@ task :fm_update do
|
|
71
71
|
}.to_json
|
72
72
|
|
73
73
|
if ! changelog.strip.empty? && version =~ %r{\A[\d\.]+\d+\z}
|
74
|
-
Net::HTTP.start(uri.host, uri.port) do |http|
|
74
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => true) do |http|
|
75
75
|
p http.post(uri.path, req, {'Content-Type'=>'application/json'})
|
76
76
|
end
|
77
77
|
else
|
data/examples/unicorn.conf.rb
CHANGED
@@ -46,6 +46,14 @@ preload_app true
|
|
46
46
|
GC.respond_to?(:copy_on_write_friendly=) and
|
47
47
|
GC.copy_on_write_friendly = true
|
48
48
|
|
49
|
+
# Enable this flag to have unicorn test client connections by writing the
|
50
|
+
# beginning of the HTTP headers before calling the application. This
|
51
|
+
# prevents calling the application for connections that have disconnected
|
52
|
+
# while queued. This is only guaranteed to detect clients on the same
|
53
|
+
# host unicorn runs on, and unlikely to detect disconnects even on a
|
54
|
+
# fast LAN.
|
55
|
+
check_client_connection false
|
56
|
+
|
49
57
|
before_fork do |server, worker|
|
50
58
|
# the following is highly recomended for Rails + "preload_app true"
|
51
59
|
# as there's no need for the master process to hold a connection
|
@@ -115,7 +115,7 @@ struct http_parser {
|
|
115
115
|
} len;
|
116
116
|
};
|
117
117
|
|
118
|
-
static ID id_clear, id_set_backtrace;
|
118
|
+
static ID id_clear, id_set_backtrace, id_response_start_sent;
|
119
119
|
|
120
120
|
static void finalize_header(struct http_parser *hp);
|
121
121
|
|
@@ -626,6 +626,7 @@ static VALUE HttpParser_clear(VALUE self)
|
|
626
626
|
|
627
627
|
http_parser_init(hp);
|
628
628
|
rb_funcall(hp->env, id_clear, 0);
|
629
|
+
rb_ivar_set(self, id_response_start_sent, Qfalse);
|
629
630
|
|
630
631
|
return self;
|
631
632
|
}
|
@@ -1031,6 +1032,7 @@ void Init_unicorn_http(void)
|
|
1031
1032
|
SET_GLOBAL(g_http_connection, "CONNECTION");
|
1032
1033
|
id_clear = rb_intern("clear");
|
1033
1034
|
id_set_backtrace = rb_intern("set_backtrace");
|
1035
|
+
id_response_start_sent = rb_intern("@response_start_sent");
|
1034
1036
|
init_unicorn_httpdate();
|
1035
1037
|
}
|
1036
1038
|
#undef SET_GLOBAL
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -45,6 +45,7 @@ class Unicorn::Configurator
|
|
45
45
|
},
|
46
46
|
:pid => nil,
|
47
47
|
:preload_app => false,
|
48
|
+
:check_client_connection => false,
|
48
49
|
:rewindable_input => true, # for Rack 2.x: (Rack::VERSION[0] <= 1),
|
49
50
|
:client_body_buffer_size => Unicorn::Const::MAX_BODY,
|
50
51
|
:trust_x_forwarded => true,
|
@@ -96,6 +97,14 @@ class Unicorn::Configurator
|
|
96
97
|
if ready_pipe = RACKUP.delete(:ready_pipe)
|
97
98
|
server.ready_pipe = ready_pipe
|
98
99
|
end
|
100
|
+
if set[:check_client_connection]
|
101
|
+
set[:listeners].each do |address|
|
102
|
+
if set[:listener_opts][address][:tcp_nopush] == true
|
103
|
+
raise ArgumentError,
|
104
|
+
"check_client_connection is incompatible with tcp_nopush:true"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
99
108
|
set.each do |key, value|
|
100
109
|
value == :unset and next
|
101
110
|
skip.include?(key) and next
|
@@ -454,6 +463,21 @@ class Unicorn::Configurator
|
|
454
463
|
set_int(:client_body_buffer_size, bytes, 0)
|
455
464
|
end
|
456
465
|
|
466
|
+
# When enabled, unicorn will check the client connection by writing
|
467
|
+
# the beginning of the HTTP headers before calling the application.
|
468
|
+
#
|
469
|
+
# This will prevent calling the application for clients who have
|
470
|
+
# disconnected while their connection was queued.
|
471
|
+
#
|
472
|
+
# This only affects clients connecting over Unix domain sockets
|
473
|
+
# and TCP via loopback (127.*.*.*). It is unlikely to detect
|
474
|
+
# disconnects if the client is on a remote host (even on a fast LAN).
|
475
|
+
#
|
476
|
+
# This option cannot be used in conjunction with :tcp_nopush.
|
477
|
+
def check_client_connection(bool)
|
478
|
+
set_bool(:check_client_connection, bool)
|
479
|
+
end
|
480
|
+
|
457
481
|
# Allow redirecting $stderr to a given path. Unlike doing this from
|
458
482
|
# the shell, this allows the unicorn process to know the path its
|
459
483
|
# writing to and rotate the file if it is used for logging. The
|
data/lib/unicorn/const.rb
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
# improve things much compared to constants.
|
9
9
|
module Unicorn::Const
|
10
10
|
|
11
|
-
UNICORN_VERSION = "4.
|
11
|
+
UNICORN_VERSION = "4.5.0pre1"
|
12
12
|
|
13
13
|
# default TCP listen host address (0.0.0.0, all interfaces)
|
14
14
|
DEFAULT_HOST = "0.0.0.0"
|
@@ -29,12 +29,16 @@ module Unicorn::Const
|
|
29
29
|
|
30
30
|
# :stopdoc:
|
31
31
|
# common errors we'll send back
|
32
|
-
ERROR_400_RESPONSE = "
|
33
|
-
ERROR_414_RESPONSE = "
|
34
|
-
ERROR_413_RESPONSE = "
|
35
|
-
ERROR_500_RESPONSE = "
|
32
|
+
ERROR_400_RESPONSE = "400 Bad Request\r\n\r\n"
|
33
|
+
ERROR_414_RESPONSE = "414 Request-URI Too Long\r\n\r\n"
|
34
|
+
ERROR_413_RESPONSE = "413 Request Entity Too Large\r\n\r\n"
|
35
|
+
ERROR_500_RESPONSE = "500 Internal Server Error\r\n\r\n"
|
36
|
+
|
36
37
|
EXPECT_100_RESPONSE = "HTTP/1.1 100 Continue\r\n\r\n"
|
38
|
+
EXPECT_100_RESPONSE_SUFFIXED = "100 Continue\r\n\r\nHTTP/1.1 "
|
37
39
|
|
40
|
+
HTTP_RESPONSE_START = ['HTTP', '/1.1 ']
|
38
41
|
HTTP_EXPECT = "HTTP_EXPECT"
|
42
|
+
|
39
43
|
# :startdoc:
|
40
44
|
end
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -22,11 +22,14 @@ class Unicorn::HttpParser
|
|
22
22
|
|
23
23
|
NULL_IO = StringIO.new("")
|
24
24
|
|
25
|
+
attr_accessor :response_start_sent
|
26
|
+
|
25
27
|
# :stopdoc:
|
26
28
|
# A frozen format for this is about 15% faster
|
27
29
|
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
28
30
|
RACK_INPUT = 'rack.input'.freeze
|
29
31
|
@@input_class = Unicorn::TeeInput
|
32
|
+
@@check_client_connection = false
|
30
33
|
|
31
34
|
def self.input_class
|
32
35
|
@@input_class
|
@@ -35,6 +38,15 @@ class Unicorn::HttpParser
|
|
35
38
|
def self.input_class=(klass)
|
36
39
|
@@input_class = klass
|
37
40
|
end
|
41
|
+
|
42
|
+
def self.check_client_connection
|
43
|
+
@@check_client_connection
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.check_client_connection=(bool)
|
47
|
+
@@check_client_connection = bool
|
48
|
+
end
|
49
|
+
|
38
50
|
# :startdoc:
|
39
51
|
|
40
52
|
# Does the majority of the IO processing. It has been written in
|
@@ -70,6 +82,13 @@ class Unicorn::HttpParser
|
|
70
82
|
# an Exception thrown from the parser will throw us out of the loop
|
71
83
|
false until add_parse(socket.kgio_read!(16384))
|
72
84
|
end
|
85
|
+
|
86
|
+
# detect if the socket is valid by writing a partial response:
|
87
|
+
if @@check_client_connection && headers?
|
88
|
+
@response_start_sent = true
|
89
|
+
Unicorn::Const::HTTP_RESPONSE_START.each { |c| socket.write(c) }
|
90
|
+
end
|
91
|
+
|
73
92
|
e[RACK_INPUT] = 0 == content_length ?
|
74
93
|
NULL_IO : @@input_class.new(socket, self)
|
75
94
|
e.merge!(DEFAULTS)
|
@@ -18,11 +18,13 @@ module Unicorn::HttpResponse
|
|
18
18
|
CRLF = "\r\n"
|
19
19
|
|
20
20
|
# writes the rack_response to socket as an HTTP response
|
21
|
-
def http_response_write(socket, status, headers, body
|
21
|
+
def http_response_write(socket, status, headers, body,
|
22
|
+
response_start_sent=false)
|
22
23
|
status = CODES[status.to_i] || status
|
23
24
|
|
25
|
+
http_response_start = response_start_sent ? '' : 'HTTP/1.1 '
|
24
26
|
if headers
|
25
|
-
buf = "
|
27
|
+
buf = "#{http_response_start}#{status}\r\n" \
|
26
28
|
"Date: #{httpdate}\r\n" \
|
27
29
|
"Status: #{status}\r\n" \
|
28
30
|
"Connection: close\r\n"
|
data/lib/unicorn/http_server.rb
CHANGED
@@ -17,6 +17,7 @@ class Unicorn::HttpServer
|
|
17
17
|
:listener_opts, :preload_app,
|
18
18
|
:reexec_pid, :orig_app, :init_listeners,
|
19
19
|
:master_pid, :config, :ready_pipe, :user
|
20
|
+
|
20
21
|
attr_reader :pid, :logger
|
21
22
|
include Unicorn::SocketHelper
|
22
23
|
include Unicorn::HttpResponse
|
@@ -355,6 +356,14 @@ class Unicorn::HttpServer
|
|
355
356
|
Unicorn::HttpParser.trust_x_forwarded = bool
|
356
357
|
end
|
357
358
|
|
359
|
+
def check_client_connection
|
360
|
+
Unicorn::HttpRequest.check_client_connection
|
361
|
+
end
|
362
|
+
|
363
|
+
def check_client_connection=(bool)
|
364
|
+
Unicorn::HttpRequest.check_client_connection = bool
|
365
|
+
end
|
366
|
+
|
358
367
|
private
|
359
368
|
|
360
369
|
# wait for a signal hander to wake us up and then consume the pipe
|
@@ -524,23 +533,33 @@ class Unicorn::HttpServer
|
|
524
533
|
Unicorn.log_error(@logger, "app error", e)
|
525
534
|
Unicorn::Const::ERROR_500_RESPONSE
|
526
535
|
end
|
536
|
+
msg = "HTTP/1.1 #{msg}" unless @request.response_start_sent
|
527
537
|
client.kgio_trywrite(msg)
|
528
538
|
client.close
|
529
539
|
rescue
|
530
540
|
end
|
531
541
|
|
542
|
+
def expect_100_response
|
543
|
+
if @request.response_start_sent
|
544
|
+
Unicorn::Const::EXPECT_100_RESPONSE_SUFFIXED
|
545
|
+
else
|
546
|
+
Unicorn::Const::EXPECT_100_RESPONSE
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
532
550
|
# once a client is accepted, it is processed in its entirety here
|
533
551
|
# in 3 easy steps: read request, call app, write app response
|
534
552
|
def process_client(client)
|
535
553
|
status, headers, body = @app.call(env = @request.read(client))
|
536
554
|
|
537
555
|
if 100 == status.to_i
|
538
|
-
client.write(
|
556
|
+
client.write(expect_100_response)
|
539
557
|
env.delete(Unicorn::Const::HTTP_EXPECT)
|
540
558
|
status, headers, body = @app.call(env)
|
541
559
|
end
|
542
560
|
@request.headers? or headers = nil
|
543
|
-
http_response_write(client, status, headers, body
|
561
|
+
http_response_write(client, status, headers, body,
|
562
|
+
@request.response_start_sent)
|
544
563
|
client.shutdown # in case of fork() in Rack app
|
545
564
|
client.close # flush and uncork socket immediately, no keepalive
|
546
565
|
rescue => e
|
data/test/exec/test_exec.rb
CHANGED
@@ -871,13 +871,14 @@ EOF
|
|
871
871
|
wait_for_death(pid)
|
872
872
|
end
|
873
873
|
|
874
|
-
def hup_test_common(preload)
|
874
|
+
def hup_test_common(preload, check_client=false)
|
875
875
|
File.open("config.ru", "wb") { |fp| fp.syswrite(HI.gsub("HI", '#$$')) }
|
876
876
|
pid_file = Tempfile.new('pid')
|
877
877
|
ucfg = Tempfile.new('unicorn_test_config')
|
878
878
|
ucfg.syswrite("listen '#@addr:#@port'\n")
|
879
879
|
ucfg.syswrite("pid '#{pid_file.path}'\n")
|
880
880
|
ucfg.syswrite("preload_app true\n") if preload
|
881
|
+
ucfg.syswrite("check_client_connection true\n") if check_client
|
881
882
|
ucfg.syswrite("stderr_path 'test_stderr.#$$.log'\n")
|
882
883
|
ucfg.syswrite("stdout_path 'test_stdout.#$$.log'\n")
|
883
884
|
pid = xfork {
|
@@ -942,6 +943,10 @@ EOF
|
|
942
943
|
hup_test_common(false)
|
943
944
|
end
|
944
945
|
|
946
|
+
def test_check_client_hup
|
947
|
+
hup_test_common(false, true)
|
948
|
+
end
|
949
|
+
|
945
950
|
def test_default_listen_hup_holds_listener
|
946
951
|
default_listen_lock do
|
947
952
|
res, pid_path = default_listen_setup
|
data/test/test_helper.rb
CHANGED
@@ -155,9 +155,8 @@ end
|
|
155
155
|
|
156
156
|
def assert_shutdown(pid)
|
157
157
|
wait_master_ready("test_stderr.#{pid}.log")
|
158
|
-
|
159
|
-
status =
|
160
|
-
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
158
|
+
Process.kill(:QUIT, pid)
|
159
|
+
pid, status = Process.waitpid2(pid)
|
161
160
|
assert status.success?, "exited successfully"
|
162
161
|
end
|
163
162
|
|
@@ -191,8 +190,8 @@ end
|
|
191
190
|
def reexec_usr2_quit_test(pid, pid_file)
|
192
191
|
assert File.exist?(pid_file), "pid file OK"
|
193
192
|
assert ! File.exist?("#{pid_file}.oldbin"), "oldbin pid file"
|
194
|
-
|
195
|
-
|
193
|
+
Process.kill(:USR2, pid)
|
194
|
+
retry_hit(["http://#{@addr}:#{@port}/"])
|
196
195
|
wait_for_file("#{pid_file}.oldbin")
|
197
196
|
wait_for_file(pid_file)
|
198
197
|
|
@@ -202,35 +201,33 @@ def reexec_usr2_quit_test(pid, pid_file)
|
|
202
201
|
# kill old master process
|
203
202
|
assert_not_equal pid, new_pid
|
204
203
|
assert_equal pid, old_pid
|
205
|
-
|
206
|
-
|
204
|
+
Process.kill(:QUIT, old_pid)
|
205
|
+
retry_hit(["http://#{@addr}:#{@port}/"])
|
207
206
|
wait_for_death(old_pid)
|
208
207
|
assert_equal new_pid, File.read(pid_file).to_i
|
209
|
-
|
210
|
-
|
208
|
+
retry_hit(["http://#{@addr}:#{@port}/"])
|
209
|
+
Process.kill(:QUIT, new_pid)
|
211
210
|
end
|
212
211
|
|
213
212
|
def reexec_basic_test(pid, pid_file)
|
214
213
|
results = retry_hit(["http://#{@addr}:#{@port}/"])
|
215
214
|
assert_equal String, results[0].class
|
216
|
-
|
215
|
+
Process.kill(0, pid)
|
217
216
|
master_log = "#{@tmpdir}/test_stderr.#{pid}.log"
|
218
217
|
wait_master_ready(master_log)
|
219
218
|
File.truncate(master_log, 0)
|
220
219
|
nr = 50
|
221
220
|
kill_point = 2
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
i == kill_point and Process.kill(:HUP, pid)
|
226
|
-
end
|
221
|
+
nr.times do |i|
|
222
|
+
hit(["http://#{@addr}:#{@port}/#{i}"])
|
223
|
+
i == kill_point and Process.kill(:HUP, pid)
|
227
224
|
end
|
228
225
|
wait_master_ready(master_log)
|
229
226
|
assert File.exist?(pid_file), "pid=#{pid_file} exists"
|
230
227
|
new_pid = File.read(pid_file).to_i
|
231
228
|
assert_not_equal pid, new_pid
|
232
|
-
|
233
|
-
|
229
|
+
Process.kill(0, new_pid)
|
230
|
+
Process.kill(:QUIT, new_pid)
|
234
231
|
end
|
235
232
|
|
236
233
|
def wait_for_file(path)
|
@@ -9,7 +9,7 @@ TestStruct = Struct.new(
|
|
9
9
|
class TestConfigurator < Test::Unit::TestCase
|
10
10
|
|
11
11
|
def test_config_init
|
12
|
-
|
12
|
+
Unicorn::Configurator.new {}
|
13
13
|
end
|
14
14
|
|
15
15
|
def test_expand_addr
|
@@ -66,7 +66,7 @@ class TestConfigurator < Test::Unit::TestCase
|
|
66
66
|
def test_config_defaults
|
67
67
|
cfg = Unicorn::Configurator.new(:use_defaults => true)
|
68
68
|
test_struct = TestStruct.new
|
69
|
-
|
69
|
+
cfg.commit!(test_struct)
|
70
70
|
Unicorn::Configurator::DEFAULTS.each do |key,value|
|
71
71
|
assert_equal value, test_struct.__send__(key)
|
72
72
|
end
|
@@ -76,7 +76,7 @@ class TestConfigurator < Test::Unit::TestCase
|
|
76
76
|
cfg = Unicorn::Configurator.new(:use_defaults => true)
|
77
77
|
skip = [ :logger ]
|
78
78
|
test_struct = TestStruct.new
|
79
|
-
|
79
|
+
cfg.commit!(test_struct, :skip => skip)
|
80
80
|
Unicorn::Configurator::DEFAULTS.each do |key,value|
|
81
81
|
next if skip.include?(key)
|
82
82
|
assert_equal value, test_struct.__send__(key)
|
@@ -89,12 +89,9 @@ class TestConfigurator < Test::Unit::TestCase
|
|
89
89
|
expect = { :sndbuf => 1, :rcvbuf => 2, :backlog => 10 }.freeze
|
90
90
|
listener = "127.0.0.1:12345"
|
91
91
|
tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
|
92
|
-
cfg =
|
93
|
-
assert_nothing_raised do
|
94
|
-
cfg = Unicorn::Configurator.new(:config_file => tmp.path)
|
95
|
-
end
|
92
|
+
cfg = Unicorn::Configurator.new(:config_file => tmp.path)
|
96
93
|
test_struct = TestStruct.new
|
97
|
-
|
94
|
+
cfg.commit!(test_struct)
|
98
95
|
assert(listener_opts = test_struct.listener_opts)
|
99
96
|
assert_equal expect, listener_opts[listener]
|
100
97
|
end
|
@@ -124,9 +121,7 @@ class TestConfigurator < Test::Unit::TestCase
|
|
124
121
|
expect = { :delay => 0.5 }
|
125
122
|
listener = "127.0.0.1:12345"
|
126
123
|
tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
|
127
|
-
|
128
|
-
Unicorn::Configurator.new(:config_file => tmp.path)
|
129
|
-
end
|
124
|
+
Unicorn::Configurator.new(:config_file => tmp.path)
|
130
125
|
end
|
131
126
|
|
132
127
|
def test_listen_option_int_delay
|
@@ -134,8 +129,30 @@ class TestConfigurator < Test::Unit::TestCase
|
|
134
129
|
expect = { :delay => 5 }
|
135
130
|
listener = "127.0.0.1:12345"
|
136
131
|
tmp.syswrite("listen '#{listener}', #{expect.inspect}\n")
|
132
|
+
Unicorn::Configurator.new(:config_file => tmp.path)
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_check_client_connection
|
136
|
+
tmp = Tempfile.new('unicorn_config')
|
137
|
+
test_struct = TestStruct.new
|
138
|
+
tmp.syswrite("check_client_connection true\n")
|
139
|
+
|
137
140
|
assert_nothing_raised do
|
138
|
-
Unicorn::Configurator.new(:config_file => tmp.path)
|
141
|
+
Unicorn::Configurator.new(:config_file => tmp.path).commit!(test_struct)
|
142
|
+
end
|
143
|
+
|
144
|
+
assert test_struct.check_client_connection
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_check_client_connection_with_tcp_bad
|
148
|
+
tmp = Tempfile.new('unicorn_config')
|
149
|
+
test_struct = TestStruct.new
|
150
|
+
listener = "127.0.0.1:12345"
|
151
|
+
tmp.syswrite("check_client_connection true\n")
|
152
|
+
tmp.syswrite("listen '#{listener}', :tcp_nopush => true\n")
|
153
|
+
|
154
|
+
assert_raises(ArgumentError) do
|
155
|
+
Unicorn::Configurator.new(:config_file => tmp.path).commit!(test_struct)
|
139
156
|
end
|
140
157
|
end
|
141
158
|
|
@@ -689,10 +689,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
689
689
|
parser = HttpParser.new
|
690
690
|
req = parser.env
|
691
691
|
s = "#{m} /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
|
692
|
-
ok =
|
693
|
-
assert_nothing_raised do
|
694
|
-
ok = parser.headers(req, s)
|
695
|
-
end
|
692
|
+
ok = parser.headers(req, s)
|
696
693
|
assert ok
|
697
694
|
assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
|
698
695
|
assert_equal 'posts-17408', req['FRAGMENT']
|
@@ -708,10 +705,7 @@ class HttpParserTest < Test::Unit::TestCase
|
|
708
705
|
req = parser.env
|
709
706
|
get = "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n\r\n"
|
710
707
|
parser.buf << get
|
711
|
-
ok =
|
712
|
-
assert_nothing_raised do
|
713
|
-
ok = parser.parse
|
714
|
-
end
|
708
|
+
ok = parser.parse
|
715
709
|
assert ok
|
716
710
|
assert_equal '/forums/1/topics/2375?page=1', req['REQUEST_URI']
|
717
711
|
assert_equal 'posts-17408', req['FRAGMENT']
|
@@ -34,7 +34,7 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
34
34
|
def test_connection_TE
|
35
35
|
@parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: TE\r\n"
|
36
36
|
@parser.buf << "TE: trailers\r\n\r\n"
|
37
|
-
|
37
|
+
@parser.parse
|
38
38
|
assert @parser.keepalive?
|
39
39
|
assert @parser.next?
|
40
40
|
end
|
@@ -94,10 +94,8 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
94
94
|
def test_default_keepalive_is_off
|
95
95
|
assert ! @parser.keepalive?
|
96
96
|
assert ! @parser.next?
|
97
|
-
|
98
|
-
|
99
|
-
@parser.parse
|
100
|
-
end
|
97
|
+
@parser.buf << "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
|
98
|
+
@parser.parse
|
101
99
|
assert @parser.keepalive?
|
102
100
|
@parser.clear
|
103
101
|
assert ! @parser.keepalive?
|
@@ -419,7 +417,7 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
419
417
|
req = @parser.env
|
420
418
|
assert_equal req, @parser.parse
|
421
419
|
assert_nil @parser.content_length
|
422
|
-
|
420
|
+
@parser.filter_body('', str)
|
423
421
|
assert ! @parser.keepalive?
|
424
422
|
end
|
425
423
|
|
@@ -427,7 +425,7 @@ class HttpParserNgTest < Test::Unit::TestCase
|
|
427
425
|
n = HttpParser::LENGTH_MAX
|
428
426
|
@parser.buf << "PUT / HTTP/1.1\r\nContent-Length: #{n}\r\n\r\n"
|
429
427
|
req = @parser.env
|
430
|
-
|
428
|
+
@parser.headers(req, @parser.buf)
|
431
429
|
assert_equal n, req['CONTENT_LENGTH'].to_i
|
432
430
|
assert ! @parser.keepalive?
|
433
431
|
end
|
data/test/unit/test_request.rb
CHANGED
@@ -30,47 +30,43 @@ class RequestTest < Test::Unit::TestCase
|
|
30
30
|
def test_options
|
31
31
|
client = MockRequest.new("OPTIONS * HTTP/1.1\r\n" \
|
32
32
|
"Host: foo\r\n\r\n")
|
33
|
-
|
34
|
-
assert_nothing_raised { env = @request.read(client) }
|
33
|
+
env = @request.read(client)
|
35
34
|
assert_equal '', env['REQUEST_PATH']
|
36
35
|
assert_equal '', env['PATH_INFO']
|
37
36
|
assert_equal '*', env['REQUEST_URI']
|
38
|
-
|
37
|
+
res = @lint.call(env)
|
39
38
|
end
|
40
39
|
|
41
40
|
def test_absolute_uri_with_query
|
42
41
|
client = MockRequest.new("GET http://e:3/x?y=z HTTP/1.1\r\n" \
|
43
42
|
"Host: foo\r\n\r\n")
|
44
|
-
|
45
|
-
assert_nothing_raised { env = @request.read(client) }
|
43
|
+
env = @request.read(client)
|
46
44
|
assert_equal '/x', env['REQUEST_PATH']
|
47
45
|
assert_equal '/x', env['PATH_INFO']
|
48
46
|
assert_equal 'y=z', env['QUERY_STRING']
|
49
|
-
|
47
|
+
res = @lint.call(env)
|
50
48
|
end
|
51
49
|
|
52
50
|
def test_absolute_uri_with_fragment
|
53
51
|
client = MockRequest.new("GET http://e:3/x#frag HTTP/1.1\r\n" \
|
54
52
|
"Host: foo\r\n\r\n")
|
55
|
-
|
56
|
-
assert_nothing_raised { env = @request.read(client) }
|
53
|
+
env = @request.read(client)
|
57
54
|
assert_equal '/x', env['REQUEST_PATH']
|
58
55
|
assert_equal '/x', env['PATH_INFO']
|
59
56
|
assert_equal '', env['QUERY_STRING']
|
60
57
|
assert_equal 'frag', env['FRAGMENT']
|
61
|
-
|
58
|
+
res = @lint.call(env)
|
62
59
|
end
|
63
60
|
|
64
61
|
def test_absolute_uri_with_query_and_fragment
|
65
62
|
client = MockRequest.new("GET http://e:3/x?a=b#frag HTTP/1.1\r\n" \
|
66
63
|
"Host: foo\r\n\r\n")
|
67
|
-
|
68
|
-
assert_nothing_raised { env = @request.read(client) }
|
64
|
+
env = @request.read(client)
|
69
65
|
assert_equal '/x', env['REQUEST_PATH']
|
70
66
|
assert_equal '/x', env['PATH_INFO']
|
71
67
|
assert_equal 'a=b', env['QUERY_STRING']
|
72
68
|
assert_equal 'frag', env['FRAGMENT']
|
73
|
-
|
69
|
+
res = @lint.call(env)
|
74
70
|
end
|
75
71
|
|
76
72
|
def test_absolute_uri_unsupported_schemes
|
@@ -82,48 +78,43 @@ class RequestTest < Test::Unit::TestCase
|
|
82
78
|
end
|
83
79
|
|
84
80
|
def test_x_forwarded_proto_https
|
85
|
-
res = env = nil
|
86
81
|
client = MockRequest.new("GET / HTTP/1.1\r\n" \
|
87
82
|
"X-Forwarded-Proto: https\r\n" \
|
88
83
|
"Host: foo\r\n\r\n")
|
89
|
-
|
84
|
+
env = @request.read(client)
|
90
85
|
assert_equal "https", env['rack.url_scheme']
|
91
|
-
|
86
|
+
res = @lint.call(env)
|
92
87
|
end
|
93
88
|
|
94
89
|
def test_x_forwarded_proto_http
|
95
|
-
res = env = nil
|
96
90
|
client = MockRequest.new("GET / HTTP/1.1\r\n" \
|
97
91
|
"X-Forwarded-Proto: http\r\n" \
|
98
92
|
"Host: foo\r\n\r\n")
|
99
|
-
|
93
|
+
env = @request.read(client)
|
100
94
|
assert_equal "http", env['rack.url_scheme']
|
101
|
-
|
95
|
+
res = @lint.call(env)
|
102
96
|
end
|
103
97
|
|
104
98
|
def test_x_forwarded_proto_invalid
|
105
|
-
res = env = nil
|
106
99
|
client = MockRequest.new("GET / HTTP/1.1\r\n" \
|
107
100
|
"X-Forwarded-Proto: ftp\r\n" \
|
108
101
|
"Host: foo\r\n\r\n")
|
109
|
-
|
102
|
+
env = @request.read(client)
|
110
103
|
assert_equal "http", env['rack.url_scheme']
|
111
|
-
|
104
|
+
res = @lint.call(env)
|
112
105
|
end
|
113
106
|
|
114
107
|
def test_rack_lint_get
|
115
108
|
client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
|
116
|
-
|
117
|
-
assert_nothing_raised { env = @request.read(client) }
|
109
|
+
env = @request.read(client)
|
118
110
|
assert_equal "http", env['rack.url_scheme']
|
119
111
|
assert_equal '127.0.0.1', env['REMOTE_ADDR']
|
120
|
-
|
112
|
+
res = @lint.call(env)
|
121
113
|
end
|
122
114
|
|
123
115
|
def test_no_content_stringio
|
124
116
|
client = MockRequest.new("GET / HTTP/1.1\r\nHost: foo\r\n\r\n")
|
125
|
-
env =
|
126
|
-
assert_nothing_raised { env = @request.read(client) }
|
117
|
+
env = @request.read(client)
|
127
118
|
assert_equal StringIO, env['rack.input'].class
|
128
119
|
end
|
129
120
|
|
@@ -131,8 +122,7 @@ class RequestTest < Test::Unit::TestCase
|
|
131
122
|
client = MockRequest.new("PUT / HTTP/1.1\r\n" \
|
132
123
|
"Content-Length: 0\r\n" \
|
133
124
|
"Host: foo\r\n\r\n")
|
134
|
-
env =
|
135
|
-
assert_nothing_raised { env = @request.read(client) }
|
125
|
+
env = @request.read(client)
|
136
126
|
assert_equal StringIO, env['rack.input'].class
|
137
127
|
end
|
138
128
|
|
@@ -140,8 +130,7 @@ class RequestTest < Test::Unit::TestCase
|
|
140
130
|
client = MockRequest.new("PUT / HTTP/1.1\r\n" \
|
141
131
|
"Content-Length: 1\r\n" \
|
142
132
|
"Host: foo\r\n\r\n")
|
143
|
-
env =
|
144
|
-
assert_nothing_raised { env = @request.read(client) }
|
133
|
+
env = @request.read(client)
|
145
134
|
assert_equal Unicorn::TeeInput, env['rack.input'].class
|
146
135
|
end
|
147
136
|
|
@@ -152,10 +141,9 @@ class RequestTest < Test::Unit::TestCase
|
|
152
141
|
"Content-Length: 5\r\n" \
|
153
142
|
"\r\n" \
|
154
143
|
"abcde")
|
155
|
-
|
156
|
-
assert_nothing_raised { env = @request.read(client) }
|
144
|
+
env = @request.read(client)
|
157
145
|
assert ! env.include?(:http_body)
|
158
|
-
|
146
|
+
res = @lint.call(env)
|
159
147
|
end
|
160
148
|
|
161
149
|
def test_rack_lint_big_put
|
@@ -179,8 +167,7 @@ class RequestTest < Test::Unit::TestCase
|
|
179
167
|
"\r\n")
|
180
168
|
count.times { assert_equal bs, client.syswrite(buf) }
|
181
169
|
assert_equal 0, client.sysseek(0)
|
182
|
-
|
183
|
-
assert_nothing_raised { env = @request.read(client) }
|
170
|
+
env = @request.read(client)
|
184
171
|
assert ! env.include?(:http_body)
|
185
172
|
assert_equal length, env['rack.input'].size
|
186
173
|
count.times {
|
@@ -189,9 +176,7 @@ class RequestTest < Test::Unit::TestCase
|
|
189
176
|
assert_equal buf, tmp
|
190
177
|
}
|
191
178
|
assert_nil env['rack.input'].read(bs)
|
192
|
-
|
193
|
-
|
179
|
+
env['rack.input'].rewind
|
180
|
+
res = @lint.call(env)
|
194
181
|
end
|
195
|
-
|
196
182
|
end
|
197
|
-
|
data/test/unit/test_server.rb
CHANGED
@@ -92,14 +92,10 @@ class WebServerTest < Test::Unit::TestCase
|
|
92
92
|
@server = HttpServer.new(app, :listeners => [ "127.0.0.1:#@port"] )
|
93
93
|
@server.start
|
94
94
|
end
|
95
|
-
sock =
|
96
|
-
|
97
|
-
sock = TCPSocket.new('127.0.0.1', @port)
|
98
|
-
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
99
|
-
end
|
100
|
-
|
95
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
96
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
101
97
|
assert_match %r{\AHTTP/1.[01] 500\b}, sock.sysread(4096)
|
102
|
-
|
98
|
+
assert_nil sock.close
|
103
99
|
end
|
104
100
|
|
105
101
|
def test_simple_server
|
@@ -108,56 +104,48 @@ class WebServerTest < Test::Unit::TestCase
|
|
108
104
|
end
|
109
105
|
|
110
106
|
def test_client_shutdown_writes
|
111
|
-
sock = nil
|
112
|
-
buf = nil
|
113
107
|
bs = 15609315 * rand
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
sleep 0.05
|
127
|
-
end
|
128
|
-
# we wrote the entire request before shutting down, server should
|
129
|
-
# continue to process our request and never hit EOFError on our sock
|
130
|
-
sock.shutdown(Socket::SHUT_WR)
|
131
|
-
buf = sock.read
|
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
|
132
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
|
133
125
|
assert_equal 'hello!\n', buf.split(/\r\n\r\n/).last
|
134
126
|
next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
|
135
127
|
assert_equal 'hello!\n', next_client
|
136
128
|
lines = File.readlines("test_stderr.#$$.log")
|
137
129
|
assert lines.grep(/^Unicorn::ClientShutdown: /).empty?
|
138
|
-
|
130
|
+
assert_nil sock.close
|
139
131
|
end
|
140
132
|
|
141
133
|
def test_client_shutdown_write_truncates
|
142
|
-
sock = nil
|
143
|
-
buf = nil
|
144
134
|
bs = 15609315 * rand
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
sock.syswrite("F" * (bs / 2.0))
|
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))
|
154
143
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end
|
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
|
161
149
|
assert_equal "", buf
|
162
150
|
next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
|
163
151
|
assert_equal 'hello!\n', next_client
|
@@ -165,27 +153,24 @@ class WebServerTest < Test::Unit::TestCase
|
|
165
153
|
lines = lines.grep(/^Unicorn::ClientShutdown: bytes_read=\d+/)
|
166
154
|
assert_equal 1, lines.size
|
167
155
|
assert_match %r{\AUnicorn::ClientShutdown: bytes_read=\d+ true$}, lines[0]
|
168
|
-
|
156
|
+
assert_nil sock.close
|
169
157
|
end
|
170
158
|
|
171
159
|
def test_client_malformed_body
|
172
|
-
sock = nil
|
173
160
|
bs = 15653984
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
sock.syswrite("F" * bs)
|
183
|
-
end
|
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)
|
184
169
|
begin
|
185
170
|
File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
|
186
171
|
rescue
|
187
172
|
end
|
188
|
-
|
173
|
+
assert_nil sock.close
|
189
174
|
next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
|
190
175
|
assert_equal 'hello!\n', next_client
|
191
176
|
lines = File.readlines("test_stderr.#$$.log")
|
@@ -240,23 +225,17 @@ class WebServerTest < Test::Unit::TestCase
|
|
240
225
|
end
|
241
226
|
|
242
227
|
def test_bad_client_400
|
243
|
-
sock =
|
244
|
-
|
245
|
-
sock = TCPSocket.new('127.0.0.1', @port)
|
246
|
-
sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
|
247
|
-
end
|
228
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
229
|
+
sock.syswrite("GET / HTTP/1.0\r\nHost: foo\rbar\r\n\r\n")
|
248
230
|
assert_match %r{\AHTTP/1.[01] 400\b}, sock.sysread(4096)
|
249
|
-
|
231
|
+
assert_nil sock.close
|
250
232
|
end
|
251
233
|
|
252
234
|
def test_http_0_9
|
253
|
-
sock =
|
254
|
-
|
255
|
-
sock = TCPSocket.new('127.0.0.1', @port)
|
256
|
-
sock.syswrite("GET /hello\r\n")
|
257
|
-
end
|
235
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
236
|
+
sock.syswrite("GET /hello\r\n")
|
258
237
|
assert_match 'hello!\n', sock.sysread(4096)
|
259
|
-
|
238
|
+
assert_nil sock.close
|
260
239
|
end
|
261
240
|
|
262
241
|
def test_header_is_too_long
|
data/test/unit/test_signals.rb
CHANGED
@@ -51,22 +51,19 @@ class SignalsTest < Test::Unit::TestCase
|
|
51
51
|
opts = @server_opts.merge(:timeout => 3)
|
52
52
|
redirect_test_io { HttpServer.new(app, opts).start.join }
|
53
53
|
}
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
|
68
|
-
t0 = Time.now
|
69
|
-
end
|
54
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
55
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
56
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
57
|
+
buf = sock.readpartial(4096)
|
58
|
+
assert_nil sock.close
|
59
|
+
buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
|
60
|
+
child = $1.to_i
|
61
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
62
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
63
|
+
Process.kill(:KILL, pid)
|
64
|
+
Process.waitpid(pid)
|
65
|
+
File.unlink("test_stderr.#{pid}.log", "test_stdout.#{pid}.log")
|
66
|
+
t0 = Time.now
|
70
67
|
assert child
|
71
68
|
assert t0
|
72
69
|
assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
|
@@ -80,17 +77,14 @@ class SignalsTest < Test::Unit::TestCase
|
|
80
77
|
app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
|
81
78
|
redirect_test_io { HttpServer.new(app, @server_opts).start.join }
|
82
79
|
}
|
83
|
-
sock = buf = nil
|
84
80
|
wr.close
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
Process.waitpid(pid)
|
93
|
-
end
|
81
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
82
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
83
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
84
|
+
buf = rd.readpartial(1)
|
85
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
86
|
+
Process.kill(:INT, pid)
|
87
|
+
Process.waitpid(pid)
|
94
88
|
assert_equal '.', buf
|
95
89
|
buf = nil
|
96
90
|
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
@@ -98,7 +92,6 @@ class SignalsTest < Test::Unit::TestCase
|
|
98
92
|
buf = sock.sysread(4096)
|
99
93
|
end
|
100
94
|
assert_nil buf
|
101
|
-
ensure
|
102
95
|
end
|
103
96
|
|
104
97
|
def test_timeout_slow_response
|
@@ -108,12 +101,9 @@ class SignalsTest < Test::Unit::TestCase
|
|
108
101
|
redirect_test_io { HttpServer.new(app, opts).start.join }
|
109
102
|
}
|
110
103
|
t0 = Time.now
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
sock = TCPSocket.new('127.0.0.1', @port)
|
115
|
-
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
116
|
-
end
|
104
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
105
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
106
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
117
107
|
|
118
108
|
buf = nil
|
119
109
|
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
@@ -134,19 +124,14 @@ class SignalsTest < Test::Unit::TestCase
|
|
134
124
|
Dd.new(@bs, @count) ]
|
135
125
|
}
|
136
126
|
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
sock = TCPSocket.new('127.0.0.1', @port)
|
141
|
-
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
142
|
-
end
|
127
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
128
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
129
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
143
130
|
buf = ''
|
144
131
|
header_len = pid = nil
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
|
149
|
-
end
|
132
|
+
buf = sock.sysread(16384, buf)
|
133
|
+
pid = buf[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
134
|
+
header_len = buf[/\A(.+?\r\n\r\n)/m, 1].size
|
150
135
|
assert pid > 0, "pid not positive: #{pid.inspect}"
|
151
136
|
read = buf.size
|
152
137
|
size_before = @tmp.stat.size
|
@@ -166,7 +151,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
166
151
|
got = read - header_len
|
167
152
|
expect = @bs * @count
|
168
153
|
assert_equal(expect, got, "expect=#{expect} got=#{got}")
|
169
|
-
|
154
|
+
assert_nil sock.close
|
170
155
|
end
|
171
156
|
|
172
157
|
def test_request_read
|
@@ -176,15 +161,12 @@ class SignalsTest < Test::Unit::TestCase
|
|
176
161
|
[ 200, {'Content-Type'=>'text/plain', 'X-Pid'=>Process.pid.to_s}, [] ]
|
177
162
|
}
|
178
163
|
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
186
|
-
sock.close
|
187
|
-
end
|
164
|
+
|
165
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
166
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
167
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
168
|
+
pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
169
|
+
assert_nil sock.close
|
188
170
|
|
189
171
|
assert pid > 0, "pid not positive: #{pid.inspect}"
|
190
172
|
sock = TCPSocket.new('127.0.0.1', @port)
|
@@ -201,7 +183,6 @@ class SignalsTest < Test::Unit::TestCase
|
|
201
183
|
# can't check for == since pending signals get merged
|
202
184
|
assert size_before < @tmp.stat.size
|
203
185
|
assert_equal pid, sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
204
|
-
sock.close
|
186
|
+
assert_nil sock.close
|
205
187
|
end
|
206
|
-
|
207
188
|
end
|
@@ -37,16 +37,13 @@ class TestSocketHelper < Test::Unit::TestCase
|
|
37
37
|
[ { :backlog => 5 }, { :sndbuf => 4096 }, { :rcvbuf => 4096 },
|
38
38
|
{ :backlog => 16, :rcvbuf => 4096, :sndbuf => 4096 }
|
39
39
|
].each do |opts|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
unix_listener.close
|
47
|
-
end
|
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
|
48
46
|
end
|
49
|
-
#system('cat', @log_tmp.path)
|
50
47
|
end
|
51
48
|
|
52
49
|
def test_bind_listen_unix
|
@@ -106,10 +103,10 @@ class TestSocketHelper < Test::Unit::TestCase
|
|
106
103
|
assert_raises(Errno::EADDRINUSE) do
|
107
104
|
new_listener = bind_listen(@unix_listener_path)
|
108
105
|
end
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
106
|
+
|
107
|
+
File.unlink(@unix_listener_path)
|
108
|
+
new_listener = bind_listen(@unix_listener_path)
|
109
|
+
|
113
110
|
assert UNIXServer === new_listener
|
114
111
|
assert new_listener.fileno != @unix_listener.fileno
|
115
112
|
assert_equal sock_name(new_listener), sock_name(@unix_listener)
|
@@ -127,10 +124,8 @@ class TestSocketHelper < Test::Unit::TestCase
|
|
127
124
|
end
|
128
125
|
|
129
126
|
def test_server_cast
|
130
|
-
|
131
|
-
|
132
|
-
test_bind_listen_tcp
|
133
|
-
end
|
127
|
+
test_bind_listen_unix
|
128
|
+
test_bind_listen_tcp
|
134
129
|
unix_listener_socket = Socket.for_fd(@unix_listener.fileno)
|
135
130
|
assert Socket === unix_listener_socket
|
136
131
|
@unix_server = server_cast(unix_listener_socket)
|
@@ -85,8 +85,7 @@ class TestStreamInput < Test::Unit::TestCase
|
|
85
85
|
assert_equal '....', si.read(4), "nr=#{x}"
|
86
86
|
}
|
87
87
|
assert_nil si.read(1)
|
88
|
-
status =
|
89
|
-
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
88
|
+
pid, status = Process.waitpid2(pid)
|
90
89
|
assert status.success?
|
91
90
|
end
|
92
91
|
|
@@ -101,13 +100,13 @@ class TestStreamInput < Test::Unit::TestCase
|
|
101
100
|
@wr.close
|
102
101
|
}
|
103
102
|
@wr.close
|
104
|
-
|
103
|
+
line = si.gets
|
105
104
|
assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
|
106
105
|
assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
|
107
|
-
|
106
|
+
line = si.gets
|
108
107
|
assert_equal "foo#$/", line
|
109
108
|
assert_nil si.gets
|
110
|
-
|
109
|
+
pid, status = Process.waitpid2(pid)
|
111
110
|
assert status.success?
|
112
111
|
end
|
113
112
|
|
data/test/unit/test_tee_input.rb
CHANGED
@@ -40,13 +40,13 @@ class TestTeeInput < Test::Unit::TestCase
|
|
40
40
|
@wr.close
|
41
41
|
}
|
42
42
|
@wr.close
|
43
|
-
|
43
|
+
line = ti.gets
|
44
44
|
assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
|
45
45
|
assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
|
46
|
-
|
46
|
+
line = ti.gets
|
47
47
|
assert_equal "foo#$/", line
|
48
48
|
assert_nil ti.gets
|
49
|
-
|
49
|
+
pid, status = Process.waitpid2(pid)
|
50
50
|
assert status.success?
|
51
51
|
end
|
52
52
|
|
@@ -60,12 +60,12 @@ class TestTeeInput < Test::Unit::TestCase
|
|
60
60
|
@wr.close
|
61
61
|
}
|
62
62
|
@wr.close
|
63
|
-
|
63
|
+
line = ti.gets
|
64
64
|
assert_equal("hello#$/", line)
|
65
|
-
|
65
|
+
line = ti.gets
|
66
66
|
assert_equal "foo", line
|
67
67
|
assert_nil ti.gets
|
68
|
-
|
68
|
+
pid, status = Process.waitpid2(pid)
|
69
69
|
assert status.success?
|
70
70
|
end
|
71
71
|
|
@@ -146,8 +146,7 @@ class TestTeeInput < Test::Unit::TestCase
|
|
146
146
|
assert_equal Unicorn::Const::MAX_BODY + 1, ti.size
|
147
147
|
}
|
148
148
|
assert_nil ti.read(1)
|
149
|
-
status =
|
150
|
-
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
149
|
+
pid, status = Process.waitpid2(pid)
|
151
150
|
assert status.success?
|
152
151
|
end
|
153
152
|
|
@@ -174,15 +173,15 @@ class TestTeeInput < Test::Unit::TestCase
|
|
174
173
|
assert @parser.body_eof?
|
175
174
|
assert_equal 25, ti.len
|
176
175
|
assert_equal 0, ti.tmp.pos
|
177
|
-
|
176
|
+
ti.rewind
|
178
177
|
assert_equal 0, ti.tmp.pos
|
179
178
|
assert_equal 'abcdeabcdeabcdeabcde', ti.read(20)
|
180
179
|
assert_equal 20, ti.tmp.pos
|
181
|
-
|
180
|
+
ti.rewind
|
182
181
|
assert_equal 0, ti.tmp.pos
|
183
182
|
assert_kind_of File, ti.tmp
|
184
183
|
status = nil
|
185
|
-
|
184
|
+
pid, status = Process.waitpid2(pid)
|
186
185
|
assert status.success?
|
187
186
|
end
|
188
187
|
|
@@ -241,8 +240,7 @@ class TestTeeInput < Test::Unit::TestCase
|
|
241
240
|
assert ! @parser.body_eof?
|
242
241
|
assert_equal 25, ti.size
|
243
242
|
assert_equal "World", @parser.env['HTTP_HELLO']
|
244
|
-
status =
|
245
|
-
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
243
|
+
pid, status = Process.waitpid2(pid)
|
246
244
|
assert status.success?
|
247
245
|
end
|
248
246
|
|
data/test/unit/test_upload.rb
CHANGED
@@ -163,7 +163,7 @@ class UploadTest < Test::Unit::TestCase
|
|
163
163
|
assert_raise(Errno::ECONNRESET, Errno::EPIPE) do
|
164
164
|
::Unicorn::Const::CHUNK_SIZE.times { sock.syswrite(buf) }
|
165
165
|
end
|
166
|
-
|
166
|
+
sock.gets
|
167
167
|
tmp.rewind
|
168
168
|
assert_equal length, tmp.read.to_i
|
169
169
|
end
|
metadata
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: -223651717
|
5
|
+
prerelease: 5
|
6
6
|
segments:
|
7
7
|
- 4
|
8
|
-
-
|
8
|
+
- 5
|
9
9
|
- 0
|
10
|
-
|
10
|
+
- pre
|
11
|
+
- 1
|
12
|
+
version: 4.5.0pre1
|
11
13
|
platform: ruby
|
12
14
|
authors:
|
13
15
|
- Unicorn hackers
|
@@ -15,7 +17,7 @@ autorequire:
|
|
15
17
|
bindir: bin
|
16
18
|
cert_chain: []
|
17
19
|
|
18
|
-
date: 2012-
|
20
|
+
date: 2012-11-29 00:00:00 Z
|
19
21
|
dependencies:
|
20
22
|
- !ruby/object:Gem::Dependency
|
21
23
|
name: rack
|
@@ -318,12 +320,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
318
320
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
319
321
|
none: false
|
320
322
|
requirements:
|
321
|
-
- - "
|
323
|
+
- - ">"
|
322
324
|
- !ruby/object:Gem::Version
|
323
|
-
hash:
|
325
|
+
hash: 25
|
324
326
|
segments:
|
325
|
-
-
|
326
|
-
|
327
|
+
- 1
|
328
|
+
- 3
|
329
|
+
- 1
|
330
|
+
version: 1.3.1
|
327
331
|
requirements: []
|
328
332
|
|
329
333
|
rubyforge_project: mongrel
|