lhc-core-interceptors 1.0.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 +7 -0
- data/.gitignore +40 -0
- data/Gemfile +11 -0
- data/README.md +108 -0
- data/Rakefile +25 -0
- data/lhc-core-interceptors.gemspec +31 -0
- data/lib/lhc-core-interceptors.rb +2 -0
- data/lib/lhc-core-interceptors/auth.rb +11 -0
- data/lib/lhc-core-interceptors/caching.rb +57 -0
- data/lib/lhc-core-interceptors/monitoring.rb +50 -0
- data/lib/lhc-core-interceptors/version.rb +3 -0
- data/spec/auth/bearer_spec.rb +19 -0
- data/spec/caching/main_spec.rb +53 -0
- data/spec/caching/parameters_spec.rb +24 -0
- data/spec/caching/to_cache_spec.rb +17 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +14 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +34 -0
- data/spec/dummy/config/environments/production.rb +75 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/assets.rb +8 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +56 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/tmp/cache/.gitkeep +0 -0
- data/spec/monitoring/main_spec.rb +65 -0
- data/spec/rails_helper.rb +4 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/cleanup_configuration.rb +18 -0
- metadata +249 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b3d8f1d606680b4dc30c467b4b7dd83cabb19423
|
4
|
+
data.tar.gz: 77ec5ff865484eb71beea6c04dc648d4f8ff4f16
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c9a8c8e363242f90b9ffb3cfb39c5ef95e93a2a9687d7c2accb19deadc6311cc1a65fa8db48d1ed101a5ec46ff3c38d2ca97b8bee643b823ca0bb1ea887ea02d
|
7
|
+
data.tar.gz: 3e419cec01d175b226a222c165170b1f5612fb77f2d155a3a368acbdba32419f722ab751886c88855a80476aa1192436a8621e08d05583c5436ead05e15153d2
|
data/.gitignore
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
*.log
|
12
|
+
spec/dummy/log/*
|
13
|
+
spec/dummy/tmp/cache/*
|
14
|
+
Gemfile.lock
|
15
|
+
.ruby-gemset
|
16
|
+
.ruby-version
|
17
|
+
|
18
|
+
## Specific to RubyMotion:
|
19
|
+
.dat*
|
20
|
+
.repl_history
|
21
|
+
build/
|
22
|
+
|
23
|
+
## Documentation cache and generated files:
|
24
|
+
/.yardoc/
|
25
|
+
/_yardoc/
|
26
|
+
/doc/
|
27
|
+
/rdoc/
|
28
|
+
|
29
|
+
## Environment normalisation:
|
30
|
+
/.bundle/
|
31
|
+
/lib/bundler/man/
|
32
|
+
|
33
|
+
# for a library or gem, you might want to ignore these files since the code is
|
34
|
+
# intended to run in multiple environments; otherwise, check them in:
|
35
|
+
# Gemfile.lock
|
36
|
+
# .ruby-version
|
37
|
+
# .ruby-gemset
|
38
|
+
|
39
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
40
|
+
.rvmrc
|
data/Gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
source 'https://rubygems.intra.local.ch/'
|
2
|
+
|
3
|
+
# Declare your gem's dependencies in lhc.gemspec.
|
4
|
+
# Bundler will treat runtime dependencies like base dependencies, and
|
5
|
+
# development dependencies will be added by default to the :development group.
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
# Declare any dependencies that are still in development here instead of in
|
9
|
+
# your gemspec. These might include edge Rails or gems from your path or
|
10
|
+
# Git. Remember to move these dependencies to your gemspec before releasing
|
11
|
+
# your gem to rubygems.org.
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
LHC Core Interceptors
|
2
|
+
===
|
3
|
+
|
4
|
+
The following set of interceptors can be used with [LHC](https://github.com/local-ch/lhc).
|
5
|
+
|
6
|
+
## Cache Interceptor
|
7
|
+
|
8
|
+
Add the cache interceptor to your basic set of LHC interceptors.
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
LHC.config.interceptors = [LHC::Caching]
|
12
|
+
```
|
13
|
+
|
14
|
+
Caching is not enabled by default, although you added it to your basic set of interceptors.
|
15
|
+
If you want to have requests served/stored and stored in/from cache, you have to enable it by request.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
LHC.get('http://local.ch', cache: true)
|
19
|
+
```
|
20
|
+
|
21
|
+
You can also enable caching when configuring an endpoint in LHS.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
class Feedbacks < LHS::Service
|
25
|
+
endpoint ':datastore/v2/feedbacks', cache: true
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
### Cache Interceptor Options
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
LHC.get('http://local.ch', cache: true, cache_expires_in: 1.day, cache_race_condition_ttl: 15.seconds)
|
33
|
+
```
|
34
|
+
|
35
|
+
`cache_expires_in` lets the cache expires every X seconds.
|
36
|
+
|
37
|
+
Set the key that is used for caching by using the `cache_key` option. Every key is prefixed with `LHC_CACHE: `.
|
38
|
+
|
39
|
+
Setting `cache_race_condition_ttl` is very useful in situations where a cache entry is used very frequently and is under heavy load.
|
40
|
+
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.
|
41
|
+
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`.
|
42
|
+
|
43
|
+
## Monitoring Interceptor
|
44
|
+
|
45
|
+
Add the monitoring interceptor to your basic set of LHC interceptors.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
LHC.config.interceptors = [LHC::Monitoring]
|
49
|
+
```
|
50
|
+
|
51
|
+
You also have to configure statsd in order to have the monitoring interceptor report.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
LHC::Monitoring.statsd = <your-instance-of-statsd>
|
55
|
+
```
|
56
|
+
|
57
|
+
The monitoring interceptor reports all the HTTP communication done with LHS.
|
58
|
+
It reports the trial always.
|
59
|
+
|
60
|
+
In case of a successful response it reports the response code with a count and the response time with a gauge value.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
LHC.get('http://local.ch')
|
64
|
+
|
65
|
+
"lhc.<app_name>.<env>.<host>.<http_method>.count", 1
|
66
|
+
"lhc.<app_name>.<env>.<host>.<http_method>.200", 1
|
67
|
+
"lhc.<app_name>.<env>.<host>.<http_method>.time", 43
|
68
|
+
```
|
69
|
+
|
70
|
+
In case a response timeouts it is also reporting that.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
"lhc.<app_name>.<env>.<host>.<http_method>.timeout", 1
|
74
|
+
```
|
75
|
+
|
76
|
+
All the dots in the host are getting replaced with underscore (_), because dot is the default separator in graphite.
|
77
|
+
|
78
|
+
It is also possible to set the key for Monitoring Interceptor on per request basis:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
LHC.get('http://local.ch', monitoring_key: 'local_website')
|
82
|
+
|
83
|
+
"local_website.count", 1
|
84
|
+
"local_website.200", 1
|
85
|
+
"local_website.time", 43
|
86
|
+
```
|
87
|
+
|
88
|
+
If you use this approach you need to add all namespaces (app, environment etc.) to the key on your own.
|
89
|
+
|
90
|
+
|
91
|
+
### Auth Interceptor
|
92
|
+
|
93
|
+
Add the auth interceptor to your basic set of LHC interceptors.
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
LHC.config.interceptors = [LHC::Auth]
|
97
|
+
```
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
LHC.get('http://local.ch', auth: { bearer: -> { access_token } })
|
101
|
+
```
|
102
|
+
|
103
|
+
Adds the following header to the request:
|
104
|
+
```
|
105
|
+
'Authorization': 'Bearer 123456'
|
106
|
+
```
|
107
|
+
|
108
|
+
Assuming the method `access_token` responds on runtime of the request with `123456`.
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'LHC'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'rspec/core/rake_task'
|
19
|
+
RSpec::Core::RakeTask.new(:spec)
|
20
|
+
task :default => :spec
|
21
|
+
rescue LoadError
|
22
|
+
# no rspec available
|
23
|
+
end
|
24
|
+
|
25
|
+
Bundler::GemHelper.install_tasks
|
@@ -0,0 +1,31 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require "lhc-core-interceptors/version"
|
5
|
+
|
6
|
+
# Describe your gem and declare its dependencies:
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "lhc-core-interceptors"
|
9
|
+
s.version = LHCCoreInterceptors::VERSION
|
10
|
+
s.authors = ['local.ch']
|
11
|
+
s.email = ['ws-operations@local.ch']
|
12
|
+
s.homepage = 'https://github.com/local-ch/lhc-core-interceptors'
|
13
|
+
s.summary = 'A set of interceptors'
|
14
|
+
s.description = 'A set of useful interceptors to use with LHC (like caching, monitoring etc.)'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
|
20
|
+
s.requirements << 'Ruby >= 1.9.2'
|
21
|
+
s.required_ruby_version = '>= 1.9.2'
|
22
|
+
|
23
|
+
s.add_dependency 'lhc'
|
24
|
+
|
25
|
+
s.add_development_dependency 'rspec-rails', '>= 3.0.0'
|
26
|
+
s.add_development_dependency 'rails', '>= 4.0.0'
|
27
|
+
s.add_development_dependency 'typhoeus', '>= 0.7.0'
|
28
|
+
s.add_development_dependency 'webmock'
|
29
|
+
s.add_development_dependency 'geminabox'
|
30
|
+
s.add_development_dependency 'pry'
|
31
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class LHC::Caching < LHC::Interceptor
|
2
|
+
|
3
|
+
# Options forwarded to the cache
|
4
|
+
FORWARDED_OPTIONS = {
|
5
|
+
cache_expires_in: :expires_in,
|
6
|
+
cache_race_condition_ttl: :race_condition_ttl
|
7
|
+
}
|
8
|
+
|
9
|
+
def before_request(request)
|
10
|
+
return unless request.options[:cache]
|
11
|
+
cached_response_data = Rails.cache.fetch(key(request))
|
12
|
+
return unless cached_response_data
|
13
|
+
Rails.logger.info "Served from cache: #{key(request)}"
|
14
|
+
from_cache(request, cached_response_data)
|
15
|
+
end
|
16
|
+
|
17
|
+
def after_response(response)
|
18
|
+
request = response.request
|
19
|
+
return if !request.options[:cache] || !response.success?
|
20
|
+
Rails.cache.write(key(request), to_cache(response), options(request.options))
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# converts json we read from the cache to an LHC::Response object
|
26
|
+
def from_cache(request, data)
|
27
|
+
raw = Typhoeus::Response.new(data)
|
28
|
+
request.response = LHC::Response.new(raw, request)
|
29
|
+
end
|
30
|
+
|
31
|
+
# converts a LHC::Response object to json, we store in the cache
|
32
|
+
def to_cache(response)
|
33
|
+
data = {}
|
34
|
+
data[:body] = response.body
|
35
|
+
data[:code] = response.code
|
36
|
+
# convert into a actual hash because the typhoeus headers object breaks marshaling
|
37
|
+
data[:headers] = response.headers ? Hash[response.headers] : response.headers
|
38
|
+
data
|
39
|
+
end
|
40
|
+
|
41
|
+
def key(request)
|
42
|
+
key = request.options[:cache_key]
|
43
|
+
unless key
|
44
|
+
key = "#{request.method.upcase} #{request.url}"
|
45
|
+
key += "?#{request.params.to_query}" unless request.params.blank?
|
46
|
+
end
|
47
|
+
"LHC_CACHE: #{key}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def options(input = {})
|
51
|
+
options = {}
|
52
|
+
FORWARDED_OPTIONS.each do |k, v|
|
53
|
+
options[v] = input[k] if input.key?(k)
|
54
|
+
end
|
55
|
+
options
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class LHC::Monitoring < LHC::Interceptor
|
2
|
+
|
3
|
+
# Options forwarded to the monitoring
|
4
|
+
FORWARDED_OPTIONS = {
|
5
|
+
monitoring_key: :key,
|
6
|
+
}
|
7
|
+
|
8
|
+
include ActiveSupport::Configurable
|
9
|
+
|
10
|
+
config_accessor :statsd
|
11
|
+
|
12
|
+
def after_request(request)
|
13
|
+
return unless statsd
|
14
|
+
key = "#{key(request)}.count"
|
15
|
+
LHC::Monitoring.statsd.count(key, 1)
|
16
|
+
end
|
17
|
+
|
18
|
+
def after_response(response)
|
19
|
+
return unless statsd
|
20
|
+
key = key(response)
|
21
|
+
LHC::Monitoring.statsd.timing("#{key}.time", response.time) if response.success?
|
22
|
+
key += response.timeout? ? '.timeout' : ".#{response.code}"
|
23
|
+
LHC::Monitoring.statsd.count(key, 1)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def key(target)
|
29
|
+
request = target.is_a?(LHC::Request) ? target : target.request
|
30
|
+
key = options(request.options)[:key]
|
31
|
+
return key if key.present?
|
32
|
+
|
33
|
+
key = [
|
34
|
+
'lhc',
|
35
|
+
Rails.application.class.parent_name.underscore,
|
36
|
+
Rails.env,
|
37
|
+
URI.parse(request.url).host.gsub(/\./, '_'),
|
38
|
+
request.method
|
39
|
+
]
|
40
|
+
key.join('.')
|
41
|
+
end
|
42
|
+
|
43
|
+
def options(input = {})
|
44
|
+
options = {}
|
45
|
+
FORWARDED_OPTIONS.each do |k, v|
|
46
|
+
options[v] = input[k] if input.key?(k)
|
47
|
+
end
|
48
|
+
options
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe LHC::Auth do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
LHC.config.interceptors = [LHC::Auth]
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'adds the bearer token to every request' do
|
10
|
+
def bearer_token
|
11
|
+
'123456'
|
12
|
+
end
|
13
|
+
options = { bearer: ->{ bearer_token } }
|
14
|
+
LHC.config.endpoint(:local, 'http://local.ch', auth: options)
|
15
|
+
stub_request(:get, 'http://local.ch').with(headers: { 'Authorization' => 'Bearer 123456'})
|
16
|
+
LHC.get(:local)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe LHC::Caching do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
LHC.config.interceptors = [LHC::Caching]
|
7
|
+
Rails.cache.clear
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:stub) { stub_request(:get, 'http://local.ch').to_return(status: 200, body: 'The Website') }
|
11
|
+
|
12
|
+
it 'serves a response from cache' do
|
13
|
+
stub
|
14
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: true, cache_expires_in: 5.minutes)
|
15
|
+
expect(Rails.cache).to receive(:write)
|
16
|
+
.with('LHC_CACHE: GET http://local.ch', {body: "The Website", code: 200, headers: nil}, {expires_in: 5.minutes})
|
17
|
+
.and_call_original
|
18
|
+
original_response = LHC.get(:local)
|
19
|
+
cached_response = LHC.get(:local)
|
20
|
+
expect(original_response.body).to eq cached_response.body
|
21
|
+
expect(original_response.code).to eq cached_response.code
|
22
|
+
expect(original_response.headers).to eq cached_response.headers
|
23
|
+
expect(original_response.headers).to eq cached_response.headers
|
24
|
+
assert_requested stub, times: 1
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'does not serve from cache if option is not set' do
|
28
|
+
LHC.config.endpoint(:local, 'http://local.ch')
|
29
|
+
expect(Rails.cache).not_to receive(:write)
|
30
|
+
expect(Rails.cache).not_to receive(:fetch)
|
31
|
+
stub
|
32
|
+
2.times { LHC.get(:local) }
|
33
|
+
assert_requested stub, times: 2
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'lets you configure the cache key that will be used' do
|
37
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: true, cache_key: 'STATICKEY')
|
38
|
+
expect(Rails.cache).to receive(:fetch).with('LHC_CACHE: STATICKEY').and_call_original
|
39
|
+
expect(Rails.cache).to receive(:write).with('LHC_CACHE: STATICKEY', anything, anything).and_call_original
|
40
|
+
stub
|
41
|
+
LHC.get(:local)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'does not store server errors in cache' do
|
45
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: true)
|
46
|
+
stub_request(:get, 'http://local.ch').to_return(status: 500, body: 'ERROR')
|
47
|
+
expect{ LHC.get(:local) }.to raise_error LHC::ServerError
|
48
|
+
stub
|
49
|
+
expect(Rails.cache).to receive(:write).once
|
50
|
+
LHC.get(:local)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe LHC::Caching do
|
4
|
+
|
5
|
+
context 'parameters' do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
LHC.config.interceptors = [LHC::Caching]
|
9
|
+
Rails.cache.clear
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'considers parameters when writing/reading from cache' do
|
13
|
+
LHC.config.endpoint(:local, 'http://local.ch', cache: true, cache_expires_in: 5.minutes)
|
14
|
+
stub_request(:get, 'http://local.ch').to_return(status: 200, body: 'The Website')
|
15
|
+
stub_request(:get, 'http://local.ch?location=zuerich').to_return(status: 200, body: 'The Website for Zuerich')
|
16
|
+
expect(
|
17
|
+
LHC.get(:local).body
|
18
|
+
).to eq 'The Website'
|
19
|
+
expect(
|
20
|
+
LHC.get(:local, params: { location: 'zuerich' }).body
|
21
|
+
).to eq 'The Website for Zuerich'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|