curb 0.7.15 → 1.0.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.
- checksums.yaml +7 -0
- data/README.markdown +283 -0
- data/Rakefile +38 -26
- data/ext/banned.h +32 -0
- data/ext/curb.c +903 -46
- data/ext/curb.h +20 -11
- data/ext/curb_easy.c +1039 -565
- data/ext/curb_easy.h +12 -0
- data/ext/curb_errors.c +127 -18
- data/ext/curb_errors.h +8 -5
- data/ext/curb_macros.h +10 -6
- data/ext/curb_multi.c +245 -167
- data/ext/curb_multi.h +0 -1
- data/ext/curb_upload.c +2 -2
- data/ext/extconf.rb +314 -20
- data/lib/curb.rb +2 -308
- data/lib/curl/easy.rb +489 -0
- data/lib/curl/multi.rb +287 -0
- data/lib/curl.rb +68 -1
- data/tests/bug_crash_on_debug.rb +39 -0
- data/tests/bug_crash_on_progress.rb +73 -0
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +2 -2
- data/tests/bug_issue102.rb +17 -0
- data/tests/bug_require_last_or_segfault.rb +1 -1
- data/tests/helper.rb +120 -16
- data/tests/signals.rb +33 -0
- data/tests/tc_curl.rb +69 -0
- data/tests/tc_curl_download.rb +4 -4
- data/tests/tc_curl_easy.rb +327 -43
- data/tests/tc_curl_easy_resolve.rb +16 -0
- data/tests/tc_curl_easy_setopt.rb +31 -0
- data/tests/tc_curl_maxfilesize.rb +12 -0
- data/tests/tc_curl_multi.rb +141 -15
- data/tests/tc_curl_postfield.rb +29 -29
- data/tests/tc_curl_protocols.rb +37 -0
- data/tests/timeout.rb +30 -6
- metadata +61 -58
- data/README +0 -177
data/lib/curb.rb
CHANGED
|
@@ -1,308 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
module Curl
|
|
4
|
-
|
|
5
|
-
class Easy
|
|
6
|
-
class << self
|
|
7
|
-
|
|
8
|
-
# call-seq:
|
|
9
|
-
# Curl::Easy.download(url, filename = url.split(/\?/).first.split(/\//).last) { |curl| ... }
|
|
10
|
-
#
|
|
11
|
-
# Stream the specified url (via perform) and save the data directly to the
|
|
12
|
-
# supplied filename (defaults to the last component of the URL path, which will
|
|
13
|
-
# usually be the filename most simple urls).
|
|
14
|
-
#
|
|
15
|
-
# If a block is supplied, it will be passed the curl instance prior to the
|
|
16
|
-
# perform call.
|
|
17
|
-
#
|
|
18
|
-
# *Note* that the semantics of the on_body handler are subtly changed when using
|
|
19
|
-
# download, to account for the automatic routing of data to the specified file: The
|
|
20
|
-
# data string is passed to the handler *before* it is written
|
|
21
|
-
# to the file, allowing the handler to perform mutative operations where
|
|
22
|
-
# necessary. As usual, the transfer will be aborted if the on_body handler
|
|
23
|
-
# returns a size that differs from the data chunk size - in this case, the
|
|
24
|
-
# offending chunk will *not* be written to the file, the file will be closed,
|
|
25
|
-
# and a Curl::Err::AbortedByCallbackError will be raised.
|
|
26
|
-
def download(url, filename = url.split(/\?/).first.split(/\//).last, &blk)
|
|
27
|
-
curl = Curl::Easy.new(url, &blk)
|
|
28
|
-
|
|
29
|
-
output = if filename.is_a? IO
|
|
30
|
-
filename.binmode if filename.respond_to?(:binmode)
|
|
31
|
-
filename
|
|
32
|
-
else
|
|
33
|
-
File.open(filename, 'wb')
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
begin
|
|
37
|
-
old_on_body = curl.on_body do |data|
|
|
38
|
-
result = old_on_body ? old_on_body.call(data) : data.length
|
|
39
|
-
output << data if result == data.length
|
|
40
|
-
result
|
|
41
|
-
end
|
|
42
|
-
curl.perform
|
|
43
|
-
ensure
|
|
44
|
-
output.close rescue IOError
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
return curl
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Allow the incoming cert string to be file:password
|
|
52
|
-
# but be careful to not use a colon from a windows file path
|
|
53
|
-
# as the split point. Mimic what curl's main does
|
|
54
|
-
alias_method :native_cert=, :cert=
|
|
55
|
-
def cert=(cert_file)
|
|
56
|
-
pos = cert_file.rindex(':')
|
|
57
|
-
if pos && pos > 1
|
|
58
|
-
self.native_cert= cert_file[0..pos-1]
|
|
59
|
-
self.certpassword= cert_file[pos+1..-1]
|
|
60
|
-
else
|
|
61
|
-
self.native_cert= cert_file
|
|
62
|
-
end
|
|
63
|
-
self.cert
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
class Multi
|
|
68
|
-
class << self
|
|
69
|
-
# call-seq:
|
|
70
|
-
# Curl::Multi.get('url1','url2','url3','url4','url5', :follow_location => true) do|easy|
|
|
71
|
-
# easy
|
|
72
|
-
# end
|
|
73
|
-
#
|
|
74
|
-
# Blocking call to fetch multiple url's in parallel.
|
|
75
|
-
def get(urls, easy_options={}, multi_options={}, &blk)
|
|
76
|
-
url_confs = []
|
|
77
|
-
urls.each do|url|
|
|
78
|
-
url_confs << {:url => url, :method => :get}.merge(easy_options)
|
|
79
|
-
end
|
|
80
|
-
self.http(url_confs, multi_options) {|c,code,method| blk.call(c) if blk }
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# call-seq:
|
|
84
|
-
#
|
|
85
|
-
# Curl::Multi.post([{:url => 'url1', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
|
|
86
|
-
# {:url => 'url2', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
|
|
87
|
-
# {:url => 'url3', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}}],
|
|
88
|
-
# { :follow_location => true, :multipart_form_post => true },
|
|
89
|
-
# {:pipeline => true }) do|easy|
|
|
90
|
-
# easy_handle_on_request_complete
|
|
91
|
-
# end
|
|
92
|
-
#
|
|
93
|
-
# Blocking call to POST multiple form's in parallel.
|
|
94
|
-
#
|
|
95
|
-
# urls_with_config: is a hash of url's pointing to the postfields to send
|
|
96
|
-
# easy_options: are a set of common options to set on all easy handles
|
|
97
|
-
# multi_options: options to set on the Curl::Multi handle
|
|
98
|
-
#
|
|
99
|
-
def post(urls_with_config, easy_options={}, multi_options={}, &blk)
|
|
100
|
-
url_confs = []
|
|
101
|
-
urls_with_config.each do|uconf|
|
|
102
|
-
url_confs << uconf.merge(:method => :post).merge(easy_options)
|
|
103
|
-
end
|
|
104
|
-
self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# call-seq:
|
|
108
|
-
#
|
|
109
|
-
# Curl::Multi.put([{:url => 'url1', :put_data => "some message"},
|
|
110
|
-
# {:url => 'url2', :put_data => IO.read('filepath')},
|
|
111
|
-
# {:url => 'url3', :put_data => "maybe another string or socket?"],
|
|
112
|
-
# {:follow_location => true},
|
|
113
|
-
# {:pipeline => true }) do|easy|
|
|
114
|
-
# easy_handle_on_request_complete
|
|
115
|
-
# end
|
|
116
|
-
#
|
|
117
|
-
# Blocking call to POST multiple form's in parallel.
|
|
118
|
-
#
|
|
119
|
-
# urls_with_config: is a hash of url's pointing to the postfields to send
|
|
120
|
-
# easy_options: are a set of common options to set on all easy handles
|
|
121
|
-
# multi_options: options to set on the Curl::Multi handle
|
|
122
|
-
#
|
|
123
|
-
def put(urls_with_config, easy_options={}, multi_options={}, &blk)
|
|
124
|
-
url_confs = []
|
|
125
|
-
urls_with_config.each do|uconf|
|
|
126
|
-
url_confs << uconf.merge(:method => :put).merge(easy_options)
|
|
127
|
-
end
|
|
128
|
-
self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
# call-seq:
|
|
133
|
-
#
|
|
134
|
-
# Curl::Multi.http( [
|
|
135
|
-
# { :url => 'url1', :method => :post,
|
|
136
|
-
# :post_fields => {'field1' => 'value1', 'field2' => 'value2'} },
|
|
137
|
-
# { :url => 'url2', :method => :get,
|
|
138
|
-
# :follow_location => true, :max_redirects => 3 },
|
|
139
|
-
# { :url => 'url3', :method => :put, :put_data => File.open('file.txt','rb') },
|
|
140
|
-
# { :url => 'url4', :method => :head }
|
|
141
|
-
# ], {:pipeline => true})
|
|
142
|
-
#
|
|
143
|
-
# Blocking call to issue multiple HTTP requests with varying verb's.
|
|
144
|
-
#
|
|
145
|
-
# urls_with_config: is a hash of url's pointing to the easy handle options as well as the special option :method, that can by one of [:get, :post, :put, :delete, :head], when no verb is provided e.g. :method => nil -> GET is used
|
|
146
|
-
# multi_options: options for the multi handle
|
|
147
|
-
# blk: a callback, that yeilds when a handle is completed
|
|
148
|
-
#
|
|
149
|
-
def http(urls_with_config, multi_options={}, &blk)
|
|
150
|
-
m = Curl::Multi.new
|
|
151
|
-
|
|
152
|
-
# maintain a sane number of easy handles
|
|
153
|
-
multi_options[:max_connects] = max_connects = multi_options.key?(:max_connects) ? multi_options[:max_connects] : 10
|
|
154
|
-
|
|
155
|
-
free_handles = [] # keep a list of free easy handles
|
|
156
|
-
|
|
157
|
-
# configure the multi handle
|
|
158
|
-
multi_options.each { |k,v| m.send("#{k}=", v) }
|
|
159
|
-
callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_body,:on_header]
|
|
160
|
-
|
|
161
|
-
add_free_handle = proc do|conf, easy|
|
|
162
|
-
c = conf.dup # avoid being destructive to input
|
|
163
|
-
url = c.delete(:url)
|
|
164
|
-
method = c.delete(:method)
|
|
165
|
-
headers = c.delete(:headers)
|
|
166
|
-
|
|
167
|
-
easy = Curl::Easy.new if easy.nil?
|
|
168
|
-
|
|
169
|
-
easy.url = url
|
|
170
|
-
|
|
171
|
-
# assign callbacks
|
|
172
|
-
callbacks.each do |cb|
|
|
173
|
-
cbproc = c.delete(cb)
|
|
174
|
-
easy.send(cb,&cbproc) if cbproc
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
case method
|
|
178
|
-
when :post
|
|
179
|
-
fields = c.delete(:post_fields)
|
|
180
|
-
# set the post post using the url fields
|
|
181
|
-
easy.post_body = fields.map{|f,k| "#{easy.escape(f)}=#{easy.escape(k)}"}.join('&')
|
|
182
|
-
when :put
|
|
183
|
-
easy.put_data = c.delete(:put_data)
|
|
184
|
-
when :head
|
|
185
|
-
easy.head = true
|
|
186
|
-
when :delete
|
|
187
|
-
easy.delete = true
|
|
188
|
-
when :get
|
|
189
|
-
else
|
|
190
|
-
# XXX: nil is treated like a GET
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
# headers is a special key
|
|
194
|
-
headers.each {|k,v| easy.headers[k] = v } if headers
|
|
195
|
-
|
|
196
|
-
#
|
|
197
|
-
# use the remaining options as specific configuration to the easy handle
|
|
198
|
-
# bad options should raise an undefined method error
|
|
199
|
-
#
|
|
200
|
-
c.each { |k,v| easy.send("#{k}=",v) }
|
|
201
|
-
|
|
202
|
-
easy.on_complete {|curl,code|
|
|
203
|
-
free_handles << curl
|
|
204
|
-
blk.call(curl,code,method) if blk
|
|
205
|
-
}
|
|
206
|
-
m.add(easy)
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
max_connects.times do
|
|
210
|
-
conf = urls_with_config.pop
|
|
211
|
-
add_free_handle.call conf, nil
|
|
212
|
-
break if urls_with_config.empty?
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
consume_free_handles = proc do
|
|
216
|
-
# as we idle consume free handles
|
|
217
|
-
if urls_with_config.size > 0 && free_handles.size > 0
|
|
218
|
-
easy = free_handles.pop
|
|
219
|
-
conf = urls_with_config.pop
|
|
220
|
-
add_free_handle.call conf, easy
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
until urls_with_config.empty?
|
|
225
|
-
m.perform do
|
|
226
|
-
consume_free_handles.call
|
|
227
|
-
end
|
|
228
|
-
consume_free_handles.call
|
|
229
|
-
end
|
|
230
|
-
free_handles = nil
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
# call-seq:
|
|
234
|
-
#
|
|
235
|
-
# Curl::Multi.download(['http://example.com/p/a/t/h/file1.txt','http://example.com/p/a/t/h/file2.txt']){|c|}
|
|
236
|
-
#
|
|
237
|
-
# will create 2 new files file1.txt and file2.txt
|
|
238
|
-
#
|
|
239
|
-
# 2 files will be opened, and remain open until the call completes
|
|
240
|
-
#
|
|
241
|
-
# when using the :post or :put method, urls should be a hash, including the individual post fields per post
|
|
242
|
-
#
|
|
243
|
-
def download(urls,easy_options={},multi_options={},download_paths=nil,&blk)
|
|
244
|
-
errors = []
|
|
245
|
-
procs = []
|
|
246
|
-
files = []
|
|
247
|
-
urls_with_config = []
|
|
248
|
-
url_to_download_paths = {}
|
|
249
|
-
|
|
250
|
-
urls.each_with_index do|urlcfg,i|
|
|
251
|
-
if urlcfg.is_a?(Hash)
|
|
252
|
-
url = url[:url]
|
|
253
|
-
else
|
|
254
|
-
url = urlcfg
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
if download_paths and download_paths[i]
|
|
258
|
-
download_path = download_paths[i]
|
|
259
|
-
else
|
|
260
|
-
download_path = File.basename(url)
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
file = lambda do|dp|
|
|
264
|
-
file = File.open(dp,"wb")
|
|
265
|
-
procs << (lambda {|data| file.write data; data.size })
|
|
266
|
-
files << file
|
|
267
|
-
file
|
|
268
|
-
end.call(download_path)
|
|
269
|
-
|
|
270
|
-
if urlcfg.is_a?(Hash)
|
|
271
|
-
urls_with_config << urlcfg.merge({:on_body => procs.last}.merge(easy_options))
|
|
272
|
-
else
|
|
273
|
-
urls_with_config << {:url => url, :on_body => procs.last, :method => :get}.merge(easy_options)
|
|
274
|
-
end
|
|
275
|
-
url_to_download_paths[url] = {:path => download_path, :file => file} # store for later
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
if blk
|
|
279
|
-
# when injecting the block, ensure file is closed before yielding
|
|
280
|
-
Curl::Multi.http(urls_with_config, multi_options) do |c,code,method|
|
|
281
|
-
info = url_to_download_paths[c.url]
|
|
282
|
-
begin
|
|
283
|
-
file = info[:file]
|
|
284
|
-
files.reject!{|f| f == file }
|
|
285
|
-
file.close
|
|
286
|
-
rescue => e
|
|
287
|
-
errors << e
|
|
288
|
-
end
|
|
289
|
-
blk.call(c,info[:path])
|
|
290
|
-
end
|
|
291
|
-
else
|
|
292
|
-
Curl::Multi.http(urls_with_config, multi_options)
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
ensure
|
|
296
|
-
files.each {|f|
|
|
297
|
-
begin
|
|
298
|
-
f.close
|
|
299
|
-
rescue => e
|
|
300
|
-
errors << e
|
|
301
|
-
end
|
|
302
|
-
}
|
|
303
|
-
raise errors unless errors.empty?
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
end
|
|
307
|
-
end
|
|
308
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'curl'
|