curb 0.1.4 → 0.7.15
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.
- data/README +131 -60
- data/Rakefile +81 -68
- data/doc.rb +1 -1
- data/ext/curb.c +107 -70
- data/ext/curb.h +23 -10
- data/ext/curb_easy.c +2156 -908
- data/ext/curb_easy.h +41 -28
- data/ext/curb_errors.c +258 -92
- data/ext/curb_errors.h +25 -2
- data/ext/curb_macros.h +41 -0
- data/ext/curb_multi.c +565 -0
- data/ext/curb_multi.h +26 -0
- data/ext/curb_postfield.c +68 -44
- data/ext/curb_upload.c +80 -0
- data/ext/curb_upload.h +30 -0
- data/ext/extconf.rb +159 -7
- data/lib/curb.rb +308 -0
- data/{ext → lib}/curl.rb +0 -1
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +52 -0
- data/tests/bug_curb_easy_post_with_string_no_content_length_header.rb +83 -0
- data/tests/bug_instance_post_differs_from_class_post.rb +1 -1
- data/tests/bug_multi_segfault.rb +14 -0
- data/tests/bug_postfields_crash.rb +26 -0
- data/tests/bug_postfields_crash2.rb +57 -0
- data/tests/bugtests.rb +9 -0
- data/tests/helper.rb +168 -0
- data/tests/mem_check.rb +65 -0
- data/tests/require_last_or_segfault_script.rb +2 -2
- data/tests/tc_curl_download.rb +75 -0
- data/tests/tc_curl_easy.rb +464 -9
- data/tests/tc_curl_multi.rb +466 -0
- data/tests/tc_curl_postfield.rb +4 -2
- data/tests/timeout.rb +100 -0
- data/tests/timeout_server.rb +33 -0
- metadata +103 -56
- data/ext/curb.rb +0 -46
- data/samples/gmail.rb +0 -48
data/tests/helper.rb
CHANGED
|
@@ -11,5 +11,173 @@ $:.unshift($EXTDIR)
|
|
|
11
11
|
|
|
12
12
|
require 'curb'
|
|
13
13
|
require 'test/unit'
|
|
14
|
+
require 'fileutils'
|
|
14
15
|
|
|
15
16
|
$TEST_URL = "file://#{URI.escape(File.expand_path(__FILE__).tr('\\','/').tr(':','|'))}"
|
|
17
|
+
|
|
18
|
+
require 'thread'
|
|
19
|
+
require 'webrick'
|
|
20
|
+
|
|
21
|
+
# set this to true to avoid testing with multiple threads
|
|
22
|
+
# or to test with multiple threads set it to false
|
|
23
|
+
# this is important since, some code paths will change depending
|
|
24
|
+
# on the presence of multiple threads
|
|
25
|
+
TEST_SINGLE_THREADED=false
|
|
26
|
+
|
|
27
|
+
# keep webrick quiet
|
|
28
|
+
class ::WEBrick::HTTPServer
|
|
29
|
+
def access_log(config, req, res)
|
|
30
|
+
# nop
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
class ::WEBrick::BasicLog
|
|
34
|
+
def log(level, data)
|
|
35
|
+
# nop
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#
|
|
40
|
+
# Simple test server to record number of times a request is sent/recieved of a specific
|
|
41
|
+
# request type, e.g. GET,POST,PUT,DELETE
|
|
42
|
+
#
|
|
43
|
+
class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
44
|
+
|
|
45
|
+
def self.port=(p)
|
|
46
|
+
@port = p
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.port
|
|
50
|
+
(@port or 9129)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.path
|
|
54
|
+
'/methods'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.url
|
|
58
|
+
"http://127.0.0.1:#{port}#{path}"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def respond_with(method,req,res)
|
|
62
|
+
res.body = method.to_s
|
|
63
|
+
$auth_header = req['Authorization']
|
|
64
|
+
res['Content-Type'] = "text/plain"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def do_GET(req,res)
|
|
68
|
+
respond_with(:GET,req,res)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def do_HEAD(req,res)
|
|
72
|
+
res['Location'] = "/nonexistent"
|
|
73
|
+
respond_with(:HEAD, req, res)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def do_POST(req,res)
|
|
77
|
+
if req.query['filename'].nil?
|
|
78
|
+
if req.body
|
|
79
|
+
params = {}
|
|
80
|
+
req.body.split('&').map{|s| k,v=s.split('='); params[k] = v }
|
|
81
|
+
end
|
|
82
|
+
if params and params['s'] == '500'
|
|
83
|
+
res.status = 500
|
|
84
|
+
else
|
|
85
|
+
respond_with("POST\n#{req.body}",req,res)
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
respond_with(req.query['filename'],req,res)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def do_PUT(req,res)
|
|
93
|
+
res['X-Requested-Content-Type'] = req.content_type
|
|
94
|
+
respond_with("PUT\n#{req.body}",req,res)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def do_DELETE(req,res)
|
|
98
|
+
respond_with(:DELETE,req,res)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def do_PURGE(req,res)
|
|
102
|
+
respond_with(:PURGE,req,res)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def do_COPY(req,res)
|
|
106
|
+
respond_with(:COPY,req,res)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
module TestServerMethods
|
|
112
|
+
def locked_file
|
|
113
|
+
File.join(File.dirname(__FILE__),"server_lock-#{@__port}")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def server_setup(port=9129,servlet=TestServlet)
|
|
117
|
+
@__port = port
|
|
118
|
+
if @server.nil? and !File.exist?(locked_file)
|
|
119
|
+
|
|
120
|
+
File.open(locked_file,'w') {|f| f << 'locked' }
|
|
121
|
+
if TEST_SINGLE_THREADED
|
|
122
|
+
rd, wr = IO.pipe
|
|
123
|
+
@__pid = fork do
|
|
124
|
+
rd.close
|
|
125
|
+
rd = nil
|
|
126
|
+
|
|
127
|
+
# start up a webrick server for testing delete
|
|
128
|
+
server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
|
129
|
+
|
|
130
|
+
server.mount(servlet.path, servlet)
|
|
131
|
+
server.mount("/ext", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__),'..','ext'))
|
|
132
|
+
|
|
133
|
+
trap("INT") { server.shutdown }
|
|
134
|
+
GC.start
|
|
135
|
+
wr.flush
|
|
136
|
+
wr.close
|
|
137
|
+
server.start
|
|
138
|
+
end
|
|
139
|
+
wr.close
|
|
140
|
+
rd.read
|
|
141
|
+
rd.close
|
|
142
|
+
else
|
|
143
|
+
# start up a webrick server for testing delete
|
|
144
|
+
@server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
|
145
|
+
|
|
146
|
+
@server.mount(servlet.path, servlet)
|
|
147
|
+
@server.mount("/ext", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__),'..','ext'))
|
|
148
|
+
queue = Queue.new # synchronize the thread startup to the main thread
|
|
149
|
+
|
|
150
|
+
@test_thread = Thread.new { queue << 1; @server.start }
|
|
151
|
+
|
|
152
|
+
# wait for the queue
|
|
153
|
+
value = queue.pop
|
|
154
|
+
if !value
|
|
155
|
+
STDERR.puts "Failed to startup test server!"
|
|
156
|
+
exit(1)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
exit_code = lambda do
|
|
162
|
+
begin
|
|
163
|
+
if File.exist?(locked_file)
|
|
164
|
+
File.unlink locked_file
|
|
165
|
+
if TEST_SINGLE_THREADED
|
|
166
|
+
Process.kill 'INT', @__pid
|
|
167
|
+
else
|
|
168
|
+
@server.shutdown unless @server.nil?
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
#@server.shutdown unless @server.nil?
|
|
172
|
+
rescue Object => e
|
|
173
|
+
puts "Error #{__FILE__}:#{__LINE__}\n#{e.message}"
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
trap("INT"){exit_code.call}
|
|
178
|
+
at_exit{exit_code.call}
|
|
179
|
+
|
|
180
|
+
end
|
|
181
|
+
rescue Errno::EADDRINUSE
|
|
182
|
+
end
|
|
183
|
+
end
|
data/tests/mem_check.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
+
#require 'rubygems'
|
|
3
|
+
#require 'rmem'
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# Run some tests to measure the memory usage of curb, these tests require fork and ps
|
|
7
|
+
#
|
|
8
|
+
class TestCurbMemory < Test::Unit::TestCase
|
|
9
|
+
|
|
10
|
+
def test_easy_memory
|
|
11
|
+
easy_avg, easy_std = measure_object_memory(Curl::Easy)
|
|
12
|
+
printf "Easy average: %.2f kilobytes +/- %.2f kilobytes\n", easy_avg.to_f, easy_std.to_f
|
|
13
|
+
|
|
14
|
+
multi_avg, multi_std = measure_object_memory(Curl::Multi)
|
|
15
|
+
printf "Multi average: %.2f kilobytes +/- %.2f kilobytes\n", multi_avg.to_f, multi_std.to_f
|
|
16
|
+
|
|
17
|
+
# now that we have the average size of an easy handle lets see how much a multi request consumes with 10 requests
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def c_avg(report)
|
|
21
|
+
sum = 0
|
|
22
|
+
report.each {|r| sum += r.last }
|
|
23
|
+
(sum.to_f / report.size)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def c_std(report,avg)
|
|
27
|
+
var = 0
|
|
28
|
+
report.each {|r| var += (r.last-avg)*(r.last-avg) }
|
|
29
|
+
Math.sqrt(var / (report.size-1))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def measure_object_memory(klass)
|
|
33
|
+
report = []
|
|
34
|
+
200.times do
|
|
35
|
+
res = mem_check do
|
|
36
|
+
obj = klass.new
|
|
37
|
+
end
|
|
38
|
+
report << res
|
|
39
|
+
end
|
|
40
|
+
avg = c_avg(report)
|
|
41
|
+
std = c_std(report,avg)
|
|
42
|
+
[avg,std]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def mem_check
|
|
46
|
+
# see: http://gist.github.com/264060 for inspiration of ps command line
|
|
47
|
+
rd, wr = IO.pipe
|
|
48
|
+
memory_usage = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes
|
|
49
|
+
fork do
|
|
50
|
+
before = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes
|
|
51
|
+
rd.close
|
|
52
|
+
yield
|
|
53
|
+
after = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes
|
|
54
|
+
wr.write((after - before))
|
|
55
|
+
wr.flush
|
|
56
|
+
wr.close
|
|
57
|
+
end
|
|
58
|
+
wr.close
|
|
59
|
+
total = rd.read.to_i
|
|
60
|
+
rd.close
|
|
61
|
+
Process.wait
|
|
62
|
+
# return the delta and the total
|
|
63
|
+
[memory_usage, total]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
# Aborted
|
|
22
22
|
# ------------------------------------------------------------------
|
|
23
23
|
#
|
|
24
|
-
$:.unshift(File.join(File.dirname(__FILE__), '..', 'ext'))
|
|
25
|
-
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
24
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'ext')))
|
|
25
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
|
|
26
26
|
require 'curb'
|
|
27
27
|
require 'uri'
|
|
28
28
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
|
2
|
+
|
|
3
|
+
class TestCurbCurlDownload < Test::Unit::TestCase
|
|
4
|
+
include TestServerMethods
|
|
5
|
+
|
|
6
|
+
def setup
|
|
7
|
+
server_setup
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_download_url_to_file_via_string
|
|
11
|
+
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
12
|
+
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
|
13
|
+
|
|
14
|
+
curb = Curl::Easy.download(dl_url, dl_path)
|
|
15
|
+
assert File.exist?(dl_path)
|
|
16
|
+
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
|
|
17
|
+
ensure
|
|
18
|
+
File.unlink(dl_path) if File.exist?(dl_path)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_download_url_to_file_via_file_io
|
|
22
|
+
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
23
|
+
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
|
24
|
+
io = File.open(dl_path, 'wb')
|
|
25
|
+
|
|
26
|
+
curb = Curl::Easy.download(dl_url, io)
|
|
27
|
+
assert io.closed?
|
|
28
|
+
assert File.exist?(dl_path)
|
|
29
|
+
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
|
|
30
|
+
ensure
|
|
31
|
+
File.unlink(dl_path) if File.exist?(dl_path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def test_download_url_to_file_via_io
|
|
35
|
+
dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
|
|
36
|
+
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
|
37
|
+
reader, writer = IO.pipe
|
|
38
|
+
|
|
39
|
+
# Write to local file
|
|
40
|
+
fork do
|
|
41
|
+
begin
|
|
42
|
+
writer.close
|
|
43
|
+
File.open(dl_path, 'wb') { |file| file << reader.read }
|
|
44
|
+
ensure
|
|
45
|
+
reader.close rescue IOError # if the stream has already been closed
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Download remote source
|
|
50
|
+
begin
|
|
51
|
+
reader.close
|
|
52
|
+
curb = Curl::Easy.download(dl_url, writer)
|
|
53
|
+
Process.wait
|
|
54
|
+
ensure
|
|
55
|
+
writer.close rescue IOError # if the stream has already been closed, which occurs in Easy::download
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
assert File.exist?(dl_path)
|
|
59
|
+
assert_equal File.read(File.join(File.dirname(__FILE__), '..','ext','curb_easy.c')), File.read(dl_path)
|
|
60
|
+
ensure
|
|
61
|
+
File.unlink(dl_path) if File.exist?(dl_path)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_download_bad_url_gives_404
|
|
65
|
+
dl_url = "http://127.0.0.1:9129/this_file_does_not_exist.html"
|
|
66
|
+
dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
|
|
67
|
+
|
|
68
|
+
curb = Curl::Easy.download(dl_url, dl_path)
|
|
69
|
+
assert_equal Curl::Easy, curb.class
|
|
70
|
+
assert_equal 404, curb.response_code
|
|
71
|
+
ensure
|
|
72
|
+
File.unlink(dl_path) if File.exist?(dl_path)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|