lhc 6.1.3 → 6.2.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/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
|