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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +40 -0
  3. data/Gemfile +11 -0
  4. data/README.md +108 -0
  5. data/Rakefile +25 -0
  6. data/lhc-core-interceptors.gemspec +31 -0
  7. data/lib/lhc-core-interceptors.rb +2 -0
  8. data/lib/lhc-core-interceptors/auth.rb +11 -0
  9. data/lib/lhc-core-interceptors/caching.rb +57 -0
  10. data/lib/lhc-core-interceptors/monitoring.rb +50 -0
  11. data/lib/lhc-core-interceptors/version.rb +3 -0
  12. data/spec/auth/bearer_spec.rb +19 -0
  13. data/spec/caching/main_spec.rb +53 -0
  14. data/spec/caching/parameters_spec.rb +24 -0
  15. data/spec/caching/to_cache_spec.rb +17 -0
  16. data/spec/dummy/README.rdoc +28 -0
  17. data/spec/dummy/Rakefile +6 -0
  18. data/spec/dummy/app/assets/images/.keep +0 -0
  19. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  20. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  21. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  22. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  23. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  24. data/spec/dummy/app/mailers/.keep +0 -0
  25. data/spec/dummy/app/models/.keep +0 -0
  26. data/spec/dummy/app/models/concerns/.keep +0 -0
  27. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  28. data/spec/dummy/bin/bundle +3 -0
  29. data/spec/dummy/bin/rails +4 -0
  30. data/spec/dummy/bin/rake +4 -0
  31. data/spec/dummy/config.ru +4 -0
  32. data/spec/dummy/config/application.rb +14 -0
  33. data/spec/dummy/config/boot.rb +5 -0
  34. data/spec/dummy/config/environment.rb +5 -0
  35. data/spec/dummy/config/environments/development.rb +34 -0
  36. data/spec/dummy/config/environments/production.rb +75 -0
  37. data/spec/dummy/config/environments/test.rb +39 -0
  38. data/spec/dummy/config/initializers/assets.rb +8 -0
  39. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  41. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  42. data/spec/dummy/config/initializers/inflections.rb +16 -0
  43. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  44. data/spec/dummy/config/initializers/session_store.rb +3 -0
  45. data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
  46. data/spec/dummy/config/locales/en.yml +23 -0
  47. data/spec/dummy/config/routes.rb +56 -0
  48. data/spec/dummy/config/secrets.yml +22 -0
  49. data/spec/dummy/lib/assets/.keep +0 -0
  50. data/spec/dummy/log/.keep +0 -0
  51. data/spec/dummy/public/404.html +67 -0
  52. data/spec/dummy/public/422.html +67 -0
  53. data/spec/dummy/public/500.html +66 -0
  54. data/spec/dummy/public/favicon.ico +0 -0
  55. data/spec/dummy/tmp/cache/.gitkeep +0 -0
  56. data/spec/monitoring/main_spec.rb +65 -0
  57. data/spec/rails_helper.rb +4 -0
  58. data/spec/spec_helper.rb +4 -0
  59. data/spec/support/cleanup_configuration.rb +18 -0
  60. 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,2 @@
1
+ require 'lhc'
2
+ Gem.find_files('lhc-core-interceptors/**/*.rb').sort.each { |path| require path }
@@ -0,0 +1,11 @@
1
+ class LHC::Auth < LHC::Interceptor
2
+
3
+ def before_request(request)
4
+ options = request.options[:auth] || {}
5
+ if token = options[:bearer]
6
+ token = token.call if token.is_a?(Proc)
7
+ request.headers['Authorization'] = "Bearer #{token}"
8
+ end
9
+ end
10
+
11
+ 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,3 @@
1
+ module LHCCoreInterceptors
2
+ VERSION = '1.0.0'
3
+ 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