ghazel-curb 0.6.2.3 → 0.7.9.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/curb.rb CHANGED
@@ -26,16 +26,24 @@ module Curl
26
26
  def download(url, filename = url.split(/\?/).first.split(/\//).last, &blk)
27
27
  curl = Curl::Easy.new(url, &blk)
28
28
 
29
- File.open(filename, "wb") do |output|
30
- old_on_body = curl.on_body do |data|
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|
31
38
  result = old_on_body ? old_on_body.call(data) : data.length
32
39
  output << data if result == data.length
33
40
  result
34
41
  end
35
-
36
42
  curl.perform
37
- end
38
-
43
+ ensure
44
+ output.close rescue IOError
45
+ end
46
+
39
47
  return curl
40
48
  end
41
49
  end
@@ -142,6 +150,7 @@ module Curl
142
150
  m = Curl::Multi.new
143
151
  # configure the multi handle
144
152
  multi_options.each { |k,v| m.send("#{k}=", v) }
153
+ callbacks = [:on_progress,:on_debug,:on_failure,:on_success,:on_body,:on_header]
145
154
 
146
155
  urls_with_config.each do|conf|
147
156
  c = conf.dup # avoid being destructive to input
@@ -151,6 +160,12 @@ module Curl
151
160
 
152
161
  easy = Curl::Easy.new(url)
153
162
 
163
+ # assign callbacks
164
+ callbacks.each do |cb|
165
+ cbproc = c.delete(cb)
166
+ easy.send(cb,&cbproc) if cbproc
167
+ end
168
+
154
169
  case method
155
170
  when :post
156
171
  fields = c.delete(:post_fields)
@@ -181,6 +196,80 @@ module Curl
181
196
  end
182
197
  m.perform
183
198
  end
199
+
200
+ # call-seq:
201
+ #
202
+ # Curl::Multi.download(['http://example.com/p/a/t/h/file1.txt','http://example.com/p/a/t/h/file2.txt']){|c|}
203
+ #
204
+ # will create 2 new files file1.txt and file2.txt
205
+ #
206
+ # 2 files will be opened, and remain open until the call completes
207
+ #
208
+ # when using the :post or :put method, urls should be a hash, including the individual post fields per post
209
+ #
210
+ def download(urls,easy_options={},multi_options={},download_paths=nil,&blk)
211
+ errors = []
212
+ procs = []
213
+ files = []
214
+ urls_with_config = []
215
+ url_to_download_paths = {}
216
+
217
+ urls.each_with_index do|urlcfg,i|
218
+ if urlcfg.is_a?(Hash)
219
+ url = url[:url]
220
+ else
221
+ url = urlcfg
222
+ end
223
+
224
+ if download_paths and download_paths[i]
225
+ download_path = download_paths[i]
226
+ else
227
+ download_path = File.basename(url)
228
+ end
229
+
230
+ file = lambda do|dp|
231
+ file = File.open(dp,"wb")
232
+ procs << (lambda {|data| file.write data; data.size })
233
+ files << file
234
+ file
235
+ end.call(download_path)
236
+
237
+ if urlcfg.is_a?(Hash)
238
+ urls_with_config << urlcfg.merge({:on_body => procs.last}.merge(easy_options))
239
+ else
240
+ urls_with_config << {:url => url, :on_body => procs.last, :method => :get}.merge(easy_options)
241
+ end
242
+ url_to_download_paths[url] = {:path => download_path, :file => file} # store for later
243
+ end
244
+
245
+ if blk
246
+ # when injecting the block, ensure file is closed before yielding
247
+ Curl::Multi.http(urls_with_config, multi_options) do |c,code,method|
248
+ info = url_to_download_paths[c.url]
249
+ begin
250
+ file = info[:file]
251
+ files.reject!{|f| f == file }
252
+ file.close
253
+ rescue => e
254
+ errors << e
255
+ end
256
+ blk.call(c,info[:path])
257
+ end
258
+ else
259
+ Curl::Multi.http(urls_with_config, multi_options)
260
+ end
261
+
262
+ ensure
263
+ files.each {|f|
264
+ begin
265
+ f.close
266
+ rescue => e
267
+ errors << e
268
+ end
269
+ }
270
+ raise errors unless errors.empty?
271
+ end
272
+
184
273
  end
185
274
  end
186
275
  end
@@ -0,0 +1,83 @@
1
+ =begin
2
+ From jwhitmire
3
+ Todd, I'm trying to use curb to post data to a REST url. We're using it to post support questions from our iphone app directly to tender. The post looks good to me, but curl is not adding the content-length header so I get a 411 length required response from the server.
4
+
5
+ Here's my post block, do you see anything obvious? Do I need to manually add the Content-Length header?
6
+
7
+ c = Curl::Easy.http_post(url) do |curl|
8
+ curl.headers["User-Agent"] = "Curl/Ruby"
9
+ if user
10
+ curl.headers["X-Multipass"] = user.multipass
11
+ else
12
+ curl.headers["X-Tender-Auth"] = TOKEN
13
+ end
14
+ curl.headers["Accept"] = "application/vnd.tender-v1+json"
15
+
16
+ curl.post_body = params.map{|f,k| "#{curl.escape(f)}=#{curl.escape(k)}"}.join('&')
17
+
18
+ curl.verbose = true
19
+ curl.follow_location = true
20
+ curl.enable_cookies = true
21
+ end
22
+ Any insight you care to share would be helpful. Thanks.
23
+ =end
24
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
25
+ require 'webrick'
26
+ class ::WEBrick::HTTPServer ; def access_log(config, req, res) ; end ; end
27
+ class ::WEBrick::BasicLog ; def log(level, data) ; end ; end
28
+
29
+ class BugCurbEasyPostWithStringNoContentLengthHeader < Test::Unit::TestCase
30
+ def test_bug_workaround
31
+ server = WEBrick::HTTPServer.new( :Port => 9999 )
32
+ server.mount_proc("/test") do|req,res|
33
+ assert_equal '15', req['Content-Length']
34
+ res.body = "hi"
35
+ res['Content-Type'] = "text/html"
36
+ end
37
+
38
+ thread = Thread.new(server) do|srv|
39
+ srv.start
40
+ end
41
+ params = {:cat => "hat", :foo => "bar"}
42
+
43
+ post_body = params.map{|f,k| "#{Curl::Easy.new.escape(f)}=#{Curl::Easy.new.escape(k)}"}.join('&')
44
+ c = Curl::Easy.http_post("http://127.0.0.1:9999/test",post_body) do |curl|
45
+ curl.headers["User-Agent"] = "Curl/Ruby"
46
+ curl.headers["X-Tender-Auth"] = "A Token"
47
+ curl.headers["Accept"] = "application/vnd.tender-v1+json"
48
+
49
+ curl.follow_location = true
50
+ curl.enable_cookies = true
51
+ end
52
+
53
+ server.shutdown
54
+ thread.join
55
+ end
56
+ def test_bug
57
+ server = WEBrick::HTTPServer.new( :Port => 9999 )
58
+ server.mount_proc("/test") do|req,res|
59
+ assert_equal '15', req['Content-Length']
60
+ res.body = "hi"
61
+ res['Content-Type'] = "text/html"
62
+ end
63
+
64
+ thread = Thread.new(server) do|srv|
65
+ srv.start
66
+ end
67
+ params = {:cat => "hat", :foo => "bar"}
68
+
69
+ c = Curl::Easy.http_post("http://127.0.0.1:9999/test") do |curl|
70
+ curl.headers["User-Agent"] = "Curl/Ruby"
71
+ curl.headers["X-Tender-Auth"] = "A Token"
72
+ curl.headers["Accept"] = "application/vnd.tender-v1+json"
73
+
74
+ curl.post_body = params.map{|f,k| "#{curl.escape(f)}=#{curl.escape(k)}"}.join('&')
75
+
76
+ curl.follow_location = true
77
+ curl.enable_cookies = true
78
+ end
79
+
80
+ server.shutdown
81
+ thread.join
82
+ end
83
+ end
@@ -6,5 +6,9 @@
6
6
  $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','ext'))
7
7
  $:.unshift File.expand_path(File.join(File.dirname(__FILE__),'..','lib'))
8
8
  require 'curb'
9
- multi = Curl::Multi.new
10
- exit
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
data/tests/bugtests.rb ADDED
@@ -0,0 +1,9 @@
1
+ $: << $TESTDIR = File.expand_path(File.dirname(__FILE__))
2
+ puts "start"
3
+ begin
4
+ Dir[File.join($TESTDIR, 'bug_*.rb')].each { |lib| require lib }
5
+ rescue Object => e
6
+ puts e.message
7
+ ensure
8
+ puts "done"
9
+ end
data/tests/helper.rb CHANGED
@@ -11,6 +11,7 @@ $:.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(':','|'))}"
16
17
 
