rest-client 1.8.0 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -26,7 +26,7 @@ module RestClient
26
26
  401 => 'Unauthorized',
27
27
  402 => 'Payment Required',
28
28
  403 => 'Forbidden',
29
- 404 => 'Resource Not Found',
29
+ 404 => 'Not Found',
30
30
  405 => 'Method Not Allowed',
31
31
  406 => 'Not Acceptable',
32
32
  407 => 'Proxy Authentication Required',
@@ -66,18 +66,6 @@ module RestClient
66
66
  511 => 'Network Authentication Required', # RFC6585
67
67
  }
68
68
 
69
- # Compatibility : make the Response act like a Net::HTTPResponse when needed
70
- module ResponseForException
71
- def method_missing symbol, *args
72
- if net_http_res.respond_to? symbol
73
- warn "[warning] The response contained in an RestClient::Exception is now a RestClient::Response instead of a Net::HTTPResponse, please update your code"
74
- net_http_res.send symbol, *args
75
- else
76
- super
77
- end
78
- end
79
- end
80
-
81
69
  # This is the base RestClient exception class. Rescue it if you want to
82
70
  # catch any exception that your request might raise
83
71
  # You can get the status code by e.http_code, or see anything about the
@@ -86,15 +74,13 @@ module RestClient
86
74
  # probably an HTML error page) is e.response.
87
75
  class Exception < RuntimeError
88
76
  attr_accessor :response
77
+ attr_accessor :original_exception
89
78
  attr_writer :message
90
79
 
91
80
  def initialize response = nil, initial_response_code = nil
92
81
  @response = response
93
82
  @message = nil
94
83
  @initial_response_code = initial_response_code
95
-
96
- # compatibility: this make the exception behave like a Net::HTTPResponse
97
- response.extend ResponseForException if response
98
84
  end
99
85
 
100
86
  def http_code
@@ -106,6 +92,10 @@ module RestClient
106
92
  end
107
93
  end
108
94
 
95
+ def http_headers
96
+ @response.headers if @response
97
+ end
98
+
109
99
  def http_body
110
100
  @response.body if @response
111
101
  end
@@ -119,9 +109,12 @@ module RestClient
119
109
  end
120
110
 
121
111
  def message
122
- @message || self.class.name
112
+ @message || self.class.default_message
123
113
  end
124
114
 
115
+ def self.default_message
116
+ self.name
117
+ end
125
118
  end
126
119
 
127
120
  # Compatibility
@@ -140,10 +133,41 @@ module RestClient
140
133
  end
141
134
  end
142
135
 
143
- # We will a create an exception for each status code, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
136
+ # RestClient exception classes. TODO: move all exceptions into this module.
137
+ #
138
+ # We will a create an exception for each status code, see
139
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
140
+ #
144
141
  module Exceptions
145
142
  # Map http status codes to the corresponding exception class
146
143
  EXCEPTIONS_MAP = {}
144
+
145
+ # Base class for request timeouts.
146
+ # NB: Previous releases of rest-client would raise RequestTimeout both for
147
+ # HTTP 408 responses and for actual connection timeouts.
148
+ class Timeout < RestClient::Exception
149
+ def initialize(message=nil, original_exception=nil)
150
+ super(nil, nil)
151
+ self.message = message if message
152
+ self.original_exception = original_exception if original_exception
153
+ end
154
+ end
155
+
156
+ # Timeout when connecting to a server. Typically wraps Net::OpenTimeout (in
157
+ # ruby 2.0 or greater).
158
+ class OpenTimeout < Timeout
159
+ def self.default_message
160
+ 'Timed out connecting to server'
161
+ end
162
+ end
163
+
164
+ # Timeout when reading from a server. Typically wraps Net::ReadTimeout (in
165
+ # ruby 2.0 or greater).
166
+ class ReadTimeout < Timeout
167
+ def self.default_message
168
+ 'Timed out reading data from server'
169
+ end
170
+ end
147
171
  end
148
172
 
149
173
  STATUSES.each_pair do |code, message|
@@ -157,25 +181,8 @@ module RestClient
157
181
  Exceptions::EXCEPTIONS_MAP[code] = klass_constant
158
182
  end
159
183
 
160
- # A redirect was encountered; caught by execute to retry with the new url.
161
- class Redirect < Exception
162
-
163
- def message
164
- 'Redirect'
165
- end
166
-
167
- attr_accessor :url
168
-
169
- def initialize(url)
170
- @url = url
171
- end
172
- end
173
-
174
- class MaxRedirectsReached < Exception
175
- def message
176
- 'Maximum number of redirect reached'
177
- end
178
- end
184
+ # Backwards compatibility. "Not Found" is the actual text in the RFCs.
185
+ ResourceNotFound = NotFound
179
186
 
