Class: Resourceful::Request

Inherits:
Object show all
Defined in:
lib/resourceful/request.rb

Constant Summary collapse

REDIRECTABLE_METHODS =
[:get, :head]
CACHEABLE_METHODS =
[:get, :head]
INVALIDATING_METHODS =
[:post, :put, :delete]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(http_method, resource, body = nil, header = nil) ⇒ Request

Returns a new instance of Request.

Parameters:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/resourceful/request.rb', line 23

def initialize(http_method, resource, body = nil, header = nil)
  @method, @resource, @body = http_method, resource, body
  @accessor = @resource.accessor
  @header = header.is_a?(Resourceful::Header) ? header : Resourceful::Header.new(header)

  # Resourceful handled gzip encoding transparently, so set that up
  @header.accept_encoding ||= 'gzip, identity'

  # 'Host' is a required HTTP/1.1 header, overrides Host in user-provided headers
  @header.host = @resource.host

  # Setting the date isn't a bad idea, either
  @header.date ||= Time.now.httpdate

  # Add any auth credentials we might want
  add_credentials!

end

Instance Attribute Details

#accessorObject (readonly)

Returns the value of attribute accessor.



16
17
18
# File 'lib/resourceful/request.rb', line 16

def accessor
  @accessor
end

#bodyObject

Returns the value of attribute body.



15
16
17
# File 'lib/resourceful/request.rb', line 15

def body
  @body
end

#headerObject

Returns the value of attribute header.



15
16
17
# File 'lib/resourceful/request.rb', line 15

def header
  @header
end

#methodObject

Returns the value of attribute method.



15
16
17
# File 'lib/resourceful/request.rb', line 15

def method
  @method
end

#request_timeObject (readonly)

Returns the value of attribute request_time.



16
17
18
# File 'lib/resourceful/request.rb', line 16

def request_time
  @request_time
end

#resourceObject

Returns the value of attribute resource.



15
16
17
# File 'lib/resourceful/request.rb', line 15

def resource
  @resource
end

Instance Method Details

#adapterObject



230
231
232
# File 'lib/resourceful/request.rb', line 230

def adapter
  accessor.http_adapter
end

#add_credentials!Object

Uses the auth manager to add any valid credentials to this request



43
44
45
# File 'lib/resourceful/request.rb', line 43

def add_credentials!
  @accessor.auth_manager.add_credentials(self)
end

#cacheable?(response) ⇒ Boolean

Is this request & response permitted to be stored in this (private) cache?

Returns:

  • (Boolean)


151
152
153
154
155
156
# File 'lib/resourceful/request.rb', line 151

def cacheable?(response)
  return false unless response.success?
  return false unless method.in? CACHEABLE_METHODS
  return false if header.cache_control && header.cache_control.include?('no-store')
  true
end

#cached_responseObject

The cached response



83
84
85
86
87
88
89
90
91
92
# File 'lib/resourceful/request.rb', line 83

def cached_response
  return if skip_cache?
  return if @cached_response.nil? && @already_checked_cache
  @cached_response ||= begin
    @already_checked_cache = true
    resp = accessor.cache_manager.lookup(self)
    logger.info("    Retrieved from cache") if resp
    resp
  end
end

#fetch_responseObject

Performs all the work. Handles caching, redirects, auth retries, etc



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/resourceful/request.rb', line 48

def fetch_response
  if cached_response
    if needs_revalidation?(cached_response)
      logger.info("    Cache needs revalidation")
      set_validation_headers!(cached_response)
    else
      # We're done!
      return cached_response
    end
  end

  response = perform!

  response = revalidate_cached_response(response) if cached_response && response.not_modified?
  response = follow_redirect(response)            if should_be_redirected?(response)
  response = retry_with_auth(response)            if needs_authorization?(response)

  raise UnsuccessfulHttpRequestError.new(self, response) if response.error?

  if cacheable?(response)
    store_in_cache(response)
  elsif invalidates_cache?
    invalidate_cache
  end

  return response
end

#follow_redirect(response) ⇒ Object

Follow a redirect response



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/resourceful/request.rb', line 102

def follow_redirect(response)
  raise MalformedServerResponse.new(self, response) unless response.header.location
  if response.moved_permanently?
    new_uri = response.header.location.first
    logger.info("    Permanently redirected to #{new_uri} - Storing new location.")
    resource.update_uri new_uri
    @header.host = resource.host
    response = fetch_response
  elsif response.see_other? # Always use GET for this redirect, regardless of initial method
    redirected_resource = Resourceful::Resource.new(self.accessor, response.header['Location'].first)
    response = Request.new(:get, redirected_resource, body, header).fetch_response
  else
    redirected_resource = Resourceful::Resource.new(self.accessor, response.header['Location'].first)
    logger.info("    Redirected to #{redirected_resource.uri} - Caching new location.")
    response = Request.new(method, redirected_resource, body, header).fetch_response
  end
end

#forces_revalidation?Boolean

Does this request force us to revalidate the cache?

Returns:

  • (Boolean)


208
209
210
211
212
213
214
215
# File 'lib/resourceful/request.rb', line 208

