rest-client 1.6.7 → 2.1.0

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.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.mailmap +10 -0
  4. data/.rspec +2 -0
  5. data/.rubocop +2 -0
  6. data/.rubocop-disables.yml +386 -0
  7. data/.rubocop.yml +8 -0
  8. data/.travis.yml +62 -0
  9. data/AUTHORS +106 -0
  10. data/Gemfile +11 -0
  11. data/LICENSE +21 -0
  12. data/README.md +901 -0
  13. data/Rakefile +109 -35
  14. data/bin/restclient +11 -12
  15. data/history.md +244 -1
  16. data/lib/restclient.rb +27 -18
  17. data/lib/restclient/abstract_response.rb +197 -51
  18. data/lib/restclient/exceptions.rb +110 -59
  19. data/lib/restclient/params_array.rb +72 -0
  20. data/lib/restclient/payload.rb +74 -75
  21. data/lib/restclient/platform.rb +49 -0
  22. data/lib/restclient/raw_response.rb +21 -6
  23. data/lib/restclient/request.rb +747 -183
  24. data/lib/restclient/resource.rb +22 -13
  25. data/lib/restclient/response.rb +75 -9
  26. data/lib/restclient/utils.rb +274 -0
  27. data/lib/restclient/version.rb +8 -0
  28. data/lib/restclient/windows.rb +8 -0
  29. data/lib/restclient/windows/root_certs.rb +105 -0
  30. data/rest-client.gemspec +32 -0
  31. data/rest-client.windows.gemspec +19 -0
  32. data/spec/ISS.jpg +0 -0
  33. data/spec/helpers.rb +54 -0
  34. data/spec/integration/_lib.rb +1 -0
  35. data/spec/integration/capath_digicert/3513523f.0 +22 -0
  36. data/spec/integration/capath_digicert/399e7759.0 +22 -0
  37. data/spec/integration/capath_digicert/README +8 -0
  38. data/spec/integration/capath_digicert/digicert.crt +22 -0
  39. data/spec/integration/capath_verisign/415660c1.0 +14 -0
  40. data/spec/integration/capath_verisign/7651b327.0 +14 -0
  41. data/spec/integration/capath_verisign/README +8 -0
  42. data/spec/integration/capath_verisign/verisign.crt +14 -0
  43. data/spec/integration/certs/digicert.crt +22 -0
  44. data/spec/integration/httpbin_spec.rb +128 -0
  45. data/spec/integration/integration_spec.rb +118 -0
  46. data/spec/integration/request_spec.rb +109 -7
  47. data/spec/spec_helper.rb +29 -0
  48. data/spec/unit/_lib.rb +1 -0
  49. data/spec/unit/abstract_response_spec.rb +145 -0
  50. data/spec/unit/exceptions_spec.rb +108 -0
  51. data/spec/unit/params_array_spec.rb +36 -0
  52. data/spec/unit/payload_spec.rb +295 -0
  53. data/spec/unit/raw_response_spec.rb +22 -0
  54. data/spec/unit/request2_spec.rb +54 -0
  55. data/spec/unit/request_spec.rb +1238 -0
  56. data/spec/unit/resource_spec.rb +134 -0
  57. data/spec/unit/response_spec.rb +252 -0
  58. data/spec/unit/restclient_spec.rb +80 -0
  59. data/spec/unit/utils_spec.rb +147 -0
  60. data/spec/unit/windows/root_certs_spec.rb +22 -0
  61. metadata +265 -117
  62. data/README.rdoc +0 -285
  63. data/VERSION +0 -1
  64. data/lib/restclient/net_http_ext.rb +0 -55
  65. data/spec/abstract_response_spec.rb +0 -85
  66. data/spec/base.rb +0 -16
  67. data/spec/exceptions_spec.rb +0 -98
  68. data/spec/integration/certs/equifax.crt +0 -19
  69. data/spec/integration_spec.rb +0 -38
  70. data/spec/master_shake.jpg +0 -0
  71. data/spec/payload_spec.rb +0 -234
  72. data/spec/raw_response_spec.rb +0 -17
  73. data/spec/request2_spec.rb +0 -40
  74. data/spec/request_spec.rb +0 -529
  75. data/spec/resource_spec.rb +0 -134
  76. data/spec/response_spec.rb +0 -169
  77. data/spec/restclient_spec.rb +0 -73