180
187
  # The server broke the connection prior to the request completing. Usually
181
188
  # this means it crashed, or sometimes that your network connection was
@@ -194,10 +201,3 @@ module RestClient
194
201
  end
195
202
  end
196
203
  end
197
-
198
- class RestClient::Request
199
- # backwards compatibility
200
- Redirect = RestClient::Redirect
201
- Unauthorized = RestClient::Unauthorized
202
- RequestFailed = RestClient::RequestFailed
203
- end
@@ -58,8 +58,8 @@ module RestClient
58
58
  @stream.seek(0)
59
59
  end
60
60
 
61
- def read(bytes=nil)
62
- @stream.read(bytes)
61
+ def read(*args)
62
+ @stream.read(*args)
63
63
  end
64
64
 
65
65
  alias :to_s :read
@@ -1,3 +1,5 @@
1
+ require 'rbconfig'
2
+
1
3
  module RestClient
2
4
  module Platform
3
5
  # Return true if we are running on a darwin-based Ruby platform. This will
@@ -26,5 +28,22 @@ module RestClient
26
28
  # defined on mri >= 1.9
27
29
  RUBY_ENGINE == 'jruby'
28
30
  end
31
+
32
+ def self.architecture
33
+ "#{RbConfig::CONFIG['host_os']} #{RbConfig::CONFIG['host_cpu']}"
34
+ end
35
+
36
+ def self.ruby_agent_version
37
+ case RUBY_ENGINE
38
+ when 'jruby'
39
+ "jruby/#{JRUBY_VERSION} (#{RUBY_VERSION}p#{RUBY_PATCHLEVEL})"
40
+ else
41
+ "#{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
42
+ end
43
+ end
44
+
45
+ def self.default_user_agent
46
+ "rest-client/#{VERSION} (#{architecture}) #{ruby_agent_version}"
47
+ end
29
48
  end
30
49
  end
@@ -15,6 +15,10 @@ module RestClient
15
15
 
16
16
  attr_reader :file, :request
17
17
 
18
+ def inspect
19
+ "<RestClient::RawResponse @code=#{code.inspect}, @file=#{file.inspect}, @request=#{request.inspect}>"
20
+ end
21
+
18
22
  def initialize(tempfile, net_http_res, args, request)
19
23
  @net_http_res = net_http_res
20
24
  @args = args
@@ -21,29 +21,40 @@ module RestClient
21
21
  # * :block_response call the provided block with the HTTPResponse as parameter
22
22
  # * :raw_response return a low-level RawResponse instead of a Response
23
23
  # * :max_redirects maximum number of redirections (default to 10)
24
+ # * :proxy An HTTP proxy URI to use for this request. Any value here
25
+ # (including nil) will override RestClient.proxy.
24
26
  # * :verify_ssl enable ssl verification, possible values are constants from
25
27
  # OpenSSL::SSL::VERIFY_*, defaults to OpenSSL::SSL::VERIFY_PEER
26
- # * :timeout and :open_timeout are how long to wait for a response and to
27
- # open a connection, in seconds. Pass nil to disable the timeout.
28
+ # * :read_timeout and :open_timeout are how long to wait for a response and
29
+ # to open a connection, in seconds. Pass nil to disable the timeout.
30
+ # * :timeout can be used to set both timeouts
28
31
  # * :ssl_client_cert, :ssl_client_key, :ssl_ca_file, :ssl_ca_path,
29
32
  # :ssl_cert_store, :ssl_verify_callback, :ssl_verify_callback_warnings
30
33
  # * :ssl_version specifies the SSL version for the underlying Net::HTTP connection
31
34
  # * :ssl_ciphers sets SSL ciphers for the connection. See
32
35
  # OpenSSL::SSL::SSLContext#ciphers=
36
+ # * :before_execution_proc a Proc to call before executing the request. This
37
+ # proc, like procs from RestClient.before_execution_procs, will be
38
+ # called with the HTTP request and request params.
33
39
  class Request
34
40
 
35
- attr_reader :method, :url, :headers, :cookies,
36
- :payload, :user, :password, :timeout, :max_redirects,
41
+ # TODO: rename timeout to read_timeout
42
+
43
+ attr_reader :method, :url, :headers, :cookies, :payload, :proxy,
44
+ :user, :password, :read_timeout, :max_redirects,
37
45
  :open_timeout, :raw_response, :processed_headers, :args,
