lhc 6.1.3 → 6.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/docs/interceptors/caching.md +7 -5
- data/lhc.gemspec +1 -1
- data/lib/lhc/interceptors/caching.rb +69 -22
- data/lib/lhc/version.rb +1 -1
- data/spec/interceptors/caching/main_spec.rb +3 -3
- data/spec/interceptors/caching/methods_spec.rb +3 -3
- data/spec/interceptors/caching/options_spec.rb +88 -0
- data/spec/interceptors/caching/parameters_spec.rb +1 -1
- data/spec/interceptors/caching/response_status_spec.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7caf5dc6e24346cc42e287a8bac832cd81011c5a
|
4
|
+
data.tar.gz: 17bd6df84e9fce4212921c8348b2053031f62da1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb849054f246290fe96388ed66abaf3ded5dc8d2a00b7eb7afd857efbfd3d9e820bf0014780c296a37f865c2665cee1b399f1a6d9bc549d48092177cb4885908
|
7
|
+
data.tar.gz: 2f3a0b17c338a1c36dca14e1fa2b3cfc3d1897263cc373a9912a4da9717c5a909677cc4c0fd987ef7e010c964ad2529ae4df816db31384560be826bc28d2256e
|
@@ -31,23 +31,25 @@ You can also enable caching when configuring an endpoint in LHS.
|
|
31
31
|
Only GET requests are cached by default. If you want to cache any other request method, just configure it:
|
32
32
|
|
33
33
|
```ruby
|
34
|
-
LHC.get('http://local.ch', cache:
|
34
|
+
LHC.get('http://local.ch', cache: { methods: [:get] })
|
35
35
|
```
|
36
36
|
|
37
37
|
## Options
|
38
38
|
|
39
39
|
```ruby
|
40
|
-
LHC.get('http://local.ch', cache:
|
40
|
+
LHC.get('http://local.ch', cache: { key: 'key' expires_in: 1.day, race_condition_ttl: 15.seconds, use: ActiveSupport::Cache::MemoryStore.new })
|
41
41
|
```
|
42
42
|
|
43
|
-
`
|
43
|
+
`expires_in` - lets the cache expires every X seconds.
|
44
44
|
|
45
|
-
`
|
45
|
+
`key` - Set the key that is used for caching by using the option. Every key is prefixed with `LHC_CACHE(v1): `.
|
46
46
|
|
47
|
-
`
|
47
|
+
`race_condition_ttl` - very useful in situations where a cache entry is used very frequently and is under heavy load.
|
48
48
|
If a cache expires and due to heavy load several different processes will try to read data natively and then they all will try to write to cache.
|
49
49
|
To avoid that case the first process to find an expired cache entry will bump the cache expiration time by the value set in `cache_race_condition_ttl`.
|
50
50
|
|
51
|
+
`use` - Set an explicit cache to be used for this request. If this option is missing `LHC::Caching.cache` is used.
|
52
|
+
|
51
53
|
## Testing
|
52
54
|
|
53
55
|
Add to your spec_helper.rb:
|
data/lhc.gemspec
CHANGED
@@ -6,31 +6,64 @@ class LHC::Caching < LHC::Interceptor
|
|
6
6
|
CACHE_VERSION = '1'
|
7
7
|
|
8
8
|
# Options forwarded to the cache
|
9
|
-
FORWARDED_OPTIONS =
|
10
|
-
cache_expires_in: :expires_in,
|
11
|
-
cache_race_condition_ttl: :race_condition_ttl
|
12
|
-
}
|
9
|
+
FORWARDED_OPTIONS = [:expires_in, :race_condition_ttl]
|
13
10
|
|
14
11
|
def before_request(request)
|
15
|
-
return unless cache
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
12
|
+
return unless cache?(request)
|
13
|
+
deprecation_warning(request.options)
|
14
|
+
options = options(request.options)
|
15
|
+
key = key(request, options[:key])
|
16
|
+
response_data = cache_for(options).fetch(key)
|
17
|
+
return unless response_data
|
18
|
+
logger.info "Served from cache: #{key}" if logger
|
19
|
+
from_cache(request, response_data)
|
22
20
|
end
|
23
21
|
|
24
22
|
def after_response(response)
|
25
|
-
return unless
|
23
|
+
return unless response.success?
|
26
24
|
request = response.request
|
27
|
-
return unless
|
28
|
-
|
29
|
-
|
25
|
+
return unless cache?(request)
|
26
|
+
options = options(request.options)
|
27
|
+
cache_for(options).write(
|
28
|
+
key(request, options[:key]),
|
29
|
+
to_cache(response),
|
30
|
+
cache_options(options)
|
31
|
+
)
|
30
32
|
end
|
31
33
|
|
32
34
|
private
|
33
35
|
|
36
|
+
# return the cache for the given options
|
37
|
+
def cache_for(options)
|
38
|
+
options.fetch(:use, cache)
|
39
|
+
end
|
40
|
+
|
41
|
+
# do we even need to bother with this interceptor?
|
42
|
+
# based on the options, this method will
|
43
|
+
# return false if this interceptor cannot work
|
44
|
+
def cache?(request)
|
45
|
+
return false unless request.options[:cache]
|
46
|
+
options = options(request.options)
|
47
|
+
cache_for(options) &&
|
48
|
+
cached_method?(request.method, options[:methods])
|
49
|
+
end
|
50
|
+
|
51
|
+
# returns the request_options
|
52
|
+
# will map deprecated options to the new format
|
53
|
+
def options(request_options)
|
54
|
+
options = request_options[:cache] == true ? {} : request_options[:cache].dup
|
55
|
+
map_deprecated_options!(request_options, options)
|
56
|
+
options
|
57
|
+
end
|
58
|
+
|
59
|
+
# maps `cache_key` -> `key`, `cache_expires_in` -> `expires_in` and so on
|
60
|
+
def map_deprecated_options!(request_options, options)
|
61
|
+
deprecated_keys(request_options).each do |deprecated_key|
|
62
|
+
new_key = deprecated_key.to_s.gsub(/^cache_/, '').to_sym
|
63
|
+
options[new_key] = request_options[deprecated_key]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
34
67
|
# converts json we read from the cache to an LHC::Response object
|
35
68
|
def from_cache(request, data)
|
36
69
|
raw = Typhoeus::Response.new(data)
|
@@ -53,8 +86,7 @@ class LHC::Caching < LHC::Interceptor
|
|
53
86
|
data
|
54
87
|
end
|
55
88
|
|
56
|
-
def key(request)
|
57
|
-
key = request.options[:cache_key]
|
89
|
+
def key(request, key)
|
58
90
|
unless key
|
59
91
|
key = "#{request.method.upcase} #{request.url}"
|
60
92
|
key += "?#{request.params.to_query}" unless request.params.blank?
|
@@ -68,11 +100,26 @@ class LHC::Caching < LHC::Interceptor
|
|
68
100
|
(cached_methods || [:get]).include?(method)
|
69
101
|
end
|
70
102
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
103
|
+
# extracts the options that should be forwarded to
|
104
|
+
# the cache
|
105
|
+
def cache_options(input = {})
|
106
|
+
input.each_with_object({}) do |(key, value), result|
|
107
|
+
result[key] = value if key.in? FORWARDED_OPTIONS
|
108
|
+
result
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# grabs the deprecated keys from the request options
|
113
|
+
def deprecated_keys(request_options)
|
114
|
+
request_options.keys.select { |k| k =~ /^cache_.*/ }.sort
|
115
|
+
end
|
116
|
+
|
117
|
+
# emits a deprecation warning if necessary
|
118
|
+
def deprecation_warning(request_options)
|
119
|
+
unless deprecated_keys(request_options).empty?
|
120
|
+
ActiveSupport::Deprecation.warn(
|
121
|
+
"Cache options have changed! #{deprecated_keys(request_options).join(', ')} are deprecated and will be removed in future versions."
|
122
|
+
)
|
75
123
|
end
|
76
|
-
options
|
77
124
|
end
|
78
125
|
end
|
data/lib/lhc/version.rb
CHANGED
@@ -11,7 +11,7 @@ describe LHC::Caching do
|
|
11
11
|
|
12
12
|
it 'serves a response from cache' do
|
13
13
|
stub
|
14
|
-
LHC.config.endpoint(:local, 'http://local.ch', cache:
|
14
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: { expires_in: 5.minutes })
|
15
15
|
expect(Rails.cache).to receive(:write)
|
16
16
|
.with(
|
17
17
|
"LHC_CACHE(v#{LHC::Caching::CACHE_VERSION}): GET http://local.ch",
|
@@ -44,7 +44,7 @@ describe LHC::Caching do
|
|
44
44
|
end
|
45
45
|
|
46
46
|
it 'lets you configure the cache key that will be used' do
|
47
|
-
LHC.config.endpoint(:local, 'http://local.ch', cache:
|
47
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: { key: 'STATICKEY' })
|
48
48
|
expect(Rails.cache).to receive(:fetch).with("LHC_CACHE(v#{LHC::Caching::CACHE_VERSION}): STATICKEY").and_call_original
|
49
49
|
expect(Rails.cache).to receive(:write).with("LHC_CACHE(v#{LHC::Caching::CACHE_VERSION}): STATICKEY", anything, anything).and_call_original
|
50
50
|
stub
|
@@ -62,7 +62,7 @@ describe LHC::Caching do
|
|
62
62
|
|
63
63
|
it 'marks response not from cache as not served from cache and from cache as served from cache' do
|
64
64
|
stub
|
65
|
-
LHC.config.endpoint(:local, 'http://local.ch', cache: true
|
65
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: true)
|
66
66
|
original_response = LHC.get(:local)
|
67
67
|
cached_response = LHC.get(:local)
|
68
68
|
expect(original_response.from_cache?).to eq false
|
@@ -10,7 +10,7 @@ describe LHC::Caching do
|
|
10
10
|
let!(:stub) { stub_request(:post, 'http://local.ch').to_return(status: 200, body: 'The Website') }
|
11
11
|
|
12
12
|
before(:each) do
|
13
|
-
LHC.config.endpoint(:local, 'http://local.ch', cache:
|
13
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: { expires_in: 5.minutes })
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'only caches GET requests by default' do
|
@@ -32,8 +32,8 @@ describe LHC::Caching do
|
|
32
32
|
}, { expires_in: 5.minutes }
|
33
33
|
)
|
34
34
|
.and_call_original
|
35
|
-
original_response = LHC.post(:local,
|
36
|
-
cached_response = LHC.post(:local,
|
35
|
+
original_response = LHC.post(:local, cache: { methods: [:post] })
|
36
|
+
cached_response = LHC.post(:local, cache: { methods: [:post] })
|
37
37
|
expect(original_response.body).to eq cached_response.body
|
38
38
|
expect(original_response.code).to eq cached_response.code
|
39
39
|
expect(original_response.headers).to eq cached_response.headers
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
# due to the fact that options passed into LHC get dup'ed
|
4
|
+
# we need a class where we can setup method expectations
|
5
|
+
# with `expect_any_instance`
|
6
|
+
class CacheMock
|
7
|
+
def fetch(*_)
|
8
|
+
end
|
9
|
+
|
10
|
+
def write(*_)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe LHC::Caching do
|
15
|
+
let(:default_cache) { LHC::Caching.cache }
|
16
|
+
before(:each) do
|
17
|
+
stub_request(:get, 'http://local.ch').to_return(status: 200, body: 'The Website')
|
18
|
+
LHC.config.interceptors = [LHC::Caching]
|
19
|
+
default_cache.clear
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'maps deprecated cache options' do
|
23
|
+
expected_options = { expires_in: 5.minutes, race_condition_ttl: 15.seconds }
|
24
|
+
expected_key = "LHC_CACHE(v1): key"
|
25
|
+
expect(default_cache).to receive(:write).with(expected_key, anything, expected_options)
|
26
|
+
expect(lambda {
|
27
|
+
LHC.get('http://local.ch', cache: true, cache_expires_in: 5.minutes, cache_key: 'key', cache_race_condition_ttl: 15.seconds)
|
28
|
+
}).to output(
|
29
|
+
%r{Cache options have changed! cache_expires_in, cache_key, cache_race_condition_ttl are deprecated and will be removed in future versions.}
|
30
|
+
).to_stderr
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'does cache' do
|
34
|
+
expect(default_cache).to receive(:fetch)
|
35
|
+
expect(default_cache).to receive(:write)
|
36
|
+
LHC.get('http://local.ch', cache: true)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'does not cache' do
|
40
|
+
expect(default_cache).not_to receive(:fetch)
|
41
|
+
expect(default_cache).not_to receive(:write)
|
42
|
+
LHC.get('http://local.ch')
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'options - directly via LHC.get' do
|
46
|
+
it 'uses the default cache' do
|
47
|
+
expect(default_cache).to receive(:fetch)
|
48
|
+
expect(default_cache).to receive(:write)
|
49
|
+
LHC.get('http://local.ch', cache: true)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'uses the provided cache' do
|
53
|
+
expect_any_instance_of(CacheMock).to receive(:fetch)
|
54
|
+
expect_any_instance_of(CacheMock).to receive(:write)
|
55
|
+
LHC.get('http://local.ch', cache: { use: CacheMock.new })
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'cache options are properly forwarded to the cache' do
|
59
|
+
cache_options = { expires_in: 5.minutes, race_condition_ttl: 15.seconds }
|
60
|
+
expect(default_cache).to receive(:write).with(anything, anything, cache_options)
|
61
|
+
LHC.get('http://local.ch', cache: cache_options)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'options - via endpoint configuration' do
|
66
|
+
it 'uses the default cache' do
|
67
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: true)
|
68
|
+
expect(default_cache).to receive(:fetch)
|
69
|
+
expect(default_cache).to receive(:write)
|
70
|
+
LHC.get(:local)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'uses the provided cache' do
|
74
|
+
options = { cache: { use: CacheMock.new } }
|
75
|
+
LHC.config.endpoint(:local, 'http://local.ch', options)
|
76
|
+
expect_any_instance_of(CacheMock).to receive(:fetch)
|
77
|
+
expect_any_instance_of(CacheMock).to receive(:write)
|
78
|
+
LHC.get(:local)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'cache options are properly forwarded to the cache' do
|
82
|
+
cache_options = { expires_in: 5.minutes, race_condition_ttl: 15.seconds }
|
83
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: cache_options)
|
84
|
+
expect(default_cache).to receive(:write).with(anything, anything, cache_options)
|
85
|
+
LHC.get(:local)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -8,7 +8,7 @@ describe LHC::Caching do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
it 'considers parameters when writing/reading from cache' do
|
11
|
-
LHC.config.endpoint(:local, 'http://local.ch', cache: true
|
11
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: true)
|
12
12
|
stub_request(:get, 'http://local.ch').to_return(status: 200, body: 'The Website')
|
13
13
|
stub_request(:get, 'http://local.ch?location=zuerich').to_return(status: 200, body: 'The Website for Zuerich')
|
14
14
|
expect(
|
@@ -3,7 +3,7 @@ require 'rails_helper'
|
|
3
3
|
describe LHC::Caching do
|
4
4
|
before(:each) do
|
5
5
|
LHC.config.interceptors = [LHC::Caching]
|
6
|
-
LHC.config.endpoint(:local, 'http://local.ch', cache: true
|
6
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: true)
|
7
7
|
Rails.cache.clear
|
8
8
|
# leverage the Typhoeus internal mock attribute in order to get Typhoeus evaluate the return_code
|
9
9
|
# lib/typhoeus/response/status.rb:48
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lhc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.
|
4
|
+
version: 6.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- https://github.com/local-ch/lhc/contributors
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-08-
|
11
|
+
date: 2017-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: typhoeus
|
@@ -264,6 +264,7 @@ files:
|
|
264
264
|
- spec/interceptors/caching/hydra_spec.rb
|
265
265
|
- spec/interceptors/caching/main_spec.rb
|
266
266
|
- spec/interceptors/caching/methods_spec.rb
|
267
|
+
- spec/interceptors/caching/options_spec.rb
|
267
268
|
- spec/interceptors/caching/parameters_spec.rb
|
268
269
|
- spec/interceptors/caching/response_status_spec.rb
|
269
270
|
- spec/interceptors/caching/to_cache_spec.rb
|
@@ -390,6 +391,7 @@ test_files:
|
|
390
391
|
- spec/interceptors/caching/hydra_spec.rb
|
391
392
|
- spec/interceptors/caching/main_spec.rb
|
392
393
|
- spec/interceptors/caching/methods_spec.rb
|
394
|
+
- spec/interceptors/caching/options_spec.rb
|
393
395
|
- spec/interceptors/caching/parameters_spec.rb
|
394
396
|
- spec/interceptors/caching/response_status_spec.rb
|
395
397
|
- spec/interceptors/caching/to_cache_spec.rb
|