codders-curb 0.8.0
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/LICENSE +51 -0
- data/README +194 -0
- data/Rakefile +320 -0
- data/doc.rb +42 -0
- data/ext/curb.c +977 -0
- data/ext/curb.h +52 -0
- data/ext/curb_easy.c +3404 -0
- data/ext/curb_easy.h +90 -0
- data/ext/curb_errors.c +647 -0
- data/ext/curb_errors.h +129 -0
- data/ext/curb_macros.h +159 -0
- data/ext/curb_multi.c +633 -0
- data/ext/curb_multi.h +26 -0
- data/ext/curb_postfield.c +523 -0
- data/ext/curb_postfield.h +40 -0
- data/ext/curb_upload.c +80 -0
- data/ext/curb_upload.h +30 -0
- data/ext/extconf.rb +399 -0
- data/lib/curb.rb +4 -0
- data/lib/curl.rb +57 -0
- data/lib/curl/easy.rb +468 -0
- data/lib/curl/multi.rb +248 -0
- data/tests/alltests.rb +3 -0
- data/tests/bug_crash_on_debug.rb +39 -0
- data/tests/bug_crash_on_progress.rb +33 -0
- 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 +53 -0
- 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/bug_require_last_or_segfault.rb +40 -0
- data/tests/bugtests.rb +9 -0
- data/tests/helper.rb +199 -0
- data/tests/mem_check.rb +65 -0
- data/tests/require_last_or_segfault_script.rb +36 -0
- data/tests/tc_curl_download.rb +75 -0
- data/tests/tc_curl_easy.rb +1011 -0
- data/tests/tc_curl_easy_setopt.rb +31 -0
- data/tests/tc_curl_multi.rb +485 -0
- data/tests/tc_curl_postfield.rb +143 -0
- data/tests/timeout.rb +100 -0
- data/tests/timeout_server.rb +33 -0
- data/tests/unittests.rb +2 -0
- metadata +133 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
# From Vlad Jebelev:
|
2
|
+
#
|
3
|
+
# - Second thing - I think you just probably didn't have the time to update
|
4
|
+
# instance methods yet but when I POST with a reusal of a previous curl
|
5
|
+
# instance, it doesnt' work for me, e.g. when I create a curl previously and
|
6
|
+
# then issue:
|
7
|
+
#
|
8
|
+
# c.http_post(login_url, *fields)
|
9
|
+
#
|
10
|
+
# instead of:
|
11
|
+
#
|
12
|
+
# c = Curl::Easy.http_post(login_url, *fields) do |curl|
|
13
|
+
# ...
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# then the result I am getting is quite different.
|
17
|
+
#
|
18
|
+
# ================
|
19
|
+
#
|
20
|
+
# Update:
|
21
|
+
#
|
22
|
+
# It seems that class httpost is incorrectly passing arguments down to
|
23
|
+
# instance httppost. This bug is intermittent, but results in an
|
24
|
+
# exception from the first post when it occurs.
|
25
|
+
#
|
26
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
27
|
+
|
28
|
+
class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
|
29
|
+
def test_bug
|
30
|
+
5.times do |i|
|
31
|
+
puts "Test ##{i}"
|
32
|
+
do_test
|
33
|
+
sleep 2
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def do_test
|
38
|
+
c = Curl::Easy.http_post('https://www.google.com/accounts/ServiceLoginAuth',
|
39
|
+
Curl::PostField.content('ltmpl','m_blanco'))
|
40
|
+
body_c, header_c = c.body_str, c.header_str
|
41
|
+
|
42
|
+
sleep 2
|
43
|
+
|
44
|
+
c.http_post('https://www.google.com/accounts/ServiceLoginAuth',
|
45
|
+
Curl::PostField.content('ltmpl','m_blanco'))
|
46
|
+
body_i, header_i = c.body_str, c.header_str
|
47
|
+
|
48
|
+
# timestamps will differ, just check first bit. We wont get here if
|
49
|
+
# the bug bites anyway...
|
50
|
+
assert_equal header_c[0..50], header_i[0..50]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# From safis http://github.com/taf2/curb/issues#issue/5
|
2
|
+
# irb: require 'curb'
|
3
|
+
# irb: multi = Curl::Multi.new
|
4
|
+
# irb: exit
|
5
|
+
# <main>:47140: [BUG] Bus Error
|
6
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','ext'))
|
7
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
|
8
|
+
require 'curb'
|
9
|
+
|
10
|
+
class BugMultiSegfault < Test::Unit::TestCase
|
11
|
+
def test_bug
|
12
|
+
multi = Curl::Multi.new
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# From GICodeWarrior:
|
2
|
+
#
|
3
|
+
# $ ruby crash_curb.rb
|
4
|
+
# crash_curb.rb:7: [BUG] Segmentation fault
|
5
|
+
# ruby 1.8.7 (2009-06-12 patchlevel 174) [x86_64-linux]
|
6
|
+
#
|
7
|
+
# Aborted
|
8
|
+
# crash_curb.rb:
|
9
|
+
# #!/usr/bin/ruby
|
10
|
+
# require 'rubygems'
|
11
|
+
# require 'curb'
|
12
|
+
#
|
13
|
+
# curl = Curl::Easy.new('http://example.com/')
|
14
|
+
# curl.multipart_form_post = true
|
15
|
+
# curl.http_post(Curl::PostField.file('test', 'test.xml'){'example data'})
|
16
|
+
# Ubuntu 9.10
|
17
|
+
# curb gem version 0.6.2.1
|
18
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
19
|
+
|
20
|
+
class BugPostFieldsCrash < Test::Unit::TestCase
|
21
|
+
def test_crash
|
22
|
+
curl = Curl::Easy.new('http://example.com/')
|
23
|
+
curl.multipart_form_post = true
|
24
|
+
curl.http_post(Curl::PostField.file('test', 'test.xml'){'example data'})
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Not sure if this is an IRB bug, but thought you guys should know.
|
2
|
+
#
|
3
|
+
# ** My Ruby version: ruby 1.8.7 (2009-06-12 patchlevel 174) [x86_64-linux]
|
4
|
+
# ** Version of Rubygems: 1.3.5
|
5
|
+
# ** Version of the Curb gem: 0.6.6.0
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# Transcript of IRB session:
|
9
|
+
# ------------------------------------------------------------------------------------------------------------------
|
10
|
+
# irb(main):001:0> a = {
|
11
|
+
# irb(main):002:1* :type => :pie,
|
12
|
+
# irb(main):003:1* :series => {
|
13
|
+
# irb(main):004:2* :names => [:a,:b],
|
14
|
+
# irb(main):005:2* :values => [70,30],
|
15
|
+
# irb(main):006:2* :colors => [:red,:green]
|
16
|
+
# irb(main):007:2> },
|
17
|
+
# irb(main):008:1* :output_format => :png
|
18
|
+
# irb(main):009:1> }
|
19
|
+
# => {:type=>:pie, :output_format=>:png, :series=>{:names=>[:a, :b], :values=>[70, 30], :colors=>[:red, :green]}}
|
20
|
+
# irb(main):010:0> post = []
|
21
|
+
# => []
|
22
|
+
# irb(main):011:0> require 'rubygems'
|
23
|
+
# => true
|
24
|
+
# irb(main):012:0> require 'curb'
|
25
|
+
# => true
|
26
|
+
# irb(main):013:0> include Curl
|
27
|
+
# => Object
|
28
|
+
# irb(main):014:0> a.each_pair do |k,v|
|
29
|
+
# irb(main):015:1* post << PostField.content(k,v)
|
30
|
+
# irb(main):016:1> end
|
31
|
+
# => {:type=>:pie, :output_format=>:png, :series=>{:names=>[:a, :b], :values=>[70, 30], :colors=>[:red, :green]}}
|
32
|
+
# irb(main):017:0> post
|
33
|
+
# /usr/lib/ruby/1.8/irb.rb:302: [BUG] Segmentation fault
|
34
|
+
# ruby 1.8.7 (2009-06-12 patchlevel 174) [x86_64-linux]
|
35
|
+
#
|
36
|
+
# Aborted
|
37
|
+
# ------------------------------------------------------------------------------------------------------------------
|
38
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
|
39
|
+
|
40
|
+
class BugPostFieldsCrash2 < Test::Unit::TestCase
|
41
|
+
def test_crash
|
42
|
+
a = {
|
43
|
+
:type => :pie,
|
44
|
+
:series => {
|
45
|
+
:names => [:a,:b],
|
46
|
+
:values => [70,30],
|
47
|
+
:colors => [:red,:green]
|
48
|
+
},
|
49
|
+
:output_format => :png
|
50
|
+
}
|
51
|
+
post = []
|
52
|
+
a.each_pair do |k,v|
|
53
|
+
post << Curl::PostField.content(k,v)
|
54
|
+
end
|
55
|
+
post.inspect
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# From Vlad Jebelev:
|
2
|
+
#
|
3
|
+
# - if I have a require statement after "require 'curb'" and there is a
|
4
|
+
# POST with at least 1 field, the script will fail with a segmentation
|
5
|
+
# fault, e.g. the following sequence fails every time for me (Ruby 1.8.5):
|
6
|
+
# -----------------------------------------------------------------
|
7
|
+
# require 'curb'
|
8
|
+
# require 'uri'
|
9
|
+
#
|
10
|
+
# url = 'https://www.google.com/accounts/ServiceLoginAuth'
|
11
|
+
#
|
12
|
+
# c = Curl::Easy.http_post(
|
13
|
+
# 'https://www.google.com/accounts/ServiceLoginAuth',
|
14
|
+
# [Curl:: PostField.content('ltmpl','m_blanco')] ) do |curl|
|
15
|
+
# end
|
16
|
+
# ------------------------------------------------------------------
|
17
|
+
# :..dev/util$ ruby seg.rb
|
18
|
+
# seg.rb:6: [BUG] Segmentation fault
|
19
|
+
# ruby 1.8.5 (2006-08-25) [i686-linux]
|
20
|
+
#
|
21
|
+
# Aborted
|
22
|
+
# ------------------------------------------------------------------
|
23
|
+
#
|
24
|
+
require 'test/unit'
|
25
|
+
require 'rbconfig'
|
26
|
+
|
27
|
+
$rubycmd = Config::CONFIG['RUBY_INSTALL_NAME'] || 'ruby'
|
28
|
+
|
29
|
+
class BugTestRequireLastOrSegfault < Test::Unit::TestCase
|
30
|
+
def test_bug
|
31
|
+
5.times do |i|
|
32
|
+
puts "Test ##{i}"
|
33
|
+
|
34
|
+
# will be empty string if it segfaults...
|
35
|
+
assert_equal 'success', `#$rubycmd #{File.dirname(__FILE__)}/require_last_or_segfault_script.rb`.chomp
|
36
|
+
sleep 5
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
data/tests/bugtests.rb
ADDED
data/tests/helper.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
# DO NOT REMOVE THIS COMMENT - PART OF TESTMODEL.
|
2
|
+
# Copyright (c)2006 Ross Bamford. See LICENSE.
|
3
|
+
$CURB_TESTING = true
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
$TOPDIR = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
7
|
+
$EXTDIR = File.join($TOPDIR, 'ext')
|
8
|
+
$LIBDIR = File.join($TOPDIR, 'lib')
|
9
|
+
$:.unshift($LIBDIR)
|
10
|
+
$:.unshift($EXTDIR)
|
11
|
+
|
12
|
+
require 'curb'
|
13
|
+
require 'test/unit'
|
14
|
+
require 'fileutils'
|
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
|
+
if req.path.match /redirect$/
|
69
|
+
res.status = 302
|
70
|
+
res['Location'] = '/foo'
|
71
|
+
elsif req.path.match /not_here$/
|
72
|
+
res.status = 404
|
73
|
+
elsif req.path.match /error$/
|
74
|
+
res.status = 500
|
75
|
+
end
|
76
|
+
respond_with("GET#{req.query_string}",req,res)
|
77
|
+
end
|
78
|
+
|
79
|
+
def do_HEAD(req,res)
|
80
|
+
res['Location'] = "/nonexistent"
|
81
|
+
respond_with("HEAD#{req.query_string}",req,res)
|
82
|
+
end
|
83
|
+
|
84
|
+
def do_POST(req,res)
|
85
|
+
if req.query['filename'].nil?
|
86
|
+
if req.body
|
87
|
+
params = {}
|
88
|
+
req.body.split('&').map{|s| k,v=s.split('='); params[k] = v }
|
89
|
+
end
|
90
|
+
if params and params['s'] == '500'
|
91
|
+
res.status = 500
|
92
|
+
else
|
93
|
+
respond_with("POST\n#{req.body}",req,res)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
respond_with(req.query['filename'],req,res)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def do_PUT(req,res)
|
101
|
+
res['X-Requested-Content-Type'] = req.content_type
|
102
|
+
respond_with("PUT\n#{req.body}",req,res)
|
103
|
+
end
|
104
|
+
|
105
|
+
def do_DELETE(req,res)
|
106
|
+
respond_with("DELETE#{req.query_string}",req,res)
|
107
|
+
end
|
108
|
+
|
109
|
+
def do_PURGE(req,res)
|
110
|
+
respond_with("PURGE#{req.query_string}",req,res)
|
111
|
+
end
|
112
|
+
|
113
|
+
def do_COPY(req,res)
|
114
|
+
respond_with("COPY#{req.query_string}",req,res)
|
115
|
+
end
|
116
|
+
|
117
|
+
def do_PATCH(req,res)
|
118
|
+
respond_with("PATCH\n#{req.body}",req,res)
|
119
|
+
end
|
120
|
+
|
121
|
+
def do_OPTIONS(req,res)
|
122
|
+
respond_with("OPTIONS#{req.query_string}",req,res)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
module TestServerMethods
|
128
|
+
def locked_file
|
129
|
+
File.join(File.dirname(__FILE__),"server_lock-#{@__port}")
|
130
|
+
end
|
131
|
+
|
132
|
+
def server_setup(port=9129,servlet=TestServlet)
|
133
|
+
@__port = port
|
134
|
+
if @server.nil? and !File.exist?(locked_file)
|
135
|
+
|
136
|
+
File.open(locked_file,'w') {|f| f << 'locked' }
|
137
|
+
if TEST_SINGLE_THREADED
|
138
|
+
rd, wr = IO.pipe
|
139
|
+
@__pid = fork do
|
140
|
+
rd.close
|
141
|
+
rd = nil
|
142
|
+
|
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
|
+
|
149
|
+
trap("INT") { server.shutdown }
|
150
|
+
GC.start
|
151
|
+
wr.flush
|
152
|
+
wr.close
|
153
|
+
server.start
|
154
|
+
end
|
155
|
+
wr.close
|
156
|
+
rd.read
|
157
|
+
rd.close
|
158
|
+
else
|
159
|
+
# start up a webrick server for testing delete
|
160
|
+
@server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
161
|
+
|
162
|
+
@server.mount(servlet.path, servlet)
|
163
|
+
@server.mount("/ext", WEBrick::HTTPServlet::FileHandler, File.join(File.dirname(__FILE__),'..','ext'))
|
164
|
+
queue = Queue.new # synchronize the thread startup to the main thread
|
165
|
+
|
166
|
+
@test_thread = Thread.new { queue << 1; @server.start }
|
167
|
+
|
168
|
+
# wait for the queue
|
169
|
+
value = queue.pop
|
170
|
+
if !value
|
171
|
+
STDERR.puts "Failed to startup test server!"
|
172
|
+
exit(1)
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
exit_code = lambda do
|
178
|
+
begin
|
179
|
+
if File.exist?(locked_file)
|
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?
|
188
|
+
rescue Object => e
|
189
|
+
puts "Error #{__FILE__}:#{__LINE__}\n#{e.message}"
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
trap("INT"){exit_code.call}
|
194
|
+
at_exit{exit_code.call}
|
195
|
+
|
196
|
+
end
|
197
|
+
rescue Errno::EADDRINUSE
|
198
|
+
end
|
199
|
+
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
|