data/lib/restclient.rb CHANGED
@@ -1,22 +1,20 @@
1
- require 'uri'
2
- require 'zlib'
1
+ require 'net/http'
2
+ require 'openssl'
3
3
  require 'stringio'
4
+ require 'uri'
4
5
 
5
- begin
6
- require 'net/https'
7
- rescue LoadError => e
8
- raise e unless RUBY_PLATFORM =~ /linux/
9
- raise LoadError, "no such file to load -- net/https. Try running apt-get install libopenssl-ruby"
10
- end
11
-
6
+ require File.dirname(__FILE__) + '/restclient/version'
7
+ require File.dirname(__FILE__) + '/restclient/platform'
12
8
  require File.dirname(__FILE__) + '/restclient/exceptions'
9
+ require File.dirname(__FILE__) + '/restclient/utils'
13
10
  require File.dirname(__FILE__) + '/restclient/request'
14
11
  require File.dirname(__FILE__) + '/restclient/abstract_response'
15
12
  require File.dirname(__FILE__) + '/restclient/response'
16
13
  require File.dirname(__FILE__) + '/restclient/raw_response'
17
14
  require File.dirname(__FILE__) + '/restclient/resource'
15
+ require File.dirname(__FILE__) + '/restclient/params_array'
18
16
  require File.dirname(__FILE__) + '/restclient/payload'
19
- require File.dirname(__FILE__) + '/restclient/net_http_ext'
17
+ require File.dirname(__FILE__) + '/restclient/windows'
20
18
 
21
19
  # This module's static methods are the entry point for using the REST client.
22
20
  #
@@ -92,8 +90,24 @@ module RestClient
92
90
  Request.execute(:method => :options, :url => url, :headers => headers, &block)
93
91
  end
94
92
 
95
- class << self
96
- attr_accessor :proxy
93
+ # A global proxy URL to use for all requests. This can be overridden on a
94
+ # per-request basis by passing `:proxy` to RestClient::Request.
95
+ def self.proxy
96
+ @proxy ||= nil
97
+ end
98
+
99
+ def self.proxy=(value)
100
+ @proxy = value
101
+ @proxy_set = true
102
+ end
103
+
104
+ # Return whether RestClient.proxy was set explicitly. We use this to
105
+ # differentiate between no value being set and a value explicitly set to nil.
106
+ #
107
+ # @return [Boolean]
108
+ #
109
+ def self.proxy_set?
110
+ @proxy_set ||= false
97
111
  end
98
112
 
99
113
  # Setup the log for RestClient calls.
@@ -103,12 +117,6 @@ module RestClient
103
117
  @@log = create_log log
104
118
  end
105
119
 
106
- def self.version
107
- version_path = File.dirname(__FILE__) + "/../VERSION"
108
- return File.read(version_path).chomp if File.file?(version_path)
109
- "0.0.0"
110
- end
111
-
112
120
  # Create a log that respond to << like a logger
113
121
  # param can be 'stdout', 'stderr', a string (then we will log to that file) or a logger (then we return it)
114
122
  def self.create_log param
@@ -159,6 +167,7 @@ module RestClient
159
167
  # Add a Proc to be called before each request in executed.
160
168
  # The proc parameters will be the http request and the request params.
161
169
  def self.add_before_execution_proc &proc
170
+ raise ArgumentError.new('block is required') unless proc
162
171
  @@before_execution_procs << proc
163
172
  end
164
173
 
@@ -1,16 +1,40 @@
1
1
  require 'cgi'
2
+ require 'http-cookie'
2
3
 
3
4
  module RestClient
4
5
 
5
6
  module AbstractResponse
6
7
 
7
- attr_reader :net_http_res, :args
8
+ attr_reader :net_http_res, :request, :start_time, :end_time, :duration
9
+
10
+ def inspect
11
+ raise NotImplementedError.new('must override in subclass')
12
+ end
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
8
28
 