38
46
  :ssl_opts
39
47
 
48
+ # An array of previous redirection responses
49
+ attr_accessor :redirection_history
50
+
40
51
  def self.execute(args, & block)
41
52
  new(args).execute(& block)
42
53
  end
43
54
 
44
- # This is similar to the list now in ruby core, but adds HIGH and RC4-MD5
45
- # for better compatibility (similar to Firefox) and moves AES-GCM cipher
46
- # suites above DHE/ECDHE CBC suites (similar to Chromium).
55
+ # This is similar to the list now in ruby core, but adds HIGH for better
56
+ # compatibility (similar to Firefox) and moves AES-GCM cipher suites above
57
+ # DHE/ECDHE CBC suites (similar to Chromium).
47
58
  # https://github.com/ruby/ruby/commit/699b209cf8cf11809620e12985ad33ae33b119ee
48
59
  #
49
60
  # This list will be used by default if the Ruby global OpenSSL default
@@ -91,7 +102,6 @@ module RestClient
91
102
 
92
103
  HIGH
93
104
  +RC4
94
- RC4-MD5
95
105
  }.join(":")
96
106
 
97
107
  # A set of weak default ciphers that we will override by default.
@@ -102,9 +112,13 @@ module RestClient
102
112
  SSLOptionList = %w{client_cert client_key ca_file ca_path cert_store
103
113
  version ciphers verify_callback verify_callback_warnings}
104
114
 
115
+ def inspect
116
+ "<RestClient::Request @method=#{@method.inspect}, @url=#{@url.inspect}>"
117
+ end
118
+
105
119
  def initialize args
106
120
  @method = args[:method] or raise ArgumentError, "must pass :method"
107
- @headers = args[:headers] || {}
121
+ @headers = (args[:headers] || {}).dup
108
122
  if args[:url]
109
123
  @url = process_url_params(args[:url], headers)
110
124
  else
@@ -115,7 +129,11 @@ module RestClient
115
129
  @user = args[:user]
116
130
  @password = args[:password]
117
131
  if args.include?(:timeout)
118
- @timeout = args[:timeout]
132
+ @read_timeout = args[:timeout]
133
+ @open_timeout = args[:timeout]
134
+ end
135
+ if args.include?(:read_timeout)
136
+ @read_timeout = args[:read_timeout]
119
137
  end
120
138
  if args.include?(:open_timeout)
121
139
  @open_timeout = args[:open_timeout]
@@ -123,6 +141,8 @@ module RestClient
123
141
  @block_response = args[:block_response]
124
142
  @raw_response = args[:raw_response] || false
125
143
 
144
+ @proxy = args.fetch(:proxy) if args.include?(:proxy)
145
+
126
146
  @ssl_opts = {}
127
147
 
128
148
  if args.include?(:verify_ssl)
@@ -169,11 +189,17 @@ module RestClient
169
189
  @max_redirects = args[:max_redirects] || 10
170
190
  @processed_headers = make_headers headers
171
191
  @args = args
192
+
193
+ @before_execution_proc = args[:before_execution_proc]
172
194
  end
173
195
 
174
196
  def execute & block
175
197
  uri = parse_url_with_auth(url)
176
- transmit uri, net_http_request_class(method).new(uri.request_uri, processed_headers), payload, & block
198
+
199
+ # With 2.0.0+, net/http accepts URI objects in requests and handles wrapping
200
+ # IPv6 addresses in [] for use in the Host request header.
201
+ request_uri = RUBY_VERSION >= "2.0.0" ? uri : uri.request_uri
202
+ transmit uri, net_http_request_class(method).new(request_uri, processed_headers), payload, & block
177
203
  ensure
178
204
  payload.close if payload
179
205
  end
@@ -249,12 +275,44 @@ module RestClient
249
275
  ! Regexp.new('[\x0-\x1f\x7f,;]').match(value)
250
276
  end
251
277
 
252
- def net_http_class
253
- if RestClient.proxy
254
- proxy_uri = URI.parse(RestClient.proxy)
255
- Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
278
+ # The proxy URI for this request. If `:proxy` was provided on this request,
279
+ # use it over `RestClient.proxy`.
280
+ #
281
+ # Return false if a proxy was explicitly set and is falsy.
282
+ #
283
+ # @return [URI, false, nil]
284
+ #
285
+ def proxy_uri
286
+ if defined?(@proxy)
287
+ if @proxy
288
+ URI.parse(@proxy)
289
+ else
290
+ false
291
+ end
292
+ elsif RestClient.proxy_set?
293
+ if RestClient.proxy
294
+ URI.parse(RestClient.proxy)
295
+ else
296
+ false
297
+ end
256
298
  else
