rest-client 2.0.2-x64-mingw32 → 2.1.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/.gitignore +1 -0
- data/.mailmap +10 -0
- data/.rubocop +2 -0
- data/.rubocop-disables.yml +27 -24
- data/.rubocop.yml +5 -0
- data/.travis.yml +2 -1
- data/AUTHORS +8 -0
- data/README.md +119 -7
- data/Rakefile +12 -4
- data/history.md +33 -0
- data/lib/restclient.rb +0 -1
- data/lib/restclient/abstract_response.rb +28 -2
- data/lib/restclient/exceptions.rb +3 -3
- data/lib/restclient/payload.rb +28 -3
- data/lib/restclient/raw_response.rb +17 -6
- data/lib/restclient/request.rb +89 -67
- data/lib/restclient/resource.rb +16 -6
- data/lib/restclient/response.rb +14 -4
- data/lib/restclient/utils.rb +47 -8
- data/lib/restclient/version.rb +2 -2
- data/rest-client.gemspec +1 -0
- data/spec/ISS.jpg +0 -0
- data/spec/helpers.rb +37 -5
- data/spec/integration/httpbin_spec.rb +41 -0
- data/spec/integration/integration_spec.rb +0 -7
- data/spec/unit/abstract_response_spec.rb +7 -7
- data/spec/unit/payload_spec.rb +51 -19
- data/spec/unit/raw_response_spec.rb +6 -2
- data/spec/unit/request2_spec.rb +8 -8
- data/spec/unit/request_spec.rb +51 -63
- data/spec/unit/resource_spec.rb +7 -7
- data/spec/unit/response_spec.rb +33 -22
- data/spec/unit/restclient_spec.rb +3 -2
- data/spec/unit/utils_spec.rb +10 -10
- metadata +29 -7
- data/spec/unit/master_shake.jpg +0 -0
data/history.md
CHANGED
@@ -1,3 +1,36 @@
|
|
1
|
+
# 2.1.0.rc1
|
2
|
+
|
3
|
+
- Add a dependency on http-accept for parsing Content-Type charset headers.
|
4
|
+
This works around a bad memory leak introduced in Ruby 2.4.x (the leak is
|
5
|
+
probably a bug in MRI). (#615)
|
6
|
+
- Use mime/types/columnar from mime-types 2.6.1+, which is leaner in memory
|
7
|
+
usage than the older storage model of mime-types. (#393)
|
8
|
+
- Add `:log` option to individual requests. This allows users to set a log on a
|
9
|
+
per-request / per-resource basis instead of the kludgy global log. (#538)
|
10
|
+
- Log request duration by tracking request start and end times. Make
|
11
|
+
`log_response` a method on the Response object, and ensure the `size` method
|
12
|
+
works on RawResponse objects. (#126)
|
13
|
+
- `# => 200 OK | text/html 1270 bytes, 0.08s`
|
14
|
+
- Drop custom handling of compression and use built-in Net::HTTP support for
|
15
|
+
supported Content-Encodings like gzip and deflate. Don't set any explicit
|
16
|
+
`Accept-Encoding` header, rely instead on Net::HTTP defaults. (#597)
|
17
|
+
- Note: this changes behavior for compressed responses when using
|
18
|
+
`:raw_response => true`. Previously the raw response would not have been
|
19
|
+
uncompressed by rest-client, but now Net::HTTP will uncompress it.
|
20
|
+
- The previous fix to avoid having Netrc username/password override an
|
21
|
+
Authorization header was case-sensitive and incomplete. Fix this by
|
22
|
+
respecting existing Authorization headers, regardless of letter case. (#550)
|
23
|
+
- Handle ParamsArray payloads. Previously, rest-client would silently drop a
|
24
|
+
ParamsArray passed as the payload. Instead, automatically use
|
25
|
+
Payload::Multipart if the ParamsArray contains a file handle, or use
|
26
|
+
Payload::UrlEncoded if it doesn't. (#508)
|
27
|
+
- Gracefully handle Payload objects (Payload::Base or subclasses) that are
|
28
|
+
passed as a payload argument. Previously, `Payload.generate` would wrap a
|
29
|
+
Payload object in Payload::Streamed, creating a pointlessly nested payload.
|
30
|
+
Also add a `closed?` method to Payload objects, and don't error in
|
31
|
+
`short_inspect` if `size` returns nil. (#603)
|
32
|
+
- Test with an image in the public domain to avoid licensing complexity. (#607)
|
33
|
+
|
1
34
|
# 2.0.2
|
2
35
|
|
3
36
|
- Suppress the header override warning introduced in 2.0.1 if the value is the
|
data/lib/restclient.rb
CHANGED
@@ -5,12 +5,27 @@ module RestClient
|
|
5
5
|
|
6
6
|
module AbstractResponse
|
7
7
|
|
8
|
-
attr_reader :net_http_res, :request
|
8
|
+
attr_reader :net_http_res, :request, :start_time, :end_time, :duration
|
9
9
|
|
10
10
|
def inspect
|
11
11
|
raise NotImplementedError.new('must override in subclass')
|
12
12
|
end
|
13
13
|
|
14
|
+
# Logger from the request, potentially nil.
|
15
|
+
def log
|
16
|
+
request.log
|
17
|
+
end
|
18
|
+
|
19
|
+
def log_response
|
20
|
+
return unless log
|
21
|
+
|
22
|
+
code = net_http_res.code
|
23
|
+
res_name = net_http_res.class.to_s.gsub(/\ANet::HTTP/, '')
|
24
|
+
content_type = (net_http_res['Content-type'] || '').gsub(/;.*\z/, '')
|
25
|
+
|
26
|
+
log << "# => #{code} #{res_name} | #{content_type} #{size} bytes, #{sprintf('%.2f', duration)}s\n"
|
27
|
+
end
|
28
|
+
|
14
29
|
# HTTP status code
|
15
30
|
def code
|
16
31
|
@code ||= @net_http_res.code.to_i
|
@@ -31,9 +46,20 @@ module RestClient
|
|
31
46
|
@raw_headers ||= @net_http_res.to_hash
|
32
47
|
end
|
33
48
|
|
34
|
-
|
49
|
+
# @param [Net::HTTPResponse] net_http_res
|
50
|
+
# @param [RestClient::Request] request
|
51
|
+
# @param [Time] start_time
|
52
|
+
def response_set_vars(net_http_res, request, start_time)
|
35
53
|
@net_http_res = net_http_res
|
36
54
|
@request = request
|
55
|
+
@start_time = start_time
|
56
|
+
@end_time = Time.now
|
57
|
+
|
58
|
+
if @start_time
|
59
|
+
@duration = @end_time - @start_time
|
60
|
+
else
|
61
|
+
@duration = nil
|
62
|
+
end
|
37
63
|
|
38
64
|
# prime redirection history
|
39
65
|
history
|
@@ -148,7 +148,7 @@ module RestClient
|
|
148
148
|
end
|
149
149
|
|
150
150
|
# Compatibility
|
151
|
-
class ExceptionWithResponse < Exception
|
151
|
+
class ExceptionWithResponse < RestClient::Exception
|
152
152
|
end
|
153
153
|
|
154
154
|
# The request failed with an error code not managed by the code
|
@@ -228,14 +228,14 @@ module RestClient
|
|
228
228
|
# The server broke the connection prior to the request completing. Usually
|
229
229
|
# this means it crashed, or sometimes that your network connection was
|
230
230
|
# severed before it could complete.
|
231
|
-
class ServerBrokeConnection < Exception
|
231
|
+
class ServerBrokeConnection < RestClient::Exception
|
232
232
|
def initialize(message = 'Server broke connection')
|
233
233
|
super nil, nil
|
234
234
|
self.message = message
|
235
235
|
end
|
236
236
|
end
|
237
237
|
|
238
|
-
class SSLCertificateNotVerified < Exception
|
238
|
+
class SSLCertificateNotVerified < RestClient::Exception
|
239
239
|
def initialize(message = 'SSL certificate not verified')
|
240
240
|
super nil, nil
|
241
241
|
self.message = message
|
data/lib/restclient/payload.rb
CHANGED
@@ -2,14 +2,22 @@ require 'tempfile'
|
|
2
2
|
require 'securerandom'
|
3
3
|
require 'stringio'
|
4
4
|
|
5
|
-
|
5
|
+
begin
|
6
|
+
# Use mime/types/columnar if available, for reduced memory usage
|
7
|
+
require 'mime/types/columnar'
|
8
|
+
rescue LoadError
|
9
|
+
require 'mime/types'
|
10
|
+
end
|
6
11
|
|
7
12
|
module RestClient
|
8
13
|
module Payload
|
9
14
|
extend self
|
10
15
|
|
11
16
|
def generate(params)
|
12
|
-
if params.is_a?(
|
17
|
+
if params.is_a?(RestClient::Payload::Base)
|
18
|
+
# pass through Payload objects unchanged
|
19
|
+
params
|
20
|
+
elsif params.is_a?(String)
|
13
21
|
Base.new(params)
|
14
22
|
elsif params.is_a?(Hash)
|
15
23
|
if params.delete(:multipart) == true || has_file?(params)
|
@@ -17,6 +25,12 @@ module RestClient
|
|
17
25
|
else
|
18
26
|
UrlEncoded.new(params)
|
19
27
|
end
|
28
|
+
elsif params.is_a?(ParamsArray)
|
29
|
+
if _has_file?(params)
|
30
|
+
Multipart.new(params)
|
31
|
+
else
|
32
|
+
UrlEncoded.new(params)
|
33
|
+
end
|
20
34
|
elsif params.respond_to?(:read)
|
21
35
|
Streamed.new(params)
|
22
36
|
else
|
@@ -76,12 +90,20 @@ module RestClient
|
|
76
90
|
@stream.close unless @stream.closed?
|
77
91
|
end
|
78
92
|
|
93
|
+
def closed?
|
94
|
+
@stream.closed?
|
95
|
+
end
|
96
|
+
|
79
97
|
def to_s_inspect
|
80
98
|
to_s.inspect
|
81
99
|
end
|
82
100
|
|
83
101
|
def short_inspect
|
84
|
-
|
102
|
+
if size && size > 500
|
103
|
+
"#{size} byte(s) length"
|
104
|
+
else
|
105
|
+
to_s_inspect
|
106
|
+
end
|
85
107
|
end
|
86
108
|
|
87
109
|
end
|
@@ -99,6 +121,9 @@ module RestClient
|
|
99
121
|
end
|
100
122
|
end
|
101
123
|
|
124
|
+
# TODO (breaks compatibility): ought to use mime_for() to autodetect the
|
125
|
+
# Content-Type for stream objects that have a filename.
|
126
|
+
|
102
127
|
alias :length :size
|
103
128
|
end
|
104
129
|
|
@@ -13,25 +13,36 @@ module RestClient
|
|
13
13
|
|
14
14
|
include AbstractResponse
|
15
15
|
|
16
|
-
attr_reader :file, :request
|
16
|
+
attr_reader :file, :request, :start_time, :end_time
|
17
17
|
|
18
18
|
def inspect
|
19
19
|
"<RestClient::RawResponse @code=#{code.inspect}, @file=#{file.inspect}, @request=#{request.inspect}>"
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
22
|
+
# @param [Tempfile] tempfile The temporary file containing the body
|
23
|
+
# @param [Net::HTTPResponse] net_http_res
|
24
|
+
# @param [RestClient::Request] request
|
25
|
+
# @param [Time] start_time
|
26
|
+
def initialize(tempfile, net_http_res, request, start_time=nil)
|
24
27
|
@file = tempfile
|
25
|
-
|
28
|
+
|
29
|
+
# reopen the tempfile so we can read it
|
30
|
+
@file.open
|
31
|
+
|
32
|
+
response_set_vars(net_http_res, request, start_time)
|
26
33
|
end
|
27
34
|
|
28
35
|
def to_s
|
29
|
-
|
36
|
+
body
|
37
|
+
end
|
38
|
+
|
39
|
+
def body
|
40
|
+
@file.rewind
|
30
41
|
@file.read
|
31
42
|
end
|
32
43
|
|
33
44
|
def size
|
34
|
-
|
45
|
+
file.size
|
35
46
|
end
|
36
47
|
|
37
48
|
end
|
data/lib/restclient/request.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
require 'tempfile'
|
2
|
-
require 'mime/types'
|
3
2
|
require 'cgi'
|
4
3
|
require 'netrc'
|
5
4
|
require 'set'
|
6
5
|
|
6
|
+
begin
|
7
|
+
# Use mime/types/columnar if available, for reduced memory usage
|
8
|
+
require 'mime/types/columnar'
|
9
|
+
rescue LoadError
|
10
|
+
require 'mime/types'
|
11
|
+
end
|
12
|
+
|
7
13
|
module RestClient
|
8
14
|
# This class is used internally by RestClient to send the request, but you can also
|
9
15
|
# call it directly if you'd like to use a method not supported by the
|
@@ -92,6 +98,12 @@ module RestClient
|
|
92
98
|
@block_response = args[:block_response]
|
93
99
|
@raw_response = args[:raw_response] || false
|
94
100
|
|
101
|
+
@stream_log_percent = args[:stream_log_percent] || 10
|
102
|
+
if @stream_log_percent <= 0 || @stream_log_percent > 100
|
103
|
+
raise ArgumentError.new(
|
104
|
+
"Invalid :stream_log_percent #{@stream_log_percent.inspect}")
|
105
|
+
end
|
106
|
+
|
95
107
|
@proxy = args.fetch(:proxy) if args.include?(:proxy)
|
96
108
|
|
97
109
|
@ssl_opts = {}
|
@@ -131,9 +143,10 @@ module RestClient
|
|
131
143
|
end
|
132
144
|
end
|
133
145
|
|
134
|
-
@
|
146
|
+
@log = args[:log]
|
135
147
|
@max_redirects = args[:max_redirects] || 10
|
136
148
|
@processed_headers = make_headers headers
|
149
|
+
@processed_headers_lowercase = Hash[@processed_headers.map {|k, v| [k.downcase, v]}]
|
137
150
|
@args = args
|
138
151
|
|
139
152
|
@before_execution_proc = args[:before_execution_proc]
|
@@ -356,6 +369,13 @@ module RestClient
|
|
356
369
|
# - headers from the payload object (e.g. Content-Type, Content-Lenth)
|
357
370
|
# - cookie headers from #make_cookie_header
|
358
371
|
#
|
372
|
+
# BUG: stringify_headers does not alter the capitalization of headers that
|
373
|
+
# are passed as strings, it only normalizes those passed as symbols. This
|
374
|
+
# behavior will probably remain for a while for compatibility, but it means
|
375
|
+
# that the warnings that attempt to detect accidental header overrides may
|
376
|
+
# not always work.
|
377
|
+
# https://github.com/rest-client/rest-client/issues/599
|
378
|
+
#
|
359
379
|
# @param [Hash] user_headers User-provided headers to include
|
360
380
|
#
|
361
381
|
# @return [Hash<String, String>] A hash of HTTP headers => values
|
@@ -493,24 +513,6 @@ module RestClient
|
|
493
513
|
cert_store
|
494
514
|
end
|
495
515
|
|
496
|
-
def self.decode content_encoding, body
|
497
|
-
if (!body) || body.empty?
|
498
|
-
body
|
499
|
-
elsif content_encoding == 'gzip'
|
500
|
-
Zlib::GzipReader.new(StringIO.new(body)).read
|
501
|
-
elsif content_encoding == 'deflate'
|
502
|
-
begin
|
503
|
-
Zlib::Inflate.new.inflate body
|
504
|
-
rescue Zlib::DataError
|
505
|
-
# No luck with Zlib decompression. Let's try with raw deflate,
|
506
|
-
# like some broken web servers do.
|
507
|
-
Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate body
|
508
|
-
end
|
509
|
-
else
|
510
|
-
body
|
511
|
-
end
|
512
|
-
end
|
513
|
-
|
514
516
|
def redacted_uri
|
515
517
|
if uri.password
|
516
518
|
sanitized_uri = uri.dup
|
@@ -525,30 +527,29 @@ module RestClient
|
|
525
527
|
redacted_uri.to_s
|
526
528
|
end
|
527
529
|
|
530
|
+
# Default to the global logger if there's not a request-specific one
|
531
|
+
def log
|
532
|
+
@log || RestClient.log
|
533
|
+
end
|
534
|
+
|
528
535
|
def log_request
|
529
|
-
return unless
|
536
|
+
return unless log
|
530
537
|
|
531
538
|
out = []
|
532
539
|
|
533
540
|
out << "RestClient.#{method} #{redacted_url.inspect}"
|
534
541
|
out << payload.short_inspect if payload
|
535
542
|
out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ")
|
536
|
-
|
537
|
-
end
|
538
|
-
|
539
|
-
def log_response res
|
540
|
-
return unless RestClient.log
|
541
|
-
|
542
|
-
size = if @raw_response
|
543
|
-
File.size(@tf.path)
|
544
|
-
else
|
545
|
-
res.body.nil? ? 0 : res.body.size
|
546
|
-
end
|
547
|
-
|
548
|
-
RestClient.log << "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes\n"
|
543
|
+
log << out.join(', ') + "\n"
|
549
544
|
end
|
550
545
|
|
551
546
|
# Return a hash of headers whose keys are capitalized strings
|
547
|
+
#
|
548
|
+
# BUG: stringify_headers does not fix the capitalization of headers that
|
549
|
+
# are already Strings. Leaving this behavior as is for now for
|
550
|
+
# backwards compatibility.
|
551
|
+
# https://github.com/rest-client/rest-client/issues/599
|
552
|
+
#
|
552
553
|
def stringify_headers headers
|
553
554
|
headers.inject({}) do |result, (key, value)|
|
554
555
|
if key.is_a? Symbol
|
@@ -573,10 +574,13 @@ module RestClient
|
|
573
574
|
end
|
574
575
|
end
|
575
576
|
|
577
|
+
# Default headers set by RestClient. In addition to these headers, servers
|
578
|
+
# will receive headers set by Net::HTTP, such as Accept-Encoding and Host.
|
579
|
+
#
|
580
|
+
# @return [Hash<Symbol, String>]
|
576
581
|
def default_headers
|
577
582
|
{
|
578
583
|
:accept => '*/*',
|
579
|
-
:accept_encoding => 'gzip, deflate',
|
580
584
|
:user_agent => RestClient::Platform.default_user_agent,
|
581
585
|
}
|
582
586
|
end
|
@@ -712,6 +716,9 @@ module RestClient
|
|
712
716
|
|
713
717
|
log_request
|
714
718
|
|
719
|
+
start_time = Time.now
|
720
|
+
tempfile = nil
|
721
|
+
|
715
722
|
net.start do |http|
|
716
723
|
established_connection = true
|
717
724
|
|
@@ -719,10 +726,16 @@ module RestClient
|
|
719
726
|
net_http_do_request(http, req, payload, &@block_response)
|
720
727
|
else
|
721
728
|
res = net_http_do_request(http, req, payload) { |http_response|
|
722
|
-
|
729
|
+
if @raw_response
|
730
|
+
# fetch body into tempfile
|
731
|
+
tempfile = fetch_body_to_tempfile(http_response)
|
732
|
+
else
|
733
|
+
# fetch body
|
734
|
+
http_response.read_body
|
735
|
+
end
|
736
|
+
http_response
|
723
737
|
}
|
724
|
-
|
725
|
-
process_result res, & block
|
738
|
+
process_result(res, start_time, tempfile, &block)
|
726
739
|
end
|
727
740
|
end
|
728
741
|
rescue EOFError
|
@@ -762,47 +775,56 @@ module RestClient
|
|
762
775
|
end
|
763
776
|
|
764
777
|
def setup_credentials(req)
|
765
|
-
|
778
|
+
if user && !@processed_headers_lowercase.include?('authorization')
|
779
|
+
req.basic_auth(user, password)
|
780
|
+
end
|
766
781
|
end
|
767
782
|
|
768
|
-
def
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
783
|
+
def fetch_body_to_tempfile(http_response)
|
784
|
+
# Taken from Chef, which as in turn...
|
785
|
+
# Stolen from http://www.ruby-forum.com/topic/166423
|
786
|
+
# Kudos to _why!
|
787
|
+
tf = Tempfile.new('rest-client.')
|
788
|
+
tf.binmode
|
789
|
+
|
790
|
+
size = 0
|
791
|
+
total = http_response['Content-Length'].to_i
|
792
|
+
stream_log_bucket = nil
|
793
|
+
|
794
|
+
http_response.read_body do |chunk|
|
795
|
+
tf.write chunk
|
796
|
+
size += chunk.size
|
797
|
+
if log
|
798
|
+
if total == 0
|
799
|
+
log << "streaming %s %s (%d of unknown) [0 Content-Length]\n" % [@method.upcase, @url, size]
|
800
|
+
else
|
801
|
+
percent = (size * 100) / total
|
802
|
+
current_log_bucket, _ = percent.divmod(@stream_log_percent)
|
803
|
+
if current_log_bucket != stream_log_bucket
|
804
|
+
stream_log_bucket = current_log_bucket
|
805
|
+
log << "streaming %s %s %d%% done (%d of %d)\n" % [@method.upcase, @url, (size * 100) / total, size, total]
|
786
806
|
end
|
787
807
|
end
|
788
808
|
end
|
789
|
-
@tf.close
|
790
|
-
@tf
|
791
|
-
else
|
792
|
-
http_response.read_body
|
793
809
|
end
|
794
|
-
|
810
|
+
tf.close
|
811
|
+
tf
|
795
812
|
end
|
796
813
|
|
797
|
-
|
814
|
+
# @param res The Net::HTTP response object
|
815
|
+
# @param start_time [Time] Time of request start
|
816
|
+
def process_result(res, start_time, tempfile=nil, &block)
|
798
817
|
if @raw_response
|
799
|
-
|
800
|
-
|
818
|
+
unless tempfile
|
819
|
+
raise ArgumentError.new('tempfile is required')
|
820
|
+
end
|
821
|
+
response = RawResponse.new(tempfile, res, self, start_time)
|
801
822
|
else
|
802
|
-
|
803
|
-
response = Response.create(decoded, res, self)
|
823
|
+
response = Response.create(res.body, res, self, start_time)
|
804
824
|
end
|
805
825
|
|
826
|
+
response.log_response
|
827
|
+
|
806
828
|
if block_given?
|
807
829
|
block.call(response, self, res, & block)
|
808
830
|
else
|