wrest 1.0.2-universal-java-1.6 → 1.1.0-universal-java-1.6
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.
- data/CHANGELOG +3 -0
- data/README.rdoc +30 -5
- data/bin/wrest.compiled.rbc +78 -0
- data/bin/wrest_shell.rb +1 -1
- data/bin/wrest_shell.rbc +659 -0
- data/lib/wrest.rb +36 -2
- data/lib/wrest/cache_proxy.rb +111 -0
- data/lib/wrest/components/cache_store/memcached.rb +34 -0
- data/lib/wrest/curl.rb +2 -2
- data/lib/wrest/curl/request.rb +2 -2
- data/lib/wrest/hash_with_case_insensitive_access.rb +52 -0
- data/lib/wrest/native/get.rb +33 -3
- data/lib/wrest/native/redirection.rb +1 -1
- data/lib/wrest/native/request.rb +6 -6
- data/lib/wrest/native/response.rb +168 -34
- data/lib/wrest/version.rb +1 -12
- metadata +112 -20
- data/lib/wrest/resource.rb +0 -18
- data/lib/wrest/resource/base.rb +0 -101
- data/lib/wrest/resource/collection.rb +0 -12
- data/lib/wrest/resource/state.rb +0 -6
data/lib/wrest.rb
CHANGED
@@ -38,15 +38,46 @@ module Wrest
|
|
38
38
|
end
|
39
39
|
|
40
40
|
# Switch Wrest to using Net::HTTP.
|
41
|
-
def self.use_native
|
41
|
+
def self.use_native!
|
42
42
|
silence_warnings{ Wrest.const_set('Http', Wrest::Native) }
|
43
43
|
end
|
44
44
|
|
45
45
|
# Switch Wrest to using libcurl.
|
46
|
-
def self.use_curl
|
46
|
+
def self.use_curl!
|
47
47
|
require "#{Wrest::Root}/wrest/curl"
|
48
48
|
silence_warnings{ Wrest.const_set('Http', Wrest::Curl) }
|
49
49
|
end
|
50
|
+
|
51
|
+
# Loads the Memcached caching back-end and the Dalli gem
|
52
|
+
def self.enable_memcached_caching!
|
53
|
+
require "#{Wrest::Root}/wrest/components/cache_store/memcached"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Assign the default cache store to be used. Default is none.
|
57
|
+
def self.default_cachestore=(cachestore)
|
58
|
+
@default_cachestore = cachestore
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the default cache store, if any is set.
|
62
|
+
def self.default_cachestore
|
63
|
+
@default_cachestore
|
64
|
+
end
|
65
|
+
|
66
|
+
# Configures Wrest to cache all requests. This will use the Memcached backend.
|
67
|
+
def self.always_cache_using_memcached!
|
68
|
+
self.enable_memcached_caching!
|
69
|
+
self.default_cachestore=Wrest::Components::CacheStore::Memcached.new
|
70
|
+
end
|
71
|
+
|
72
|
+
# Configures Wrest to cache all requests. This will use a Ruby Hash.
|
73
|
+
# WARNING: This should NEVER be used in a real environment. The Hash will keep on growing since Wrest does not limit the size of a cache store.
|
74
|
+
#
|
75
|
+
# Use the Memcached caching back-end for production since the Memcached process uses an LRU based cache removal policy
|
76
|
+
# that keeps the number of entries stored within bounds.
|
77
|
+
def self.always_cache_using_hash!
|
78
|
+
Wrest.logger.warn "Using an in-memory Hash as a cache store. This is dangerous if used in a production environment."
|
79
|
+
self.default_cachestore=Hash.new
|
80
|
+
end
|
50
81
|
end
|
51
82
|
|
52
83
|
Wrest.logger = ActiveSupport::BufferedLogger.new(STDOUT)
|
@@ -56,16 +87,19 @@ RUBY_PLATFORM =~ /java/ ? gem('json-jruby', '>= 1.4.2') : gem('json', '>= 1.4.2'
|
|
56
87
|
ActiveSupport::JSON.backend = "JSONGem"
|
57
88
|
|
58
89
|
require "#{Wrest::Root}/wrest/core_ext/string"
|
90
|
+
require "#{Wrest::Root}/wrest/hash_with_case_insensitive_access"
|
59
91
|
|
60
92
|
# Load XmlMini Extensions
|
61
93
|
require "#{Wrest::Root}/wrest/xml_mini"
|
62
94
|
|
63
95
|
# Load Wrest Core
|
64
96
|
require "#{Wrest::Root}/wrest/version"
|
97
|
+
require "#{Wrest::Root}/wrest/cache_proxy"
|
65
98
|
require "#{Wrest::Root}/wrest/http_shared"
|
66
99
|
require "#{Wrest::Root}/wrest/http_codes"
|
67
100
|
require "#{Wrest::Root}/wrest/native"
|
68
101
|
|
102
|
+
|
69
103
|
# Load Wrest Wrappers
|
70
104
|
require "#{Wrest::Root}/wrest/uri"
|
71
105
|
require "#{Wrest::Root}/wrest/uri_template"
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Wrest
|
2
|
+
|
3
|
+
class CacheProxy
|
4
|
+
class << self
|
5
|
+
def new(get, cache_store)
|
6
|
+
if cache_store
|
7
|
+
DefaultCacheProxy.new(get, cache_store)
|
8
|
+
else
|
9
|
+
NullCacheProxy.new(get)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class NullCacheProxy
|
15
|
+
def initialize(get)
|
16
|
+
@get = get
|
17
|
+
end
|
18
|
+
def get
|
19
|
+
@get.invoke_without_cache_check
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class DefaultCacheProxy
|
24
|
+
HOP_BY_HOP_HEADERS = ["connection",
|
25
|
+
"keep-alive",
|
26
|
+
"proxy-authenticate",
|
27
|
+
"proxy-authorization",
|
28
|
+
"te",
|
29
|
+
"trailers",
|
30
|
+
"transfer-encoding",
|
31
|
+
"upgrade"]
|
32
|
+
|
33
|
+
def initialize(get, cache_store)
|
34
|
+
@get = get
|
35
|
+
@cache_store = cache_store
|
36
|
+
end
|
37
|
+
|
38
|
+
def log_cached_response
|
39
|
+
Wrest.logger.debug "<*> (GET #{@get.hash}) #{@get.uri.protocol}://#{@get.uri.host}:#{@get.uri.port}#{@get.http_request.path}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def get
|
43
|
+
cached_response = @cache_store[@get.hash]
|
44
|
+
return get_fresh_response if cached_response.nil?
|
45
|
+
|
46
|
+
if cached_response.expired?
|
47
|
+
if cached_response.can_be_validated?
|
48
|
+
get_validated_response_for(cached_response)
|
49
|
+
else
|
50
|
+
get_fresh_response
|
51
|
+
end
|
52
|
+
else
|
53
|
+
log_cached_response
|
54
|
+
cached_response
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def update_cache_headers_for(cached_response, new_response)
|
59
|
+
# RFC 2616 13.5.3 (Combining Headers)
|
60
|
+
cached_response.headers.merge!(new_response.headers.select {|key, value| not (HOP_BY_HOP_HEADERS.include? key.downcase)})
|
61
|
+
end
|
62
|
+
|
63
|
+
def cache(response)
|
64
|
+
@cache_store[@get.hash] = response.clone if response && response.cacheable?
|
65
|
+
end
|
66
|
+
|
67
|
+
#:nodoc:
|
68
|
+
def get_fresh_response
|
69
|
+
@cache_store.delete @get.hash
|
70
|
+
|
71
|
+
response = @get.invoke_without_cache_check
|
72
|
+
|
73
|
+
cache(response)
|
74
|
+
|
75
|
+
response
|
76
|
+
end
|
77
|
+
|
78
|
+
#:nodoc:
|
79
|
+
def get_validated_response_for(cached_response)
|
80
|
+
new_response = send_validation_request_for(cached_response)
|
81
|
+
if new_response.code == "304"
|
82
|
+
update_cache_headers_for(cached_response, new_response)
|
83
|
+
log_cached_response
|
84
|
+
cached_response
|
85
|
+
else
|
86
|
+
cache(new_response)
|
87
|
+
new_response
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
#:nodoc:
|
92
|
+
# Send a cache-validation request to the server. This would be the actual Get request with extra cache-validation headers.
|
93
|
+
# If a 304 (Not Modified) is received, Wrest would use the cached_response itself. Otherwise the new response is cached and used.
|
94
|
+
def send_validation_request_for(cached_response)
|
95
|
+
last_modified = cached_response.last_modified
|
96
|
+
etag = cached_response.headers["etag"]
|
97
|
+
|
98
|
+
cache_validation_headers = {}
|
99
|
+
cache_validation_headers["if-modified-since"] = last_modified unless last_modified.nil?
|
100
|
+
cache_validation_headers["if-none-match"] = etag unless etag.nil?
|
101
|
+
|
102
|
+
new_headers =@get.headers.clone.merge cache_validation_headers
|
103
|
+
new_options =@get.options.clone.tap { |opts| opts.delete :cache_store } # do not run this through the caching mechanism.
|
104
|
+
|
105
|
+
new_request = Wrest::Native::Get.new(@get.uri, @get.parameters, new_headers, new_options)
|
106
|
+
|
107
|
+
new_request.invoke
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
gem 'dalli', '~> 1.0.1'
|
3
|
+
rescue Gem::LoadError => e
|
4
|
+
Wrest.logger.debug "Dalli ~> 1.0.1 not found. Dalli is necessary to use the memcached caching back-end. To install dalli run `(sudo) gem install dalli`."
|
5
|
+
raise e
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'dalli'
|
9
|
+
|
10
|
+
module Wrest::Components::CacheStore
|
11
|
+
class Memcached
|
12
|
+
|
13
|
+
def initialize(server_urls=nil, options={})
|
14
|
+
@memcached = Dalli::Client.new(server_urls, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](key)
|
18
|
+
@memcached.get(key)
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(key, value)
|
22
|
+
@memcached.set(key, value)
|
23
|
+
end
|
24
|
+
|
25
|
+
# should be compatible with Hash - return value of the deleted element.
|
26
|
+
def delete(key)
|
27
|
+
value = self[key]
|
28
|
+
|
29
|
+
@memcached.delete key
|
30
|
+
|
31
|
+
return value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/wrest/curl.rb
CHANGED
@@ -8,9 +8,9 @@
|
|
8
8
|
# See the License for the specific language governing permissions and limitations under the License.
|
9
9
|
|
10
10
|
begin
|
11
|
-
gem 'patron', '~> 0.4.
|
11
|
+
gem 'patron', '~> 0.4.11'
|
12
12
|
rescue Gem::LoadError => e
|
13
|
-
Wrest.logger.debug "Patron ~> 0.4.
|
13
|
+
Wrest.logger.debug "Patron ~> 0.4.11 not found. Patron is necessary to use libcurl. To install Patron run `sudo gem install patron` (patron is not available on JRuby, but you shouldn't need it anyway)."
|
14
14
|
raise e
|
15
15
|
end
|
16
16
|
require 'patron'
|
data/lib/wrest/curl/request.rb
CHANGED
@@ -68,8 +68,8 @@ module Wrest
|
|
68
68
|
# Data about the request is and logged to Wrest.logger
|
69
69
|
# The log entry contains the following information:
|
70
70
|
#
|
71
|
-
#
|
72
|
-
#
|
71
|
+
# <- indicates a request
|
72
|
+
# -> indicates a response
|
73
73
|
#
|
74
74
|
# The type of request is mentioned in caps, followed by a hash
|
75
75
|
# uniquely uniquely identifying a particular request/response pair.
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Wrest
|
2
|
+
|
3
|
+
# A hash with case-insensitive key access.
|
4
|
+
#
|
5
|
+
# hash = Wrest::HashWithCaseInsensitiveAccess.new 'Abcd' => 1, 'xyz' => 2
|
6
|
+
#
|
7
|
+
# hash['abcd'] #=> 1
|
8
|
+
# hash['aBCd'] #=> 1
|
9
|
+
#
|
10
|
+
class HashWithCaseInsensitiveAccess < ::Hash #:nodoc:
|
11
|
+
|
12
|
+
def initialize(hash={})
|
13
|
+
super()
|
14
|
+
hash.each do |key, value|
|
15
|
+
self[convert_key(key)] = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
def [](key)
|
19
|
+
super(convert_key(key))
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(key, value)
|
23
|
+
super(convert_key(key), value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(key)
|
27
|
+
super(convert_key(key))
|
28
|
+
end
|
29
|
+
|
30
|
+
def values_at(*indices)
|
31
|
+
indices.collect { |key| self[convert_key(key)] }
|
32
|
+
end
|
33
|
+
|
34
|
+
def merge(other)
|
35
|
+
dup.merge!(other)
|
36
|
+
end
|
37
|
+
|
38
|
+
def merge!(other)
|
39
|
+
other.each do |key, value|
|
40
|
+
self[convert_key(key)] = value
|
41
|
+
end
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
def convert_key(key)
|
48
|
+
key.is_a?(String) ? key.downcase : key
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
data/lib/wrest/native/get.rb
CHANGED
@@ -8,18 +8,48 @@
|
|
8
8
|
|
9
9
|
module Wrest::Native
|
10
10
|
class Get < Request
|
11
|
+
|
12
|
+
attr_reader :cache_proxy
|
13
|
+
|
11
14
|
def initialize(wrest_uri, parameters = {}, headers = {}, options = {})
|
12
15
|
follow_redirects = options[:follow_redirects]
|
13
16
|
options[:follow_redirects] = (follow_redirects == nil ? true : follow_redirects)
|
14
|
-
options[:cache_store]
|
17
|
+
@cache_proxy = Wrest::CacheProxy::new(self, options[:cache_store] || Wrest.default_cachestore)
|
15
18
|
super(
|
16
|
-
wrest_uri,
|
17
|
-
Net::HTTP::Get,
|
19
|
+
wrest_uri,
|
20
|
+
Net::HTTP::Get,
|
18
21
|
parameters,
|
19
22
|
nil,
|
20
23
|
headers,
|
21
24
|
options
|
22
25
|
)
|
23
26
|
end
|
27
|
+
|
28
|
+
# Checks equality between two Wrest::Native::Get objects.
|
29
|
+
# Comparing two Wrest::Native::Get objects with identical values for the following properties would return True.
|
30
|
+
# uri, parameters, username, password and ssh verify_mode.
|
31
|
+
def ==(other)
|
32
|
+
return true if self.equal?(other)
|
33
|
+
return false unless other.class == self.class
|
34
|
+
return true if self.uri == other.uri and
|
35
|
+
self.parameters == other.parameters and
|
36
|
+
self.username == other.username and
|
37
|
+
self.password == other.password and
|
38
|
+
self.verify_mode == other.verify_mode
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns a hash value for this Wrest::Native::Get object.
|
43
|
+
# Objects that returns true when compared using the == operator would return the same hash value also.
|
44
|
+
def hash
|
45
|
+
self.uri.hash + self.parameters.hash + self.username.hash + self.password.hash + self.verify_mode.hash + 20110106
|
46
|
+
end
|
47
|
+
|
48
|
+
#:nodoc:
|
49
|
+
def invoke_with_cache_check
|
50
|
+
cache_proxy.get
|
51
|
+
end
|
52
|
+
|
53
|
+
alias_method_chain :invoke, :cache_check
|
24
54
|
end
|
25
55
|
end
|
@@ -30,7 +30,7 @@ module Wrest #:nodoc:
|
|
30
30
|
|
31
31
|
raise Wrest::Exceptions::AutoRedirectLimitExceeded if (redirect_request_options[:follow_redirects_count] += 1) > redirect_request_options[:follow_redirects_limit]
|
32
32
|
|
33
|
-
Wrest.logger.debug "
|
33
|
+
Wrest.logger.debug "-| Redirecting to #{target}"
|
34
34
|
Wrest::Uri.new(target, redirect_request_options).get
|
35
35
|
end
|
36
36
|
end
|
data/lib/wrest/native/request.rb
CHANGED
@@ -13,7 +13,7 @@ module Wrest::Native
|
|
13
13
|
# or Wrest::Native::Get etc. instead.
|
14
14
|
class Request
|
15
15
|
attr_reader :http_request, :uri, :body, :headers, :username, :password, :follow_redirects,
|
16
|
-
:follow_redirects_limit, :follow_redirects_count, :timeout, :connection, :parameters, :cache_store
|
16
|
+
:follow_redirects_limit, :follow_redirects_count, :timeout, :connection, :parameters, :cache_store, :verify_mode, :options
|
17
17
|
# Valid tuples for the options are:
|
18
18
|
# :username => String, defaults to nil
|
19
19
|
# :password => String, defaults to nil
|
@@ -32,12 +32,12 @@ module Wrest::Native
|
|
32
32
|
# :connection => The HTTP Connection object to use. This is how a keep-alive connection can be
|
33
33
|
# used for multiple requests.
|
34
34
|
# :verify_mode => The verification mode to be used for Net::HTTP https connections. Defaults to OpenSSL::SSL::VERIFY_PEER
|
35
|
-
# :cache_store => The object which should be used as cache store for cacheable responses
|
35
|
+
# :cache_store => The object which should be used as cache store for cacheable responses. If not supplied, caching will be disabled.
|
36
36
|
# :detailed_http_logging => nil/$stdout/$stderr or File/Logger/IO object. Defaults to nil (recommended).
|
37
37
|
# :callback => A Hash whose keys are the response codes (or Range of response codes),
|
38
38
|
# and the values are the callback functions to be executed.
|
39
39
|
# eg: { <response code> => lambda { |response| some_operation } }
|
40
|
-
#
|
40
|
+
#
|
41
41
|
# *WARNING* : detailed_http_logging causes a serious security hole. Never use it in production code.
|
42
42
|
#
|
43
43
|
def initialize(wrest_uri, http_request_klass, parameters = {}, body = nil, headers = {}, options = {})
|
@@ -66,8 +66,8 @@ module Wrest::Native
|
|
66
66
|
# Data about the request is and logged to Wrest.logger
|
67
67
|
# The log entry contains the following information:
|
68
68
|
#
|
69
|
-
#
|
70
|
-
#
|
69
|
+
# <- indicates a request
|
70
|
+
# -> indicates a response
|
71
71
|
#
|
72
72
|
# The type of request is mentioned in caps, followed by a hash
|
73
73
|
# uniquely identifying a particular request/response pair.
|
@@ -85,7 +85,7 @@ module Wrest::Native
|
|
85
85
|
@connection.set_debug_output @detailed_http_logging
|
86
86
|
http_request.basic_auth username, password unless username.nil? || password.nil?
|
87
87
|
|
88
|
-
prefix = "#{http_request.method} #{
|
88
|
+
prefix = "#{http_request.method} #{self.hash} #{@connection.hash}"
|
89
89
|
|
90
90
|
Wrest.logger.debug "<- (#{prefix}) #{@uri.protocol}://#{@uri.host}:#{@uri.port}#{@http_request.path}"
|
91
91
|
time = Benchmark.realtime { response = Wrest::Native::Response.new( do_request ) }
|
@@ -27,24 +27,50 @@ module Wrest #:nodoc:
|
|
27
27
|
include HttpCodes
|
28
28
|
|
29
29
|
extend Forwardable
|
30
|
-
def_delegators :@http_response, :code, :message, :body, :
|
31
|
-
:
|
32
|
-
:get_fields, :key?, :type_params
|
30
|
+
def_delegators :@http_response, :code, :message, :body, :http_version,
|
31
|
+
:content_length, :content_type
|
33
32
|
|
34
|
-
|
33
|
+
def_delegators :headers, :[]
|
34
|
+
|
35
|
+
# TODO : Are these needed in the Response namespace itself? Can be accessed from the headers method.
|
36
|
+
def_delegators :@http_response, :each_header, :each_name, :each_value, :fetch,
|
37
|
+
:get_fields, :key?, :type_params
|
38
|
+
|
39
|
+
# We're overriding :new to act as a factory so
|
35
40
|
# we can build the appropriate Response instance based
|
36
|
-
# on
|
41
|
+
# on the response code.
|
37
42
|
def self.new(http_response)
|
38
43
|
code = http_response.code.to_i
|
39
44
|
instance = ((300..303).include?(code) || (305..399).include?(code) ? Wrest::Native::Redirection : self).allocate
|
40
45
|
instance.send :initialize, http_response
|
41
46
|
instance
|
42
47
|
end
|
43
|
-
|
48
|
+
|
44
49
|
def initialize(http_response)
|
45
50
|
@http_response = http_response
|
46
51
|
end
|
47
52
|
|
53
|
+
def initialize_copy(source)
|
54
|
+
@headers = source.headers.clone
|
55
|
+
end
|
56
|
+
|
57
|
+
# Checks equality between two Wrest::Native::Response objects.
|
58
|
+
def ==(other)
|
59
|
+
return true if self.equal?(other)
|
60
|
+
return false unless other.class == self.class
|
61
|
+
return true if self.code == other.code and
|
62
|
+
self.headers == other.headers and
|
63
|
+
self.http_version == other.http_version and
|
64
|
+
self.message == other.message and
|
65
|
+
self.body == other.body
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
# Return the hash of a Wrest::Native::Response object.
|
70
|
+
def hash
|
71
|
+
self.code.hash + self.message.hash + self.headers.hash + self.http_version.hash + self.body.hash
|
72
|
+
end
|
73
|
+
|
48
74
|
|
49
75
|
def deserialise(options = {})
|
50
76
|
deserialise_using(Wrest::Components::Translators.lookup(@http_response.content_type),options)
|
@@ -54,13 +80,22 @@ module Wrest #:nodoc:
|
|
54
80
|
translator.deserialise(@http_response,options)
|
55
81
|
end
|
56
82
|
|
83
|
+
# Gives a hash of the response headers. The keys of the hash are case-insensitive.
|
57
84
|
def headers
|
58
|
-
@
|
85
|
+
return @headers if @headers
|
86
|
+
|
87
|
+
nethttp_headers_with_string_values=@http_response.to_hash.inject({}) {|new_headers, (old_key, old_value)|
|
88
|
+
new_headers[old_key] = old_value.is_a?(Array) ? old_value.join(",") : old_value
|
89
|
+
new_headers
|
90
|
+
}
|
91
|
+
|
92
|
+
@headers=Wrest::HashWithCaseInsensitiveAccess.new(nethttp_headers_with_string_values)
|
93
|
+
|
59
94
|
end
|
60
|
-
|
95
|
+
|
61
96
|
# A null object implementation - invoking this method on
|
62
97
|
# a response simply returns the same response unless
|
63
|
-
# the response is Redirection (code 3xx), in which case a
|
98
|
+
# the response is Redirection (code 3xx), in which case a
|
64
99
|
# get is invoked on the url stored in the response headers
|
65
100
|
# under the key 'location' and the new Response is returned.
|
66
101
|
def follow(redirect_request_options = {})
|
@@ -71,12 +106,29 @@ module Wrest #:nodoc:
|
|
71
106
|
self[Native::StandardHeaders::Connection].downcase == Native::StandardTokens::Close.downcase
|
72
107
|
end
|
73
108
|
|
109
|
+
# Returns whether this response is cacheable.
|
74
110
|
def cacheable?
|
75
|
-
code_cacheable? && no_cache_flag_not_set? && no_store_flag_not_set? &&
|
111
|
+
code_cacheable? && no_cache_flag_not_set? && no_store_flag_not_set? &&
|
112
|
+
(not max_age.nil? or (expires_not_in_our_past? && expires_not_in_its_past?)) && pragma_nocache_not_set? &&
|
113
|
+
vary_tag_not_set?
|
76
114
|
end
|
77
115
|
|
116
|
+
#:nodoc:
|
78
117
|
def code_cacheable?
|
79
|
-
!code.nil? &&
|
118
|
+
!code.nil? && ([200, 203, 300, 301, 302, 304, 307].include?(code.to_i))
|
119
|
+
end
|
120
|
+
|
121
|
+
#:nodoc:
|
122
|
+
def max_age
|
123
|
+
return @max_age if @max_age
|
124
|
+
|
125
|
+
max_age =cache_control_headers.grep(/max-age/)
|
126
|
+
|
127
|
+
@max_age = unless max_age.empty?
|
128
|
+
max_age.first.split('=').last.to_i
|
129
|
+
else
|
130
|
+
nil
|
131
|
+
end
|
80
132
|
end
|
81
133
|
|
82
134
|
def no_cache_flag_not_set?
|
@@ -87,40 +139,122 @@ module Wrest #:nodoc:
|
|
87
139
|
not cache_control_headers.include?('no-store')
|
88
140
|
end
|
89
141
|
|
90
|
-
def
|
91
|
-
|
92
|
-
|
93
|
-
|
142
|
+
def pragma_nocache_not_set?
|
143
|
+
headers['pragma'].nil? || (not headers['pragma'].include? 'no-cache')
|
144
|
+
end
|
145
|
+
|
146
|
+
#:nodoc:
|
147
|
+
def vary_tag_not_set?
|
148
|
+
headers['vary'].nil?
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns the Date from the response headers.
|
152
|
+
def response_date
|
153
|
+
return @response_date if @response_date
|
154
|
+
@response_date = parse_datefield(headers, "date")
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
# Returns the Expires date from the response headers.
|
159
|
+
def expires
|
160
|
+
return @expires if @expires
|
161
|
+
@expires = parse_datefield(headers, "expires")
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns whether the Expires header of this response is earlier than current time.
|
165
|
+
def expires_not_in_our_past?
|
166
|
+
if expires.nil?
|
167
|
+
false
|
94
168
|
else
|
95
|
-
|
96
|
-
expires_on > DateTime.now
|
169
|
+
expires.to_i > Time.now.to_i
|
97
170
|
end
|
98
171
|
end
|
99
172
|
|
173
|
+
# Is the Expires of this response earlier than its Date header.
|
174
|
+
def expires_not_in_its_past?
|
175
|
+
# Invalid header value for Date or Expires means the response is not cacheable
|
176
|
+
if expires.nil? || response_date.nil?
|
177
|
+
false
|
178
|
+
else
|
179
|
+
expires > response_date
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Age of the response calculated according to RFC 2616 13.2.3
|
184
|
+
def current_age
|
185
|
+
current_time = Time.now.to_i
|
186
|
+
|
187
|
+
# RFC 2616 13.2.3 Age Calculations. TODO: include response_delay in the calculation as defined in RFC. For this, include original Request with Response.
|
188
|
+
date_value = DateTime.parse(headers['date']).to_i rescue current_time
|
189
|
+
age_value = headers['age'].to_i || 0
|
190
|
+
|
191
|
+
apparent_age = current_time - date_value
|
192
|
+
|
193
|
+
[apparent_age, age_value].max
|
194
|
+
end
|
195
|
+
|
196
|
+
# The values in Cache-Control header as an array.
|
100
197
|
def cache_control_headers
|
101
|
-
@cache_control_headers
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
198
|
+
@cache_control_headers ||= recalculate_cache_control_headers
|
199
|
+
end
|
200
|
+
|
201
|
+
#:nodoc:
|
202
|
+
def recalculate_cache_control_headers
|
203
|
+
headers['cache-control'].split(",").collect {|cc| cc.strip } rescue []
|
204
|
+
end
|
205
|
+
|
206
|
+
# How long (in seconds) is this response expected to be fresh
|
207
|
+
def freshness_lifetime
|
208
|
+
@freshness_lifetime ||= recalculate_freshness_lifetime
|
209
|
+
end
|
210
|
+
|
211
|
+
#:nodoc:
|
212
|
+
def recalculate_freshness_lifetime
|
213
|
+
return max_age if max_age
|
214
|
+
|
215
|
+
response_date = DateTime.parse(headers['date']).to_i
|
216
|
+
expires_date = DateTime.parse(headers['expires']).to_i
|
217
|
+
|
218
|
+
return (expires_date - response_date)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Has this response expired? The expiry is calculated from the Max-Age/Expires header.
|
222
|
+
def expired?
|
223
|
+
freshness=freshness_lifetime
|
224
|
+
if freshness <= 0
|
225
|
+
return true
|
108
226
|
end
|
227
|
+
|
228
|
+
freshness <= current_age
|
109
229
|
end
|
110
230
|
|
111
|
-
|
231
|
+
def last_modified
|
232
|
+
headers['last-modified']
|
233
|
+
end
|
234
|
+
|
235
|
+
# Can this response be validated by sending a validation request to the server. The response need to have either
|
236
|
+
# Last-Modified or ETag header (or both) for it to be validatable.
|
237
|
+
def can_be_validated?
|
238
|
+
not (last_modified.nil? and headers['etag'].nil?)
|
239
|
+
end
|
112
240
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
241
|
+
|
242
|
+
#:nodoc:
|
243
|
+
# helper function. Used to parse date fields.
|
244
|
+
# this function is used and tested by the expires and response_date methods
|
245
|
+
def parse_datefield(hash, key)
|
246
|
+
if hash[key]
|
247
|
+
# Can't trust external input. Do not crash even if invalid dates are passed.
|
248
|
+
begin
|
249
|
+
DateTime.parse(hash[key].to_s)
|
250
|
+
rescue ArgumentError
|
251
|
+
nil
|
252
|
+
end
|
253
|
+
else
|
254
|
+
nil
|
121
255
|
end
|
122
|
-
cache_headers
|
123
256
|
end
|
257
|
+
|
124
258
|
end
|
125
259
|
end
|
126
260
|
end
|