patron 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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:
|