patron 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/Gemfile.lock +3 -1
- data/README.md +15 -11
- data/Rakefile +6 -7
- data/ext/patron/extconf.rb +1 -0
- data/ext/patron/session_ext.c +72 -46
- data/lib/patron.rb +5 -2
- data/lib/patron/error.rb +11 -7
- data/lib/patron/proxy_type.rb +1 -0
- data/lib/patron/request.rb +50 -1
- data/lib/patron/response.rb +29 -16
- data/lib/patron/session.rb +157 -56
- data/lib/patron/util.rb +1 -0
- data/lib/patron/version.rb +1 -1
- data/patron.gemspec +1 -0
- data/spec/patron_spec.rb +2 -0
- data/spec/request_spec.rb +2 -0
- data/spec/response_spec.rb +5 -0
- data/spec/session_spec.rb +18 -2
- data/spec/session_ssl_spec.rb +11 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/test_server.rb +2 -0
- data/spec/util_spec.rb +2 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 10e347aa0ab1915c0d428cbfaf4bd6888ae2a9ba
|
4
|
+
data.tar.gz: 76476a77a366bd7b5b2aacc7fe946b26e142b995
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66045fc85f95a730550b061b255cc1420c4b0e6feee1a5cf5252d5cda68dad5792a146ea290208b683d5fe05c596d767864ef6189cade817c84792b4ae963f47
|
7
|
+
data.tar.gz: c7292c8749d8c70e051b032069fe136e6715c8158c4b8b5a9b9c24967a629aa2314b77ea8e5addca9880163facebe728b255e7a3063a803b8fbc33ee100b2bc6
|
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
patron (0.
|
4
|
+
patron (0.6.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -30,6 +30,7 @@ GEM
|
|
30
30
|
json (~> 1.8)
|
31
31
|
simplecov-html (~> 0.10.0)
|
32
32
|
simplecov-html (0.10.0)
|
33
|
+
yard (0.8.7.6)
|
33
34
|
|
34
35
|
PLATFORMS
|
35
36
|
ruby
|
@@ -40,6 +41,7 @@ DEPENDENCIES
|
|
40
41
|
rake-compiler (>= 0.7.5)
|
41
42
|
rspec (>= 2.3.0)
|
42
43
|
simplecov (>= 0.10.0)
|
44
|
+
yard (~> 0.8)
|
43
45
|
|
44
46
|
BUNDLED WITH
|
45
47
|
1.11.2
|
data/README.md
CHANGED
@@ -1,15 +1,10 @@
|
|
1
|
-
# Ruby HTTP Client
|
2
|
-
|
3
|
-
## SYNOPSIS
|
4
|
-
|
5
1
|
Patron is a Ruby HTTP client library based on libcurl. It does not try to expose
|
6
2
|
the full "power" (read complexity) of libcurl but instead tries to provide a
|
7
3
|
sane API while taking advantage of libcurl under the hood.
|
8
4
|
|
5
|
+
## Usage
|
9
6
|
|
10
|
-
|
11
|
-
|
12
|
-
Usage is very simple. First, you instantiate a Session object. You can set a few
|
7
|
+
First, you instantiate a Session object. You can set a few
|
13
8
|
default options on the Session instance that will be used by all subsequent
|
14
9
|
requests:
|
15
10
|
|
@@ -55,18 +50,27 @@ You can ship custom headers with a single request:
|
|
55
50
|
|
56
51
|
sess.post("/foo/stuff", "some data", {"Content-Type" => "text/plain"})
|
57
52
|
|
58
|
-
|
53
|
+
## Threading
|
59
54
|
|
55
|
+
By itself, the `Patron::Session` objects are not thread safe (each `Session` holds a single `curl_state` pointer
|
56
|
+
during the request/response cycle). At this time, Patron has no support for `curl_multi_*` family of functions
|
57
|
+
for doing concurrent requests. However, the actual code that interacts with libCURL does unlock the RVM GIL,
|
58
|
+
so using multiple `Session` objects in different threads is possible with a high degree of concurrency.
|
59
|
+
For sharing a resource of sessions between threads we recommend using the excellent [connection_pool](https://rubygems.org/gems/connection_pool) gem by Mike Perham.
|
60
60
|
|
61
|
-
|
61
|
+
patron_pool = ConnectionPool.new(size: 5, timeout: 5) { Patron::Session.new }
|
62
|
+
patron_pool.with do |session|
|
63
|
+
session.get(...)
|
64
|
+
end
|
65
|
+
|
66
|
+
## Requirements
|
62
67
|
|
63
68
|
You need a recent version of libcurl in order to install this gem. On MacOS X
|
64
69
|
the provided libcurl is sufficient. You will have to install the libcurl
|
65
70
|
development packages on Debian or Ubuntu. Other Linux systems are probably
|
66
71
|
similar. Windows users are on your own. Good luck with that.
|
67
72
|
|
68
|
-
|
69
|
-
## INSTALL
|
73
|
+
## Installation
|
70
74
|
|
71
75
|
sudo gem install patron
|
72
76
|
|
data/Rakefile
CHANGED
@@ -22,10 +22,10 @@
|
|
22
22
|
##
|
23
23
|
## -------------------------------------------------------------------
|
24
24
|
require 'rake/clean'
|
25
|
-
require 'rdoc/task'
|
26
25
|
require 'rake/extensiontask'
|
27
26
|
require 'rspec/core/rake_task'
|
28
27
|
require 'bundler'
|
28
|
+
require 'yard'
|
29
29
|
|
30
30
|
Rake::ExtensionTask.new do |ext|
|
31
31
|
ext.name = 'session_ext' # indicate the name of the extension.
|
@@ -43,12 +43,11 @@ task :shell => :compile do
|
|
43
43
|
sh 'irb -I./lib -I./ext -r patron'
|
44
44
|
end
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
46
|
+
desc "Generate YARD documentation"
|
47
|
+
YARD::Rake::YardocTask.new do |t|
|
48
|
+
t.files = ['lib/**/*.rb', 'ext/**/*.c' ]
|
49
|
+
t.options = ['--markup markdown']
|
50
|
+
t.stats_options = ['--list-undoc']
|
52
51
|
end
|
53
52
|
|
54
53
|
desc "Run specs"
|
data/ext/patron/extconf.rb
CHANGED
data/ext/patron/session_ext.c
CHANGED
@@ -38,6 +38,7 @@ static VALUE cSession = Qnil;
|
|
38
38
|
static VALUE cRequest = Qnil;
|
39
39
|
static VALUE ePatronError = Qnil;
|
40
40
|
static VALUE eUnsupportedProtocol = Qnil;
|
41
|
+
static VALUE eUnsupportedSSLVersion = Qnil;
|
41
42
|
static VALUE eURLFormatError = Qnil;
|
42
43
|
static VALUE eHostResolutionError = Qnil;
|
43
44
|
static VALUE eConnectionFailed = Qnil;
|
@@ -206,10 +207,10 @@ static struct curl_state* get_curl_state(VALUE self) {
|
|
206
207
|
/*----------------------------------------------------------------------------*/
|
207
208
|
/* Method implementations */
|
208
209
|
|
209
|
-
/*
|
210
|
-
|
211
|
-
|
212
|
-
|
210
|
+
/*
|
211
|
+
* Returns the version of the embedded libcurl.
|
212
|
+
*
|
213
|
+
* @return [String] libcurl version string
|
213
214
|
*/
|
214
215
|
static VALUE libcurl_version(VALUE klass) {
|
215
216
|
char* value = curl_version();
|
@@ -217,10 +218,11 @@ static VALUE libcurl_version(VALUE klass) {
|
|
217
218
|
return rb_str_new2(value);
|
218
219
|
}
|
219
220
|
|
220
|
-
/*
|
221
|
-
*
|
221
|
+
/*
|
222
|
+
* Escapes the provided string using libCURL URL escaping functions.
|
222
223
|
*
|
223
|
-
*
|
224
|
+
* @param [String] value plain string to URL-escape
|
225
|
+
* @return [String] the escaped string
|
224
226
|
*/
|
225
227
|
static VALUE session_escape(VALUE self, VALUE value) {
|
226
228
|
|
@@ -240,10 +242,11 @@ static VALUE session_escape(VALUE self, VALUE value) {
|
|
240
242
|
return retval;
|
241
243
|
}
|
242
244
|
|
243
|
-
/*
|
244
|
-
*
|
245
|
+
/*
|
246
|
+
* Unescapes the provided string using libCURL URL escaping functions.
|
245
247
|
*
|
246
|
-
*
|
248
|
+
* @param [String] value URL-encoded String to unescape
|
249
|
+
* @return [String] unescaped (decoded) string
|
247
250
|
*/
|
248
251
|
static VALUE session_unescape(VALUE self, VALUE value) {
|
249
252
|
VALUE string = StringValue(value);
|
@@ -271,7 +274,8 @@ static int each_http_header(VALUE header_key, VALUE header_value, VALUE self) {
|
|
271
274
|
VALUE name = rb_obj_as_string(header_key);
|
272
275
|
VALUE value = rb_obj_as_string(header_value);
|
273
276
|
VALUE header_str = Qnil;
|
274
|
-
|
277
|
+
|
278
|
+
// TODO: see how to combine this with automatic_content_encoding
|
275
279
|
if (rb_str_cmp(name, rb_str_new2("Accept-Encoding")) == 0) {
|
276
280
|
if (rb_funcall(value, rb_intern("include?"), 1, rb_str_new2("gzip"))) {
|
277
281
|
#ifdef CURLOPT_ACCEPT_ENCODING
|
@@ -461,7 +465,14 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
461
465
|
// Enable automatic content-encoding support via gzip/deflate if set in the request,
|
462
466
|
// see https://curl.haxx.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html
|
463
467
|
if(RTEST(a_c_encoding)) {
|
464
|
-
|
468
|
+
#ifdef CURLOPT_ACCEPT_ENCODING
|
469
|
+
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
|
470
|
+
#elif defined CURLOPT_ENCODING
|
471
|
+
curl_easy_setopt(curl, CURLOPT_ENCODING, "");
|
472
|
+
#else
|
473
|
+
rb_raise(rb_eArgError,
|
474
|
+
"The libcurl version installed doesn't support automatic content negotiation");
|
475
|
+
#endif
|
465
476
|
}
|
466
477
|
|
467
478
|
url = rb_iv_get(request, "@url");
|
@@ -494,12 +505,12 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
494
505
|
|
495
506
|
proxy_type = rb_iv_get(request, "@proxy_type");
|
496
507
|
if (!NIL_P(proxy_type)) {
|
497
|
-
curl_easy_setopt(curl, CURLOPT_PROXYTYPE,
|
508
|
+
curl_easy_setopt(curl, CURLOPT_PROXYTYPE, NUM2LONG(proxy_type));
|
498
509
|
}
|
499
510
|
|
500
511
|
credentials = rb_funcall(request, rb_intern("credentials"), 0);
|
501
512
|
if (!NIL_P(credentials)) {
|
502
|
-
curl_easy_setopt(curl, CURLOPT_HTTPAUTH,
|
513
|
+
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, NUM2LONG(rb_iv_get(request, "@auth_type")));
|
503
514
|
curl_easy_setopt(curl, CURLOPT_USERPWD, StringValuePtr(credentials));
|
504
515
|
}
|
505
516
|
|
@@ -516,13 +527,16 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
516
527
|
|
517
528
|
ssl_version = rb_iv_get(request, "@ssl_version");
|
518
529
|
if(!NIL_P(ssl_version)) {
|
519
|
-
|
530
|
+
VALUE ssl_version_str = rb_funcall(ssl_version, rb_intern("to_s"), 0);
|
531
|
+
char* version = StringValuePtr(ssl_version_str);
|
520
532
|
if(strcmp(version, "SSLv2") == 0) {
|
521
533
|
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv2);
|
522
534
|
} else if(strcmp(version, "SSLv3") == 0) {
|
523
535
|
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3);
|
524
536
|
} else if(strcmp(version, "TLSv1") == 0) {
|
525
537
|
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
|
538
|
+
} else {
|
539
|
+
rb_raise(eUnsupportedSSLVersion, "Unsupported SSL version: %s", version);
|
526
540
|
}
|
527
541
|
}
|
528
542
|
|
@@ -533,7 +547,7 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
533
547
|
|
534
548
|
buffer_size = rb_iv_get(request, "@buffer_size");
|
535
549
|
if (!NIL_P(buffer_size)) {
|
536
|
-
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE,
|
550
|
+
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, NUM2LONG(buffer_size));
|
537
551
|
}
|
538
552
|
|
539
553
|
if(state->debug_file) {
|
@@ -631,7 +645,9 @@ static VALUE perform_request(VALUE self) {
|
|
631
645
|
VALUE header_str = membuffer_to_rb_str(header_buffer);
|
632
646
|
VALUE body_str = Qnil;
|
633
647
|
if (!state->download_file) { body_str = membuffer_to_rb_str(body_buffer); }
|
634
|
-
|
648
|
+
|
649
|
+
curl_easy_setopt(curl, CURLOPT_COOKIELIST, "FLUSH"); // Flush cookies to the cookie jar
|
650
|
+
|
635
651
|
return create_response(self, curl, header_str, body_str);
|
636
652
|
} else {
|
637
653
|
rb_raise(select_error(ret), "%s", state->error_buf);
|
@@ -669,9 +685,7 @@ static VALUE cleanup(VALUE self) {
|
|
669
685
|
return Qnil;
|
670
686
|
}
|
671
687
|
|
672
|
-
/*
|
673
|
-
* session.handle_request( request ) -> response
|
674
|
-
*
|
688
|
+
/*
|
675
689
|
* Peform the actual HTTP request by calling libcurl. Each filed in the
|
676
690
|
* +request+ object will be used to set the appropriate option on the libcurl
|
677
691
|
* library. After the request completes, a Response object will be created and
|
@@ -680,17 +694,25 @@ static VALUE cleanup(VALUE self) {
|
|
680
694
|
* In the event of an error in the libcurl library, a Ruby exception will be
|
681
695
|
* created and raised. The exception will return the libcurl error code and
|
682
696
|
* error message.
|
697
|
+
*
|
698
|
+
* @param request[Patron::Request] the request to use when filling the CURL options
|
699
|
+
* @return [Patron::Response] the result of calling `response_class` on the Session
|
683
700
|
*/
|
684
701
|
static VALUE session_handle_request(VALUE self, VALUE request) {
|
685
702
|
set_options_from_request(self, request);
|
686
703
|
return rb_ensure(&perform_request, self, &cleanup, self);
|
687
704
|
}
|
688
705
|
|
689
|
-
/*
|
690
|
-
*
|
691
|
-
*
|
706
|
+
/*
|
707
|
+
* FIXME: figure out how this method should be used at all given Session is not multithreaded.
|
708
|
+
* FIXME: also: what is the difference with `interrupt()` and also relationship with `cleanup()`?
|
692
709
|
* Reset the underlying cURL session. This effectively closes all open
|
693
|
-
* connections and disables debug output.
|
710
|
+
* connections and disables debug output. There is no need to call this method
|
711
|
+
* manually after performing a request, since cleanup is performed automatically
|
712
|
+
* but the method can be used from another thread
|
713
|
+
* to abort a request currently in progress.
|
714
|
+
*
|
715
|
+
* @return self
|
694
716
|
*/
|
695
717
|
static VALUE session_reset(VALUE self) {
|
696
718
|
struct curl_state *state;
|
@@ -706,11 +728,10 @@ static VALUE session_reset(VALUE self) {
|
|
706
728
|
return self;
|
707
729
|
}
|
708
730
|
|
709
|
-
/*
|
710
|
-
* session.interrupt -> session
|
711
|
-
*
|
712
|
-
* Interrupt any currently executing request. This will cause the current
|
731
|
+
/* Interrupt any currently executing request. This will cause the current
|
713
732
|
* request to error and raise an exception.
|
733
|
+
*
|
734
|
+
* @return [void] This method always raises
|
714
735
|
*/
|
715
736
|
static VALUE session_interrupt(VALUE self) {
|
716
737
|
struct curl_state *state = get_curl_state(self);
|
@@ -718,18 +739,21 @@ static VALUE session_interrupt(VALUE self) {
|
|
718
739
|
return self;
|
719
740
|
}
|
720
741
|
|
721
|
-
/*
|
722
|
-
* session.enable_cookie_session( file ) -> session
|
723
|
-
*
|
742
|
+
/*
|
724
743
|
* Turn on cookie handling for this session, storing them in memory by
|
725
|
-
* default or in +file+ if specified. The
|
744
|
+
* default or in +file+ if specified. The `file` must be readable and
|
726
745
|
* writable. Calling multiple times will add more files.
|
746
|
+
* FIXME: what does the empty string actually do here?
|
747
|
+
*
|
748
|
+
* @param [String] file path to the existing cookie file, or nil to store in memory.
|
749
|
+
* @return self
|
727
750
|
*/
|
728
|
-
static VALUE
|
751
|
+
static VALUE add_cookie_file(VALUE self, VALUE file) {
|
729
752
|
struct curl_state *state = get_curl_state(self);
|
730
753
|
CURL* curl = state->handle;
|
731
754
|
char* file_path = NULL;
|
732
755
|
|
756
|
+
// FIXME: http://websystemsengineering.blogspot.nl/2013/03/curloptcookiefile-vs-curloptcookiejar.html
|
733
757
|
file_path = RSTRING_PTR(file);
|
734
758
|
if (file_path != NULL && strlen(file_path) != 0) {
|
735
759
|
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, file_path);
|
@@ -739,10 +763,11 @@ static VALUE enable_cookie_session(VALUE self, VALUE file) {
|
|
739
763
|
return self;
|
740
764
|
}
|
741
765
|
|
742
|
-
/*
|
743
|
-
* session.set_debug_file( file ) -> session
|
744
|
-
*
|
766
|
+
/*
|
745
767
|
* Enable debug output to stderr or to specified +file+.
|
768
|
+
*
|
769
|
+
* @param [String, nil] file path to the debug file, or nil to write to STDERR
|
770
|
+
* @return self
|
746
771
|
*/
|
747
772
|
static VALUE set_debug_file(VALUE self, VALUE file) {
|
748
773
|
struct curl_state *state = get_curl_state(self);
|
@@ -774,6 +799,7 @@ void Init_session_ext() {
|
|
774
799
|
ePatronError = rb_const_get(mPatron, rb_intern("Error"));
|
775
800
|
|
776
801
|
eUnsupportedProtocol = rb_const_get(mPatron, rb_intern("UnsupportedProtocol"));
|
802
|
+
eUnsupportedSSLVersion = rb_const_get(mPatron, rb_intern("UnsupportedSSLVersion"));
|
777
803
|
eURLFormatError = rb_const_get(mPatron, rb_intern("URLFormatError"));
|
778
804
|
eHostResolutionError = rb_const_get(mPatron, rb_intern("HostResolutionError"));
|
779
805
|
eConnectionFailed = rb_const_get(mPatron, rb_intern("ConnectionFailed"));
|
@@ -798,20 +824,20 @@ void Init_session_ext() {
|
|
798
824
|
rb_define_method(cSession, "handle_request", session_handle_request, 1);
|
799
825
|
rb_define_method(cSession, "reset", session_reset, 0);
|
800
826
|
rb_define_method(cSession, "interrupt", session_interrupt, 0);
|
801
|
-
rb_define_method(cSession, "
|
827
|
+
rb_define_method(cSession, "add_cookie_file", add_cookie_file, 1);
|
802
828
|
rb_define_method(cSession, "set_debug_file", set_debug_file, 1);
|
803
829
|
rb_define_alias(cSession, "urlencode", "escape");
|
804
830
|
rb_define_alias(cSession, "urldecode", "unescape");
|
805
831
|
|
806
|
-
rb_define_const(cRequest, "AuthBasic",
|
807
|
-
rb_define_const(cRequest, "AuthDigest",
|
808
|
-
rb_define_const(cRequest, "AuthAny",
|
832
|
+
rb_define_const(cRequest, "AuthBasic", LONG2NUM(CURLAUTH_BASIC));
|
833
|
+
rb_define_const(cRequest, "AuthDigest", LONG2NUM(CURLAUTH_DIGEST));
|
834
|
+
rb_define_const(cRequest, "AuthAny", LONG2NUM(CURLAUTH_ANY));
|
809
835
|
|
810
836
|
mProxyType = rb_define_module_under(mPatron, "ProxyType");
|
811
|
-
rb_define_const(mProxyType, "HTTP",
|
812
|
-
rb_define_const(mProxyType, "HTTP_1_0",
|
813
|
-
rb_define_const(mProxyType, "SOCKS4",
|
814
|
-
rb_define_const(mProxyType, "SOCKS5",
|
815
|
-
rb_define_const(mProxyType, "SOCKS4A",
|
816
|
-
rb_define_const(mProxyType, "SOCKS5_HOSTNAME",
|
837
|
+
rb_define_const(mProxyType, "HTTP", LONG2NUM(CURLPROXY_HTTP));
|
838
|
+
rb_define_const(mProxyType, "HTTP_1_0", LONG2NUM(CURLPROXY_HTTP_1_0));
|
839
|
+
rb_define_const(mProxyType, "SOCKS4", LONG2NUM(CURLPROXY_SOCKS4));
|
840
|
+
rb_define_const(mProxyType, "SOCKS5", LONG2NUM(CURLPROXY_SOCKS5));
|
841
|
+
rb_define_const(mProxyType, "SOCKS4A", LONG2NUM(CURLPROXY_SOCKS4A));
|
842
|
+
rb_define_const(mProxyType, "SOCKS5_HOSTNAME", LONG2NUM(CURLPROXY_SOCKS5_HOSTNAME));
|
817
843
|
}
|
data/lib/patron.rb
CHANGED
@@ -22,6 +22,8 @@
|
|
22
22
|
## THE SOFTWARE.
|
23
23
|
##
|
24
24
|
## -------------------------------------------------------------------
|
25
|
+
|
26
|
+
|
25
27
|
require 'pathname'
|
26
28
|
|
27
29
|
cwd = Pathname(__FILE__).dirname
|
@@ -30,8 +32,9 @@ $:.unshift(cwd.to_s) unless $:.include?(cwd.to_s) || $:.include?(cwd.expand_path
|
|
30
32
|
require 'patron/session'
|
31
33
|
require 'patron/version'
|
32
34
|
|
33
|
-
module Patron
|
34
|
-
# Returns the version number of the
|
35
|
+
module Patron
|
36
|
+
# Returns the version number of the gem
|
37
|
+
# @return [String]
|
35
38
|
def self.version
|
36
39
|
VERSION
|
37
40
|
end
|
data/lib/patron/error.rb
CHANGED
@@ -23,33 +23,37 @@
|
|
23
23
|
##
|
24
24
|
## -------------------------------------------------------------------
|
25
25
|
|
26
|
+
|
26
27
|
module Patron
|
27
28
|
|
28
29
|
# Base class for Patron exceptions.
|
29
30
|
class Error < StandardError; end
|
30
31
|
|
31
|
-
#
|
32
|
+
# Gets raised when the URL passed to Patron used a protocol that it does not support.
|
32
33
|
# This most likely the result of a misspelled protocol string.
|
33
34
|
class UnsupportedProtocol < Error; end
|
34
35
|
|
35
|
-
#
|
36
|
+
# Gets raised when a request is attempted with an unsupported SSL version.
|
37
|
+
class UnsupportedSSLVersion < Error; end
|
38
|
+
|
39
|
+
# Gets raised when the URL was not properly formatted.
|
36
40
|
class URLFormatError < Error; end
|
37
41
|
|
38
|
-
#
|
42
|
+
# Gets raised when the remote host name could not be resolved.
|
39
43
|
class HostResolutionError < Error; end
|
40
44
|
|
41
|
-
#
|
45
|
+
# Gets raised when failing to connect to the remote host.
|
42
46
|
class ConnectionFailed < Error; end
|
43
47
|
|
44
|
-
#
|
48
|
+
# Gets raised when the response was shorter or larger than expected.
|
45
49
|
# This happens when the server first reports an expected transfer size,
|
46
50
|
# and then delivers data that doesn't match the previously given size.
|
47
51
|
class PartialFileError < Error; end
|
48
52
|
|
49
|
-
#
|
53
|
+
# Gets raised on an operation timeout. The specified time-out period was reached.
|
50
54
|
class TimeoutError < Error; end
|
51
55
|
|
52
|
-
#
|
56
|
+
# Gets raised on too many redirects. When following redirects, Patron hit the maximum amount.
|
53
57
|
class TooManyRedirects < Error; end
|
54
58
|
|
55
59
|
end
|
data/lib/patron/proxy_type.rb
CHANGED
data/lib/patron/request.rb
CHANGED
@@ -23,6 +23,7 @@
|
|
23
23
|
##
|
24
24
|
## -------------------------------------------------------------------
|
25
25
|
|
26
|
+
|
26
27
|
require 'patron/util'
|
27
28
|
|
28
29
|
module Patron
|
@@ -32,8 +33,11 @@ module Patron
|
|
32
33
|
# used in every request.
|
33
34
|
class Request
|
34
35
|
|
36
|
+
# Contains the valid HTTP verbs that can be used to perform requests
|
35
37
|
VALID_ACTIONS = %w[GET PUT POST DELETE HEAD COPY]
|
36
38
|
|
39
|
+
# Initializes a new Request, which defaults to the GET HTTP verb and
|
40
|
+
# has it's timeouts set to 0
|
37
41
|
def initialize
|
38
42
|
@action = :get
|
39
43
|
@headers = {}
|
@@ -59,7 +63,7 @@ module Patron
|
|
59
63
|
|
60
64
|
# Set the type of authentication to use for this request.
|
61
65
|
#
|
62
|
-
# @param [String, Symbol]
|
66
|
+
# @param [String, Symbol]type The type of authentication to use for this request, can be one of
|
63
67
|
# :basic, :digest, or :any
|
64
68
|
#
|
65
69
|
# @example
|
@@ -79,6 +83,16 @@ module Patron
|
|
79
83
|
end
|
80
84
|
end
|
81
85
|
|
86
|
+
# Sets the upload data (request body) for the request. If the
|
87
|
+
# given argument is a Hash, the contents of the hash will be handled
|
88
|
+
# as form fields and will be form-encoded. The somposition of the request
|
89
|
+
# body is then going to be handled by Curl.
|
90
|
+
#
|
91
|
+
# If the given `data` is any other object, it is going to be treated as a stringable
|
92
|
+
# request body (JSON or other verbatim type) and will have it's `to_s` method called
|
93
|
+
# before sending out the request.
|
94
|
+
#
|
95
|
+
# @param data[Hash, #to_s] a Hash of form fields to values, or an object that responds to `to_s`
|
82
96
|
def upload_data=(data)
|
83
97
|
@upload_data = case data
|
84
98
|
when Hash
|
@@ -88,6 +102,9 @@ module Patron
|
|
88
102
|
end
|
89
103
|
end
|
90
104
|
|
105
|
+
# Sets the HTTP verb for the request
|
106
|
+
#
|
107
|
+
# @param action[String] the name of the HTTP verb
|
91
108
|
def action=(action)
|
92
109
|
if !VALID_ACTIONS.include?(action.to_s.upcase)
|
93
110
|
raise ArgumentError, "Action must be one of #{VALID_ACTIONS.join(', ')}"
|
@@ -95,6 +112,9 @@ module Patron
|
|
95
112
|
@action = action.downcase.to_sym
|
96
113
|
end
|
97
114
|
|
115
|
+
# Sets the read timeout for the CURL request, in seconds
|
116
|
+
#
|
117
|
+
# @param new_timeout[Integer] the number of seconds to wait before raising a timeout error
|
98
118
|
def timeout=(new_timeout)
|
99
119
|
if new_timeout && new_timeout.to_i < 1
|
100
120
|
raise ArgumentError, "Timeout must be a positive integer greater than 0"
|
@@ -103,6 +123,9 @@ module Patron
|
|
103
123
|
@timeout = new_timeout.to_i
|
104
124
|
end
|
105
125
|
|
126
|
+
# Sets the connect timeout for the CURL request, in seconds.
|
127
|
+
#
|
128
|
+
# @param new_timeout[Integer] the number of seconds to wait before raising a timeout error
|
106
129
|
def connect_timeout=(new_timeout)
|
107
130
|
if new_timeout && new_timeout.to_i < 1
|
108
131
|
raise ArgumentError, "Timeout must be a positive integer greater than 0"
|
@@ -111,6 +134,9 @@ module Patron
|
|
111
134
|
@connect_timeout = new_timeout.to_i
|
112
135
|
end
|
113
136
|
|
137
|
+
# Sets the maximum number of redirects that are going to be followed.
|
138
|
+
#
|
139
|
+
# @param new_max_redirects[Integer] The number of redirects to follow, or `-1` for unlimited redirects.
|
114
140
|
def max_redirects=(new_max_redirects)
|
115
141
|
if new_max_redirects.to_i < -1
|
116
142
|
raise ArgumentError, "Max redirects must be a positive integer, 0 or -1"
|
@@ -119,6 +145,10 @@ module Patron
|
|
119
145
|
@max_redirects = new_max_redirects.to_i
|
120
146
|
end
|
121
147
|
|
148
|
+
# Sets the headers for the request. Headers muse be set with the right capitalization.
|
149
|
+
# The previously set headers will be replaced.
|
150
|
+
#
|
151
|
+
# @param new_headers[Hash] the hash of headers to set.
|
122
152
|
def headers=(new_headers)
|
123
153
|
if !new_headers.kind_of?(Hash)
|
124
154
|
raise ArgumentError, "Headers must be a hash"
|
@@ -127,6 +157,12 @@ module Patron
|
|
127
157
|
@headers = new_headers
|
128
158
|
end
|
129
159
|
|
160
|
+
# Sets the receive buffer size. This is a recommendedation value, as CURL is not guaranteed to
|
161
|
+
# honor this value internally (see https://curl.haxx.se/libcurl/c/CURLOPT_BUFFERSIZE.html).
|
162
|
+
# By default, CURL uses the maximum possible buffer size, which will be the best especially
|
163
|
+
# for smaller and quickly-executing requests.
|
164
|
+
#
|
165
|
+
# @param buffer_size[Integer,nil] the desired buffer size, or `nil` for automatic buffer size
|
130
166
|
def buffer_size=(buffer_size)
|
131
167
|
if buffer_size != nil && buffer_size.to_i < 1
|
132
168
|
raise ArgumentError, "Buffer size must be a positive integer greater than 0 or nil"
|
@@ -135,15 +171,23 @@ module Patron
|
|
135
171
|
@buffer_size = buffer_size != nil ? buffer_size.to_i : nil
|
136
172
|
end
|
137
173
|
|
174
|
+
# Returns the set HTTP authentication string for basic authentication.
|
175
|
+
#
|
176
|
+
# @return [String, NilClass] the authentication string or nil if no authentication is used
|
138
177
|
def credentials
|
139
178
|
return nil if username.nil? || password.nil?
|
140
179
|
"#{username}:#{password}"
|
141
180
|
end
|
142
181
|
|
182
|
+
# Returns the set HTTP verb
|
183
|
+
#
|
184
|
+
# @return [String] the HTTP verb
|
143
185
|
def action_name
|
144
186
|
@action.to_s.upcase
|
145
187
|
end
|
146
188
|
|
189
|
+
# Tells whether this Request is configured the same as the other request
|
190
|
+
# @return [TrueClass, FalseClass]
|
147
191
|
def eql?(request)
|
148
192
|
return false unless Request === request
|
149
193
|
|
@@ -154,12 +198,17 @@ module Patron
|
|
154
198
|
|
155
199
|
alias_method :==, :eql?
|
156
200
|
|
201
|
+
# Returns a Marshalable representation of the Request
|
202
|
+
# @return [Array]
|
157
203
|
def marshal_dump
|
158
204
|
[ @url, @username, @password, @file_name, @proxy, @proxy_type, @insecure,
|
159
205
|
@ignore_content_length, @multipart, @action, @timeout, @connect_timeout,
|
160
206
|
@max_redirects, @headers, @auth_type, @upload_data, @buffer_size, @cacert ]
|
161
207
|
end
|
162
208
|
|
209
|
+
# Reinstates instance variables from a marshaled representation
|
210
|
+
# @param data[Array]
|
211
|
+
# @return [void]
|
163
212
|
def marshal_load(data)
|
164
213
|
@url, @username, @password, @file_name, @proxy, @proxy_type, @insecure,
|
165
214
|
@ignore_content_length, @multipart, @action, @timeout, @connect_timeout,
|
data/lib/patron/response.rb
CHANGED
@@ -23,10 +23,38 @@
|
|
23
23
|
##
|
24
24
|
## -------------------------------------------------------------------
|
25
25
|
|
26
|
+
|
26
27
|
module Patron
|
27
28
|
|
28
29
|
# Represents the response from the HTTP server.
|
29
30
|
class Response
|
31
|
+
# @return [String] the original URL used to perform the request (contains the final URL after redirects)
|
32
|
+
attr_reader :url
|
33
|
+
|
34
|
+
# @return [Fixnum] the HTTP status code of the final response after all the redirects
|
35
|
+
attr_reader :status
|
36
|
+
|
37
|
+
# @return [String] the complete status line (code and message)
|
38
|
+
attr_reader :status_line
|
39
|
+
|
40
|
+
# @return [Fixnum] how many redirects were followed when fulfilling this request
|
41
|
+
attr_reader :redirect_count
|
42
|
+
|
43
|
+
# @return [String, nil] the response body, or nil if the response was written directly to a file
|
44
|
+
attr_reader :body
|
45
|
+
|
46
|
+
# @return [Hash] the response headers. If there were multiple headers received for the same value
|
47
|
+
# (like "Cookie"), the header values will be within an Array under the key for the header, in order.
|
48
|
+
attr_reader :headers
|
49
|
+
|
50
|
+
# @return [String] the recognized name of the charset for the response
|
51
|
+
attr_reader :charset
|
52
|
+
|
53
|
+
# Overridden so that the output is shorter and there is no response body printed
|
54
|
+
def inspect
|
55
|
+
# Avoid spamming the console with the header and body data
|
56
|
+
"#<Patron::Response @status_line='#{@status_line}'>"
|
57
|
+
end
|
30
58
|
|
31
59
|
def initialize(url, status, redirect_count, header_data, body, default_charset = nil)
|
32
60
|
# Don't let a response clear out the default charset, which would cause encoding to fail
|
@@ -48,22 +76,7 @@ module Patron
|
|
48
76
|
end
|
49
77
|
end
|
50
78
|
|
51
|
-
|
52
|
-
|
53
|
-
def inspect
|
54
|
-
# Avoid spamming the console with the header and body data
|
55
|
-
"#<Patron::Response @status_line='#{@status_line}'>"
|
56
|
-
end
|
57
|
-
|
58
|
-
def marshal_dump
|
59
|
-
[@url, @status, @status_line, @redirect_count, @body, @headers, @charset]
|
60
|
-
end
|
61
|
-
|
62
|
-
def marshal_load(data)
|
63
|
-
@url, @status, @status_line, @redirect_count, @body, @headers, @charset = data
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
79
|
+
private
|
67
80
|
|
68
81
|
def determine_charset(header_data, body)
|
69
82
|
header_data.match(charset_regex) || (body && body.match(charset_regex))
|
data/lib/patron/session.rb
CHANGED
@@ -23,6 +23,7 @@
|
|
23
23
|
##
|
24
24
|
## -------------------------------------------------------------------
|
25
25
|
|
26
|
+
|
26
27
|
require 'uri'
|
27
28
|
require 'patron/error'
|
28
29
|
require 'patron/request'
|
@@ -36,63 +37,80 @@ module Patron
|
|
36
37
|
# server. This is the primary API for Patron.
|
37
38
|
class Session
|
38
39
|
|
39
|
-
# HTTP connection timeout in seconds. Defaults to 1 second.
|
40
|
+
# @return [Integer] HTTP connection timeout in seconds. Defaults to 1 second.
|
40
41
|
attr_accessor :connect_timeout
|
41
42
|
|
42
|
-
# HTTP transaction timeout in seconds. Defaults to 5 seconds.
|
43
|
+
# @return [Integer] HTTP transaction timeout in seconds. Defaults to 5 seconds.
|
43
44
|
attr_accessor :timeout
|
44
45
|
|
45
|
-
# Maximum number of
|
46
|
+
# Maximum number of redirects to follow
|
46
47
|
# Set to 0 to disable and -1 to follow all redirects. Defaults to 5.
|
48
|
+
# @return [Integer]
|
47
49
|
attr_accessor :max_redirects
|
48
50
|
|
49
|
-
#
|
51
|
+
# @return [String] The URL to prepend to all requests.
|
50
52
|
attr_accessor :base_url
|
51
53
|
|
52
|
-
# Username
|
53
|
-
|
54
|
+
# Username for http authentication
|
55
|
+
# @return [String,nil] the HTTP basic auth username
|
56
|
+
attr_accessor :username
|
57
|
+
|
58
|
+
# Password for http authentication
|
59
|
+
# @return [String,nil] the HTTP basic auth password
|
60
|
+
attr_accessor :password
|
54
61
|
|
55
|
-
# Proxy URL in cURL format ('hostname:8080')
|
62
|
+
# @return [String] Proxy URL in cURL format ('hostname:8080')
|
56
63
|
attr_accessor :proxy
|
57
64
|
|
58
|
-
# Proxy type (default is HTTP)
|
65
|
+
# @return [Integer] Proxy type (default is HTTP)
|
66
|
+
# @see Patron::ProxyType
|
59
67
|
attr_accessor :proxy_type
|
60
68
|
|
61
|
-
#
|
69
|
+
# @return [Hash] headers used in all requests.
|
62
70
|
attr_accessor :headers
|
63
71
|
|
64
|
-
#
|
72
|
+
# @return [Symbol] the authentication type for the request (`:basic`, `:digest` or `:token`).
|
65
73
|
# @see Patron::Request#auth_type
|
66
74
|
attr_accessor :auth_type
|
67
75
|
|
68
|
-
#
|
76
|
+
# @return [Boolean] `true` if SSL certificate verification is disabled.
|
77
|
+
# Please consider twice before using this option..
|
69
78
|
attr_accessor :insecure
|
70
79
|
|
71
|
-
#
|
80
|
+
# @return [String] the SSL version for the requests, or nil if all versions are permitted
|
81
|
+
# The supported values are nil, "SSLv2", "SSLv3", and "TLSv1".
|
72
82
|
attr_accessor :ssl_version
|
73
83
|
|
74
|
-
#
|
84
|
+
# @return [String] path to the CA file used for certificate verification, or `nil` if CURL default is used
|
75
85
|
attr_accessor :cacert
|
76
86
|
|
77
|
-
#
|
87
|
+
# @return [Boolean] whether Content-Range and Content-Length headers should be ignored
|
78
88
|
attr_accessor :ignore_content_length
|
79
89
|
|
90
|
+
# @return [Integer, nil]
|
80
91
|
# Set the buffer size for this request. This option will
|
81
92
|
# only be set if buffer_size is non-nil
|
82
93
|
attr_accessor :buffer_size
|
83
94
|
|
84
|
-
#
|
95
|
+
# @return [String, nil]
|
96
|
+
# Sets the name of the charset to assume for the response. The argument should be a String that
|
97
|
+
# is an acceptable argument for `Encoding.find()` in Ruby. The variable will only be used if the
|
98
|
+
# response does not specify a charset in it's `Content-Type` header already, if it does that charset
|
99
|
+
# will take precedence.
|
85
100
|
attr_accessor :default_response_charset
|
86
101
|
|
87
|
-
# Force curl to use IPv4
|
102
|
+
# @return [Boolean] Force curl to use IPv4
|
88
103
|
attr_accessor :force_ipv4
|
89
104
|
|
90
|
-
# Support automatic Content-Encoding decompression and set liberal Accept-Encoding headers
|
105
|
+
# @return [Boolean] Support automatic Content-Encoding decompression and set liberal Accept-Encoding headers
|
91
106
|
attr_accessor :automatic_content_encoding
|
92
107
|
|
93
|
-
private :handle_request, :
|
108
|
+
private :handle_request, :add_cookie_file, :set_debug_file
|
94
109
|
|
95
110
|
# Create a new Session object.
|
111
|
+
#
|
112
|
+
# @param args[Hash] options for the Session (same names as the writable attributes of the Session)
|
113
|
+
# @yield self
|
96
114
|
def initialize(args = {}, &block)
|
97
115
|
|
98
116
|
# Allows accessors to be set via constructor hash. Ex: {:base_url => 'www.home.com'}
|
@@ -114,73 +132,123 @@ module Patron
|
|
114
132
|
end
|
115
133
|
|
116
134
|
# Turn on cookie handling for this session, storing them in memory by
|
117
|
-
# default or in +file+ if specified. The
|
135
|
+
# default or in +file+ if specified. The `file` must be readable and
|
118
136
|
# writable. Calling multiple times will add more files.
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
137
|
+
#
|
138
|
+
# @todo the cookie jar and cookie file path options should be split
|
139
|
+
# @param file_path[String] path to an existing cookie jar file, or nil to store cookies in memory
|
140
|
+
# @return self
|
141
|
+
def handle_cookies(file_path = nil)
|
142
|
+
if file_path
|
143
|
+
path = Pathname(file_path).expand_path
|
144
|
+
|
145
|
+
if !File.exists?(file_path) && !File.writable?(path.dirname)
|
123
146
|
raise ArgumentError, "Can't create file #{path} (permission error)"
|
124
|
-
|
125
|
-
unless File.readable?(file) or File.writable?(path)
|
147
|
+
elsif File.exists?(file_path) && !File.writable?(file_path)
|
126
148
|
raise ArgumentError, "Can't read or write file #{path} (permission error)"
|
127
149
|
end
|
150
|
+
else
|
151
|
+
path = nil
|
128
152
|
end
|
129
|
-
|
153
|
+
|
154
|
+
# Apparently calling this with an empty string sets the cookie file,
|
155
|
+
# but calling it with a path to a writable file sets that file to be
|
156
|
+
# the cookie jar (new cookies are written there)
|
157
|
+
add_cookie_file(path.to_s)
|
158
|
+
|
130
159
|
self
|
131
160
|
end
|
132
161
|
|
133
|
-
# Enable debug output to stderr or to specified
|
162
|
+
# Enable debug output to stderr or to specified `file`.
|
163
|
+
#
|
164
|
+
# @todo Change to an assignment of an IO object
|
165
|
+
# @param file[String, nil] path to the file to write debug data to, or `nil` to print to `STDERR`
|
166
|
+
# @return self
|
134
167
|
def enable_debug(file = nil)
|
135
168
|
set_debug_file(file.to_s)
|
169
|
+
self
|
136
170
|
end
|
137
171
|
|
138
|
-
|
139
|
-
### Standard HTTP methods
|
140
|
-
###
|
141
|
-
|
142
|
-
# Retrieve the contents of the specified +url+ optionally sending the
|
172
|
+
# Retrieve the contents of the specified `url` optionally sending the
|
143
173
|
# specified headers. If the +base_url+ varaible is set then it is prepended
|
144
174
|
# to the +url+ parameter. Any custom headers are merged with the contents
|
145
175
|
# of the +headers+ instance variable. The results are returned in a
|
146
176
|
# Response object.
|
147
|
-
# Notice: this method doesn't accept any
|
148
|
-
# a
|
177
|
+
# Notice: this method doesn't accept any `data` argument: if you need to send a request body
|
178
|
+
# with a GET request, when using ElasticSearch for example, please, use the #request method.
|
179
|
+
#
|
180
|
+
# @param url[String] the URL to fetch
|
181
|
+
# @param headers[Hash] the hash of header keys to values
|
182
|
+
# @return [Patron::Response]
|
149
183
|
def get(url, headers = {})
|
150
184
|
request(:get, url, headers)
|
151
185
|
end
|
152
186
|
|
153
187
|
# Retrieve the contents of the specified +url+ as with #get, but the
|
154
|
-
# content at the URL is downloaded directly into the specified file.
|
188
|
+
# content at the URL is downloaded directly into the specified file. The file will be accessed
|
189
|
+
# by libCURL bypassing the Ruby runtime entirely.
|
190
|
+
#
|
191
|
+
# Note that when using this option, the Response object will have ++nil++ as the body, and you
|
192
|
+
# will need to read your target file for access to the body string).
|
193
|
+
#
|
194
|
+
# @param url[String] the URL to fetch
|
195
|
+
# @param filename[String] path to the file to save the response body in
|
196
|
+
# @return [Patron::Response]
|
155
197
|
def get_file(url, filename, headers = {})
|
156
198
|
request(:get, url, headers, :file => filename)
|
157
199
|
end
|
158
200
|
|
159
|
-
#
|
201
|
+
# Same as #get but performs a HEAD request.
|
202
|
+
#
|
203
|
+
# @see #get
|
204
|
+
# @param url[String] the URL to fetch
|
205
|
+
# @param headers[Hash] the hash of header keys to values
|
206
|
+
# @return [Patron::Response]
|
160
207
|
def head(url, headers = {})
|
161
208
|
request(:head, url, headers)
|
162
209
|
end
|
163
210
|
|
164
|
-
#
|
165
|
-
#
|
166
|
-
#
|
211
|
+
# Same as #get but performs a DELETE request.
|
212
|
+
#
|
213
|
+
# Notice: this method doesn't accept any `data` argument: if you need to send data with
|
214
|
+
# a delete request (as might be needed for ElasticSearch), please, use the #request method.
|
215
|
+
#
|
216
|
+
# @param url[String] the URL to fetch
|
217
|
+
# @param headers[Hash] the hash of header keys to values
|
218
|
+
# @return [Patron::Response]
|
167
219
|
def delete(url, headers = {})
|
168
220
|
request(:delete, url, headers)
|
169
221
|
end
|
170
222
|
|
171
|
-
# Uploads the passed
|
172
|
-
#
|
223
|
+
# Uploads the passed `data` to the specified `url` using an HTTP PUT. Note that
|
224
|
+
# unline ++post++, a Hash is not accepted as the ++data++ argument.
|
225
|
+
#
|
226
|
+
# @todo inconsistency with "post" - Hash not accepted
|
227
|
+
# @param url[String] the URL to fetch
|
228
|
+
# @param data[#to_s] an object that can be converted to a String to create the request body
|
229
|
+
# @param headers[Hash] the hash of header keys to values
|
230
|
+
# @return [Patron::Response]
|
173
231
|
def put(url, data, headers = {})
|
174
232
|
request(:put, url, headers, :data => data)
|
175
233
|
end
|
176
234
|
|
177
|
-
# Uploads the contents of
|
235
|
+
# Uploads the contents of `file` to the specified `url` using an HTTP PUT. The file will be
|
236
|
+
# sent "as-is" without any multipart encoding.
|
237
|
+
#
|
238
|
+
# @param url[String] the URL to fetch
|
239
|
+
# @param filename[String] path to the file to be uploaded
|
240
|
+
# @param headers[Hash] the hash of header keys to values
|
241
|
+
# @return [Patron::Response]
|
178
242
|
def put_file(url, filename, headers = {})
|
179
243
|
request(:put, url, headers, :file => filename)
|
180
244
|
end
|
181
245
|
|
182
|
-
# Uploads the passed
|
183
|
-
#
|
246
|
+
# Uploads the passed `data` to the specified `url` using an HTTP POST.
|
247
|
+
#
|
248
|
+
# @param url[String] the URL to fetch
|
249
|
+
# @param data[Hash, #to_s] a Hash of form fields/values, or an object that can be converted to a String to create the request body
|
250
|
+
# @param headers[Hash] the hash of header keys to values
|
251
|
+
# @return [Patron::Response]
|
184
252
|
def post(url, data, headers = {})
|
185
253
|
if data.is_a?(Hash)
|
186
254
|
data = data.map {|k,v| urlencode(k.to_s) + '=' + urlencode(v.to_s) }.join('&')
|
@@ -189,31 +257,52 @@ module Patron
|
|
189
257
|
request(:post, url, headers, :data => data)
|
190
258
|
end
|
191
259
|
|
192
|
-
# Uploads the contents of
|
260
|
+
# Uploads the contents of `file` to the specified `url` using an HTTP POST.
|
261
|
+
# The file will be sent "as-is" without any multipart encoding.
|
262
|
+
#
|
263
|
+
# @param url[String] the URL to fetch
|
264
|
+
# @param filename[String] path to the file to be uploaded
|
265
|
+
# @param headers[Hash] the hash of header keys to values
|
266
|
+
# @return [Patron::Response]
|
193
267
|
def post_file(url, filename, headers = {})
|
194
268
|
request(:post, url, headers, :file => filename)
|
195
269
|
end
|
196
270
|
|
197
|
-
# Uploads the contents of
|
271
|
+
# Uploads the contents of `filename` to the specified `url` using an HTTP POST,
|
272
|
+
# in combination with given form fields passed in `data`.
|
273
|
+
#
|
274
|
+
# @param url[String] the URL to fetch
|
275
|
+
# @param data[Hash] hash of the form fields
|
276
|
+
# @param filename[String] path to the file to be uploaded
|
277
|
+
# @param headers[Hash] the hash of header keys to values
|
278
|
+
# @return [Patron::Response]
|
198
279
|
def post_multipart(url, data, filename, headers = {})
|
199
280
|
request(:post, url, headers, {:data => data, :file => filename, :multipart => true})
|
200
281
|
end
|
201
282
|
|
202
|
-
###################################################################
|
203
|
-
### WebDAV methods
|
204
|
-
###
|
205
283
|
|
284
|
+
# @!group WebDAV methods
|
206
285
|
# Sends a WebDAV COPY request to the specified +url+.
|
286
|
+
#
|
287
|
+
# @param url[String] the URL to copy
|
288
|
+
# @param dest[String] the URL of the COPY destination
|
289
|
+
# @param headers[Hash] the hash of header keys to values
|
290
|
+
# @return [Patron::Response]
|
207
291
|
def copy(url, dest, headers = {})
|
208
292
|
headers['Destination'] = dest
|
209
293
|
request(:copy, url, headers)
|
210
294
|
end
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
#
|
295
|
+
# @!endgroup
|
296
|
+
|
297
|
+
# @!group Basic API methods
|
298
|
+
# Send an HTTP request to the specified `url`.
|
299
|
+
#
|
300
|
+
# @param action[#to_s] the HTTP verb
|
301
|
+
# @param url[String] the URL for the request
|
302
|
+
# @param headers[Hash] headers to send along with the request
|
303
|
+
# @param options[Hash] any additonal setters to call on the Request
|
304
|
+
# @see Patron::Request
|
305
|
+
# @return [Patron::Response]
|
217
306
|
def request(action, url, headers, options = {})
|
218
307
|
req = build_request(action, url, headers, options)
|
219
308
|
handle_request(req)
|
@@ -227,11 +316,22 @@ module Patron
|
|
227
316
|
# various headers/status codes. The method must return
|
228
317
|
# a module that supports the same interface for +new+
|
229
318
|
# as ++Patron::Response++
|
319
|
+
#
|
320
|
+
# @return [#new] Returns any object that responds to `.new` with 6 arguments
|
321
|
+
# @see Patron::Response#initialize
|
230
322
|
def response_class
|
231
323
|
::Patron::Response
|
232
324
|
end
|
233
325
|
|
234
|
-
#
|
326
|
+
# Builds a request object that can be used by ++handle_request++
|
327
|
+
# Note that internally, ++handle_request++ uses instance variables of
|
328
|
+
# the Request object, and not it's public methods.
|
329
|
+
#
|
330
|
+
# @param action[String] the HTTP verb
|
331
|
+
# @paran url[#to_s] the addition to the base url component, or a complete URL
|
332
|
+
# @paran headers[Hash] a hash of headers, "Accept" will be automatically set to an empty string if not provided
|
333
|
+
# @paran options[Hash] any overriding options (will shadow the options from the Session object)
|
334
|
+
# @return [Patron::Request] the request that will be passed to ++handle_request++
|
235
335
|
def build_request(action, url, headers, options = {})
|
236
336
|
# If the Expect header isn't set uploads are really slow
|
237
337
|
headers['Expect'] ||= ''
|
@@ -270,5 +370,6 @@ module Patron
|
|
270
370
|
req.url = url
|
271
371
|
end
|
272
372
|
end
|
373
|
+
# @!endgroup
|
273
374
|
end
|
274
375
|
end
|
data/lib/patron/util.rb
CHANGED
data/lib/patron/version.rb
CHANGED
data/patron.gemspec
CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.add_development_dependency "rake-compiler", ">= 0.7.5"
|
19
19
|
s.add_development_dependency "rspec", ">= 2.3.0"
|
20
20
|
s.add_development_dependency "simplecov", ">= 0.10.0"
|
21
|
+
s.add_development_dependency "yard", "~> 0.8"
|
21
22
|
|
22
23
|
s.files = `git ls-files`.split("\n")
|
23
24
|
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
data/spec/patron_spec.rb
CHANGED
data/spec/request_spec.rb
CHANGED
data/spec/response_spec.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
2
|
+
# Encoding pragma is needed for loading this test properly on Ruby < 2.0
|
3
|
+
|
1
4
|
## -------------------------------------------------------------------
|
2
5
|
##
|
3
6
|
## Copyright (c) 2009 Phillip Toland <phil.toland@gmail.com>
|
@@ -21,6 +24,8 @@
|
|
21
24
|
## THE SOFTWARE.
|
22
25
|
##
|
23
26
|
## -------------------------------------------------------------------
|
27
|
+
|
28
|
+
|
24
29
|
require File.expand_path("./spec") + '/spec_helper.rb'
|
25
30
|
require 'webrick'
|
26
31
|
require 'base64'
|
data/spec/session_spec.rb
CHANGED
@@ -21,6 +21,8 @@
|
|
21
21
|
## THE SOFTWARE.
|
22
22
|
##
|
23
23
|
## -------------------------------------------------------------------
|
24
|
+
|
25
|
+
|
24
26
|
require File.expand_path("./spec") + '/spec_helper.rb'
|
25
27
|
require 'webrick'
|
26
28
|
require 'yaml'
|
@@ -286,6 +288,18 @@ describe Patron::Session do
|
|
286
288
|
expect(body.header['authorization']).to be == [encode_authz("foo", "bar")]
|
287
289
|
end
|
288
290
|
|
291
|
+
it "should store cookies across multiple requests" do
|
292
|
+
tf = Tempfile.new('cookiejar')
|
293
|
+
cookie_jar_path = tf.path
|
294
|
+
|
295
|
+
@session.handle_cookies(cookie_jar_path)
|
296
|
+
response = @session.get("/setcookie").body
|
297
|
+
|
298
|
+
cookie_jar_contents = tf.read
|
299
|
+
expect(cookie_jar_contents).not_to be_empty
|
300
|
+
expect(cookie_jar_contents).to include('Netscape HTTP Cookie File')
|
301
|
+
end
|
302
|
+
|
289
303
|
it "should handle cookies if set" do
|
290
304
|
@session.handle_cookies
|
291
305
|
response = @session.get("/setcookie").body
|
@@ -471,10 +485,12 @@ describe Patron::Session do
|
|
471
485
|
it 'it should not clobber stderr' do
|
472
486
|
rdev = STDERR.stat.rdev
|
473
487
|
|
474
|
-
@session.enable_debug
|
488
|
+
retval = @session.enable_debug
|
489
|
+
expect(retval).to eq(@session)
|
475
490
|
expect(STDERR.stat.rdev).to be == rdev
|
476
491
|
|
477
|
-
@session.enable_debug
|
492
|
+
retval = @session.enable_debug
|
493
|
+
expect(retval).to eq(@session)
|
478
494
|
expect(STDERR.stat.rdev).to be == rdev
|
479
495
|
end
|
480
496
|
end
|
data/spec/session_ssl_spec.rb
CHANGED
@@ -21,6 +21,8 @@
|
|
21
21
|
## THE SOFTWARE.
|
22
22
|
##
|
23
23
|
## -------------------------------------------------------------------
|
24
|
+
|
25
|
+
|
24
26
|
require File.expand_path("./spec") + '/spec_helper.rb'
|
25
27
|
require 'webrick'
|
26
28
|
require 'yaml'
|
@@ -267,6 +269,15 @@ describe Patron::Session do
|
|
267
269
|
end
|
268
270
|
end
|
269
271
|
|
272
|
+
it "should raise when an unsupported or unknown SSL version is requested" do
|
273
|
+
['something', 1].each do |version|
|
274
|
+
@session.ssl_version = version
|
275
|
+
expect {
|
276
|
+
@session.get("/test")
|
277
|
+
}.to raise_error(Patron::UnsupportedSSLVersion)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
270
281
|
# ------------------------------------------------------------------------
|
271
282
|
describe 'when debug is enabled' do
|
272
283
|
it 'it should not clobber stderr' do
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/test_server.rb
CHANGED
data/spec/util_spec.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: patron
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Phillip Toland
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-04-
|
11
|
+
date: 2016-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 0.10.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: yard
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.8'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.8'
|
69
83
|
description: Ruby HTTP client library based on libcurl
|
70
84
|
email:
|
71
85
|
- phil.toland@gmail.com
|
@@ -77,6 +91,7 @@ files:
|
|
77
91
|
- ".autotest"
|
78
92
|
- ".gitignore"
|
79
93
|
- ".rspec"
|
94
|
+
- ".travis.yml"
|
80
95
|
- Gemfile
|
81
96
|
- Gemfile.lock
|
82
97
|
- LICENSE
|
@@ -135,3 +150,4 @@ signing_key:
|
|
135
150
|
specification_version: 4
|
136
151
|
summary: Patron HTTP Client
|
137
152
|
test_files: []
|
153
|
+
has_rdoc:
|