257
- Net::HTTP
299
+ nil
300
+ end
301
+ end
302
+
303
+ def net_http_object(hostname, port)
304
+ p_uri = proxy_uri
305
+
306
+ if p_uri.nil?
307
+ # no proxy set
308
+ Net::HTTP.new(hostname, port)
309
+ elsif !p_uri
310
+ # proxy explicitly set to none
311
+ Net::HTTP.new(hostname, port, nil, nil, nil, nil)
312
+ else
313
+ Net::HTTP.new(hostname, port,
314
+ p_uri.hostname, p_uri.port, p_uri.user, p_uri.password)
315
+
258
316
  end
259
317
  end
260
318
 
@@ -263,7 +321,7 @@ module RestClient
263
321
  end
264
322
 
265
323
  def net_http_do_request(http, req, body=nil, &block)
266
- if body != nil && body.respond_to?(:read)
324
+ if body && body.respond_to?(:read)
267
325
  req.body_stream = body
268
326
  return http.request(req, nil, &block)
269
327
  else
@@ -271,8 +329,22 @@ module RestClient
271
329
  end
272
330
  end
273
331
 
332
+ # Parse a string into a URI object. If the string has no HTTP-like scheme
333
+ # (i.e. scheme followed by '//'), a scheme of 'http' will be added. This
334
+ # mimics the behavior of browsers and user agents like cURL.
335
+ #
336
+ # @param url [String] A URL string.
337
+ #
338
+ # @return [URI]
339
+ #
340
+ # @raise URI::InvalidURIError on invalid URIs
341
+ #
274
342
  def parse_url(url)
