redcache 0.0.2 → 0.0.3
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/redcache/configuration.rb +2 -0
- data/lib/redcache/version.rb +1 -1
- data/lib/redcache.rb +70 -54
- data/spec/redcache_spec.rb +83 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9134505b631ba6721ae691c4a9031dd3e62a551
|
4
|
+
data.tar.gz: f4a352cc66f24de9d52b8907f62b96d4aa413e91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 04bb02ff0a7e38f07ccd7741edd113c0eb7e7b4c3c6cd10d2405a1ea03261caad59e6d7bacfa48b7b8a2a7c5cf242ef59673faee1d004657bbf08d8dfb6ac9dc
|
7
|
+
data.tar.gz: c1ff6af3a7bc067f2844440e4a33876d2a88f9d6c472caad29a78b3dfe3c01770e272c4038f0b4e740713eee162d442fe34d72d151d7c7a1f37ea94e018e85a8
|
@@ -6,6 +6,7 @@ module Redcache
|
|
6
6
|
attr_accessor :stale_time
|
7
7
|
attr_accessor :encrypt
|
8
8
|
attr_accessor :skip_cache
|
9
|
+
attr_accessor :silent
|
9
10
|
attr_accessor :logger
|
10
11
|
attr_accessor :log_prefix
|
11
12
|
|
@@ -15,6 +16,7 @@ module Redcache
|
|
15
16
|
@encrypt = false
|
16
17
|
@secret = nil
|
17
18
|
@skip_cache = false
|
19
|
+
@silent = false
|
18
20
|
@logger = nil
|
19
21
|
@log_prefix = "redcache"
|
20
22
|
end
|
data/lib/redcache/version.rb
CHANGED
data/lib/redcache.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
require "redcache/version"
|
2
|
+
require "fernet"
|
2
3
|
require "redcache/configuration"
|
3
4
|
|
5
|
+
Fernet::Configuration.run do |config|
|
6
|
+
config.enforce_ttl = false
|
7
|
+
end
|
8
|
+
|
4
9
|
module Redcache
|
5
10
|
class << self
|
6
11
|
attr_writer :configuration
|
@@ -10,15 +15,67 @@ module Redcache
|
|
10
15
|
end
|
11
16
|
|
12
17
|
def cache(redis_key, &block)
|
13
|
-
return
|
14
|
-
if redis_up?
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
# return immediately if we shouldn't or can't cache
|
19
|
+
return block.call if skip_cache? || !redis_up?
|
20
|
+
with_redis do
|
21
|
+
# attempt to read from cache, running and caching the block if cold
|
22
|
+
value = read_from_cache(redis_key, block)
|
23
|
+
if value.nil?
|
24
|
+
value = block.call if value.nil?
|
25
|
+
write_into_cache(redis_key, value)
|
26
|
+
end
|
27
|
+
return value
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def read_from_cache(redis_key, block)
|
32
|
+
value = get_value(redis_key)
|
33
|
+
value.nil? ? log("cache.miss", redis_key) : log("cache.hit", redis_key)
|
34
|
+
refresh_cache(redis_key, block) if key_stale?(redis_key) && !value.nil?
|
35
|
+
return value
|
36
|
+
end
|
37
|
+
|
38
|
+
def refresh_cache(redis_key, block)
|
39
|
+
log("cache.stale_refresh", redis_key)
|
40
|
+
Thread.new do
|
41
|
+
write_into_cache(redis_key, block.call)
|
19
42
|
end
|
20
43
|
end
|
21
44
|
|
45
|
+
def write_into_cache(redis_key, value)
|
46
|
+
with_redis do
|
47
|
+
log("cache.write", redis_key)
|
48
|
+
set_value(redis_key, value)
|
49
|
+
end
|
50
|
+
value
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_value(key)
|
54
|
+
decrypt redis.get(key)
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_value(key, value)
|
58
|
+
redis.setex key, configuration.cache_time, encrypt(value)
|
59
|
+
end
|
60
|
+
|
61
|
+
def key_stale?(redis_key)
|
62
|
+
ttl = redis.ttl(redis_key)
|
63
|
+
return ttl < (configuration.cache_time - configuration.stale_time)
|
64
|
+
end
|
65
|
+
|
66
|
+
def encrypt(value)
|
67
|
+
return prep_value(value) unless encrypt?
|
68
|
+
fernet.generate(secret, prep_value(value))
|
69
|
+
end
|
70
|
+
|
71
|
+
def decrypt(value)
|
72
|
+
return nil if value.nil?
|
73
|
+
return value unless encrypt?
|
74
|
+
verifier = fernet.verifier(secret, value)
|
75
|
+
return MultiJson.load(verifier.message) if verifier.valid?
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
|
22
79
|
def configuration
|
23
80
|
@configuration ||= Configuration.new
|
24
81
|
end
|
@@ -53,73 +110,32 @@ module Redcache
|
|
53
110
|
configuration.skip_cache
|
54
111
|
end
|
55
112
|
|
56
|
-
def read_from_cache(redis_key, block)
|
57
|
-
value = get_value(redis_key)
|
58
|
-
value.nil? ? log("cache.miss", redis_key) : log("cache.hit", redis_key)
|
59
|
-
refresh_cache(redis_key, block) if key_stale?(redis_key) && !value.nil?
|
60
|
-
return value
|
61
|
-
end
|
62
|
-
|
63
113
|
def test?
|
64
114
|
ENV["RACK_ENV"] == 'test'
|
65
115
|
end
|
66
116
|
|
67
|
-
def
|
68
|
-
|
69
|
-
Thread.new do
|
70
|
-
write_into_cache(redis_key, block)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def write_into_cache(redis_key, block)
|
75
|
-
json = block.call
|
76
|
-
with_redis do
|
77
|
-
log("cache.write", redis_key)
|
78
|
-
set_value(redis_key, json)
|
79
|
-
end
|
80
|
-
json
|
81
|
-
end
|
82
|
-
|
83
|
-
def key_stale?(redis_key)
|
84
|
-
ttl = redis.ttl(redis_key)
|
85
|
-
return ttl < (configuration.cache_time - configuration.stale_time)
|
86
|
-
end
|
87
|
-
|
88
|
-
def get_value(key)
|
89
|
-
decrypt redis.get(key)
|
90
|
-
end
|
91
|
-
|
92
|
-
def set_value(key, value)
|
93
|
-
redis.setex key, configuration.cache_time, encrypt(value)
|
94
|
-
end
|
95
|
-
|
96
|
-
def encrypt(value)
|
97
|
-
return value unless encrypt?
|
98
|
-
Fernet.generate(secret, MultiJson.encode(value))
|
117
|
+
def prep_value(value)
|
118
|
+
MultiJson.encode(value)
|
99
119
|
end
|
100
120
|
|
101
121
|
def encrypt?
|
102
122
|
configuration.encrypt
|
103
123
|
end
|
104
124
|
|
105
|
-
def decrypt(value)
|
106
|
-
return nil if value.nil?
|
107
|
-
return value unless encrypt?
|
108
|
-
verifier = Fernet.verifier(secret, value)
|
109
|
-
return MultiJson.load(verifier.message) if verifier.valid?
|
110
|
-
return nil
|
111
|
-
end
|
112
|
-
|
113
125
|
def secret
|
114
126
|
configuration.secret
|
115
127
|
end
|
116
128
|
|
117
129
|
def log(str, key)
|
118
|
-
configuration.logger.log(log_prefix(str) => 1, :key => key)
|
130
|
+
configuration.logger.log(log_prefix(str) => 1, :key => key) unless configuration.silent
|
119
131
|
end
|
120
132
|
|
121
133
|
def log_prefix(str)
|
122
134
|
[configuration.log_prefix, str].join(".")
|
123
135
|
end
|
136
|
+
|
137
|
+
def fernet
|
138
|
+
::Fernet
|
139
|
+
end
|
124
140
|
end
|
125
141
|
end
|
data/spec/redcache_spec.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'fernet'
|
2
3
|
|
3
4
|
Redcache.configure do |c|
|
4
5
|
c.redis = Redis.new
|
5
6
|
c.logger = Pliny
|
7
|
+
c.silent = true
|
6
8
|
end
|
7
9
|
|
8
10
|
class Dummy
|
@@ -18,46 +20,116 @@ class Dummy
|
|
18
20
|
end
|
19
21
|
|
20
22
|
describe Redcache do
|
23
|
+
let(:rc) { Redcache }
|
24
|
+
let(:config) { Redcache.configuration }
|
25
|
+
|
26
|
+
before do
|
27
|
+
allow(rc.fernet).to receive(:generate){ "abd" }
|
28
|
+
end
|
21
29
|
|
22
30
|
context 'when skipping caching' do
|
23
31
|
before do
|
24
|
-
|
32
|
+
config.skip_cache = true
|
25
33
|
end
|
34
|
+
|
26
35
|
it 'does not use the cache' do
|
27
|
-
expect(
|
36
|
+
expect(rc).to_not receive(:read_from_cache)
|
28
37
|
Dummy.run
|
29
38
|
end
|
30
39
|
end
|
31
40
|
|
32
41
|
context 'when caching' do
|
33
42
|
before do
|
34
|
-
|
43
|
+
config.skip_cache = false
|
35
44
|
end
|
36
45
|
|
37
46
|
it 'uses the cached' do
|
38
|
-
expect(
|
47
|
+
expect(rc).to receive(:read_from_cache){ {} }
|
39
48
|
Dummy.run
|
40
49
|
end
|
41
50
|
|
42
51
|
it 'triggers a cache write if the cache is cold' do
|
43
|
-
allow(
|
44
|
-
expect(
|
52
|
+
allow(rc).to receive(:read_from_cache){ nil }
|
53
|
+
expect(rc).to receive(:write_into_cache) { "" }
|
45
54
|
Dummy.run
|
46
55
|
end
|
47
56
|
|
48
57
|
it 'skips cache when redis is down' do
|
49
|
-
allow(
|
50
|
-
expect(
|
58
|
+
allow(rc).to receive(:redis_up?){ false }
|
59
|
+
expect(rc).to_not receive(:read_from_cache)
|
51
60
|
expect(Dummy).to receive(:get_value)
|
52
61
|
Dummy.run
|
53
62
|
end
|
54
63
|
|
55
64
|
it 'triggers a cache refresh with a stale warm cache' do
|
56
|
-
allow(
|
57
|
-
allow(
|
58
|
-
expect(
|
65
|
+
allow(rc).to receive(:key_stale?){ true }
|
66
|
+
allow(rc).to receive(:get_value){ "" }
|
67
|
+
expect(rc).to receive(:refresh_cache).once
|
59
68
|
Dummy.run
|
60
69
|
end
|
61
70
|
end
|
62
71
|
|
72
|
+
describe 'configuration' do
|
73
|
+
it 'respects cache_time' do
|
74
|
+
config.cache_time = 100
|
75
|
+
expect(rc.cache_time).to eq(100)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'respects stale_time' do
|
79
|
+
config.stale_time = 100
|
80
|
+
expect(rc.stale_time).to eq(100)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'respects skip_cache' do
|
84
|
+
config.skip_cache = true
|
85
|
+
expect(rc.skip_cache?).to eq(true)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'knows the secret' do
|
89
|
+
config.secret = "bar"
|
90
|
+
expect(rc.secret).to eq("bar")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'knows when a key is stale' do
|
95
|
+
config.cache_time = 100
|
96
|
+
config.stale_time = 10
|
97
|
+
allow(rc.redis).to receive(:ttl){ 80 }
|
98
|
+
expect(rc.key_stale?("foo")).to eq(true)
|
99
|
+
|
100
|
+
allow(rc.redis).to receive(:ttl){ 91 }
|
101
|
+
expect(rc.key_stale?("foo")).to eq(false)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'can refresh the cache' do
|
105
|
+
p = Proc.new {}
|
106
|
+
expect(Thread).to receive(:new)
|
107
|
+
rc.refresh_cache("foo", p)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'can write into the cache' do
|
111
|
+
p = Proc.new { "" }
|
112
|
+
expect(rc).to receive(:set_value).with("foo", "")
|
113
|
+
rc.write_into_cache("foo", p.call)
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'can get a value' do
|
117
|
+
expect(rc.redis).to receive(:get).with("foo"){ "bar" }
|
118
|
+
expect(rc).to receive(:decrypt).with("bar")
|
119
|
+
rc.get_value("foo")
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'can set a value' do
|
123
|
+
config.cache_time = 100
|
124
|
+
config.encrypt = false
|
125
|
+
expect(rc.redis).to receive(:setex).with("foo", 100, '"bar"')
|
126
|
+
rc.set_value("foo", "bar")
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'encrypts' do
|
130
|
+
config.encrypt = true
|
131
|
+
config.secret = "foo"
|
132
|
+
expect(rc.fernet).to receive(:generate).with("foo", '"bar"'){ "abc" }
|
133
|
+
rc.encrypt("bar")
|
134
|
+
end
|
63
135
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redcache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil Middleton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-10-
|
11
|
+
date: 2014-10-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|