9
29
  # HTTP status code
10
30
  def code
11
31
  @code ||= @net_http_res.code.to_i
12
32
  end
13
33
 
34
+ def history
35
+ @history ||= request.redirection_history || []
36
+ end
37
+
14
38
  # A hash of the headers, beautified with symbols and underscores.
15
39
  # e.g. "Content-type" will become :content_type.
16
40
  def headers
@@ -22,85 +46,207 @@ module RestClient
22
46
  @raw_headers ||= @net_http_res.to_hash
23
47
  end
24
48
 
25
- # Hash of cookies extracted from response headers
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)
53
+ @net_http_res = net_http_res
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
63
+
64
+ # prime redirection history
65
+ history
66
+ end
67
+
68
+ # Hash of cookies extracted from response headers.
69
+ #
70
+ # NB: This will return only cookies whose domain matches this request, and
71
+ # may not even return all of those cookies if there are duplicate names.
72
+ # Use the full cookie_jar for more nuanced access.
73
+ #
74
+ # @see #cookie_jar
75
+ #
76
+ # @return [Hash]
77
+ #
26
78
  def cookies
27
- @cookies ||= (self.headers[:set_cookie] || {}).inject({}) do |out, cookie_content|
28
- out.merge parse_cookie(cookie_content)
79
+ hash = {}
80
+
81
+ cookie_jar.cookies(@request.uri).each do |cookie|
82
+ hash[cookie.name] = cookie.value
83
+ end
84
+
85
+ hash
86
+ end
87
+
88
+ # Cookie jar extracted from response headers.
89
+ #
90
+ # @return [HTTP::CookieJar]
91
+ #
92
+ def cookie_jar
93
+ return @cookie_jar if defined?(@cookie_jar) && @cookie_jar
94
+
95
+ jar = @request.cookie_jar.dup
96
+ headers.fetch(:set_cookie, []).each do |cookie|
97
+ jar.parse(cookie, @request.uri)
29
98
  end
99
+
100
+ @cookie_jar = jar
30
101
  end
31
102
 
32
103
  # Return the default behavior corresponding to the response code:
33
- # the response itself for code in 200..206, redirection for 301, 302 and 307 in get and head cases, redirection for 303 and an exception in other cases
34
- def return! request = nil, result = nil, & block
35
- if (200..207).include? code
104
+ #
105
+ # For 20x status codes: return the response itself
106
+ #
107
+ # For 30x status codes:
108
+ # 301, 302, 307: redirect GET / HEAD if there is a Location header
109
+ # 303: redirect, changing method to GET, if there is a Location header
110
+ #
111
+ # For all other responses, raise a response exception
112
+ #
113
+ def return!(&block)
114
+ case code
115
+ when 200..207
36
116
  self
37
- elsif [301, 302, 307].include? code
38
- unless [:get, :head].include? args[:method]
39
- raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
117
+ when 301, 302, 307
118
+ case request.method
119
+ when 'get', 'head'
120
+ check_max_redirects
121
+ follow_redirection(&block)
40
122
  else
41
- follow_redirection(request, result, & block)
123
+ raise exception_with_response
42
124
  end
43
- elsif code == 303
44
- args[:method] = :get
45
- args.delete :payload
46
- follow_redirection(request, result, & block)
47
- elsif Exceptions::EXCEPTIONS_MAP[code]
48
- raise Exceptions::EXCEPTIONS_MAP[code].new(self, code)
125
+ when 303
126
+ check_max_redirects
127
+ follow_get_redirection(&block)
49
128
  else
50
- raise RequestFailed.new(self, code)
129
+ raise exception_with_response
51
130
  end
52
131
  end
53
132
 
54
133
  def to_i
55
- code
134
+ warn('warning: calling Response#to_i is not recommended')
135
+ super
56
136
  end
57
137
 
58
138
  def description
59
139
  "#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n"
60
140
  end
61
141
 
