lhc-core-interceptors 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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