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.
data/lib/curl/multi.rb ADDED
@@ -0,0 +1,287 @@
1
+ # frozen_string_literal: true
2
+ module Curl
3
+ class Multi
4
+ class << self
5
+ # call-seq:
6
+ # Curl::Multi.get(['url1','url2','url3','url4','url5'], :follow_location => true) do|easy|
7
+ # easy
8
+ # end
9
+ #
10
+ # Blocking call to fetch multiple url's in parallel.
11
+ def get(urls, easy_options={}, multi_options={}, &blk)
12
+ url_confs = []
13
+ urls.each do|url|
14
+ url_confs << {:url => url, :method => :get}.merge(easy_options)
15
+ end
16
+ self.http(url_confs, multi_options) {|c,code,method| blk.call(c) if blk }
17
+ end
18
+
19
+ # call-seq:
20
+ #
21
+ # Curl::Multi.post([{:url => 'url1', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
22
+ # {:url => 'url2', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}},
23
+ # {:url => 'url3', :post_fields => {'field1' => 'value1', 'field2' => 'value2'}}],
24
+ # { :follow_location => true, :multipart_form_post => true },
25
+ # {:pipeline => Curl::CURLPIPE_HTTP1}) do|easy|
26
+ # easy_handle_on_request_complete
27
+ # end
28
+ #
29
+ # Blocking call to POST multiple form's in parallel.
30
+ #
31
+ # urls_with_config: is a hash of url's pointing to the postfields to send
32
+ # easy_options: are a set of common options to set on all easy handles
33
+ # multi_options: options to set on the Curl::Multi handle
34
+ #
35
+ def post(urls_with_config, easy_options={}, multi_options={}, &blk)
36
+ url_confs = []
37
+ urls_with_config.each do|uconf|
38
+ url_confs << uconf.merge(:method => :post).merge(easy_options)
39
+ end
40
+ self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
41
+ end
42
+
43
+ # call-seq:
44
+ #
45
+ # Curl::Multi.put([{:url => 'url1', :put_data => "some message"},
46
+ # {:url => 'url2', :put_data => IO.read('filepath')},
47
+ # {:url => 'url3', :put_data => "maybe another string or socket?"],
48
+ # {:follow_location => true},
49
+ # {:pipeline => Curl::CURLPIPE_HTTP1}) do|easy|
50
+ # easy_handle_on_request_complete
51
+ # end
52
+ #
53
+ # Blocking call to POST multiple form's in parallel.
54
+ #
55
+ # urls_with_config: is a hash of url's pointing to the postfields to send
56
+ # easy_options: are a set of common options to set on all easy handles
57
+ # multi_options: options to set on the Curl::Multi handle
58
+ #
59
+ def put(urls_with_config, easy_options={}, multi_options={}, &blk)
60
+ url_confs = []
61
+ urls_with_config.each do|uconf|
62
+ url_confs << uconf.merge(:method => :put).merge(easy_options)
63
+ end
64
+ self.http(url_confs, multi_options) {|c,code,method| blk.call(c) }
65
+ end
66
+
67
+
68
+ # call-seq:
69
+ #
70
+ # Curl::Multi.http( [
71
+ # { :url => 'url1', :method => :post,
72
+ # :post_fields => {'field1' => 'value1', 'field2' => 'value2'} },
73
+ # { :url => 'url2', :method => :get,
74
+ # :follow_location => true, :max_redirects => 3 },
75
+ # { :url => 'url3', :method => :put, :put_data => File.open('file.txt','rb') },
76
+ # { :url => 'url4', :method => :head }
77
+ # ], {:pipeline => Curl::CURLPIPE_HTTP1})
78
+ #
79
+ # Blocking call to issue multiple HTTP requests with varying verb's.
80
+ #
81
+ # 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
82
+ # multi_options: options for the multi handle
83
+ # blk: a callback, that yeilds when a handle is completed
84
+ #
85
+ def http(urls_with_config, multi_options={}, &blk)
86
+ m = Curl::Multi.new
87
+
88
+ # maintain a sane number of easy handles
89
+ multi_options[:max_connects] = max_connects = multi_options.key?(:max_connects) ? multi_options[:max_connects] : 10
90
+
91
+ free_handles = [] # keep a list of free easy handles
92
+
93
+ # configure the multi handle
94
+ multi_options.each { |k,v| m.send("#{k}=", v) }
95
+ callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_redirect,:on_body,:on_header]
96
+
97
+ add_free_handle = proc do|conf, easy|
98
+ c = conf.dup # avoid being destructive to input
99
+ url = c.delete(:url)
100
+ method = c.delete(:method)
101
+ headers = c.delete(:headers)
102
+
103
+ easy = Curl::Easy.new if easy.nil?
104
+
105
+ easy.url = url
106
+
107
+ # assign callbacks
108
+ callbacks.each do |cb|
109
+ cbproc = c.delete(cb)
110
+ easy.send(cb,&cbproc) if cbproc
111
+ end
112
+
113
+ case method
114
+ when :post
115
+ fields = c.delete(:post_fields)
116
+ # set the post post using the url fields
117
+ easy.post_body = fields.map{|f,k| "#{easy.escape(f)}=#{easy.escape(k)}"}.join('&')
118
+ when :put
119
+ easy.put_data = c.delete(:put_data)
120
+ when :head
121
+ easy.head = true
122
+ when :delete
123
+ easy.delete = true
124
+ when :get
125
+ else
126
+ # XXX: nil is treated like a GET
127
+ end
128
+
129
+ # headers is a special key
130
+ headers.each {|k,v| easy.headers[k] = v } if headers
131
+
132
+ #
133
+ # use the remaining options as specific configuration to the easy handle
134
+ # bad options should raise an undefined method error
135
+ #
136
+ c.each { |k,v| easy.send("#{k}=",v) }
137
+
138
+ easy.on_complete {|curl|
139
+ free_handles << curl
140
+ blk.call(curl,curl.response_code,method) if blk
141
+ }
142
+ m.add(easy)
143
+ end
144
+
145
+ max_connects.times do
146
+ conf = urls_with_config.pop
147
+ add_free_handle.call(conf, nil) if conf
148
+ break if urls_with_config.empty?
149
+ end
150
+
151
+ consume_free_handles = proc do
152
+ # as we idle consume free handles
153
+ if urls_with_config.size > 0 && free_handles.size > 0
154
+ easy = free_handles.pop
155
+ conf = urls_with_config.pop
156
+ add_free_handle.call(conf, easy) if conf
157
+ end
158
+ end
159
+
160
+ if urls_with_config.empty?
161
+ m.perform
162
+ else
163
+ until urls_with_config.empty?
164
+ m.perform do
165
+ consume_free_handles.call
166
+ end
167
+ consume_free_handles.call
168
+ end
169
+ free_handles = nil
170
+ end
171
+
172
+ end
173
+
174
+ # call-seq:
175
+ #
176
+ # Curl::Multi.download(['http://example.com/p/a/t/h/file1.txt','http://example.com/p/a/t/h/file2.txt']){|c|}
177
+ #
178
+ # will create 2 new files file1.txt and file2.txt
179
+ #
180
+ # 2 files will be opened, and remain open until the call completes
181
+ #
182
+ # when using the :post or :put method, urls should be a hash, including the individual post fields per post
183
+ #
184
+ def download(urls,easy_options={},multi_options={},download_paths=nil,&blk)
185
+ errors = []
186
+ procs = []
187
+ files = []
188
+ urls_with_config = []
189
+ url_to_download_paths = {}
190
+
191
+ urls.each_with_index do|urlcfg,i|
192
+ if urlcfg.is_a?(Hash)
193
+ url = url[:url]
194
+ else
195
+ url = urlcfg
196
+ end
197
+
198
+ if download_paths and download_paths[i]
199
+ download_path = download_paths[i]
200
+ else
201
+ download_path = File.basename(url)
202
+ end
203
+
204
+ file = lambda do|dp|
205
+ file = File.open(dp,"wb")
206
+ procs << (lambda {|data| file.write data; data.size })
207
+ files << file
208
+ file
209
+ end.call(download_path)
210
+
211
+ if urlcfg.is_a?(Hash)
212
+ urls_with_config << urlcfg.merge({:on_body => procs.last}.merge(easy_options))
213
+ else
214
+ urls_with_config << {:url => url, :on_body => procs.last, :method => :get}.merge(easy_options)
215
+ end
216
+ url_to_download_paths[url] = {:path => download_path, :file => file} # store for later
217
+ end
218
+
219
+ if blk
220
+ # when injecting the block, ensure file is closed before yielding
221
+ Curl::Multi.http(urls_with_config, multi_options) do |c,code,method|
222
+ info = url_to_download_paths[c.url]
223
+ begin
224
+ file = info[:file]
225
+ files.reject!{|f| f == file }
226
+ file.close
227
+ rescue => e
228
+ errors << e
229
+ end
230
+ blk.call(c,info[:path])
231
+ end
232
+ else
233
+ Curl::Multi.http(urls_with_config, multi_options)
234
+ end
235
+
236
+ ensure
237
+ files.each {|f|
238
+ begin
239
+ f.close
240
+ rescue => e
241
+ errors << e
242
+ end
243
+ }
244
+ raise errors unless errors.empty?
245
+ end
246
+ end
247
+
248
+ def cancel!
249
+ requests.each do |_,easy|
250
+ remove(easy)
251
+ end
252
+ end
253
+
254
+ def idle?
255
+ requests.empty?
256
+ end
257
+
258
+ def requests
259
+ @requests ||= {}
260
+ end
261
+
262
+ def add(easy)
263
+ return self if requests[easy.object_id]
264
+ requests[easy.object_id] = easy
265
+ _add(easy)
266
+ self
267
+ end
268
+
269
+ def remove(easy)
270
+ return self if !requests[easy.object_id]
271
+ requests.delete(easy.object_id)
272
+ _remove(easy)
273
+ self
274
+ end
275
+
276
+ def close
277
+ requests.values.each {|easy|
278
+ _remove(easy)
279
+ }
280
+ @requests = {}
281
+ _close
282
+ self
283
+ end
284
+
285
+
286
+ end
287
+ end
data/lib/curl.rb CHANGED
@@ -1 +1,68 @@
1
- require 'curb'
1
+ # frozen_string_literal: true
2
+ require 'curb_core'
3
+ require 'curl/easy'
4
+ require 'curl/multi'
5
+ require 'uri'
6
+ require 'cgi'
7
+
8
+ # expose shortcut methods
9
+ module Curl
10
+
11
+ def self.http(verb, url, post_body=nil, put_data=nil, &block)
12
+ handle = Thread.current[:curb_curl] ||= Curl::Easy.new
13
+ handle.reset
14
+ handle.url = url
15
+ handle.post_body = post_body if post_body
16
+ handle.put_data = put_data if put_data
17
+ yield handle if block_given?
18
+ handle.http(verb)
19
+ handle
20
+ end
21
+
22
+ def self.get(url, params={}, &block)
23
+ http :GET, urlalize(url, params), nil, nil, &block
24
+ end
25
+
26
+ def self.post(url, params={}, &block)
27
+ http :POST, url, postalize(params), nil, &block
28
+ end
29
+
30
+ def self.put(url, params={}, &block)
31
+ http :PUT, url, nil, postalize(params), &block
32
+ end
33
+
34
+ def self.delete(url, params={}, &block)
35
+ http :DELETE, url, postalize(params), nil, &block
36
+ end
37
+
38
+ def self.patch(url, params={}, &block)
39
+ http :PATCH, url, postalize(params), nil, &block
40
+ end
41
+
42
+ def self.head(url, params={}, &block)
43
+ http :HEAD, urlalize(url, params), nil, nil, &block
44
+ end
45
+
46
+ def self.options(url, params={}, &block)
47
+ http :OPTIONS, urlalize(url, params), nil, nil, &block
48
+ end
49
+
50
+ def self.urlalize(url, params={})
51
+ uri = URI(url)
52
+ # early return if we didn't specify any extra params
53
+ return uri.to_s if (params || {}).empty?
54
+
55
+ params_query = URI.encode_www_form(params || {})
56
+ uri.query = [uri.query.to_s, params_query].reject(&:empty?).join('&')
57
+ uri.to_s
58
+ end
59
+
60
+ def self.postalize(params={})
61
+ params.respond_to?(:map) ? URI.encode_www_form(params) : (params.respond_to?(:to_s) ? params.to_s : params)
62
+ end
63
+
64
+ def self.reset
65
+ Thread.current[:curb_curl] = Curl::Easy.new
66
+ end
67
+
68
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ require 'webrick'
4
+ class ::WEBrick::HTTPServer ; def access_log(config, req, res) ; end ; end
5
+ class ::WEBrick::BasicLog ; def log(level, data) ; end ; end
6
+
7
+ require 'curl'
8
+
9
+ class BugCrashOnDebug < Test::Unit::TestCase
10
+
11
+ def test_on_debug
12
+ server = WEBrick::HTTPServer.new( :Port => 9999 )
13
+ server.mount_proc("/test") do|req,res|
14
+ res.body = "hi"
15
+ res['Content-Type'] = "text/html"
16
+ end
17
+ puts 'a'
18
+ thread = Thread.new(server) do|srv|
19
+ srv.start
20
+ end
21
+ puts 'b'
22
+ c = Curl::Easy.new('http://127.0.0.1:9999/test')
23
+ c.on_debug do|x|
24
+ puts x.inspect
25
+ raise "error" # this will get swallowed
26
+ end
27
+ c.perform
28
+ puts 'c'
29
+ ensure
30
+ puts 'd'
31
+ server.shutdown
32
+ puts 'e'
33
+ puts thread.exit
34
+ puts 'f'
35
+ end
36
+
37
+ end
38
+
39
+ #test_on_debug
@@ -0,0 +1,73 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+ require 'webrick'
3
+ class ::WEBrick::HTTPServer ; def access_log(config, req, res) ; end ; end
4
+ class ::WEBrick::BasicLog ; def log(level, data) ; end ; end
5
+
6
+ class BugCrashOnDebug < Test::Unit::TestCase
7
+
8
+ def test_on_progress_raise
9
+ server = WEBrick::HTTPServer.new( :Port => 9999 )
10
+ server.mount_proc("/test") do|req,res|
11
+ res.body = "hi"
12
+ res['Content-Type'] = "text/html"
13
+ end
14
+
15
+ thread = Thread.new(server) do|srv|
16
+ srv.start
17
+ end
18
+
19
+ c = Curl::Easy.new('http://127.0.0.1:9999/test')
20
+ c.on_progress do|x|
21
+ raise "error"
22
+ end
23
+ c.perform
24
+
25
+ assert false, "should not reach this point"
26
+
27
+ rescue => e
28
+ assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s
29
+ c.close
30
+ ensure
31
+ server.shutdown
32
+ end
33
+
34
+ def test_on_progress_abort
35
+ server = WEBrick::HTTPServer.new( :Port => 9999 )
36
+ server.mount_proc("/test") do|req,res|
37
+ res.body = "hi"
38
+ res['Content-Type'] = "text/html"
39
+ end
40
+
41
+ thread = Thread.new(server) do|srv|
42
+ srv.start
43
+ end
44
+
45
+ # see: https://github.com/taf2/curb/issues/192,
46
+ # to pass:
47
+ #
48
+ # c = Curl::Easy.new('http://127.0.0.1:9999/test')
49
+ # c.on_progress do|x|
50
+ # puts "we're in the progress callback"
51
+ # false
52
+ # end
53
+ # c.perform
54
+ #
55
+ # notice no return keyword
56
+ #
57
+ c = Curl::Easy.new('http://127.0.0.1:9999/test')
58
+ c.on_progress do|x|
59
+ puts "we're in the progress callback"
60
+ return false
61
+ end
62
+ c.perform
63
+
64
+ assert false, "should not reach this point"
65
+
66
+ rescue => e
67
+ assert_equal 'Curl::Err::AbortedByCallbackError', e.class.to_s
68
+ c.close
69
+ ensure
70
+ server.shutdown
71
+ end
72
+
73
+ end
@@ -21,7 +21,7 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
21
21
 