62
- # Follow a redirection
63
- def follow_redirection request = nil, result = nil, & block
64
- url = headers[:location]
65
- if url !~ /^http/
66
- url = URI.parse(args[:url]).merge(url).to_s
67
- end
68
- args[:url] = url
69
- if request
70
- if request.max_redirects == 0
71
- raise MaxRedirectsReached
72
- end
73
- args[:password] = request.password
74
- args[:user] = request.user
75
- args[:headers] = request.headers
76
- args[:max_redirects] = request.max_redirects - 1
77
- # pass any cookie set in the result
78
- if result && result['set-cookie']
79
- args[:headers][:cookies] = (args[:headers][:cookies] || {}).merge(parse_cookie(result['set-cookie']))
80
- end
81
- end
82
- Request.execute args, &block
142
+ # Follow a redirection response by making a new HTTP request to the
143
+ # redirection target.
144
+ def follow_redirection(&block)
145
+ _follow_redirection(request.args.dup, &block)
146
+ end
147
+
148
+ # Follow a redirection response, but change the HTTP method to GET and drop
149
+ # the payload from the original request.
150
+ def follow_get_redirection(&block)
151
+ new_args = request.args.dup
152
+ new_args[:method] = :get
153
+ new_args.delete(:payload)
154
+
155
+ _follow_redirection(new_args, &block)
83
156
  end
84
157
 
85
- def AbstractResponse.beautify_headers(headers)
158
+ # Convert headers hash into canonical form.
159
+ #
160
+ # Header names will be converted to lowercase symbols with underscores
161
+ # instead of hyphens.
162
+ #
163
+ # Headers specified multiple times will be joined by comma and space,
164
+ # except for Set-Cookie, which will always be an array.
165
+ #
166
+ # Per RFC 2616, if a server sends multiple headers with the same key, they
167
+ # MUST be able to be joined into a single header by a comma. However,
168
+ # Set-Cookie (RFC 6265) cannot because commas are valid within cookie
169
+ # definitions. The newer RFC 7230 notes (3.2.2) that Set-Cookie should be
170
+ # handled as a special case.
171
+ #
172
+ # http://tools.ietf.org/html/rfc2616#section-4.2
173
+ # http://tools.ietf.org/html/rfc7230#section-3.2.2
174
+ # http://tools.ietf.org/html/rfc6265
175
+ #
176
+ # @param headers [Hash]
177
+ # @return [Hash]
178
+ #
179
+ def self.beautify_headers(headers)
86
180
  headers.inject({}) do |out, (key, value)|
87
- out[key.gsub(/-/, '_').downcase.to_sym] = %w{ set-cookie }.include?(key.downcase) ? value : value.first
181
+ key_sym = key.tr('-', '_').downcase.to_sym
182
+
183
+ # Handle Set-Cookie specially since it cannot be joined by comma.
184
+ if key.downcase == 'set-cookie'
185
+ out[key_sym] = value
186
+ else
187
+ out[key_sym] = value.join(', ')
188
+ end
189
+
88
190
  out
89
191
  end
90
192
  end
91
193
 
92
194
  private
93
195
 
94
- # Parse a cookie value and return its content in an Hash
95
- def parse_cookie cookie_content
96
- out = {}
97
- CGI::Cookie::parse(cookie_content).each do |key, cookie|
98
- unless ['expires', 'path'].include? key
99
- out[CGI::escape(key)] = cookie.value[0] ? (CGI::escape(cookie.value[0]) || '') : ''
100
- end
196
+ # Follow a redirection
197
+ #
198
+ # @param new_args [Hash] Start with this hash of arguments for the
199
+ # redirection request. The hash will be mutated, so be sure to dup any
200
+ # existing hash that should not be modified.
201
+ #
202
+ def _follow_redirection(new_args, &block)
203
+
204
+ # parse location header and merge into existing URL
205
+ url = headers[:location]
206
+
207
+ # cannot follow redirection if there is no location header
208
+ unless url
209
+ raise exception_with_response
210
+ end
211
+
212
+ # handle relative redirects
213
+ unless url.start_with?('http')
214
+ url = URI.parse(request.url).merge(url).to_s
215
+ end
216
+ new_args[:url] = url
217
+
218
+ new_args[:password] = request.password
219
+ new_args[:user] = request.user
220
+ new_args[:headers] = request.headers
221
+ new_args[:max_redirects] = request.max_redirects - 1
222
+
223
+ # pass through our new cookie jar
224
+ new_args[:cookies] = cookie_jar
225
+
226
+ # prepare new request
227
+ new_req = Request.new(new_args)
228
+
229
+ # append self to redirection history
230
+ new_req.redirection_history = history + [self]
231
+
232
+ # execute redirected request
233
+ new_req.execute(&block)
234
+ end
235
+
236
+ def check_max_redirects
237
+ if request.max_redirects <= 0
238
+ raise exception_with_response
101
239
  end
