rest-client 1.8.0-x86-mswin32 → 2.0.0.rc1-x86-mswin32
Sign up to get free protection for your applications and to get access to all the features.
- 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
|