@@ -73,14 +74,18 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
73
74
  end
74
75
 
75
76
  def do_POST(req,res)
76
- if req.body
77
- params = {}
78
- req.body.split('&').map{|s| k,v=s.split('='); params[k] = v }
79
- end
80
- if params and params['s'] == '500'
81
- res.status = 500
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
82
87
  else
83
- respond_with("POST\n#{req.body}",req,res)
88
+ respond_with(req.query['filename'],req,res)
84
89
  end
85
90
  end
86
91
 
@@ -93,6 +98,14 @@ class TestServlet < WEBrick::HTTPServlet::AbstractServlet
93
98
  respond_with(:DELETE,req,res)
94
99
  end
95
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
+
96
109
  end
97
110
 
98
111
  module TestServerMethods
@@ -165,5 +178,6 @@ module TestServerMethods
165
178
  at_exit{exit_code.call}
166
179
 
167
180
  end
181
+ rescue Errno::EADDRINUSE
168
182
  end
169
183
  end
@@ -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
@@ -7,7 +7,7 @@ class TestCurbCurlDownload < Test::Unit::TestCase
7
7
  server_setup
8
8
  end
9
9
 
10
- def test_download_url_to_file
10
+ def test_download_url_to_file_via_string
11
11
  dl_url = "http://127.0.0.1:9129/ext/curb_easy.c"
12
12
  dl_path = File.join(Dir::tmpdir, "dl_url_test.file")
13
13
 
@@ -18,6 +18,49 @@ class TestCurbCurlDownload < Test::Unit::TestCase
18
18
  File.unlink(dl_path) if File.exist?(dl_path)
19
19
  end
20
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
+
21
64
  def test_download_bad_url_gives_404
22
65
  dl_url = "http://127.0.0.1:9129/this_file_does_not_exist.html"
23
66
  dl_path = File.join(Dir::tmpdir, "dl_url_test.file")