275
- url = "http://#{url}" unless url.match(/^http/)
343
+ # Prepend http:// unless the string already contains an RFC 3986 scheme
344
+ # followed by two forward slashes. (The slashes are not part of the URI
345
+ # RFC, but specified by the URL RFC 1738.)
346
+ # https://tools.ietf.org/html/rfc3986#section-3.1
347
+ url = 'http://' + url unless url.match(%r{\A[a-z][a-z0-9+.-]*://}i)
276
348
  URI.parse(url)
277
349
  end
278
350
 
@@ -281,7 +353,7 @@ module RestClient
281
353
  @user = CGI.unescape(uri.user) if uri.user
282
354
  @password = CGI.unescape(uri.password) if uri.password
283
355
  if !@user && !@password
284
- @user, @password = Netrc.read[uri.host]
356
+ @user, @password = Netrc.read[uri.hostname]
285
357
  end
286
358
  uri
287
359
  end
@@ -347,9 +419,14 @@ module RestClient
347
419
  end
348
420
 
349
421
  def transmit uri, req, payload, & block
422
+
423
+ # We set this to true in the net/http block so that we can distinguish
424
+ # read_timeout from open_timeout. This isn't needed in Ruby >= 2.0.
425
+ established_connection = false
426
+
350
427
  setup_credentials req
351
428
 
352
- net = net_http_class.new(uri.host, uri.port)
429
+ net = net_http_object(uri.hostname, uri.port)
353
430
  net.use_ssl = uri.is_a?(URI::HTTPS)
354
431
  net.ssl_version = ssl_version if ssl_version
355
432
  net.ciphers = ssl_ciphers if ssl_ciphers
@@ -388,16 +465,16 @@ module RestClient
388
465
  warn('Try passing :verify_ssl => false instead.')
389
466
  end
390
467
 
391
- if defined? @timeout
392
- if @timeout == -1
393
- warn 'To disable read timeouts, please set timeout to nil instead of -1'
394
- @timeout = nil
468
+ if defined? @read_timeout
469
+ if @read_timeout == -1
470
+ warn 'Deprecated: to disable timeouts, please use nil instead of -1'
471
+ @read_timeout = nil
395
472
  end
396
- net.read_timeout = @timeout
473
+ net.read_timeout = @read_timeout
397
474
  end
398
475
  if defined? @open_timeout
399
476
  if @open_timeout == -1
400
- warn 'To disable open timeouts, please set open_timeout to nil instead of -1'
477
+ warn 'Deprecated: to disable timeouts, please use nil instead of -1'
401
478
  @open_timeout = nil
402
479
  end
403
480
  net.open_timeout = @open_timeout
@@ -407,24 +484,45 @@ module RestClient
407
484
  before_proc.call(req, args)
408
485
  end
409
486
 
487
+ if @before_execution_proc
488
+ @before_execution_proc.call(req, args)
489
+ end
490
+
410
491
  log_request
411
492
 
412
493
 
413
494
  net.start do |http|
495
+ established_connection = true
496
+
414
497
  if @block_response
415
- net_http_do_request(http, req, payload ? payload.to_s : nil,
416
- &@block_response)
498
+ net_http_do_request(http, req, payload, &@block_response)
417
499
  else
418
- res = net_http_do_request(http, req, payload ? payload.to_s : nil) \
419
- { |http_response| fetch_body(http_response) }
500
+ res = net_http_do_request(http, req, payload) { |http_response|
501
+ fetch_body(http_response)
502
+ }
420
503
  log_response res
421
504
  process_result res, & block
422
505
  end
423
506
  end
424
507
  rescue EOFError
425
508
  raise RestClient::ServerBrokeConnection
426
- rescue Timeout::Error, Errno::ETIMEDOUT
427
- raise RestClient::RequestTimeout
509
+ rescue Timeout::Error, Errno::ETIMEDOUT => err
510
+ # Net::HTTP has OpenTimeout, ReadTimeout in Ruby >= 2.0
511
+ if defined?(Net::OpenTimeout)
512
+ case err
513
+ when Net::OpenTimeout
514
+ raise RestClient::Exceptions::OpenTimeout.new(nil, err)
515
+ when Net::ReadTimeout
516
+ raise RestClient::Exceptions::ReadTimeout.new(nil, err)
517
+ end
518
+ end
519
+
520
+ # compatibility for Ruby 1.9.3, handling for non-Net::HTTP timeouts
521
+ if established_connection
522
+ raise RestClient::Exceptions::ReadTimeout.new(nil, err)
523
+ else
524
+ raise RestClient::Exceptions::OpenTimeout.new(nil, err)
525
+ end
428
526
 
429
527
  rescue OpenSSL::SSL::SSLError => error
430
528
  # TODO: deprecate and remove RestClient::SSLCertificateNotVerified and just
@@ -449,7 +547,7 @@ module RestClient
449
547
  end
450
548
 
451
549
  def setup_credentials(req)
452
- req.basic_auth(user, password) if user
550
+ req.basic_auth(user, password) if user && !headers.has_key?("Authorization")
453
551
  end
454
552
 
455
553
  def fetch_body(http_response)
@@ -457,7 +555,7 @@ module RestClient
457
555
  # Taken from Chef, which as in turn...
458
556
  # Stolen from http://www.ruby-forum.com/topic/166423
459
557
  # Kudos to _why!
460
- @tf = Tempfile.new("rest-client")
558
+ @tf = Tempfile.new('rest-client.')
461
559
  @tf.binmode
462
560
  size, total = 0, http_response.header['Content-Length'].to_i
463
561
  http_response.read_body do |chunk|
@@ -486,13 +584,14 @@ module RestClient
486
584
  # We don't decode raw requests
487
585
  response = RawResponse.new(@tf, res, args, self)
488
586
  else
489
- response = Response.create(Request.decode(res['content-encoding'], res.body), res, args, self)
587
+ decoded = Request.decode(res['content-encoding'], res.body)
588
+ response = Response.create(decoded, res, args, self)
490
589
  end
491
590
 
492
591
  if block_given?
493
592
  block.call(response, self, res, & block)
494
593
  else
495
- response.return!(self, res, & block)
594
+ response.return!(&block)
496
595
  end
497
596
 
498
597
  end
@@ -552,7 +651,7 @@ module RestClient
552
651
  def stringify_headers headers
553
652
  headers.inject({}) do |result, (key, value)|
554
653
  if key.is_a? Symbol
555
- key = key.to_s.split(/_/).map { |w| w.capitalize }.join('-')
654
+ key = key.to_s.split(/_/).map(&:capitalize).join('-')
556
655
  end
557
656
  if 'CONTENT-TYPE' == key.upcase
558
657
  result[key] = maybe_convert_extension(value.to_s)
@@ -574,7 +673,11 @@ module RestClient
574
673
  end
575
674
 
576
675
  def default_headers
577
- {:accept => '*/*; q=0.5, application/xml', :accept_encoding => 'gzip, deflate'}
676
+ {
677
+ :accept => '*/*',
678
+ :accept_encoding => 'gzip, deflate',
679
+ :user_agent => RestClient::Platform.default_user_agent,
680
+ }
578
681
  end
579
682
 
580
683
  private