almodovar 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. data/vendor/resourceful-0.5.3-patched/MIT-LICENSE +21 -0
  2. data/vendor/resourceful-0.5.3-patched/Manifest +29 -0
  3. data/vendor/resourceful-0.5.3-patched/README.markdown +84 -0
  4. data/vendor/resourceful-0.5.3-patched/Rakefile +71 -0
  5. data/vendor/resourceful-0.5.3-patched/lib/resourceful.rb +18 -0
  6. data/vendor/resourceful-0.5.3-patched/lib/resourceful/authentication_manager.rb +108 -0
  7. data/vendor/resourceful-0.5.3-patched/lib/resourceful/cache_manager.rb +240 -0
  8. data/vendor/resourceful-0.5.3-patched/lib/resourceful/exceptions.rb +34 -0
  9. data/vendor/resourceful-0.5.3-patched/lib/resourceful/header.rb +126 -0
  10. data/vendor/resourceful-0.5.3-patched/lib/resourceful/http_accessor.rb +98 -0
  11. data/vendor/resourceful-0.5.3-patched/lib/resourceful/memcache_cache_manager.rb +75 -0
  12. data/vendor/resourceful-0.5.3-patched/lib/resourceful/net_http_adapter.rb +70 -0
  13. data/vendor/resourceful-0.5.3-patched/lib/resourceful/options_interpreter.rb +78 -0
  14. data/vendor/resourceful-0.5.3-patched/lib/resourceful/request.rb +230 -0
  15. data/vendor/resourceful-0.5.3-patched/lib/resourceful/resource.rb +163 -0
  16. data/vendor/resourceful-0.5.3-patched/lib/resourceful/response.rb +221 -0
  17. data/vendor/resourceful-0.5.3-patched/lib/resourceful/stubbed_resource_proxy.rb +47 -0
  18. data/vendor/resourceful-0.5.3-patched/lib/resourceful/util.rb +6 -0
  19. data/vendor/resourceful-0.5.3-patched/resourceful.gemspec +48 -0
  20. data/vendor/resourceful-0.5.3-patched/spec/acceptance/authorization_spec.rb +16 -0
  21. data/vendor/resourceful-0.5.3-patched/spec/acceptance/caching_spec.rb +192 -0
  22. data/vendor/resourceful-0.5.3-patched/spec/acceptance/header_spec.rb +24 -0
  23. data/vendor/resourceful-0.5.3-patched/spec/acceptance/redirecting_spec.rb +12 -0
  24. data/vendor/resourceful-0.5.3-patched/spec/acceptance/resource_spec.rb +84 -0
  25. data/vendor/resourceful-0.5.3-patched/spec/acceptance_shared_specs.rb +44 -0
  26. data/vendor/resourceful-0.5.3-patched/spec/old_acceptance_specs.rb +378 -0
  27. data/vendor/resourceful-0.5.3-patched/spec/simple_sinatra_server.rb +74 -0
  28. data/vendor/resourceful-0.5.3-patched/spec/simple_sinatra_server_spec.rb +98 -0
  29. data/vendor/resourceful-0.5.3-patched/spec/spec.opts +3 -0
  30. data/vendor/resourceful-0.5.3-patched/spec/spec_helper.rb +28 -0
  31. metadata +32 -2