def forces_revalidation?
  if max_age == 0 || skip_cache?
    logger.info("    Client forced revalidation")
    true
  else 
    false
  end
end

#invalidate_cacheObject

Invalidated the cache for this uri (eg, after a POST)



146
147
148
# File 'lib/resourceful/request.rb', line 146

def invalidate_cache
  accessor.cache_manager.invalidate(resource.uri)
end

#invalidates_cache?Boolean

Does this request invalidate the cache?

Returns:

  • (Boolean)


159
160
161
# File 'lib/resourceful/request.rb', line 159

def invalidates_cache?
  return true if method.in? INVALIDATING_METHODS
end

#loggerObject



226
227
228
# File 'lib/resourceful/request.rb', line 226

def logger
  resource.logger
end

#max_ageObject

Indicates the maxmimum response age in seconds we are willing to accept

Returns nil if we don’t care how old the response is



220
221
222
223
224
# File 'lib/resourceful/request.rb', line 220

def max_age
  if header['Cache-Control'] and header['Cache-Control'].include?('max-age')
    header['Cache-Control'].split(',').grep(/max-age/).first.split('=').last.to_i
  end
end

#needs_authorization?(response) ⇒ Boolean

Does this request need to be authorized? Will only be true if we haven’t already tried with auth

Returns:

  • (Boolean)


131
132
133
# File 'lib/resourceful/request.rb', line 131

def needs_authorization?(response)
  !@already_tried_with_auth && response.unauthorized?
end

#needs_revalidation?(response) ⇒ Boolean

Do we need to revalidate our cache?

Returns:

  • (Boolean)


187
188
189
190
191
192
193
# File 'lib/resourceful/request.rb', line 187

def needs_revalidation?(response)
  return true if forces_revalidation?
  return true if response.stale?
  return true if max_age && response.current_age > max_age
  return true if response.must_be_revalidated?
  false
end

#perform!Object

Perform the request, with no magic handling of anything.



164
165
166
167
168
169
170
171
172
173
# File 'lib/resourceful/request.rb', line 164

def perform!
  @request_time = Time.now

  http_resp = adapter.make_request(@method, @resource.uri, @body, @header)
  @response = Resourceful::Response.new(uri, *http_resp)
  @response.request_time = @request_time
  @response.authoritative = true

  @response
end

#retry_with_auth(response) ⇒ Object

Add any auth headers from the response to the auth manager, and try the request again



121
122
123
124
125
126
127
128
# File 'lib/resourceful/request.rb', line 121

def retry_with_auth(response)
  @already_tried_with_auth = true
  logger.info("Authentication Required. Retrying with auth info")
  accessor.auth_manager.associate_auth_info(response)
  add_credentials!
  @body.rewind if @body # Its a stringIO, and we already fed it to the adapter once, so rewind it when we try again
  response = fetch_response
end

#revalidate_cached_response(not_modified_response) ⇒ Object

Revalidate the cached response with what we got from a 304 response



95
96
97
98
99
# File 'lib/resourceful/request.rb', line 95

def revalidate_cached_response(not_modified_response)
  logger.info("    Resource not modified")
  cached_response.revalidate!(not_modified_response)
  cached_response
end

#set_validation_headers!(response) ⇒ Object

Set the validation headers of a request based on the response in the cache



196
197
198
199
200
# File 'lib/resourceful/request.rb', line 196

def set_validation_headers!(response)
  @header['If-None-Match'] = response.header['ETag'] if response.header.has_key?('ETag')
  @header['If-Modified-Since'] = response.header['Last-Modified'] if response.header.has_key?('Last-Modified')
  @header['Cache-Control'] = 'max-age=0' if response.must_be_revalidated?    
end

#should_be_redirected?(response) ⇒ Boolean

Is this a response a redirect, and are we permitted to follow it?

Returns:

  • (Boolean)


176
177
178
179
180
181
182
183
184
# File 'lib/resourceful/request.rb', line 176

def should_be_redirected?(response)
  return false unless response.redirect?
  if resource.on_redirect.nil?
    return true if method.in? REDIRECTABLE_METHODS
    false
  else
    resource.on_redirect.call(self, response)
  end
end

#skip_cache?Boolean

Should we look for a response to this request in the cache?

Returns:

  • (Boolean)


77
78
79
80
# File 'lib/resourceful/request.rb', line 77

def skip_cache?
  return true unless method.in? CACHEABLE_METHODS
  header.cache_control && header.cache_control.include?('no-cache')
end

#store_in_cache(response) ⇒ Object

Store the response to this request in the cache



136
137
138
139
140
141
142
143
# File 'lib/resourceful/request.rb', line 136

def store_in_cache(response)
  # RFC2618 - 14.18 : A received message that does not have a Date header 
  # field MUST be assigned one by the recipient if the message will be cached 
  # by that recipient.
  response.header.date ||= response.response_time.httpdate

  accessor.cache_manager.store(self, response) 
end

#uriString

Returns The URI against which this request will be, or was, made.

Returns:

  • (String)

    The URI against which this request will be, or was, made.



203
204
205
# File 'lib/resourceful/request.rb', line 203

def uri
  resource.uri
end