curb 0.1.4 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of curb might be problematic. Click here for more details.
- data/README +26 -3
- data/Rakefile +20 -69
- data/doc.rb +1 -1
- data/ext/curb.c +21 -20
- data/ext/curb.h +12 -4
- data/ext/curb_config.h +42 -0
- data/ext/curb_easy.c +626 -207
- data/ext/curb_easy.h +23 -3
- data/ext/curb_errors.c +61 -8
- data/ext/curb_errors.h +11 -0
- data/ext/curb_multi.c +353 -0
- data/ext/curb_multi.h +25 -0
- data/ext/curb_postfield.c +14 -4
- data/ext/extconf.rb +104 -7
- data/{ext → lib}/curb.rb +2 -0
- data/{ext → lib}/curl.rb +0 -0
- data/tests/bug_curb_easy_blocks_ruby_threads.rb +52 -0
- data/tests/helper.rb +141 -0
- data/tests/tc_curl_download.rb +27 -0
- data/tests/tc_curl_easy.rb +67 -3
- data/tests/tc_curl_multi.rb +249 -0
- metadata +66 -61
- data/samples/gmail.rb +0 -48
data/ext/curb_multi.h
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
/* curb_multi.h - Curl easy mode
|
2
|
+
* Copyright (c)2008 Todd A. Fisher.
|
3
|
+
* Licensed under the Ruby License. See LICENSE for details.
|
4
|
+
*
|
5
|
+
* $Id$
|
6
|
+
*/
|
7
|
+
#ifndef __CURB_MULTI_H
|
8
|
+
#define __CURB_MULTI_H
|
9
|
+
|
10
|
+
#include "curb.h"
|
11
|
+
#include "curb_easy.h"
|
12
|
+
#include <curl/multi.h>
|
13
|
+
|
14
|
+
typedef struct {
|
15
|
+
int active;
|
16
|
+
int running;
|
17
|
+
VALUE requests; /* hash of handles currently added */
|
18
|
+
CURLM *handle;
|
19
|
+
} ruby_curl_multi;
|
20
|
+
|
21
|
+
extern VALUE cCurlMulti;
|
22
|
+
void init_curb_multi();
|
23
|
+
|
24
|
+
|
25
|
+
#endif
|
data/ext/curb_postfield.c
CHANGED
@@ -429,9 +429,10 @@ static VALUE ruby_curl_postfield_to_str(VALUE self) {
|
|
429
429
|
|
430
430
|
if ((rbcpf->local_file == Qnil) && (rbcpf->remote_file == Qnil)) {
|
431
431
|
if (rbcpf->name != Qnil) {
|
432
|
-
|
432
|
+
|
433
|
+
char *tmpchrs = curl_escape(RSTRING_PTR(rbcpf->name), RSTRING_LEN(rbcpf->name));
|
433
434
|
|
434
|
-
if (
|
435
|
+
if (!tmpchrs) {
|
435
436
|
rb_raise(eCurlErrInvalidPostField, "Failed to url-encode name `%s'", tmpchrs);
|
436
437
|
} else {
|
437
438
|
VALUE tmpcontent = Qnil;
|
@@ -445,8 +446,17 @@ static VALUE ruby_curl_postfield_to_str(VALUE self) {
|
|
445
446
|
} else {
|
446
447
|
tmpcontent = rb_str_new2("");
|
447
448
|
}
|
448
|
-
|
449
|
-
|
449
|
+
if (TYPE(tmpcontent) != T_STRING) {
|
450
|
+
if (rb_respond_to(tmpcontent, rb_intern("to_s"))) {
|
451
|
+
tmpcontent = rb_funcall(tmpcontent, rb_intern("to_s"), 0);
|
452
|
+
}
|
453
|
+
else {
|
454
|
+
rb_raise(rb_eRuntimeError, "postfield(%s) is not a string and does not respond_to to_s", RSTRING_PTR(escd_name) );
|
455
|
+
}
|
456
|
+
}
|
457
|
+
//fprintf(stderr, "encoding content: %ld - %s\n", RSTRING_LEN(tmpcontent), RSTRING_PTR(tmpcontent) );
|
458
|
+
tmpchrs = curl_escape(RSTRING_PTR(tmpcontent), RSTRING_LEN(tmpcontent));
|
459
|
+
if (!tmpchrs) {
|
450
460
|
rb_raise(eCurlErrInvalidPostField, "Failed to url-encode content `%s'", tmpchrs);
|
451
461
|
} else {
|
452
462
|
VALUE escd_content = rb_str_new2(tmpchrs);
|
data/ext/extconf.rb
CHANGED
@@ -2,23 +2,120 @@ require 'mkmf'
|
|
2
2
|
|
3
3
|
dir_config('curl')
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
if find_executable('curl-config')
|
6
|
+
$CFLAGS << " #{`curl-config --cflags`.strip}"
|
7
|
+
$LIBS << " #{`curl-config --libs`.strip}"
|
8
|
+
elsif !have_library('curl') or !have_header('curl/curl.h')
|
7
9
|
fail <<-EOM
|
8
10
|
Can't find libcurl or curl/curl.h
|
9
|
-
|
11
|
+
|
10
12
|
Try passing --with-curl-dir or --with-curl-lib and --with-curl-include
|
11
13
|
options to extconf.
|
12
14
|
EOM
|
13
15
|
end
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
def define(s)
|
18
|
+
$defs.push( format("-D HAVE_%s", s.to_s.upcase) )
|
19
|
+
end
|
20
|
+
|
21
|
+
def have_constant(name)
|
22
|
+
checking_for name do
|
23
|
+
src = %{
|
24
|
+
#include <curl/curl.h>
|
25
|
+
int main() {
|
26
|
+
int test = (int)#{name.upcase};
|
27
|
+
return 0;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
if try_compile(src,"#{$CFLAGS} #{$LIBS}")
|
31
|
+
define name
|
32
|
+
true
|
33
|
+
else
|
34
|
+
false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
have_constant "curlinfo_redirect_time"
|
40
|
+
have_constant "curlinfo_response_code"
|
41
|
+
have_constant "curlinfo_filetime"
|
42
|
+
have_constant "curlinfo_redirect_count"
|
43
|
+
have_constant "curlinfo_os_errno"
|
44
|
+
have_constant "curlinfo_num_connects"
|
45
|
+
have_constant "curlinfo_ftp_entry_path"
|
46
|
+
have_constant "curl_version_ssl"
|
47
|
+
have_constant "curl_version_libz"
|
48
|
+
have_constant "curl_version_ntlm"
|
49
|
+
have_constant "curl_version_gssnegotiate"
|
50
|
+
have_constant "curl_version_debug"
|
51
|
+
have_constant "curl_version_asynchdns"
|
52
|
+
have_constant "curl_version_spnego"
|
53
|
+
have_constant "curl_version_largefile"
|
54
|
+
have_constant "curl_version_idn"
|
55
|
+
have_constant "curl_version_sspi"
|
56
|
+
have_constant "curl_version_conv"
|
57
|
+
have_constant "curlproxy_http"
|
58
|
+
have_constant "curlproxy_socks4"
|
59
|
+
have_constant "curlproxy_socks5"
|
60
|
+
have_constant "curlauth_basic"
|
61
|
+
have_constant "curlauth_digest"
|
62
|
+
have_constant "curlauth_gssnegotiate"
|
63
|
+
have_constant "curlauth_ntlm"
|
64
|
+
have_constant "curlauth_anysafe"
|
65
|
+
have_constant "curlauth_any"
|
66
|
+
have_constant "curle_tftp_notfound"
|
67
|
+
have_constant "curle_tftp_perm"
|
68
|
+
have_constant "curle_tftp_diskfull"
|
69
|
+
have_constant "curle_tftp_illegal"
|
70
|
+
have_constant "curle_tftp_unknownid"
|
71
|
+
have_constant "curle_tftp_exists"
|
72
|
+
have_constant "curle_tftp_nosuchuser"
|
73
|
+
# older versions of libcurl 7.12
|
74
|
+
have_constant "curle_send_fail_rewind"
|
75
|
+
have_constant "curle_ssl_engine_initfailed"
|
76
|
+
have_constant "curle_login_denied"
|
18
77
|
|
19
78
|
if try_compile('int main() { return 0; }','-Wall')
|
20
79
|
$CFLAGS << ' -Wall'
|
21
80
|
end
|
22
81
|
|
23
|
-
|
82
|
+
# do some checking to detect ruby 1.8 hash.c vs ruby 1.9 hash.c
|
83
|
+
def test_for(name, const, src)
|
84
|
+
checking_for name do
|
85
|
+
if try_compile(src,"#{$CFLAGS} #{$LIBS}")
|
86
|
+
define const
|
87
|
+
true
|
88
|
+
else
|
89
|
+
false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
test_for("Ruby 1.9 Hash", "RUBY19_HASH", %{
|
94
|
+
#include <ruby.h>
|
95
|
+
int main() {
|
96
|
+
VALUE hash = rb_hash_new();
|
97
|
+
if( RHASH(hash)->ntbl->num_entries ) {
|
98
|
+
return 0;
|
99
|
+
}
|
100
|
+
return 1;
|
101
|
+
}
|
102
|
+
})
|
103
|
+
test_for("Ruby 1.9 st.h", "RUBY19_ST_H", %{
|
104
|
+
#include <ruby.h>
|
105
|
+
#include <ruby/st.h>
|
106
|
+
int main() {
|
107
|
+
return 0;
|
108
|
+
}
|
109
|
+
})
|
24
110
|
|
111
|
+
test_for("curl_easy_escape", "CURL_EASY_ESCAPE", %{
|
112
|
+
#include <curl/curl.h>
|
113
|
+
int main() {
|
114
|
+
CURL *easy = curl_easy_init();
|
115
|
+
curl_easy_escape(easy,"hello",5);
|
116
|
+
return 0;
|
117
|
+
}
|
118
|
+
})
|
119
|
+
|
120
|
+
create_header('curb_config.h')
|
121
|
+
create_makefile('curb_core')
|
data/{ext → lib}/curb.rb
RENAMED
data/{ext → lib}/curl.rb
RENAMED
File without changes
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 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 BugTestInstancePostDiffersFromClassPost < Test::Unit::TestCase
|
7
|
+
def test_bug
|
8
|
+
server = WEBrick::HTTPServer.new( :Port => 9999 )
|
9
|
+
server.mount_proc("/test") do|req,res|
|
10
|
+
sleep 0.5
|
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
|
+
threads = []
|
20
|
+
timer = Time.now
|
21
|
+
|
22
|
+
5.times do |i|
|
23
|
+
t = Thread.new do
|
24
|
+
c = Curl::Easy.perform('http://localhost:9999/test')
|
25
|
+
c.header_str
|
26
|
+
end
|
27
|
+
threads << t
|
28
|
+
end
|
29
|
+
|
30
|
+
multi_responses = threads.collect do|t|
|
31
|
+
t.value
|
32
|
+
end
|
33
|
+
|
34
|
+
multi_time = (Time.now - timer)
|
35
|
+
puts "requested in #{multi_time}"
|
36
|
+
|
37
|
+
timer = Time.now
|
38
|
+
single_responses = []
|
39
|
+
5.times do |i|
|
40
|
+
c = Curl::Easy.perform('http://localhost:9999/test')
|
41
|
+
single_responses << c.header_str
|
42
|
+
end
|
43
|
+
|
44
|
+
single_time = (Time.now - timer)
|
45
|
+
puts "requested in #{single_time}"
|
46
|
+
|
47
|
+
assert single_time > multi_time
|
48
|
+
|
49
|
+
server.shutdown
|
50
|
+
thread.join
|
51
|
+
end
|
52
|
+
end
|
data/tests/helper.rb
CHANGED
@@ -13,3 +13,144 @@ require 'curb'
|
|
13
13
|
require 'test/unit'
|
14
14
|
|
15
15
|
$TEST_URL = "file://#{URI.escape(File.expand_path(__FILE__).tr('\\','/').tr(':','|'))}"
|
16
|
+
|
17
|
+
require 'thread'
|
18
|
+
require 'webrick'
|
19
|
+
|
20
|
+
# set this to true to avoid testing with multiple threads
|
21
|
+
# or to test with multiple threads set it to false
|
22
|
+
# this is important since, some code paths will change depending
|
23
|
+
# on the presence of multiple threads
|
24
|
+
TEST_SINGLE_THREADED=false
|
25
|
+
|
26
|
+
# keep webrick quiet
|
27
|
+
class ::WEBrick::HTTPServer
|
28
|
+
def access_log(config, req, res)
|
29
|
+
# nop
|
30
|
+
end
|
31
|
+
end
|
32
|
+
class ::WEBrick::BasicLog
|
33
|
+
def log(level, data)
|
34
|
+
# nop
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Simple test server to record number of times a request is sent/recieved of a specific
|
40
|
+
# request type, e.g. GET,POST,PUT,DELETE
|
41
|
+
#
|
42
|
+
class TestServlet < WEBrick::HTTPServlet::AbstractServlet
|
43
|
+
|
44
|
+
def self.port=(p)
|
45
|
+
@port = p
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.port
|
49
|
+
(@port or 9129)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.path
|
53
|
+
'/methods'
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.url
|
57
|
+
"http://127.0.0.1:#{port}#{path}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def respond_with(method,req,res)
|
61
|
+
res.body = method.to_s
|
62
|
+
res['Content-Type'] = "text/plain"
|
63
|
+
end
|
64
|
+
|
65
|
+
def do_GET(req,res)
|
66
|
+
respond_with(:GET,req,res)
|
67
|
+
end
|
68
|
+
|
69
|
+
def do_HEAD(req,res)
|
70
|
+
res['Location'] = "/nonexistent"
|
71
|
+
respond_with(:HEAD, req, res)
|
72
|
+
end
|
73
|
+
|
74
|
+
def do_POST(req,res)
|
75
|
+
respond_with(:POST,req,res)
|
76
|
+
end
|
77
|
+
|
78
|
+
def do_PUT(req,res)
|
79
|
+
respond_with("PUT\n#{req.body}",req,res)
|
80
|
+
end
|
81
|
+
|
82
|
+
def do_DELETE(req,res)
|
83
|
+
respond_with(:DELETE,req,res)
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
module TestServerMethods
|
89
|
+
def locked_file
|
90
|
+
File.join(File.dirname(__FILE__),"server_lock-#{@__port}")
|
91
|
+
end
|
92
|
+
|
93
|
+
def server_setup(port=9129,servlet=TestServlet)
|
94
|
+
@__port = port
|
95
|
+
if @server.nil? and !File.exist?(locked_file)
|
96
|
+
|
97
|
+
File.open(locked_file,'w') {|f| f << 'locked' }
|
98
|
+
if TEST_SINGLE_THREADED
|
99
|
+
rd, wr = IO.pipe
|
100
|
+
@__pid = fork do
|
101
|
+
rd.close
|
102
|
+
rd = nil
|
103
|
+
|
104
|
+
# start up a webrick server for testing delete
|
105
|
+
server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
106
|
+
|
107
|
+
server.mount(servlet.path, servlet)
|
108
|
+
trap("INT") { server.shutdown }
|
109
|
+
GC.start
|
110
|
+
wr.flush
|
111
|
+
wr.close
|
112
|
+
server.start
|
113
|
+
end
|
114
|
+
wr.close
|
115
|
+
rd.read
|
116
|
+
rd.close
|
117
|
+
else
|
118
|
+
# start up a webrick server for testing delete
|
119
|
+
@server = WEBrick::HTTPServer.new :Port => port, :DocumentRoot => File.expand_path(File.dirname(__FILE__))
|
120
|
+
|
121
|
+
@server.mount(servlet.path, servlet)
|
122
|
+
queue = Queue.new # synchronize the thread startup to the main thread
|
123
|
+
|
124
|
+
@test_thread = Thread.new { queue << 1; @server.start }
|
125
|
+
|
126
|
+
# wait for the queue
|
127
|
+
value = queue.pop
|
128
|
+
if !value
|
129
|
+
STDERR.puts "Failed to startup test server!"
|
130
|
+
exit(1)
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
exit_code = lambda do
|
136
|
+
begin
|
137
|
+
if File.exist?(locked_file)
|
138
|
+
File.unlink locked_file
|
139
|
+
if TEST_SINGLE_THREADED
|
140
|
+
Process.kill 'INT', @__pid
|
141
|
+
else
|
142
|
+
@server.shutdown unless @server.nil?
|
143
|
+
end
|
144
|
+
end
|
145
|
+
#@server.shutdown unless @server.nil?
|
146
|
+
rescue Object => e
|
147
|
+
puts "Error #{__FILE__}:#{__LINE__}\n#{e.message}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
trap("INT"){exit_code.call}
|
152
|
+
at_exit{exit_code.call}
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
2
|
+
|
3
|
+
class TestCurbCurlDownload < Test::Unit::TestCase
|
4
|
+
include TestServerMethods
|
5
|
+
|
6
|
+
def setup
|
7
|
+
server_setup(9130)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_download_url_to_file
|
11
|
+
dl_url = "http://127.0.0.1:9130/ext/curb_easy.c"
|
12
|
+
dl_path = File.join("/tmp/dl_url_test.file")
|
13
|
+
|
14
|
+
curb = Curl::Easy.download(dl_url, dl_path)
|
15
|
+
assert File.exist?(dl_path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_download_bad_url_gives_404
|
19
|
+
dl_url = "http://127.0.0.1:9130/this_file_does_not_exist.html"
|
20
|
+
dl_path = File.join("/tmp/dl_url_test.file")
|
21
|
+
|
22
|
+
curb = Curl::Easy.download(dl_url, dl_path)
|
23
|
+
assert_equal Curl::Easy, curb.class
|
24
|
+
assert_equal 404, curb.response_code
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/tests/tc_curl_easy.rb
CHANGED
@@ -86,7 +86,7 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
86
86
|
|
87
87
|
def test_get_01
|
88
88
|
c = Curl::Easy.new($TEST_URL)
|
89
|
-
assert_equal true, c.http_get
|
89
|
+
assert_equal true, c.http_get
|
90
90
|
assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
|
91
91
|
assert_equal "", c.header_str
|
92
92
|
end
|
@@ -110,7 +110,8 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
110
110
|
assert_equal "", c.body_str
|
111
111
|
assert_equal "", c.header_str
|
112
112
|
end
|
113
|
-
|
113
|
+
|
114
|
+
|
114
115
|
def test_last_effective_url_01
|
115
116
|
c = Curl::Easy.new($TEST_URL)
|
116
117
|
|
@@ -438,5 +439,68 @@ class TestCurbCurlEasy < Test::Unit::TestCase
|
|
438
439
|
assert_equal "some.file", c.cookiejar = "some.file"
|
439
440
|
assert_equal "some.file", c.cookiejar
|
440
441
|
end
|
442
|
+
|
443
|
+
def test_on_success
|
444
|
+
curl = Curl::Easy.new($TEST_URL)
|
445
|
+
on_success_called = false
|
446
|
+
curl.on_success {|c|
|
447
|
+
on_success_called = true
|
448
|
+
assert_not_nil c.body_str
|
449
|
+
assert_equal "", c.header_str
|
450
|
+
assert_match(/^# DO NOT REMOVE THIS COMMENT/, c.body_str)
|
451
|
+
}
|
452
|
+
curl.perform
|
453
|
+
assert on_success_called, "Success handler not called"
|
454
|
+
end
|
455
|
+
|
456
|
+
def test_on_success_with_on_failure
|
457
|
+
curl = Curl::Easy.new("#{$TEST_URL.gsub(/file:\/\//,'')}/not_here")
|
458
|
+
on_failure_called = false
|
459
|
+
curl.on_success {|c| } # make sure we get the failure call even though this handler is defined
|
460
|
+
curl.on_failure {|c| on_failure_called = true }
|
461
|
+
curl.perform
|
462
|
+
assert on_failure_called, "Failure handler not called"
|
463
|
+
end
|
464
|
+
|
465
|
+
def test_get_remote
|
466
|
+
curl = Curl::Easy.new(TestServlet.url)
|
467
|
+
curl.http_get
|
468
|
+
assert_equal 'GET', curl.body_str
|
469
|
+
end
|
441
470
|
|
442
|
-
|
471
|
+
def test_post_remote
|
472
|
+
curl = Curl::Easy.new(TestServlet.url)
|
473
|
+
curl.http_post
|
474
|
+
assert_equal 'POST', curl.body_str
|
475
|
+
end
|
476
|
+
|
477
|
+
def test_delete_remote
|
478
|
+
curl = Curl::Easy.new(TestServlet.url)
|
479
|
+
curl.http_delete
|
480
|
+
assert_equal 'DELETE', curl.body_str
|
481
|
+
end
|
482
|
+
|
483
|
+
def test_head_remote
|
484
|
+
curl = Curl::Easy.new(TestServlet.url)
|
485
|
+
curl.http_head
|
486
|
+
|
487
|
+
redirect = curl.header_str.match(/Location: (.*)/)
|
488
|
+
|
489
|
+
assert_equal '', curl.body_str
|
490
|
+
assert_match '/nonexistent', redirect[1]
|
491
|
+
end
|
492
|
+
|
493
|
+
def test_put_remote
|
494
|
+
curl = Curl::Easy.new(TestServlet.url)
|
495
|
+
assert curl.http_put("message")
|
496
|
+
assert_match /^PUT/, curl.body_str
|
497
|
+
assert_match /message$/, curl.body_str
|
498
|
+
end
|
499
|
+
|
500
|
+
include TestServerMethods
|
501
|
+
|
502
|
+
def setup
|
503
|
+
server_setup
|
504
|
+
end
|
505
|
+
|
506
|
+
end
|