hurley-http-cache 0.1.0.beta → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|