@@ -0,0 +1,230 @@
1
+ require 'pathname'
2
+ require 'benchmark'
3
+ require 'resourceful/response'
4
+ require 'resourceful/net_http_adapter'
5
+ require 'resourceful/exceptions'
6
+
7
+ module Resourceful
8
+
9
+ class Request
10
+
11
+ REDIRECTABLE_METHODS = [:get, :head]
12
+ CACHEABLE_METHODS = [:get, :head]
13
+ INVALIDATING_METHODS = [:post, :put, :delete]
14
+
15
+ attr_accessor :method, :resource, :body, :header
16
+ attr_reader :request_time, :accessor
17
+
18
+ # @param [Symbol] http_method
19
+ # :get, :put, :post, :delete or :head
20
+ # @param [Resourceful::Resource] resource
21
+ # @param [String] body
22
+ # @param [Resourceful::Header, Hash] header
23
+ def initialize(http_method, resource, body = nil, header = nil)
24
+ @method, @resource, @body = http_method, resource, body
25
+ @accessor = @resource.accessor
26
+ @header = header.is_a?(Resourceful::Header) ? header : Resourceful::Header.new(header)
27
+
28
+ # Resourceful handled gzip encoding transparently, so set that up
29
+ @header.accept_encoding ||= 'gzip, identity'
30
+
31
+ # 'Host' is a required HTTP/1.1 header, overrides Host in user-provided headers
32
+ @header.host = @resource.host
33
+
34
+ # Setting the date isn't a bad idea, either
35
+ @header.date ||= Time.now.httpdate
36
+
37
+ # Add any auth credentials we might want
38
+ add_credentials!
39
+
40
+ end
41
+
42
+ # Uses the auth manager to add any valid credentials to this request
43
+ def add_credentials!
44
+ @accessor.auth_manager.add_credentials(self)
45
+ end
46
+
47
+ # Performs all the work. Handles caching, redirects, auth retries, etc
48
+ def fetch_response
49
+ if cached_response
50
+ if needs_revalidation?(cached_response)
51
+ logger.info(" Cache needs revalidation")
52
+ set_validation_headers!(cached_response)
53
+ else
54
+ # We're done!
55
+ return cached_response
56
+ end
57
+ end
58
+
59
+ response = perform!
60
+
61
+ response = revalidate_cached_response(response) if cached_response && response.not_modified?
62
+ response = follow_redirect(response) if should_be_redirected?(response)
63
+ response = retry_with_auth(response) if needs_authorization?(response)
64
+
65
+ raise UnsuccessfulHttpRequestError.new(self, response) if response.error?
66
+
67
+ if cacheable?(response)
68
+ store_in_cache(response)
69
+ elsif invalidates_cache?
70
+ invalidate_cache
71
+ end
72
+
73
+ return response
74
+ end
75
+
76
+ # Should we look for a response to this request in the cache?
77
+ def skip_cache?
78
+ return true unless method.in? CACHEABLE_METHODS
79
+ header.cache_control && header.cache_control.include?('no-cache')
80
+ end
81
+
82
+ # The cached response
83
+ def cached_response
84
+ return if skip_cache?
85
+ return if @cached_response.nil? && @already_checked_cache
86
+ @cached_response ||= begin
87
+ @already_checked_cache = true
88
+ resp = accessor.cache_manager.lookup(self)
89
+ logger.info(" Retrieved from cache") if resp
90
+ resp
91
+ end
92
+ end
93
+
94
+ # Revalidate the cached response with what we got from a 304 response
95
+ def revalidate_cached_response(not_modified_response)
96
+ logger.info(" Resource not modified")
97
+ cached_response.revalidate!(not_modified_response)
98
+ cached_response
99
+ end
100
+
101
+ # Follow a redirect response
102
+ def follow_redirect(response)
103
+ raise MalformedServerResponse.new(self, response) unless response.header.location
104
+ if response.moved_permanently?
105
+ new_uri = response.header.location.first
106
+ logger.info(" Permanently redirected to #{new_uri} - Storing new location.")
107
+ resource.update_uri new_uri
108
+ @header.host = resource.host
109
+ response = fetch_response
110
+ elsif response.see_other? # Always use GET for this redirect, regardless of initial method
111
+ redirected_resource = Resourceful::Resource.new(self.accessor, response.header['Location'].first)
112
+ response = Request.new(:get, redirected_resource, body, header).fetch_response
113
+ else
114
+ redirected_resource = Resourceful::Resource.new(self.accessor, response.header['Location'].first)
115
+ logger.info(" Redirected to #{redirected_resource.uri} - Caching new location.")
116
+ response = Request.new(method, redirected_resource, body, header).fetch_response
117
+ end
118
+ end
119
+
120
+ # Add any auth headers from the response to the auth manager, and try the request again
121
+ def retry_with_auth(response)
122
+ @already_tried_with_auth = true
123
+ logger.info("Authentication Required. Retrying with auth info")
124
+ accessor.auth_manager.associate_auth_info(response)
125
+ add_credentials!
126
+ response = fetch_response
127
+ end
128
+
129
+ # Does this request need to be authorized? Will only be true if we haven't already tried with auth
130
+ def needs_authorization?(response)
131
+ !@already_tried_with_auth && response.unauthorized?
132
+ end
133
+
134
+ # Store the response to this request in the cache
135
+ def store_in_cache(response)
136
+ # RFC2618 - 14.18 : A received message that does not have a Date header
137
+ # field MUST be assigned one by the recipient if the message will be cached
138
+ # by that recipient.
139
+ response.header.date ||= response.response_time.httpdate
140
+
141
+ accessor.cache_manager.store(self, response)
142
+ end
143
+
144
+ # Invalidated the cache for this uri (eg, after a POST)
145
+ def invalidate_cache
146
+ accessor.cache_manager.invalidate(resource.uri)
147
+ end
148
+
149
+ # Is this request & response permitted to be stored in this (private) cache?
150
+ def cacheable?(response)
151
+ return false unless response.success?
152
+ return false unless method.in? CACHEABLE_METHODS
153
+ return false if header.cache_control && header.cache_control.include?('no-store')
154
+ true
155
+ end
156
+
157
+ # Does this request invalidate the cache?
158
+ def invalidates_cache?
159
+ return true if method.in? INVALIDATING_METHODS
160
+ end
161
+
162
+ # Perform the request, with no magic handling of anything.
163
+ def perform!
164
+ @request_time = Time.now
165
+
166
+ http_resp = NetHttpAdapter.make_request(@method, @resource.uri, @body, @header)
167
+ @response = Resourceful::Response.new(uri, *http_resp)
168
+ @response.request_time = @request_time
169
+ @response.authoritative = true
170
+
171
+ @response
172
+ end
173
+
174
+ # Is this a response a redirect, and are we permitted to follow it?
175
+ def should_be_redirected?(response)
176
+ return false unless response.redirect?
177
+ if resource.on_redirect.nil?
178
+ return true if method.in? REDIRECTABLE_METHODS
179
+ false
180
+ else
181
+ resource.on_redirect.call(self, response)
182
+ end
183
+ end
184
+
185
+ # Do we need to revalidate our cache?
186
+ def needs_revalidation?(response)
187
+ return true if forces_revalidation?
188
+ return true if response.stale?
189
+ return true if max_age && response.current_age > max_age
190
+ return true if response.must_be_revalidated?
191
+ false
192
+ end
193
+
194
+ # Set the validation headers of a request based on the response in the cache
195
+ def set_validation_headers!(response)
196
+ @header['If-None-Match'] = response.header['ETag'] if response.header.has_key?('ETag')
197
+ @header['If-Modified-Since'] = response.header['Last-Modified'] if response.header.has_key?('Last-Modified')
198
+ @header['Cache-Control'] = 'max-age=0' if response.must_be_revalidated?
199
+ end
200
+
201
+ # @return [String] The URI against which this request will be, or was, made.
202
+ def uri
203
+ resource.uri
204
+ end
205
+
206
+ # Does this request force us to revalidate the cache?
207
+ def forces_revalidation?
208
+ if max_age == 0 || header.cache_control && cc.include?('no-cache')
209
+ logger.info(" Client forced revalidation")
210
+ true
211
+ else
212
+ false
213
+ end
214
+ end
215
+
216
+ # Indicates the maxmimum response age in seconds we are willing to accept
217
+ #
218
+ # Returns nil if we don't care how old the response is
219
+ def max_age
220
+ if header['Cache-Control'] and header['Cache-Control'].include?('max-age')
221
+ header['Cache-Control'].split(',').grep(/max-age/).first.split('=').last.to_i
222
+ end
223
+ end
224
+
225
+ def logger
226
+ resource.logger
227
+ end
228
+ end
229
+
230
+ end
@@ -0,0 +1,163 @@
1
+ require 'pathname'
2
+ require 'resourceful/request'
3
+
4
+ module Resourceful
5
+
6
+ class Resource
7
+ attr_reader :accessor
8
+ attr_accessor :default_header
9
+
10
+ # Build a new resource for a uri
11
+ #
12
+ # @param accessor<HttpAccessor>
13
+ # The parent http accessor
14
+ # @param uri<String, Addressable::URI>
15
+ # The uri for the location of the resource
16
+ def initialize(accessor, uri, default_header = {})
17
+ @accessor, @uris = accessor, [uri]
18
+ @default_header = Resourceful::Header.new({'User-Agent' => Resourceful::RESOURCEFUL_USER_AGENT_TOKEN}.merge(default_header))
19
+ @on_redirect = nil
20
+ end
21
+
22
+ # The uri used to identify this resource. This is almost always the uri
23
+ # used to create the resource, but in the case of a permanent redirect, this
24
+ # will always reflect the lastest uri.
25
+ #
26
+ # @return Addressable::URI
27
+ # The current uri of the resource
28
+ def effective_uri
29
+ @uris.first
30
+ end
31
+ alias uri effective_uri
32
+
33
+ # Returns the host for this Resource's current uri
34
+ def host
35
+ Addressable::URI.parse(uri).host
36
+ end
37
+
38
+ # Updates the effective uri after following a permanent redirect
39
+ def update_uri(uri)
40
+ @uris.unshift(uri)
41
+ end
42
+
43
+ # When performing a redirect, this callback will be executed first. If the callback
44
+ # returns true, then the redirect is followed, otherwise it is not. The request that
45
+ # triggered the redirect and the response will be passed into the block. This can be
46
+ # used to update any links on the client side.
47
+ #
48
+ # Example:
49
+ #
50
+ # author_resource.on_redirect do |req, resp|
51
+ # post.author_uri = resp.header['Location']
52
+ # end
53
+ #
54
+ # @yieldparam callback<request, response>
55
+ # The action to be executed when a request results in a redirect. Yields the
56
+ # current request and result objects to the callback.
57
+ #
58
+ # @raise ArgumentError if called without a block
59
+ def on_redirect(&callback)
60
+ if block_given?
61
+ @on_redirect = callback
62
+ else
63
+ @on_redirect
64
+ end
65
+ end
66
+
67
+ # Performs a GET on the resource, following redirects as neccessary, and retriving
68
+ # it from the local cache if its available and valid.
69
+ #
70
+ # @return [Response] The Response to the final request made.
71
+ #
72
+ # @raise [UnsuccessfulHttpRequestError] unless the request is a
73
+ # success, ie the final request returned a 2xx response code
74
+ def get(header = {})
75
+ request(:get, nil, header)
76
+ end
77
+
78
+ # :call-seq:
79
+ # post(data = "", :content_type => mime_type)
80
+ #
81
+ # Performs a POST with the given data to the resource, following redirects as
82
+ # neccessary.
83
+ #
84
+ # @param [String] data
85
+ # The body of the data to be posted
86
+ # @param [Hash] options
87
+ # Options to pass into the request header. At the least, :content_type is required.
88
+ #
89
+ # @return [Response] The Response to the final request that was made.
90
+ #
91
+ # @raise [ArgumentError] unless :content-type is specified in options
92
+ # @raise [UnsuccessfulHttpRequestError] unless the request is a
93
+ # success, ie the final request returned a 2xx response code
94
+ def post(data = nil, header = {})
95
+ check_content_type_exists(data, header)
96
+ request(:post, data, header)
97
+ end
98
+
99
+ # :call-seq:
100
+ # put(data = "", :content_type => mime_type)
101
+ #
102
+ # Performs a PUT with the given data to the resource, following redirects as
103
+ # neccessary.
104
+ #
105
+ # @param [String] data
106
+ # The body of the data to be posted
107
+ # @param [Hash] options
108
+ # Options to pass into the request header. At the least, :content_type is required.
109
+ #
110
+ # @return [Response] The response to the final request made.
111
+ #
112
+ # @raise [ArgumentError] unless :content-type is specified in options
113
+ # @raise [UnsuccessfulHttpRequestError] unless the request is a
114
+ # success, ie the final request returned a 2xx response code
115
+ def put(data, header = {})
116
+ check_content_type_exists(data, header)
117
+ request(:put, data, header)
118
+ end
119
+
120
+ # Performs a DELETE on the resource, following redirects as neccessary.
121
+ #
122
+ # @return <Response>
123
+ #
124
+ # @raise [UnsuccessfulHttpRequestError] unless the request is a
125
+ # success, ie the final request returned a 2xx response code
126
+ def delete(header = {})
127
+ request(:delete, nil, header)
128
+ end
129
+
130
+ def logger
131
+ accessor.logger
132
+ end
133
+
134
+ private
135
+
136
+ # Ensures that the request has a content type header
137
+ # TODO Move this to request
138
+ def check_content_type_exists(body, header)
139
+ if body
140
+ raise MissingContentType unless header.has_key?(:content_type) or default_header.has_key?(:content_type)
141
+ end
142
+ end
143
+
144
+ # Actually make the request
145
+ def request(method, data, header)
146
+ log_request_with_time "#{method.to_s.upcase} [#{uri}]" do
147
+ request = Request.new(method, self, data, default_header.merge(header))
148
+ request.fetch_response
149
+ end
150
+ end
151
+
152
+ # Log it took the time to make the request
153
+ def log_request_with_time(msg, indent = 2)
154
+ logger.info(" " * indent + msg)
155
+ result = nil
156
+ time = Benchmark.measure { result = yield }
157
+ logger.info(" " * indent + "-> Returned #{result.code} in %.4fs" % time.real)
158
+ result
159
+ end
160
+
161
+ end
162
+
163
+ end
@@ -0,0 +1,221 @@
1
+ require 'net/http'
2
+ require 'time'
3
+ require 'zlib'
4
+
5
+ module Resourceful
6
+
7
+ class Response
8
+ REDIRECT_RESPONSE_CODES = [301,302,303,307]
9
+ NORMALLY_CACHEABLE_RESPONSE_CODES = [200, 203, 300, 301, 410]
10
+
11
+ attr_reader :uri, :code, :header, :body, :response_time
12
+ alias headers header
13
+
14
+ attr_accessor :authoritative, :request_time
15
+ alias authoritative? authoritative
16
+
17
+ def initialize(uri, code, header, body)
18
+ @uri, @code, @header, @body = uri, code, header, body
19
+ @response_time = Time.now
20
+ end
21
+
22
+ # Is this a cached response that has expired?
23
+ #
24
+ # @return true|false
25
+ def expired?
26
+ if header['Cache-Control'] and header['Cache-Control'].first.include?('max-age')
27
+ max_age = header['Cache-Control'].first.split(',').grep(/max-age/).first.split('=').last.to_i
28
+ return true if current_age > max_age
29
+ elsif header['Expire']
30
+ return true if Time.httpdate(header['Expire'].first) < Time.now
31
+ end
32
+
33
+ false
34
+ end
35
+
36
+ # Is this a cached response that is stale?
37
+ #
38
+ # @return true|false
39
+ def stale?
40
+ return true if expired?
41
+ if header['Cache-Control']
42
+ return true if header['Cache-Control'].include?('must-revalidate')
43
+ return true if header['Cache-Control'].include?('no-cache')
44
+ end
45
+
46
+ false
47
+ end
48
+
49
+ # Is this response cachable?
50
+ #
51
+ # @return true|false
52
+ def cacheable?
53
+ @cacheable ||= begin
54
+ @cacheable = true if NORMALLY_CACHEABLE_RESPONSE_CODES.include?(code.to_i)
55
+ @cacheable = false if header.vary && header.vary.include?('*')
56
+ @cacheable = false if header.cache_control && header.cache_control.include?('no-cache')
57
+ @cacheable = true if header.cache_control && header.cache_control.include?('public')
58
+ @cacheable = true if header.cache_control && header.cache_control.include?('private')
59
+ @cacheable || false
60
+ end
61
+ end
62
+
63
+ # Does this response force revalidation?
64
+ def must_be_revalidated?
65
+ header.cache_control && header.cache_control.include?('must-revalidate')
66
+ end
67
+
68
+ # Update our headers from a later 304 response
69
+ def revalidate!(not_modified_response)
70
+ header.merge!(not_modified_response.header)
71
+ request_time = not_modified_response.request_time
72
+ @authoritative = true
73
+ end
74
+
75
+ # Algorithm taken from RCF2616#13.2.3
76
+ def current_age
77
+ age_value = header['Age'] ? header['Age'].first.to_i : 0
78
+ date_value = Time.httpdate(header['Date'].first)
79
+ now = Time.now
80
+
81
+ apparent_age = [0, response_time - date_value].max
82
+ corrected_received_age = [apparent_age, age_value].max
83
+ current_age = corrected_received_age + (response_time - request_time) + (now - response_time)
84
+ end
85
+
86
+ def body
87
+ encoding = header['Content-Encoding'] && header['Content-Encoding'].first
88
+ case encoding
89
+ when nil
90
+ # body is identity encoded; just return it
91
+ @body
92
+ when /^\s*gzip\s*$/i
93
+ gz_in = ::Zlib::GzipReader.new(StringIO.new(@body, 'r'))
94
+ @body = gz_in.read
95
+ gz_in.close
96
+ header.delete('Content-Encoding')
97
+ @body
98
+ else
99
+ raise UnsupportedContentCoding, "Resourceful does not support #{encoding} content coding"
100
+ end
101
+ end
102
+
103
+ CODE_NAMES = {
104
+ 100 => "Continue".freeze,
105
+ 101 => "Switching Protocols".freeze,
106
+
107
+ 200 => "OK".freeze,
108
+ 201 => "Created".freeze,
109
+ 202 => "Accepted".freeze,
110
+ 203 => "Non-Authoritative Information".freeze,
111
+ 204 => "No Content".freeze,
112
+ 205 => "Reset Content".freeze,
113
+ 206 => "Partial Content".freeze,
114
+
115
+ 300 => "Multiple Choices".freeze,
116
+ 301 => "Moved Permanently".freeze,
117
+ 302 => "Found".freeze,
118
+ 303 => "See Other".freeze,
119
+ 304 => "Not Modified".freeze,
120
+ 305 => "Use Proxy".freeze,
121
+ 307 => "Temporary Redirect".freeze,
122
+
123
+ 400 => "Bad Request".freeze,
124
+ 401 => "Unauthorized".freeze,
125
+ 402 => "Payment Required".freeze,
126
+ 403 => "Forbidden".freeze,
127
+ 404 => "Not Found".freeze,
128
+ 405 => "Method Not Allowed".freeze,
129
+ 406 => "Not Acceptable".freeze,
130
+ 407 => "Proxy Authentication Required".freeze,
131
+ 408 => "Request Timeout".freeze,
132
+ 409 => "Conflict".freeze,
133
+ 410 => "Gone".freeze,
134
+ 411 => "Length Required".freeze,
135
+ 412 => "Precondition Failed".freeze,
136
+ 413 => "Request Entity Too Large".freeze,
137
+ 414 => "Request-URI Too Long".freeze,
138
+ 415 => "Unsupported Media Type".freeze,
139
+ 416 => "Requested Range Not Satisfiable".freeze,
140
+ 417 => "Expectation Failed".freeze,
141
+
142
+ 500 => "Internal Server Error".freeze,
143
+ 501 => "Not Implemented".freeze,
144
+ 502 => "Bad Gateway".freeze,
145
+ 503 => "Service Unavailable".freeze,
146
+ 504 => "Gateway Timeout".freeze,
147
+ 505 => "HTTP Version Not Supported".freeze,
148
+ }.freeze
149
+
150
+ CODES = CODE_NAMES.keys
151
+
152
+ CODE_NAMES.each do |code, msg|
153
+ method_name = msg.downcase.gsub(/[- ]/, "_")
154
+
155
+ class_eval <<-RUBY
156
+ def #{method_name}? # def ok?
157
+ @code == #{code} # @code == 200
158
+ end # end
159
+ RUBY
160
+ end
161
+
162
+ # Is the response informational? True for
163
+ # 1xx series response codes
164
+ #
165
+ # @return true|false
166
+ def informational?
167
+ @code.in? 100..199
168
+ end
169
+
170
+ # Is the response code sucessful? True for only 2xx series
171
+ # response codes.
172
+ #
173
+ # @return true|false
174
+ def successful?
175
+ @code.in? 200..299
176
+ end
177
+ alias success? successful?
178
+
179
+ # Is the response a redirect? True for
180
+ # 3xx series response codes
181
+ #
182
+ # @return true|false
183
+ def redirection?
184
+ @code.in? 300..399
185
+ end
186
+
187
+ # Is the response a actual redirect? True for
188
+ # 301, 302, 303, 307 response codes
189
+ #
190
+ # @return true|false
191
+ def redirect?
192
+ @code.in? REDIRECT_RESPONSE_CODES
193
+ end
194
+
195
+ # Is the response the result of a client error? True for
196
+ # 4xx series response codes
197
+ #
198
+ # @return true|false
199
+ def client_error?
200
+ @code.in? 400..499
201
+ end
202
+
203
+ # Is the response the result of a server error? True for
204
+ # 5xx series response codes
205
+ #
206
+ # @return true|false
207
+ def server_error?
208
+ @code.in? 500..599
209
+ end
210
+
211
+ # Is the response the result of any kind of error? True for
212
+ # 4xx and 5xx series response codes
213
+ #
214
+ # @return true|false
215
+ def error?
216
+ server_error? || client_error?
217
+ end
218
+
219
+ end
220
+
221
+ end