102
- out
103
240
  end
104
- end
105
241
 
242
+ def exception_with_response
243
+ begin
244
+ klass = Exceptions::EXCEPTIONS_MAP.fetch(code)
245
+ rescue KeyError
246
+ raise RequestFailed.new(self, code)
247
+ end
248
+
249
+ raise klass.new(self, code)
250
+ end
251
+ end
106
252
  end
@@ -1,5 +1,19 @@
1
1
  module RestClient
2
2
 
3
+ # Hash of HTTP status code => message.
4
+ #
5
+ # 1xx: Informational - Request received, continuing process
6
+ # 2xx: Success - The action was successfully received, understood, and
7
+ # accepted
8
+ # 3xx: Redirection - Further action must be taken in order to complete the
9
+ # request
10
+ # 4xx: Client Error - The request contains bad syntax or cannot be fulfilled
11
+ # 5xx: Server Error - The server failed to fulfill an apparently valid
12
+ # request
13
+ #
14
+ # @see
15
+ # http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
16
+ #
3
17
  STATUSES = {100 => 'Continue',
4
18
  101 => 'Switching Protocols',
5
19
  102 => 'Processing', #WebDAV
@@ -12,6 +26,8 @@ module RestClient
12
26
  205 => 'Reset Content',
13
27
  206 => 'Partial Content',
14
28
  207 => 'Multi-Status', #WebDAV
29
+ 208 => 'Already Reported', # RFC5842
30
+ 226 => 'IM Used', # RFC3229
15
31
 
16
32
  300 => 'Multiple Choices',
17
33
  301 => 'Moved Permanently',
@@ -21,12 +37,13 @@ module RestClient
21
37
  305 => 'Use Proxy', # http/1.1
22
38
  306 => 'Switch Proxy', # no longer used
23
39
  307 => 'Temporary Redirect', # http/1.1
24
-
40
+ 308 => 'Permanent Redirect', # RFC7538
41
+
25
42
  400 => 'Bad Request',
26
43
  401 => 'Unauthorized',
27
44
  402 => 'Payment Required',
28
45
  403 => 'Forbidden',
29
- 404 => 'Resource Not Found',
46
+ 404 => 'Not Found',
30
47
  405 => 'Method Not Allowed',
31
48
  406 => 'Not Acceptable',
32
49
  407 => 'Proxy Authentication Required',
@@ -35,18 +52,21 @@ module RestClient
35
52
  410 => 'Gone',
36
53
  411 => 'Length Required',
37
54
  412 => 'Precondition Failed',
38
- 413 => 'Request Entity Too Large',
39
- 414 => 'Request-URI Too Long',
55
+ 413 => 'Payload Too Large', # RFC7231 (renamed, see below)
56
+ 414 => 'URI Too Long', # RFC7231 (renamed, see below)
40
57
  415 => 'Unsupported Media Type',
41
- 416 => 'Requested Range Not Satisfiable',
58
+ 416 => 'Range Not Satisfiable', # RFC7233 (renamed, see below)
42
59
  417 => 'Expectation Failed',
43
- 418 => 'I\'m A Teapot',
60
+ 418 => 'I\'m A Teapot', #RFC2324
44
61
  421 => 'Too Many Connections From This IP',
45
62
  422 => 'Unprocessable Entity', #WebDAV
46
63
  423 => 'Locked', #WebDAV
47
64
  424 => 'Failed Dependency', #WebDAV
48
65
  425 => 'Unordered Collection', #WebDAV
49
- 426 => 'Upgrade Required',
66
+ 426 => 'Upgrade Required',
67
+ 428 => 'Precondition Required', #RFC6585
68
+ 429 => 'Too Many Requests', #RFC6585
69
+ 431 => 'Request Header Fields Too Large', #RFC6585
50
70
  449 => 'Retry With', #Microsoft
51
71
  450 => 'Blocked By Windows Parental Controls', #Microsoft
52
72
 
@@ -58,20 +78,27 @@ module RestClient
58
78
  505 => 'HTTP Version Not Supported',
59
79
  506 => 'Variant Also Negotiates',
60
80
  507 => 'Insufficient Storage', #WebDAV
81
+ 508 => 'Loop Detected', # RFC5842
61
82
  509 => 'Bandwidth Limit Exceeded', #Apache
62
- 510 => 'Not Extended'}
63
-
64
- # Compatibility : make the Response act like a Net::HTTPResponse when needed
65
- module ResponseForException
66
- def method_missing symbol, *args
67
- if net_http_res.respond_to? symbol
68
- warn "[warning] The response contained in an RestClient::Exception is now a RestClient::Response instead of a Net::HTTPResponse, please update your code"
69
- net_http_res.send symbol, *args
70
- else
71
- super
72
- end
73
- end
74
- end
83
+ 510 => 'Not Extended',
84
+ 511 => 'Network Authentication Required', # RFC6585
85
+ }
86
+
87
+ STATUSES_COMPATIBILITY = {
88
+ # The RFCs all specify "Not Found", but "Resource Not Found" was used in
89
+ # earlier RestClient releases.
90
+ 404 => ['ResourceNotFound'],
91
+
92
+ # HTTP 413 was renamed to "Payload Too Large" in RFC7231.
93
+ 413 => ['RequestEntityTooLarge'],
94
+
95
+ # HTTP 414 was renamed to "URI Too Long" in RFC7231.
96
+ 414 => ['RequestURITooLong'],
97
+
98
+ # HTTP 416 was renamed to "Range Not Satisfiable" in RFC7233.
99
+ 416 => ['RequestedRangeNotSatisfiable'],
100
+ }
101
+
75
102
 
