rest-client 1.8.0-x64-mingw32 → 2.0.0.rc1-x64-mingw32
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/.rspec +2 -1
- data/.rubocop-disables.yml +375 -0
- data/.rubocop.yml +3 -0
- data/.travis.yml +3 -4
- data/AUTHORS +11 -1
- data/README.rdoc +84 -16
- data/Rakefile +16 -0
- data/bin/restclient +3 -5
- data/history.md +61 -0
- data/lib/restclient.rb +19 -2
- data/lib/restclient/abstract_response.rb +107 -42
- data/lib/restclient/exceptions.rb +44 -44
- data/lib/restclient/payload.rb +2 -2
- data/lib/restclient/platform.rb +19 -0
- data/lib/restclient/raw_response.rb +4 -0
- data/lib/restclient/request.rb +141 -38
- data/lib/restclient/resource.rb +3 -3
- data/lib/restclient/response.rb +61 -5
- data/lib/restclient/utils.rb +93 -0
- data/lib/restclient/version.rb +2 -1
- data/rest-client.gemspec +6 -5
- data/spec/helpers.rb +14 -0
- data/spec/integration/_lib.rb +1 -0
- data/spec/integration/httpbin_spec.rb +72 -0
- data/spec/integration/integration_spec.rb +79 -1
- data/spec/integration/request_spec.rb +24 -1
- data/spec/spec_helper.rb +21 -1
- data/spec/unit/_lib.rb +1 -0
- data/spec/unit/abstract_response_spec.rb +22 -8
- data/spec/unit/exceptions_spec.rb +9 -17
- data/spec/unit/payload_spec.rb +10 -10
- data/spec/unit/raw_response_spec.rb +1 -1
- data/spec/unit/request2_spec.rb +6 -6
- data/spec/unit/request_spec.rb +234 -43
- data/spec/unit/resource_spec.rb +1 -1
- data/spec/unit/response_spec.rb +72 -28
- data/spec/unit/restclient_spec.rb +3 -3
- data/spec/unit/utils_spec.rb +71 -0
- data/spec/unit/windows/root_certs_spec.rb +1 -1
- metadata +41 -14
data/lib/restclient/resource.rb
CHANGED
@@ -14,7 +14,7 @@ module RestClient
|
|
14
14
|
#
|
15
15
|
# With a timeout (seconds):
|
16
16
|
#
|
17
|
-
# RestClient::Resource.new('http://slow', :
|
17
|
+
# RestClient::Resource.new('http://slow', :read_timeout => 10)
|
18
18
|
#
|
19
19
|
# With an open timeout (seconds):
|
20
20
|
#
|
@@ -113,8 +113,8 @@ module RestClient
|
|
113
113
|
options[:headers] || {}
|
114
114
|
end
|
115
115
|
|
116
|
-
def
|
117
|
-
options[:
|
116
|
+
def read_timeout
|
117
|
+
options[:read_timeout]
|
118
118
|
end
|
119
119
|
|
120
120
|
def open_timeout
|
data/lib/restclient/response.rb
CHANGED
@@ -2,20 +2,76 @@ module RestClient
|
|
2
2
|
|
3
3
|
# A Response from RestClient, you can access the response body, the code or the headers.
|
4
4
|
#
|
5
|
-
|
5
|
+
class Response < String
|
6
6
|
|
7
7
|
include AbstractResponse
|
8
8
|
|
9
|
+
# Return the HTTP response body.
|
10
|
+
#
|
11
|
+
# Future versions of RestClient will deprecate treating response objects
|
12
|
+
# directly as strings, so it will be necessary to call `.body`.
|
13
|
+
#
|
14
|
+
# @return [String]
|
15
|
+
#
|
9
16
|
def body
|
10
|
-
self
|
17
|
+
# Benchmarking suggests that "#{self}" is fastest, and that caching the
|
18
|
+
# body string in an instance variable doesn't make it enough faster to be
|
19
|
+
# worth the extra memory storage.
|
20
|
+
String.new(self)
|
11
21
|
end
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
|
23
|
+
# Convert the HTTP response body to a pure String object.
|
24
|
+
#
|
25
|
+
# @return [String]
|
26
|
+
def to_s
|
27
|
+
body
|
28
|
+
end
|
29
|
+
|
30
|
+
# Convert the HTTP response body to a pure String object.
|
31
|
+
#
|
32
|
+
# @return [String]
|
33
|
+
def to_str
|
34
|
+
body
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
"<RestClient::Response #{code.inspect} #{body_truncated(10).inspect}>"
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.create(body, net_http_res, args, request)
|
42
|
+
result = self.new(body || '')
|
43
|
+
|
16
44
|
result.response_set_vars(net_http_res, args, request)
|
45
|
+
fix_encoding(result)
|
46
|
+
|
17
47
|
result
|
18
48
|
end
|
19
49
|
|
50
|
+
private
|
51
|
+
|
52
|
+
def self.fix_encoding(response)
|
53
|
+
charset = RestClient::Utils.get_encoding_from_headers(response.headers)
|
54
|
+
encoding = nil
|
55
|
+
|
56
|
+
begin
|
57
|
+
encoding = Encoding.find(charset) if charset
|
58
|
+
rescue ArgumentError
|
59
|
+
RestClient.log << "No such encoding: #{charset.inspect}"
|
60
|
+
end
|
61
|
+
|
62
|
+
return unless encoding
|
63
|
+
|
64
|
+
response.force_encoding(encoding)
|
65
|
+
|
66
|
+
response
|
67
|
+
end
|
68
|
+
|
69
|
+
def body_truncated(length)
|
70
|
+
if body.length > length
|
71
|
+
body[0..length] + '...'
|
72
|
+
else
|
73
|
+
body
|
74
|
+
end
|
75
|
+
end
|
20
76
|
end
|
21
77
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module RestClient
|
2
|
+
# Various utility methods
|
3
|
+
module Utils
|
4
|
+
|
5
|
+
# Return encoding from an HTTP header hash.
|
6
|
+
#
|
7
|
+
# We use the RFC 7231 specification and do not impose a default encoding on
|
8
|
+
# text. This differs from the older RFC 2616 behavior, which specifies
|
9
|
+
# using ISO-8859-1 for text/* content types without a charset.
|
10
|
+
#
|
11
|
+
# Strings will effectively end up using `Encoding.default_external` when
|
12
|
+
# this method returns nil.
|
13
|
+
#
|
14
|
+
# @param headers [Hash]
|
15
|
+
#
|
16
|
+
# @return [String, nil] encoding Return the string encoding or nil if no
|
17
|
+
# header is found.
|
18
|
+
#
|
19
|
+
def self.get_encoding_from_headers(headers)
|
20
|
+
type_header = headers[:content_type]
|
21
|
+
return nil unless type_header
|
22
|
+
|
23
|
+
_content_type, params = cgi_parse_header(type_header)
|
24
|
+
|
25
|
+
if params.include?('charset')
|
26
|
+
return params.fetch('charset').gsub(/(\A["']*)|(["']*\z)/, '')
|
27
|
+
end
|
28
|
+
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
32
|
+
# Parse semi-colon separated, potentially quoted header string iteratively.
|
33
|
+
#
|
34
|
+
# @private
|
35
|
+
#
|
36
|
+
def self._cgi_parseparam(s)
|
37
|
+
return enum_for(__method__, s) unless block_given?
|
38
|
+
|
39
|
+
while s[0] == ';'
|
40
|
+
s = s[1..-1]
|
41
|
+
ends = s.index(';')
|
42
|
+
while ends && ends > 0 \
|
43
|
+
&& (s[0...ends].count('"') -
|
44
|
+
s[0...ends].scan('\"').count) % 2 != 0
|
45
|
+
ends = s.index(';', ends + 1)
|
46
|
+
end
|
47
|
+
if ends.nil?
|
48
|
+
ends = s.length
|
49
|
+
end
|
50
|
+
f = s[0...ends]
|
51
|
+
yield f.strip
|
52
|
+
s = s[ends..-1]
|
53
|
+
end
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# Parse a Content-type like header.
|
58
|
+
#
|
59
|
+
# Return the main content-type and a hash of options.
|
60
|
+
#
|
61
|
+
# This method was ported directly from Python's cgi.parse_header(). It
|
62
|
+
# probably doesn't read or perform particularly well in ruby.
|
63
|
+
# https://github.com/python/cpython/blob/3.4/Lib/cgi.py#L301-L331
|
64
|
+
#
|
65
|
+
#
|
66
|
+
# @param [String] line
|
67
|
+
# @return [Array(String, Hash)]
|
68
|
+
#
|
69
|
+
def self.cgi_parse_header(line)
|
70
|
+
parts = _cgi_parseparam(';' + line)
|
71
|
+
key = parts.next
|
72
|
+
pdict = {}
|
73
|
+
|
74
|
+
begin
|
75
|
+
while (p = parts.next)
|
76
|
+
i = p.index('=')
|
77
|
+
if i
|
78
|
+
name = p[0...i].strip.downcase
|
79
|
+
value = p[i+1..-1].strip
|
80
|
+
if value.length >= 2 && value[0] == '"' && value[-1] == '"'
|
81
|
+
value = value[1...-1]
|
82
|
+
value = value.gsub('\\\\', '\\').gsub('\\"', '"')
|
83
|
+
end
|
84
|
+
pdict[name] = value
|
85
|
+
end
|
86
|
+
end
|
87
|
+
rescue StopIteration
|
88
|
+
end
|
89
|
+
|
90
|
+
[key, pdict]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/restclient/version.rb
CHANGED
data/rest-client.gemspec
CHANGED
@@ -17,14 +17,15 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.summary = 'Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions.'
|
18
18
|
|
19
19
|
s.add_development_dependency('webmock', '~> 1.4')
|
20
|
-
s.add_development_dependency('rspec', '~> 2.
|
21
|
-
s.add_development_dependency('pry')
|
22
|
-
s.add_development_dependency('pry-doc')
|
20
|
+
s.add_development_dependency('rspec', '~> 2.99')
|
21
|
+
s.add_development_dependency('pry', '~> 0')
|
22
|
+
s.add_development_dependency('pry-doc', '~> 0')
|
23
23
|
s.add_development_dependency('rdoc', '>= 2.4.2', '< 5.0')
|
24
|
+
s.add_development_dependency('rubocop', '~> 0')
|
24
25
|
|
25
26
|
s.add_dependency('http-cookie', '>= 1.0.2', '< 2.0')
|
26
27
|
s.add_dependency('mime-types', '>= 1.16', '< 3.0')
|
27
|
-
s.add_dependency('netrc', '~> 0.
|
28
|
+
s.add_dependency('netrc', '~> 0.8')
|
28
29
|
|
29
|
-
s.required_ruby_version = '>= 1.9.
|
30
|
+
s.required_ruby_version = '>= 1.9.3'
|
30
31
|
end
|
data/spec/helpers.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
module Helpers
|
2
|
+
def response_double(opts={})
|
3
|
+
double('response', {:to_hash => {}}.merge(opts))
|
4
|
+
end
|
5
|
+
|
6
|
+
def fake_stderr
|
7
|
+
original_stderr = $stderr
|
8
|
+
$stderr = StringIO.new
|
9
|
+
yield
|
10
|
+
$stderr.string
|
11
|
+
ensure
|
12
|
+
$stderr = original_stderr
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative '../spec_helper'
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative '_lib'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe RestClient::Request do
|
5
|
+
before(:all) do
|
6
|
+
WebMock.disable!
|
7
|
+
end
|
8
|
+
|
9
|
+
after(:all) do
|
10
|
+
WebMock.enable!
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_httpbin_url
|
14
|
+
# add a hack to work around java/jruby bug
|
15
|
+
# java.lang.RuntimeException: Could not generate DH keypair with backtrace
|
16
|
+
if ENV['TRAVIS_RUBY_VERSION'] == 'jruby-19mode'
|
17
|
+
'http://httpbin.org/'
|
18
|
+
else
|
19
|
+
'https://httpbin.org/'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def httpbin(suffix='')
|
24
|
+
url = ENV.fetch('HTTPBIN_URL', default_httpbin_url)
|
25
|
+
unless url.end_with?('/')
|
26
|
+
url += '/'
|
27
|
+
end
|
28
|
+
|
29
|
+
url + suffix
|
30
|
+
end
|
31
|
+
|
32
|
+
def execute_httpbin(suffix, opts={})
|
33
|
+
opts = {url: httpbin(suffix)}.merge(opts)
|
34
|
+
RestClient::Request.execute(opts)
|
35
|
+
end
|
36
|
+
|
37
|
+
def execute_httpbin_json(suffix, opts={})
|
38
|
+
JSON.parse(execute_httpbin(suffix, opts))
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '.execute' do
|
42
|
+
it 'sends a user agent' do
|
43
|
+
data = execute_httpbin_json('user-agent', method: :get)
|
44
|
+
data['user-agent'].should match(/rest-client/)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'receives cookies on 302' do
|
48
|
+
expect {
|
49
|
+
execute_httpbin('cookies/set?foo=bar', method: :get, max_redirects: 0)
|
50
|
+
}.to raise_error(RestClient::Found) { |ex|
|
51
|
+
ex.http_code.should eq 302
|
52
|
+
ex.response.cookies['foo'].should eq 'bar'
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'passes along cookies through 302' do
|
57
|
+
data = execute_httpbin_json('cookies/set?foo=bar', method: :get)
|
58
|
+
data.should have_key('cookies')
|
59
|
+
data['cookies']['foo'].should eq 'bar'
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'handles quote wrapped cookies' do
|
63
|
+
expect {
|
64
|
+
execute_httpbin('cookies/set?foo=' + CGI.escape('"bar:baz"'),
|
65
|
+
method: :get, max_redirects: 0)
|
66
|
+
}.to raise_error(RestClient::Found) { |ex|
|
67
|
+
ex.http_code.should eq 302
|
68
|
+
ex.response.cookies['foo'].should eq '"bar:baz"'
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require_relative '_lib'
|
3
|
+
require 'base64'
|
2
4
|
|
3
5
|
describe RestClient do
|
4
6
|
|
@@ -31,5 +33,81 @@ describe RestClient do
|
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
36
|
+
describe 'charset parsing' do
|
37
|
+
it 'handles utf-8' do
|
38
|
+
body = "λ".force_encoding('ASCII-8BIT')
|
39
|
+
stub_request(:get, "www.example.com").to_return(
|
40
|
+
:body => body, :status => 200, :headers => {
|
41
|
+
'Content-Type' => 'text/plain; charset=UTF-8'
|
42
|
+
})
|
43
|
+
response = RestClient.get "www.example.com"
|
44
|
+
response.encoding.should eq Encoding::UTF_8
|
45
|
+
response.valid_encoding?.should eq true
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'handles windows-1252' do
|
49
|
+
body = "\xff".force_encoding('ASCII-8BIT')
|
50
|
+
stub_request(:get, "www.example.com").to_return(
|
51
|
+
:body => body, :status => 200, :headers => {
|
52
|
+
'Content-Type' => 'text/plain; charset=windows-1252'
|
53
|
+
})
|
54
|
+
response = RestClient.get "www.example.com"
|
55
|
+
response.encoding.should eq Encoding::WINDOWS_1252
|
56
|
+
response.encode('utf-8').should eq "ÿ"
|
57
|
+
response.valid_encoding?.should eq true
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'handles binary' do
|
61
|
+
body = "\xfe".force_encoding('ASCII-8BIT')
|
62
|
+
stub_request(:get, "www.example.com").to_return(
|
63
|
+
:body => body, :status => 200, :headers => {
|
64
|
+
'Content-Type' => 'application/octet-stream; charset=binary'
|
65
|
+
})
|
66
|
+
response = RestClient.get "www.example.com"
|
67
|
+
response.encoding.should eq Encoding::BINARY
|
68
|
+
lambda {
|
69
|
+
response.encode('utf-8')
|
70
|
+
}.should raise_error(Encoding::UndefinedConversionError)
|
71
|
+
response.valid_encoding?.should eq true
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'handles euc-jp' do
|
75
|
+
body = "\xA4\xA2\xA4\xA4\xA4\xA6\xA4\xA8\xA4\xAA".
|
76
|
+
force_encoding(Encoding::BINARY)
|
77
|
+
body_utf8 = 'あいうえお'
|
78
|
+
body_utf8.encoding.should eq Encoding::UTF_8
|
79
|
+
|
80
|
+
stub_request(:get, 'www.example.com').to_return(
|
81
|
+
:body => body, :status => 200, :headers => {
|
82
|
+
'Content-Type' => 'text/plain; charset=EUC-JP'
|
83
|
+
})
|
84
|
+
response = RestClient.get 'www.example.com'
|
85
|
+
response.encoding.should eq Encoding::EUC_JP
|
86
|
+
response.valid_encoding?.should eq true
|
87
|
+
response.length.should eq 5
|
88
|
+
response.encode('utf-8').should eq body_utf8
|
89
|
+
end
|
34
90
|
|
91
|
+
it 'defaults to Encoding.default_external' do
|
92
|
+
stub_request(:get, 'www.example.com').to_return(
|
93
|
+
body: 'abc', status: 200, headers: {
|
94
|
+
'Content-Type' => 'text/plain'
|
95
|
+
})
|
96
|
+
|
97
|
+
response = RestClient.get 'www.example.com'
|
98
|
+
response.encoding.should eq Encoding.default_external
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'leaves images as binary' do
|
102
|
+
gif = Base64.strict_decode64('R0lGODlhAQABAAAAADs=')
|
103
|
+
|
104
|
+
stub_request(:get, 'www.example.com').to_return(
|
105
|
+
body: gif, status: 200, headers: {
|
106
|
+
'Content-Type' => 'image/gif'
|
107
|
+
})
|
108
|
+
|
109
|
+
response = RestClient.get 'www.example.com'
|
110
|
+
response.encoding.should eq Encoding::BINARY
|
111
|
+
end
|
112
|
+
end
|
35
113
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
require_relative '_lib'
|
2
2
|
|
3
3
|
describe RestClient::Request do
|
4
4
|
before(:all) do
|
@@ -101,4 +101,27 @@ describe RestClient::Request do
|
|
101
101
|
expect { request.execute }.to_not raise_error
|
102
102
|
end
|
103
103
|
end
|
104
|
+
|
105
|
+
describe "timeouts" do
|
106
|
+
it "raises OpenTimeout when it hits an open timeout" do
|
107
|
+
request = RestClient::Request.new(
|
108
|
+
:method => :get,
|
109
|
+
:url => 'http://www.mozilla.org',
|
110
|
+
:open_timeout => 1e-10,
|
111
|
+
)
|
112
|
+
expect { request.execute }.to(
|
113
|
+
raise_error(RestClient::Exceptions::OpenTimeout))
|
114
|
+
end
|
115
|
+
|
116
|
+
it "raises ReadTimeout when it hits a read timeout via :read_timeout" do
|
117
|
+
request = RestClient::Request.new(
|
118
|
+
:method => :get,
|
119
|
+
:url => 'https://www.mozilla.org',
|
120
|
+
:read_timeout => 1e-10,
|
121
|
+
)
|
122
|
+
expect { request.execute }.to(
|
123
|
+
raise_error(RestClient::Exceptions::ReadTimeout))
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
104
127
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,2 +1,22 @@
|
|
1
1
|
require 'webmock/rspec'
|
2
|
-
require '
|
2
|
+
require 'rest-client'
|
3
|
+
|
4
|
+
require_relative './helpers'
|
5
|
+
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
|
10
|
+
# Run specs in random order to surface order dependencies. If you find an
|
11
|
+
# order dependency and want to debug it, you can fix the order by providing
|
12
|
+
# the seed, which is printed after each run.
|
13
|
+
# --seed 1234
|
14
|
+
config.order = 'random'
|
15
|
+
|
16
|
+
# add helpers
|
17
|
+
config.include Helpers, :include_helpers
|
18
|
+
|
19
|
+
config.mock_with :rspec do |mocks|
|
20
|
+
mocks.yield_receiver_to_any_instance_implementation_blocks = true
|
21
|
+
end
|
22
|
+
end
|