curb 0.8.4 → 1.3.5
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.
- checksums.yaml +7 -0
- data/README.md +522 -0
- data/Rakefile +79 -26
- data/ext/banned.h +32 -0
- data/ext/curb.c +549 -230
- data/ext/curb.h +19 -10
- data/ext/curb_easy.c +1935 -366
- data/ext/curb_easy.h +16 -0
- data/ext/curb_errors.c +115 -18
- data/ext/curb_errors.h +8 -5
- data/ext/curb_macros.h +33 -21
- data/ext/curb_multi.c +1858 -272
- data/ext/curb_multi.h +10 -3
- data/ext/curb_postfield.c +149 -77
- data/ext/curb_postfield.h +1 -0
- data/ext/curb_upload.c +38 -11
- data/ext/curb_upload.h +2 -0
- data/ext/extconf.rb +353 -35
- data/lib/curb.rb +1 -0
- data/lib/curl/easy.rb +314 -78
- data/lib/curl/multi.rb +134 -28
- data/lib/curl.rb +217 -11
- data/tests/bug_crash_on_debug.rb +14 -28
- data/tests/bug_crash_on_progress.rb +32 -16
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +10 -15
- data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +6 -30
- data/tests/bug_follow_redirect_288.rb +83 -0
- data/tests/bug_instance_post_differs_from_class_post.rb +3 -5
- data/tests/bug_issue102.rb +1 -1
- data/tests/bug_issue_noproxy.rb +56 -0
- data/tests/bug_issue_post_redirect.rb +93 -0
- data/tests/bug_issue_spnego.rb +41 -0
- data/tests/bug_multi_segfault.rb +1 -0
- data/tests/bug_raise_on_callback.rb +30 -0
- data/tests/helper.rb +400 -44
- data/tests/leak_trace.rb +237 -0
- data/tests/mem_check.rb +3 -0
- data/tests/tc_curl.rb +31 -1
- data/tests/tc_curl_download.rb +12 -7
- data/tests/tc_curl_easy.rb +911 -63
- data/tests/tc_curl_easy_cookielist.rb +277 -0
- data/tests/tc_curl_easy_request_target.rb +41 -0
- data/tests/tc_curl_easy_resolve.rb +48 -0
- data/tests/tc_curl_maxfilesize.rb +12 -0
- data/tests/tc_curl_multi.rb +855 -53
- data/tests/tc_curl_native_coverage.rb +145 -0
- data/tests/tc_curl_postfield.rb +207 -30
- data/tests/tc_curl_protocols.rb +37 -0
- data/tests/tc_fiber_scheduler.rb +543 -0
- data/tests/tc_ftp_options.rb +39 -0
- data/tests/tc_gc_compact.rb +223 -0
- data/tests/tc_test_server_methods.rb +110 -0
- data/tests/timeout.rb +30 -6
- metadata +59 -36
- data/README +0 -194
data/tests/helper.rb
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
# Copyright (c)2006 Ross Bamford. See LICENSE.
|
|
3
3
|
$CURB_TESTING = true
|
|
4
4
|
require 'uri'
|
|
5
|
+
require 'stringio'
|
|
6
|
+
require 'digest/md5'
|
|
7
|
+
require 'rbconfig'
|
|
8
|
+
require File.join(RbConfig::CONFIG['rubylibdir'], 'timeout')
|
|
5
9
|
|
|
6
10
|
$TOPDIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
|
7
11
|
$EXTDIR = File.join($TOPDIR, 'ext')
|
|
@@ -9,14 +13,53 @@ $LIBDIR = File.join($TOPDIR, 'lib')
|
|
|
9
13
|
$:.unshift($LIBDIR)
|
|
10
14
|
$:.unshift($EXTDIR)
|
|
11
15
|
|
|
16
|
+
# Setup SimpleCov for Ruby code coverage if COVERAGE env var is set
|
|
17
|
+
if ENV['COVERAGE']
|
|
18
|
+
begin
|
|
19
|
+
require 'simplecov'
|
|
20
|
+
require 'simplecov-lcov'
|
|
21
|
+
|
|
22
|
+
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
|
|
23
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
|
24
|
+
SimpleCov::Formatter::HTMLFormatter,
|
|
25
|
+
SimpleCov::Formatter::LcovFormatter
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
SimpleCov.start do
|
|
29
|
+
add_filter '/tests/'
|
|
30
|
+
add_filter '/spec/'
|
|
31
|
+
add_filter '/vendor/'
|
|
32
|
+
add_filter '/.bundle/'
|
|
33
|
+
add_group 'Library', 'lib'
|
|
34
|
+
add_group 'Extensions', 'ext'
|
|
35
|
+
|
|
36
|
+
# Track branch coverage if available
|
|
37
|
+
enable_coverage :branch if respond_to?(:enable_coverage)
|
|
38
|
+
end
|
|
39
|
+
rescue LoadError
|
|
40
|
+
puts "SimpleCov not available. Install it with: gem install simplecov simplecov-lcov"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
12
44
|
require 'curb'
|
|
13
|
-
|
|
45
|
+
begin
|
|
46
|
+
require 'test/unit'
|
|
47
|
+
rescue LoadError
|
|
48
|
+
gem 'test/unit'
|
|
49
|
+
require 'test/unit'
|
|
50
|
+
end
|
|
14
51
|
require 'fileutils'
|
|
52
|
+
require 'rbconfig'
|
|
53
|
+
|
|
54
|
+
# Platform helpers
|
|
55
|
+
WINDOWS = /mswin|msys|mingw|cygwin|bccwin|wince|emc|windows/i.match?(RbConfig::CONFIG['host_os'])
|
|
56
|
+
NO_FORK = !Process.respond_to?(:fork)
|
|
15
57
|
|
|
16
|
-
$TEST_URL = "file://#{
|
|
58
|
+
$TEST_URL = "file://#{'/' if RUBY_DESCRIPTION =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/}#{File.expand_path(__FILE__).tr('\\','/')}"
|
|
17
59
|
|
|
18
60
|
require 'thread'
|
|
19
61
|
require 'webrick'
|
|
62
|
+
require 'socket'
|
|
20
63
|
|
|
21
64
|
# set this to true to avoid testing with multiple threads
|
|
22
65
|
# or to test with multiple threads set it to false
|
|
@@ -24,20 +67,41 @@ require 'webrick'
|
|
|
24
67
|
# on the presence of multiple threads
|
|
25
68
|
TEST_SINGLE_THREADED=false
|
|
26
69
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
70
|
+
WEBRICK_TEST_LOG = WEBrick::Log.new(File.open(File::NULL, 'w'), WEBrick::BasicLog::ERROR)
|
|
71
|
+
|
|
72
|
+
module CurbTestResourceCleanup
|
|
73
|
+
def teardown
|
|
74
|
+
super
|
|
75
|
+
ensure
|
|
76
|
+
begin
|
|
77
|
+
if Curl::Easy.respond_to?(:flush_deferred_multi_closes)
|
|
78
|
+
Curl::Easy.flush_deferred_multi_closes(all_threads: true)
|
|
79
|
+
end
|
|
80
|
+
rescue StandardError
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
begin
|
|
85
|
+
ObjectSpace.each_object(Curl::Multi) do |multi|
|
|
86
|
+
begin
|
|
87
|
+
next if multi.instance_variable_defined?(:@deferred_close) && multi.instance_variable_get(:@deferred_close)
|
|
88
|
+
multi.instance_variable_set(:@requests, {})
|
|
89
|
+
multi._close
|
|
90
|
+
rescue StandardError
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
rescue StandardError
|
|
95
|
+
nil
|
|
96
|
+
end
|
|
97
|
+
|
|
36
98
|
end
|
|
37
99
|
end
|
|
38
100
|
|
|
101
|
+
Test::Unit::TestCase.prepend(CurbTestResourceCleanup)
|
|
102
|
+
|
|
39
103
|
#
|
|
40
|
-
# Simple test server to record number of times a request is sent/recieved of a specific
|
|
104
|
+
# Simple test server to record number of times a request is sent/recieved of a specific
|
|
41
105
|
# request type, e.g. GET,POST,PUT,DELETE
|
|
42
106
|
#
|
|
43
107
|
class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
@@ -47,13 +111,12 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
|
47
111
|
end
|
|
48
112
|
|
|
49
113
|
def self.port
|
|
50
|
-
|
|
114
|
+
@port ||= 9129
|
|
51
115
|
end
|
|
52
116
|
|
|
53
117
|
def self.path
|
|
54
118
|
'/methods'
|
|
55
119
|
end
|
|
56
|
-
|
|
57
120
|
def self.url
|
|
58
121
|
"http://127.0.0.1:#{port}#{path}"
|
|
59
122
|
end
|
|
@@ -65,13 +128,17 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
|
65
128
|
end
|
|
66
129
|
|
|
67
130
|
def do_GET(req,res)
|
|
68
|
-
if req.path.match
|
|
131
|
+
if req.path.match(/redirect$/)
|
|
69
132
|
res.status = 302
|
|
70
133
|
res['Location'] = '/foo'
|
|
71
|
-
elsif req.path.match
|
|
134
|
+
elsif req.path.match(/not_here$/)
|
|
72
135
|
res.status = 404
|
|
73
|
-
elsif req.path.match
|
|
136
|
+
elsif req.path.match(/error$/)
|
|
74
137
|
res.status = 500
|
|
138
|
+
elsif req.path.match(/get_cookies$/)
|
|
139
|
+
res['Content-Type'] = "text/plain"
|
|
140
|
+
res.body = req['Cookie']
|
|
141
|
+
return
|
|
75
142
|
end
|
|
76
143
|
respond_with("GET#{req.query_string}",req,res)
|
|
77
144
|
end
|
|
@@ -82,13 +149,27 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
|
82
149
|
end
|
|
83
150
|
|
|
84
151
|
def do_POST(req,res)
|
|
85
|
-
if req.
|
|
152
|
+
if req.path.match(/set_cookies$/)
|
|
153
|
+
JSON.parse(req.body || '[]', symbolize_names: true).each do |hash|
|
|
154
|
+
cookie = WEBrick::Cookie.new(hash.fetch(:name), hash.fetch(:value))
|
|
155
|
+
cookie.domain = hash[:domain] if hash.key?(:domain)
|
|
156
|
+
cookie.expires = hash[:expires] if hash.key?(:expires)
|
|
157
|
+
cookie.path = hash[:path] if hash.key?(:path)
|
|
158
|
+
cookie.secure = hash[:secure] if hash.key?(:secure)
|
|
159
|
+
cookie.max_age = hash[:max_age] if hash.key?(:max_age)
|
|
160
|
+
res.cookies.push(cookie)
|
|
161
|
+
end
|
|
162
|
+
respond_with('OK', req, res)
|
|
163
|
+
elsif req.query['filename'].nil?
|
|
86
164
|
if req.body
|
|
87
165
|
params = {}
|
|
88
166
|
req.body.split('&').map{|s| k,v=s.split('='); params[k] = v }
|
|
89
167
|
end
|
|
90
168
|
if params and params['s'] == '500'
|
|
91
169
|
res.status = 500
|
|
170
|
+
elsif params and params['c']
|
|
171
|
+
cookie = URI.decode_www_form_component(params['c']).split('=')
|
|
172
|
+
res.cookies << WEBrick::Cookie.new(*cookie)
|
|
92
173
|
else
|
|
93
174
|
respond_with("POST\n#{req.body}",req,res)
|
|
94
175
|
end
|
|
@@ -124,50 +205,255 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
|
124
205
|
|
|
125
206
|
end
|
|
126
207
|
|
|
208
|
+
module BugTestServerSetupTeardown
|
|
209
|
+
def unused_local_port
|
|
210
|
+
socket = TCPServer.new('127.0.0.1', 0)
|
|
211
|
+
socket.addr[1]
|
|
212
|
+
ensure
|
|
213
|
+
socket.close if socket
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def setup
|
|
217
|
+
@port ||= 9992
|
|
218
|
+
@server = WEBrick::HTTPServer.new(:Port => @port, :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
|
219
|
+
@server.mount_proc("/test") do|req,res|
|
|
220
|
+
if @response_proc
|
|
221
|
+
@response_proc.call(res)
|
|
222
|
+
else
|
|
223
|
+
res.body = "hi"
|
|
224
|
+
res['Content-Type'] = "text/html"
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
@thread = Thread.new(@server) do|srv|
|
|
229
|
+
srv.start
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def teardown
|
|
234
|
+
while @server.status != :Shutdown
|
|
235
|
+
@server.shutdown
|
|
236
|
+
end
|
|
237
|
+
@thread.join
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
127
241
|
module TestServerMethods
|
|
242
|
+
def server_responding?(port)
|
|
243
|
+
socket = TCPSocket.new('127.0.0.1', port)
|
|
244
|
+
socket.close
|
|
245
|
+
true
|
|
246
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT, IOError
|
|
247
|
+
false
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def server_startup_timeout
|
|
251
|
+
ENV['RUBY_MEMCHECK_RUNNING'] ? 30 : 5
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def wait_for_server_ready(port, thread: nil)
|
|
255
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + server_startup_timeout
|
|
256
|
+
|
|
257
|
+
loop do
|
|
258
|
+
if thread && !thread.alive?
|
|
259
|
+
return false
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
return true if server_responding?(port)
|
|
263
|
+
|
|
264
|
+
raise "Failed to startup test server on port #{port}" if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
265
|
+
|
|
266
|
+
sleep 0.01
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def server_shutdown_timeout
|
|
271
|
+
[server_startup_timeout, 0.25].max
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def wait_for_server_stopped(port, thread: nil)
|
|
275
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + server_shutdown_timeout
|
|
276
|
+
|
|
277
|
+
loop do
|
|
278
|
+
return true unless server_responding?(port)
|
|
279
|
+
|
|
280
|
+
if thread && !thread.alive?
|
|
281
|
+
return !server_responding?(port)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
return false if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
|
|
285
|
+
|
|
286
|
+
sleep 0.01
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
128
290
|
def locked_file
|
|
129
291
|
File.join(File.dirname(__FILE__),"server_lock-#{@__port}")
|
|
130
292
|
end
|
|
131
293
|
|
|
294
|
+
def read_server_lock_pid
|
|
295
|
+
Integer(File.read(locked_file).strip, 10)
|
|
296
|
+
rescue Errno::ENOENT, ArgumentError, TypeError
|
|
297
|
+
nil
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def write_server_lock(pid = Process.pid)
|
|
301
|
+
File.open(locked_file, 'w') { |f| f << "#{pid}\n" }
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def process_alive?(pid)
|
|
305
|
+
return false unless pid && pid.positive?
|
|
306
|
+
|
|
307
|
+
Process.kill(0, pid)
|
|
308
|
+
true
|
|
309
|
+
rescue Errno::ESRCH
|
|
310
|
+
false
|
|
311
|
+
rescue Errno::EPERM
|
|
312
|
+
true
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def server_lock_fresh?
|
|
316
|
+
(Time.now - File.mtime(locked_file)) < server_startup_timeout
|
|
317
|
+
rescue Errno::ENOENT
|
|
318
|
+
false
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def stale_server_lock?(port)
|
|
322
|
+
return false unless File.exist?(locked_file)
|
|
323
|
+
|
|
324
|
+
pid = read_server_lock_pid
|
|
325
|
+
return false if pid && process_alive?(pid) && server_lock_fresh?
|
|
326
|
+
return false if server_responding?(port)
|
|
327
|
+
|
|
328
|
+
true
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def clear_stale_server_lock(port)
|
|
332
|
+
return unless stale_server_lock?(port)
|
|
333
|
+
|
|
334
|
+
File.unlink(locked_file)
|
|
335
|
+
rescue Errno::ENOENT
|
|
336
|
+
nil
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def stop_test_server
|
|
340
|
+
server = instance_variable_defined?(:@server) ? @server : nil
|
|
341
|
+
pid = instance_variable_defined?(:@__pid) ? @__pid : nil
|
|
342
|
+
return unless server || pid
|
|
343
|
+
|
|
344
|
+
if TEST_SINGLE_THREADED
|
|
345
|
+
@__pid = nil
|
|
346
|
+
|
|
347
|
+
if pid
|
|
348
|
+
begin
|
|
349
|
+
Process.kill('INT', pid)
|
|
350
|
+
rescue Errno::ESRCH
|
|
351
|
+
nil
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
begin
|
|
355
|
+
Process.wait(pid)
|
|
356
|
+
rescue Errno::ECHILD, Errno::ESRCH
|
|
357
|
+
nil
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
else
|
|
361
|
+
thread = instance_variable_defined?(:@test_thread) ? @test_thread : nil
|
|
362
|
+
port = instance_variable_defined?(:@__port) ? @__port : nil
|
|
363
|
+
@server = nil
|
|
364
|
+
@test_thread = nil
|
|
365
|
+
|
|
366
|
+
server.shutdown if server
|
|
367
|
+
wait_for_server_stopped(port, thread: thread) if port
|
|
368
|
+
thread.join(server_shutdown_timeout) if thread
|
|
369
|
+
|
|
370
|
+
if thread&.alive?
|
|
371
|
+
thread.kill
|
|
372
|
+
thread.join(server_shutdown_timeout)
|
|
373
|
+
wait_for_server_stopped(port) if port
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
File.unlink(locked_file) if File.exist?(locked_file)
|
|
378
|
+
rescue Errno::ENOENT
|
|
379
|
+
nil
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def teardown
|
|
383
|
+
super
|
|
384
|
+
ensure
|
|
385
|
+
stop_test_server
|
|
386
|
+
end
|
|
387
|
+
|
|
132
388
|
def server_setup(port=9129,servlet=TestServlet)
|
|
133
389
|
@__port = port
|
|
134
|
-
|
|
390
|
+
@server = nil unless instance_variable_defined?(:@server)
|
|
391
|
+
@__pid = nil unless instance_variable_defined?(:@__pid)
|
|
392
|
+
@test_thread = nil unless instance_variable_defined?(:@test_thread)
|
|
393
|
+
clear_stale_server_lock(port)
|
|
394
|
+
|
|
395
|
+
if @server.nil? && File.exist?(locked_file)
|
|
396
|
+
begin
|
|
397
|
+
wait_for_server_ready(port)
|
|
398
|
+
return
|
|
399
|
+
rescue RuntimeError
|
|
400
|
+
clear_stale_server_lock(port)
|
|
401
|
+
end
|
|
402
|
+
end
|
|
135
403
|
|
|
136
|
-
|
|
404
|
+
if @server.nil? and !File.exist?(locked_file)
|
|
405
|
+
write_server_lock
|
|
137
406
|
if TEST_SINGLE_THREADED
|
|
138
407
|
rd, wr = IO.pipe
|
|
139
408
|
@__pid = fork do
|
|
140
409
|
rd.close
|
|
141
410
|
rd = nil
|
|
142
411
|
|
|
143
|
-
# start up a webrick server for testing delete
|
|
144
|
-
server = WEBrick::HTTPServer.new
|
|
412
|
+
# start up a webrick server for testing delete
|
|
413
|
+
server = WEBrick::HTTPServer.new(:Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__)), :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
|
145
414
|
|
|
146
415
|
server.mount(servlet.path, servlet)
|
|
147
416
|
server.mount("/ext", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__),'..','ext'))
|
|
148
417
|
|
|
149
418
|
trap("INT") { server.shutdown }
|
|
150
419
|
GC.start
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
420
|
+
server_thread = Thread.new { server.start }
|
|
421
|
+
|
|
422
|
+
begin
|
|
423
|
+
if wait_for_server_ready(port, thread: server_thread)
|
|
424
|
+
wr.write('1')
|
|
425
|
+
else
|
|
426
|
+
wr.write('0')
|
|
427
|
+
end
|
|
428
|
+
rescue StandardError
|
|
429
|
+
wr.write('0')
|
|
430
|
+
ensure
|
|
431
|
+
wr.flush
|
|
432
|
+
wr.close
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
server_thread.join
|
|
154
436
|
end
|
|
155
437
|
wr.close
|
|
156
|
-
rd.read
|
|
438
|
+
ready = rd.read
|
|
157
439
|
rd.close
|
|
440
|
+
if ready != '1'
|
|
441
|
+
STDERR.puts "Failed to startup test server!"
|
|
442
|
+
exit(1)
|
|
443
|
+
end
|
|
158
444
|
else
|
|
159
|
-
# start up a webrick server for testing delete
|
|
160
|
-
|
|
445
|
+
# start up a webrick server for testing delete
|
|
446
|
+
server = WEBrick::HTTPServer.new(:Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__)), :Logger => WEBRICK_TEST_LOG, :AccessLog => [])
|
|
161
447
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
queue = Queue.new # synchronize the thread startup to the main thread
|
|
448
|
+
server.mount(servlet.path, servlet)
|
|
449
|
+
server.mount("/ext", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__),'..','ext'))
|
|
165
450
|
|
|
166
|
-
@
|
|
451
|
+
@server = server
|
|
452
|
+
# Keep a stable reference inside the thread so helper shutdown can clear
|
|
453
|
+
# @server without racing the server startup path.
|
|
454
|
+
@test_thread = Thread.new(server) { |srv| srv.start }
|
|
167
455
|
|
|
168
|
-
|
|
169
|
-
value = queue.pop
|
|
170
|
-
if !value
|
|
456
|
+
if !wait_for_server_ready(port, thread: @test_thread)
|
|
171
457
|
STDERR.puts "Failed to startup test server!"
|
|
172
458
|
exit(1)
|
|
173
459
|
end
|
|
@@ -176,15 +462,7 @@ module TestServerMethods
|
|
|
176
462
|
|
|
177
463
|
exit_code = lambda do
|
|
178
464
|
begin
|
|
179
|
-
|
|
180
|
-
File.unlink locked_file
|
|
181
|
-
if TEST_SINGLE_THREADED
|
|
182
|
-
Process.kill 'INT', @__pid
|
|
183
|
-
else
|
|
184
|
-
@server.shutdown unless @server.nil?
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
#@server.shutdown unless @server.nil?
|
|
465
|
+
stop_test_server
|
|
188
466
|
rescue Object => e
|
|
189
467
|
puts "Error #{__FILE__}:#{__LINE__}\n#{e.message}"
|
|
190
468
|
end
|
|
@@ -197,3 +475,81 @@ module TestServerMethods
|
|
|
197
475
|
rescue Errno::EADDRINUSE
|
|
198
476
|
end
|
|
199
477
|
end
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
# Backport for Ruby 1.8
|
|
482
|
+
module Backports
|
|
483
|
+
module Ruby18
|
|
484
|
+
module URIFormEncoding
|
|
485
|
+
TBLENCWWWCOMP_ = {}
|
|
486
|
+
TBLDECWWWCOMP_ = {}
|
|
487
|
+
|
|
488
|
+
def encode_www_form_component(str)
|
|
489
|
+
if TBLENCWWWCOMP_.empty?
|
|
490
|
+
256.times do |i|
|
|
491
|
+
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
|
|
492
|
+
end
|
|
493
|
+
TBLENCWWWCOMP_[' '] = '+'
|
|
494
|
+
TBLENCWWWCOMP_.freeze
|
|
495
|
+
end
|
|
496
|
+
str.to_s.gsub( /([^*\-.0-9A-Z_a-z])/ ) {|*| TBLENCWWWCOMP_[$1] }
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
def decode_www_form_component(str)
|
|
500
|
+
if TBLDECWWWCOMP_.empty?
|
|
501
|
+
256.times do |i|
|
|
502
|
+
h, l = i>>4, i&15
|
|
503
|
+
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
|
|
504
|
+
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
|
|
505
|
+
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
|
|
506
|
+
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
|
|
507
|
+
end
|
|
508
|
+
TBLDECWWWCOMP_['+'] = ' '
|
|
509
|
+
TBLDECWWWCOMP_.freeze
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
raise ArgumentError, "invalid %-encoding (#{str.dump})" unless /\A(?:%[[:xdigit:]]{2}|[^%]+)*\z/ =~ str
|
|
513
|
+
str.gsub( /(\+|%[[:xdigit:]]{2})/ ) {|*| TBLDECWWWCOMP_[$1] }
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def encode_www_form( enum )
|
|
517
|
+
enum.map do |k,v|
|
|
518
|
+
if v.nil?
|
|
519
|
+
encode_www_form_component(k)
|
|
520
|
+
elsif v.respond_to?(:to_ary)
|
|
521
|
+
v.to_ary.map do |w|
|
|
522
|
+
str = encode_www_form_component(k)
|
|
523
|
+
unless w.nil?
|
|
524
|
+
str << '='
|
|
525
|
+
str << encode_www_form_component(w)
|
|
526
|
+
end
|
|
527
|
+
end.join('&')
|
|
528
|
+
else
|
|
529
|
+
str = encode_www_form_component(k)
|
|
530
|
+
str << '='
|
|
531
|
+
str << encode_www_form_component(v)
|
|
532
|
+
end
|
|
533
|
+
end.join('&')
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
WFKV_ = '(?:%\h\h|[^%#=;&])'
|
|
537
|
+
def decode_www_form(str, _)
|
|
538
|
+
return [] if str.to_s == ''
|
|
539
|
+
|
|
540
|
+
unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/ =~ str
|
|
541
|
+
raise ArgumentError, "invalid data of application/x-www-form-urlencoded (#{str})"
|
|
542
|
+
end
|
|
543
|
+
ary = []
|
|
544
|
+
$&.scan(/([^=;&]+)=([^;&]*)/) do
|
|
545
|
+
ary << [decode_www_form_component($1, enc), decode_www_form_component($2, enc)]
|
|
546
|
+
end
|
|
547
|
+
ary
|
|
548
|
+
end
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
unless URI.methods.include?(:encode_www_form)
|
|
554
|
+
URI.extend(Backports::Ruby18::URIFormEncoding)
|
|
555
|
+
end
|