76
103
  # This is the base RestClient exception class. Rescue it if you want to
77
104
  # catch any exception that your request might raise
@@ -81,14 +108,13 @@ module RestClient
81
108
  # probably an HTML error page) is e.response.
82
109
  class Exception < RuntimeError
83
110
  attr_accessor :response
84
- attr_writer :message
111
+ attr_accessor :original_exception
112
+ attr_writer :message
85
113
 
86
114
  def initialize response = nil, initial_response_code = nil
87
115
  @response = response
116
+ @message = nil
88
117
  @initial_response_code = initial_response_code
89
-
90
- # compatibility: this make the exception behave like a Net::HTTPResponse
91
- response.extend ResponseForException if response
92
118
  end
93
119
 
94
120
  def http_code
@@ -100,32 +126,35 @@ module RestClient
100
126
  end
101
127
  end
102
128
 
103
- def http_body
104
- @response.body if @response
129
+ def http_headers
130
+ @response.headers if @response
105
131
  end
106
132
 
107
- def inspect
108
- "#{message}: #{http_body}"
133
+ def http_body
134
+ @response.body if @response
109
135
  end
110
136
 
111
137
  def to_s
112
- inspect
138
+ message
113
139
  end
114
-
140
+
115
141
  def message
116
- @message || self.class.name
142
+ @message || default_message
117
143
  end
118
144
 
145
+ def default_message
146
+ self.class.name
147
+ end
119
148
  end
120
149
 
121
150
  # Compatibility
122
- class ExceptionWithResponse < Exception
151
+ class ExceptionWithResponse < RestClient::Exception
123
152
  end
124
153
 
125
154
  # The request failed with an error code not managed by the code
126
155
  class RequestFailed < ExceptionWithResponse
127
156
 
128
- def message
157
+ def default_message
129
158
  "HTTP status code #{http_code}"
130
159
  end
131
160
 
@@ -134,60 +163,82 @@ module RestClient
134
163
  end
135
164
  end
136
165
 