22
22
  5.times do |i|
23
23
  t = Thread.new do
24
- c = Curl::Easy.perform('http://localhost:9999/test')
24
+ c = Curl::Easy.perform('http://127.0.0.1:9999/test')
25
25
  c.header_str
26
26
  end
27
27
  threads << t
@@ -37,7 +37,7 @@ class BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
37
37
  timer = Time.now
38
38
  single_responses = []
39
39
  5.times do |i|
40
- c = Curl::Easy.perform('http://localhost:9999/test')
40
+ c = Curl::Easy.perform('http://127.0.0.1:9999/test')
41
41
  single_responses << c.header_str
42
42
  end
43
43
 
@@ -0,0 +1,17 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class BugIssue102 < Test::Unit::TestCase
4
+
5
+ def test_interface
6
+ test = "https://api.twitter.com/1/users/show.json?screen_name=TwitterAPI&include_entities=true"
7
+ ip = "0.0.0.0"
8
+
9
+ c = Curl::Easy.new do |curl|
10
+ curl.url = test
11
+ curl.interface = ip
12
+ end
13
+
14
+ c.perform
15
+ end
16
+
17
+ end
@@ -24,7 +24,7 @@
24
24
  require 'test/unit'
25
25
  require 'rbconfig'
26
26
 
27
- $rubycmd = Config::CONFIG['RUBY_INSTALL_NAME'] || 'ruby'
27
+ $rubycmd = RbConfig::CONFIG['RUBY_INSTALL_NAME'] || 'ruby'
28
28
 
29
29
  class BugTestRequireLastOrSegfault < Test::Unit::TestCase
30
30
  def test_bug