hurley-http-cache 0.1.0.beta → 0.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.
- checksums.yaml +4 -4
- data/lib/hurley/http_cache.rb +1 -1
- data/lib/hurley/http_cache/cache_control.rb +2 -1
- data/lib/hurley/http_cache/request.rb +30 -0
- data/lib/hurley/http_cache/response.rb +1 -1
- data/lib/hurley/http_cache/storage.rb +22 -55
- data/test/http_cache/request_test.rb +54 -0
- data/test/http_cache/storage_test.rb +1 -1
- metadata +32 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8f6cd27a44789b425f61cb2f6290640ec1895e6
|
4
|
+
data.tar.gz: a42afa9bf4a1308195da0d408c1c44ba7adb1bb2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f6b829f62715e840786ba9c4583145992adf98ef16f682b322b376b30ea5e3ed622f5a0a8952cac8709666af39fccf498f06de74b14e05d9c146df836eafef26
|
7
|
+
data.tar.gz: fa0d454ada4b5413dd64a366d2928ebfb123ac487c992d5dc1c377be02a2867547696bf1b632a76ce9933806cae8e9f4ea9515af042e7a1a25e25e51abb2dfb9
|
data/lib/hurley/http_cache.rb
CHANGED
@@ -17,6 +17,17 @@ module Hurley
|
|
17
17
|
true
|
18
18
|
end
|
19
19
|
|
20
|
+
# Internal: Check if a pair of existing request and response matches the
|
21
|
+
# current request object.
|
22
|
+
#
|
23
|
+
# request - The Hash of the request that was cached.
|
24
|
+
# response - The Hash of the response that was cached.
|
25
|
+
#
|
26
|
+
# Returns true or false.
|
27
|
+
def matches?(request, response)
|
28
|
+
verb.to_s == request['verb'] && vary_matches?(request, response)
|
29
|
+
end
|
30
|
+
|
20
31
|
# Internal: Check if the request can't be cached, accordingly to the
|
21
32
|
# 'Cache-Control' header.
|
22
33
|
#
|
@@ -39,6 +50,25 @@ module Hurley
|
|
39
50
|
|
40
51
|
private
|
41
52
|
|
53
|
+
# Internal: Check if the header fields described in a response 'Vary'
|
54
|
+
# header matches between the current request and an existing request
|
55
|
+
# Hash. The 'Vary' header can be empty (meaning that the requests match),
|
56
|
+
# a '*' String (meaning that the request never match) or be a comma
|
57
|
+
# separated list of the headers that should be compared.
|
58
|
+
#
|
59
|
+
# request - The Hash of the request that was cached.
|
60
|
+
# response - The Hash of the response that was cached.
|
61
|
+
#
|
62
|
+
# Returns true or false.
|
63
|
+
def vary_matches?(request, response)
|
64
|
+
response_headers = Hurley::Header.new(response['header'])
|
65
|
+
vary = response_headers['Vary'].to_s
|
66
|
+
|
67
|
+
vary.empty? || (vary != '*' && vary.split(/[\s,]+/).all? do |key|
|
68
|
+
header[key] == request['header'][key]
|
69
|
+
end)
|
70
|
+
end
|
71
|
+
|
42
72
|
# Internal: Get a CacheControl object to inspect the directives in the
|
43
73
|
# 'Cache-Control' header.
|
44
74
|
#
|
@@ -156,7 +156,7 @@ module Hurley
|
|
156
156
|
# Internal: The logic behind cacheable_in_private_cache? and
|
157
157
|
# cacheable_in_shared_cache? The logic is the same except for the
|
158
158
|
# treatment of the private Cache-Control directive.
|
159
|
-
def cacheable?(shared:)
|
159
|
+
def cacheable?(shared: false)
|
160
160
|
return false if (cache_control.private? && shared) || cache_control.no_store?
|
161
161
|
|
162
162
|
cacheable_status_code? && (validateable? || fresh?)
|
@@ -32,7 +32,9 @@ module Hurley
|
|
32
32
|
assert_valid_store!
|
33
33
|
end
|
34
34
|
|
35
|
-
# Internal: Store a response inside the cache.
|
35
|
+
# Internal: Store a response inside the cache. Existing entries for the
|
36
|
+
# exact same request will be removed from the cache and replaced by the
|
37
|
+
# given response object.
|
36
38
|
#
|
37
39
|
# request - A Hurley::HttpCache::::Request instance of the executed HTTP
|
38
40
|
# request.
|
@@ -41,16 +43,12 @@ module Hurley
|
|
41
43
|
# Returns nothing.
|
42
44
|
def write(request, response)
|
43
45
|
key = cache_key_for(request.url)
|
44
|
-
entry =
|
46
|
+
entry = read_entry(key) || []
|
45
47
|
|
46
|
-
|
48
|
+
entry.reject! { |(req, res)| request.matches?(req, res) }
|
49
|
+
entry << [request.serializable_hash, response.serializable_hash]
|
47
50
|
|
48
|
-
|
49
|
-
response_matches?(request, cached_request, cached_response)
|
50
|
-
end
|
51
|
-
|
52
|
-
entries << entry
|
53
|
-
cache.write(key, entries)
|
51
|
+
write_entry(key, entry)
|
54
52
|
rescue Encoding::UndefinedConversionError => e
|
55
53
|
warn { "Response could not be serialized: #{e.message}. Try using Marshal to serialize." }
|
56
54
|
raise e
|
@@ -65,8 +63,8 @@ module Hurley
|
|
65
63
|
# Returns a Hash.
|
66
64
|
def read(request)
|
67
65
|
cache_key = cache_key_for(request.url)
|
68
|
-
|
69
|
-
lookup_response(request,
|
66
|
+
entry = read_entry(cache_key)
|
67
|
+
lookup_response(request, entry)
|
70
68
|
end
|
71
69
|
|
72
70
|
def delete(url)
|
@@ -76,61 +74,30 @@ module Hurley
|
|
76
74
|
|
77
75
|
private
|
78
76
|
|
77
|
+
def read_entry(key)
|
78
|
+
entry = cache.read(key)
|
79
|
+
@serializer.load(entry) if entry
|
80
|
+
end
|
81
|
+
|
82
|
+
def write_entry(key, entry)
|
83
|
+
cache.write(key, @serializer.dump(entry))
|
84
|
+
end
|
85
|
+
|
79
86
|
# Internal: Retrieve a response Hash from the list of entries that match
|
80
87
|
# the given request.
|
81
88
|
#
|
82
89
|
# request - A Hurley::HttpCache::::Request instance of the incoming HTTP
|
83
90
|
# request.
|
84
|
-
#
|
91
|
+
# entry - An Array of pairs of Hashes (request, response).
|
85
92
|
#
|
86
93
|
# Returns a Hash or nil.
|
87
|
-
def lookup_response(request,
|
88
|
-
if
|
89
|
-
|
90
|
-
_, response = entries.find { |req, res| response_matches?(request, req, res) }
|
94
|
+
def lookup_response(request, entry)
|
95
|
+
if entry
|
96
|
+
_, response = entry.find { |req, res| request.matches?(req, res) }
|
91
97
|
response
|
92
98
|
end
|
93
99
|
end
|
94
100
|
|
95
|
-
# Internal: Check if a cached response and request matches the given
|
96
|
-
# request.
|
97
|
-
#
|
98
|
-
# request - A Hurley::HttpCache::::Request instance of the
|
99
|
-
# current HTTP request.
|
100
|
-
# cached_request - The Hash of the request that was cached.
|
101
|
-
# cached_response - The Hash of the response that was cached.
|
102
|
-
#
|
103
|
-
# Returns true or false.
|
104
|
-
def response_matches?(request, cached_request, cached_response)
|
105
|
-
request.verb.to_s == cached_request['verb'] &&
|
106
|
-
vary_matches?(cached_response, request, cached_request)
|
107
|
-
end
|
108
|
-
|
109
|
-
def vary_matches?(cached_response, request, cached_request)
|
110
|
-
headers = Hurley::Header.new(cached_response['header'])
|
111
|
-
vary = headers['Vary'].to_s
|
112
|
-
|
113
|
-
vary.empty? || (vary != '*' && vary.split(/[\s,]+/).all? do |header|
|
114
|
-
request.header[header] == cached_request['header'][header]
|
115
|
-
end)
|
116
|
-
end
|
117
|
-
|
118
|
-
def serialize_entry(*objects)
|
119
|
-
objects.map { |object| serialize_object(object) }
|
120
|
-
end
|
121
|
-
|
122
|
-
def serialize_object(object)
|
123
|
-
@serializer.dump(object)
|
124
|
-
end
|
125
|
-
|
126
|
-
def deserialize_entry(*objects)
|
127
|
-
objects.map { |object| deserialize_object(object) }
|
128
|
-
end
|
129
|
-
|
130
|
-
def deserialize_object(object)
|
131
|
-
@serializer.load(object)
|
132
|
-
end
|
133
|
-
|
134
101
|
# Internal: Computes the cache key for a specific request, taking in
|
135
102
|
# account the current serializer to avoid cross serialization issues.
|
136
103
|
#
|
@@ -55,6 +55,60 @@ class RequestTest < MiniTest::Test
|
|
55
55
|
assert_predicate request, :no_cache?
|
56
56
|
end
|
57
57
|
|
58
|
+
def test_request_verb_matches
|
59
|
+
request = new_request(verb: :get)
|
60
|
+
|
61
|
+
refute request.matches?({ 'verb' => 'post' }, {})
|
62
|
+
refute request.matches?({ 'verb' => 'delete' }, {})
|
63
|
+
|
64
|
+
assert request.matches?({ 'verb' => 'get' }, {})
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_request_verb_and_Vary_matches
|
68
|
+
request = new_request(verb: :get, header: { 'User-Agent' => 'FooBar/1.0' })
|
69
|
+
|
70
|
+
req = new_request(verb: :get, header: { 'User-Agent' => 'FooBar/1.0' }).serializable_hash
|
71
|
+
res = { 'header' => { 'Vary' => 'User-Agent' } }
|
72
|
+
|
73
|
+
assert request.matches?(req, res)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_request_verb_and_multiple_Vary_matches
|
77
|
+
request = new_request(verb: :get, header: { 'User-Agent' => 'FooBar/1.0', 'Accept' => 'application/json' })
|
78
|
+
|
79
|
+
req = new_request(verb: :get, header: { 'User-Agent' => 'FooBar/1.0', 'Accept' => 'application/json' }).serializable_hash
|
80
|
+
res = { 'header' => { 'Vary' => 'User-Agent, Accept' } }
|
81
|
+
|
82
|
+
assert request.matches?(req, res)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_request_matches_wrong_Vary
|
86
|
+
request = new_request(verb: :get, header: { 'User-Agent' => 'FooBar/2.0' })
|
87
|
+
|
88
|
+
req = new_request(verb: :get, header: { 'User-Agent' => 'FooBar/1.0' }).serializable_hash
|
89
|
+
res = { 'header' => { 'Vary' => 'User-Agent' } }
|
90
|
+
|
91
|
+
refute request.matches?(req, res)
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_request_verb_and_any_wrong_Vary
|
95
|
+
request = new_request(verb: :get, header: { 'User-Agent' => 'FooBar/1.0', 'Accept' => 'application/json' })
|
96
|
+
|
97
|
+
req = new_request(verb: :get, header: { 'User-Agent' => 'FooBar/1.0', 'Accept' => 'text/html' }).serializable_hash
|
98
|
+
res = { 'header' => { 'Vary' => 'User-Agent, Accept' } }
|
99
|
+
|
100
|
+
refute request.matches?(req, res)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_request_Vary_star_never_matches
|
104
|
+
request = new_request(verb: :get, header: { 'User-Agent' => 'FooBar/1.0' })
|
105
|
+
|
106
|
+
req = new_request(verb: :get, header: { 'User-Agent' => 'FooBar/1.0' }).serializable_hash
|
107
|
+
res = { 'header' => { 'Vary' => '*' } }
|
108
|
+
|
109
|
+
refute request.matches?(req, res)
|
110
|
+
end
|
111
|
+
|
58
112
|
def test_serializable_hash
|
59
113
|
request = new_request(header: { 'User-Agent' => 'FooBar' })
|
60
114
|
hash = request.serializable_hash
|
@@ -26,7 +26,7 @@ module StorageTests
|
|
26
26
|
|
27
27
|
def test_writes_the_response_object_to_the_underlying_cache
|
28
28
|
@storage.write(@request, @response)
|
29
|
-
|
29
|
+
refute_nil @storage.cache.read(cache_key)
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_retrieves_the_response_from_the_cache
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hurley-http-cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lucas Mazza
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hurley
|
@@ -58,6 +58,34 @@ dependencies:
|
|
58
58
|
- - "~>"
|
59
59
|
- !ruby/object:Gem::Version
|
60
60
|
version: '10.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: sinatra
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: minitest
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 5.5.1
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 5.5.1
|
61
89
|
description: Hurley connection to handle HTTP caching
|
62
90
|
email:
|
63
91
|
- opensource@plataformatec.com.br
|
@@ -95,9 +123,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
95
123
|
version: 2.0.0
|
96
124
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
125
|
requirements:
|
98
|
-
- - "
|
126
|
+
- - ">="
|
99
127
|
- !ruby/object:Gem::Version
|
100
|
-
version:
|
128
|
+
version: '0'
|
101
129
|
requirements: []
|
102
130
|
rubyforge_project:
|
103
131
|
rubygems_version: 2.4.5
|