137
- # We will a create an exception for each status code, see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
166
+ # RestClient exception classes. TODO: move all exceptions into this module.
167
+ #
168
+ # We will a create an exception for each status code, see
169
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
170
+ #
138
171
  module Exceptions
139
172
  # Map http status codes to the corresponding exception class
140
173
  EXCEPTIONS_MAP = {}
141
174
  end
142
175
 
176
+ # Create HTTP status exception classes
143
177
  STATUSES.each_pair do |code, message|
144
-
145
- # Compatibility
146
- superclass = ([304, 401, 404].include? code) ? ExceptionWithResponse : RequestFailed
147
- klass = Class.new(superclass) do
148
- send(:define_method, :message) {"#{http_code ? "#{http_code} " : ''}#{message}"}
178
+ klass = Class.new(RequestFailed) do
179
+ send(:define_method, :default_message) {"#{http_code ? "#{http_code} " : ''}#{message}"}
149
180
  end
150
- klass_constant = const_set message.delete(' \-\''), klass
181
+ klass_constant = const_set(message.delete(' \-\''), klass)
151
182
  Exceptions::EXCEPTIONS_MAP[code] = klass_constant
152
183
  end
153
184
 
154
- # A redirect was encountered; caught by execute to retry with the new url.
155
- class Redirect < Exception
185
+ # Create HTTP status exception classes used for backwards compatibility
186
+ STATUSES_COMPATIBILITY.each_pair do |code, compat_list|
187
+ klass = Exceptions::EXCEPTIONS_MAP.fetch(code)
188
+ compat_list.each do |old_name|
189
+ const_set(old_name, klass)
190
+ end
191
+ end
156
192
 
157
- message = 'Redirect'
193
+ module Exceptions
194
+ # We have to split the Exceptions module like we do here because the
195
+ # EXCEPTIONS_MAP is under Exceptions, but we depend on
196
+ # RestClient::RequestTimeout below.
197
+
198
+ # Base class for request timeouts.
199
+ #
200
+ # NB: Previous releases of rest-client would raise RequestTimeout both for
201
+ # HTTP 408 responses and for actual connection timeouts.
202
+ class Timeout < RestClient::RequestTimeout
203
+ def initialize(message=nil, original_exception=nil)
204
+ super(nil, nil)
205
+ self.message = message if message
206
+ self.original_exception = original_exception if original_exception
207
+ end
208
+ end
158
209
 
159
- attr_accessor :url
210
+ # Timeout when connecting to a server. Typically wraps Net::OpenTimeout (in
211
+ # ruby 2.0 or greater).
212
+ class OpenTimeout < Timeout
213
+ def default_message
214
+ 'Timed out connecting to server'
215
+ end
216
+ end
160
217
 
161
- def initialize(url)
162
- @url = url
218
+ # Timeout when reading from a server. Typically wraps Net::ReadTimeout (in
219
+ # ruby 2.0 or greater).
220
+ class ReadTimeout < Timeout
221
+ def default_message
222
+ 'Timed out reading data from server'
223
+ end
163
224
  end
164
225
  end
165
226
 
166
- class MaxRedirectsReached < Exception
167
- message = 'Maximum number of redirect reached'
168
- end
169
227
 
170
228
  # The server broke the connection prior to the request completing. Usually
171
229
  # this means it crashed, or sometimes that your network connection was
172
230
  # severed before it could complete.
173
- class ServerBrokeConnection < Exception
231
+ class ServerBrokeConnection < RestClient::Exception
174
232
  def initialize(message = 'Server broke connection')
175
233
  super nil, nil
176
234
  self.message = message
177
235
  end
178
236
  end
179
237
 
180
- class SSLCertificateNotVerified < Exception
181
- def initialize(message)
238
+ class SSLCertificateNotVerified < RestClient::Exception
239
+ def initialize(message = 'SSL certificate not verified')
182
240
  super nil, nil
183
241
  self.message = message
184
242
  end
185
243
  end
186
244
  end
187
-
188
- # backwards compatibility
189
- class RestClient::Request
190
- Redirect = RestClient::Redirect
191
- Unauthorized = RestClient::Unauthorized
192
- RequestFailed = RestClient::RequestFailed
193
- end