lhc 3.8.1 → 4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e327e8cf911398de6860f5e328bfee3d4ec4a117
4
- data.tar.gz: eee4da06a4953d23eaef467a935a0a6ab7198454
3
+ metadata.gz: 11c5724e0fa2091b89c7589c00d0c052cab1dffd
4
+ data.tar.gz: '06869e81b8d6c8f9c4f85422504631a9bb76a5c3'
5
5
  SHA512:
6
- metadata.gz: 63b5203325d62df8aec412bb04e0e4bd6cffdafa29527641a8e0319a91e7fd39300135d31a0a9e3e169ad287c5c678f248994a71eb786a558957017ff6c8bed5
7
- data.tar.gz: b13e872cab4a98c6e06a49d1c5e59ce86fdb1c1ae0ddc2b1bfb11366b5157e2444953b0d6cb52db4d0192656d64c413a24f83295b040afec96cfb795378db954
6
+ metadata.gz: b63b042df636aff266df087f8b75c7542884d7e2e2bf952fba0d5f13b52f9a495616ff707994c72eec5d5669400921310940f2c0a14b3d8ffddf8d0c4deca6cd
7
+ data.tar.gz: f7497777305dad01fcbfa28d08893c1b00cdc0b74a23c5b6bbe091a6724cab37ea721279f1e5381fb38afa9a1680949cc9d81a66b438f85b1b7073352e51c92f
@@ -16,6 +16,7 @@ AllCops:
16
16
  - 'config/unicorn.rb'
17
17
  - 'config/compass.rb'
18
18
  - 'Rakefile'
19
+ - 'spec/error/to_s_spec.rb' # this file contains invalid UTF8 character on purpose!
19
20
 
20
21
  Rails:
21
22
  Enabled: true
@@ -3,6 +3,9 @@
3
3
  inherit_from:
4
4
  - ./.rubocop.localch.yml
5
5
 
6
+ AllCops:
7
+ TargetRubyVersion: 2.3
8
+
6
9
  Lint/IneffectiveAccessModifier:
7
10
  Enabled: false
8
11
 
@@ -1 +1 @@
1
- ruby-2.2.0
1
+ ruby-2.3.3
data/README.md CHANGED
@@ -66,7 +66,7 @@ You can also access response data directly through the response object (with squ
66
66
  ## Parallel requests
67
67
 
68
68
  If you pass an array of requests to `LHC.request`, it will perform those requests in parallel.
69
- You will get back an array of LHC::Response objects.
69
+ You will get back an array of LHC::Response objects in the same order of the passed requests.
70
70
 
71
71
  ```ruby
72
72
  options = []
@@ -75,6 +75,11 @@ You will get back an array of LHC::Response objects.
75
75
  responses = LHC.request(options)
76
76
  ```
77
77
 
78
+ ```ruby
79
+ LHC.get([request1, request2, request3])
80
+ # returns [response1, response2, response3]
81
+ ```
82
+
78
83
  ## Follow redirects
79
84
 
80
85
  ```ruby
@@ -163,6 +168,12 @@ To monitor and manipulate the http communication done with LHC, you can define i
163
168
 
164
169
  → [Read more about interceptors](docs/interceptors.md)
165
170
 
171
+ A set of core interceptors is part of LHC,
172
+ like [Caching](/docs/interceptors/caching.md), [Monitoring](/docs/interceptors/monitoring.md), [Authentication](/docs/interceptors/authentication.md), [Rollbar](/docs/interceptors/rollbar.md).
173
+
174
+ → [Read more about core interceptors](docs/interceptors.md#core-interceptors)
175
+
176
+
166
177
  ## License
167
178
 
168
179
  [GNU Affero General Public License Version 3.](https://www.gnu.org/licenses/agpl-3.0.en.html)
@@ -20,10 +20,10 @@ Interceptors
20
20
  LHC.request({url: 'http://local.ch', interceptors: []}) # no interceptor for this request
21
21
  ```
22
22
 
23
- ## Basic Interceptors
23
+ ## Core Interceptors
24
24
 
25
- There are some interceptors available, that cover some basic usecases:
26
- [lhc-core-interceptors](https://github.com/local-ch/lhc-core-interceptors)
25
+ There are some interceptors that are part of LHC already, that cover some basic usecases:
26
+ like [Caching](/docs/interceptors/caching.md), [Monitoring](/docs/interceptors/monitoring.md), [Authentication](/docs/interceptors/authentication.md), [Rollbar](/docs/interceptors/rollbar.md).
27
27
 
28
28
  ## Callbacks
29
29
 
@@ -0,0 +1,33 @@
1
+ # Authentication Interceptor
2
+
3
+ Add the auth interceptor to your basic set of LHC interceptors.
4
+
5
+ ```ruby
6
+ LHC.config.interceptors = [LHC::Auth]
7
+ ```
8
+
9
+ ## Bearer Authentication
10
+
11
+ ```ruby
12
+ LHC.get('http://local.ch', auth: { bearer: -> { access_token } })
13
+ ```
14
+
15
+ Adds the following header to the request:
16
+ ```
17
+ 'Authorization': 'Bearer 123456'
18
+ ```
19
+
20
+ Assuming the method `access_token` responds on runtime of the request with `123456`.
21
+
22
+ ## Basic Authentication
23
+
24
+ ```ruby
25
+ LHC.get('http://local.ch', auth: { basic: { username: 'steve', password: 'can' } })
26
+ ```
27
+
28
+ Adds the following header to the request:
29
+ ```
30
+ 'Authorization': 'Basic c3RldmU6Y2Fu'
31
+ ```
32
+
33
+ Which is the base64 encoded credentials "username:password".
@@ -0,0 +1,54 @@
1
+ # Caching Interceptor
2
+
3
+ Add the cache interceptor to your basic set of LHC interceptors.
4
+
5
+ ```ruby
6
+ LHC.config.interceptors = [LHC::Caching]
7
+ ```
8
+
9
+ You can configure your own cache (default Rails.cache) and logger (default Rails.logger):
10
+
11
+ ```ruby
12
+ LHC::Caching.cache = ActiveSupport::Cache::MemoryStore.new
13
+ LHC::Caching.logger = Logger.new(STDOUT)
14
+ ```
15
+
16
+
17
+ Caching is not enabled by default, although you added it to your basic set of interceptors.
18
+ If you want to have requests served/stored and stored in/from cache, you have to enable it by request.
19
+
20
+ ```ruby
21
+ LHC.get('http://local.ch', cache: true)
22
+ ```
23
+
24
+ You can also enable caching when configuring an endpoint in LHS.
25
+
26
+ ```ruby
27
+ class Feedbacks < LHS::Service
28
+ endpoint ':datastore/v2/feedbacks', cache: true
29
+ end
30
+ ```
31
+
32
+ ## Options
33
+
34
+ ```ruby
35
+ LHC.get('http://local.ch', cache: true, cache_expires_in: 1.day, cache_race_condition_ttl: 15.seconds)
36
+ ```
37
+
38
+ `cache_expires_in` - lets the cache expires every X seconds.
39
+
40
+ `cache_key` - Set the key that is used for caching by using the option. Every key is prefixed with `LHC_CACHE(v1): `.
41
+
42
+ `cache_race_condition_ttl` - very useful in situations where a cache entry is used very frequently and is under heavy load.
43
+ 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.
44
+ 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`.
45
+
46
+ ## Testing
47
+
48
+ Add to your spec_helper.rb:
49
+
50
+ ```ruby
51
+ require 'lhc/test/cache_helper.rb'
52
+ ```
53
+
54
+ This will initialize a MemoryStore cache for LHC::Caching interceptor and resets the cache before every test.
@@ -0,0 +1,54 @@
1
+ # Monitoring Interceptor
2
+
3
+ Add the monitoring interceptor to your basic set of LHC interceptors.
4
+
5
+ ```ruby
6
+ LHC.config.interceptors = [LHC::Monitoring]
7
+ ```
8
+
9
+ You also have to configure statsd in order to have the monitoring interceptor report.
10
+
11
+ ```ruby
12
+ LHC::Monitoring.statsd = <your-instance-of-statsd>
13
+ ```
14
+
15
+ The monitoring interceptor reports all the HTTP communication done with LHS.
16
+ It reports the trial always.
17
+
18
+ In case of a successful response it reports the response code with a count and the response time with a gauge value.
19
+
20
+ ```ruby
21
+ LHC.get('http://local.ch')
22
+
23
+ "lhc.<app_name>.<env>.<host>.<http_method>.count", 1
24
+ "lhc.<app_name>.<env>.<host>.<http_method>.200", 1
25
+ "lhc.<app_name>.<env>.<host>.<http_method>.time", 43
26
+ ```
27
+
28
+ In case your workers/processes are getting killed due limited time constraints,
29
+ you are able to detect deltas with relying on "before_request", and "after_request" counts:
30
+
31
+ ```ruby
32
+ "lhc.<app_name>.<env>.<host>.<http_method>.before_request", 1
33
+ "lhc.<app_name>.<env>.<host>.<http_method>.after_request", 1
34
+ ```
35
+
36
+ Timeouts are also reported:
37
+
38
+ ```ruby
39
+ "lhc.<app_name>.<env>.<host>.<http_method>.timeout", 1
40
+ ```
41
+
42
+ All the dots in the host are getting replaced with underscore (_), because dot is the default separator in graphite.
43
+
44
+ It is also possible to set the key for Monitoring Interceptor on per request basis:
45
+
46
+ ```ruby
47
+ LHC.get('http://local.ch', monitoring_key: 'local_website')
48
+
49
+ "local_website.count", 1
50
+ "local_website.200", 1
51
+ "local_website.time", 43
52
+ ```
53
+
54
+ If you use this approach you need to add all namespaces (app, environment etc.) to the key on your own.
@@ -0,0 +1,19 @@
1
+ # Rollbar Interceptor
2
+
3
+ Forward errors to rollbar when exceptions occur during http requests.
4
+
5
+ ```ruby
6
+ LHC.config.interceptors = [LHC::Rollbar]
7
+ ```
8
+
9
+ ```ruby
10
+ LHC.get('http://local.ch')
11
+ ```
12
+
13
+ If it raises, it forwards the request and response object to rollbar, which contain all necessary data.
14
+
15
+ ## Forward additional parameters
16
+
17
+ ```ruby
18
+ LHC.get('http://local.ch', rollbar: { tracking_key: 'this particular request' })
19
+ ```
@@ -7,7 +7,7 @@ require "lhc/version"
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "lhc"
9
9
  s.version = LHC::VERSION
10
- s.authors = ['local.ch']
10
+ s.authors = ['https://github.com/local-ch/lhc/contributors']
11
11
  s.email = ['ws-operations@local.ch']
12
12
  s.homepage = 'https://github.com/local-ch/lhc'
13
13
  s.summary = 'LocalHttpClient'
@@ -18,18 +18,18 @@ Gem::Specification.new do |s|
18
18
  `git ls-files -- non_rails_spec/*`.split("\n")
19
19
  s.require_paths = ['lib']
20
20
 
21
- s.requirements << 'Ruby >= 1.9.2'
22
- s.required_ruby_version = '>= 1.9.2'
21
+ s.requirements << 'Ruby >= 2.0.0'
22
+ s.required_ruby_version = '>= 2.0.0'
23
23
 
24
24
  s.add_dependency 'typhoeus'
25
- s.add_dependency 'activesupport', '>= 4.1'
25
+ s.add_dependency 'activesupport', '>= 4.2'
26
26
 
27
27
  s.add_development_dependency 'rspec-rails', '>= 3.0.0'
28
- s.add_development_dependency 'rails', '~> 4.1.1'
28
+ s.add_development_dependency 'rails', '~> 4.2'
29
29
  s.add_development_dependency 'webmock'
30
30
  s.add_development_dependency 'geminabox'
31
31
  s.add_development_dependency 'pry'
32
- s.add_development_dependency 'rubocop'
32
+ s.add_development_dependency 'rubocop', '~> 0.36.0'
33
33
  s.add_development_dependency 'rubocop-rspec'
34
34
 
35
35
  s.license = 'GPL-3'
data/lib/lhc.rb CHANGED
@@ -14,4 +14,10 @@ module LHC
14
14
  end
15
15
  end
16
16
 
17
- Gem.find_files('lhc/**/*.rb').sort.each { |path| require path }
17
+ Gem.find_files('lhc/**/*.rb')
18
+ .sort
19
+ .each do |path|
20
+ require path if defined?(Rails) || !File.basename(path).include?('railtie.rb')
21
+ end
22
+
23
+ require 'lhc/railtie' if defined?(Rails)
@@ -10,7 +10,7 @@ class LHC::Config
10
10
 
11
11
  def endpoint(name, url, options = {})
12
12
  name = name.to_sym
13
- raise 'Endpoint already exists for that name' if @endpoints[name]
13
+ fail 'Endpoint already exists for that name' if @endpoints[name]
14
14
  @endpoints[name] = LHC::Endpoint.new(url, options)
15
15
  end
16
16
 
@@ -20,7 +20,7 @@ class LHC::Config
20
20
 
21
21
  def placeholder(name, value)
22
22
  name = name.to_sym
23
- raise 'Placeholder already exists for that name' if @placeholders[name]
23
+ fail 'Placeholder already exists for that name' if @placeholders[name]
24
24
  @placeholders[name] = value
25
25
  end
26
26
 
@@ -33,7 +33,7 @@ class LHC::Config
33
33
  end
34
34
 
35
35
  def interceptors=(interceptors)
36
- raise 'Default interceptors already set and can only be set once' if @interceptors
36
+ fail 'Default interceptors already set and can only be set once' if @interceptors
37
37
  @interceptors = interceptors
38
38
  end
39
39
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # An endpoint is an url that leads to a backend resource.
2
3
  # The url can also be an url-template.
3
4
  class LHC::Endpoint
@@ -20,7 +21,7 @@ class LHC::Endpoint
20
21
  else
21
22
  find_value(match, params)
22
23
  end
23
- replacement || raise("Compilation incomplete. Unable to find value for #{match.gsub(':', '')}.")
24
+ replacement || fail("Compilation incomplete. Unable to find value for #{match.gsub(':', '')}.")
24
25
  end
25
26
  end
26
27
 
@@ -15,7 +15,7 @@ class LHC::InterceptorProcessor
15
15
  interceptors.each do |interceptor|
16
16
  result = interceptor.send(name, target)
17
17
  if result.is_a? LHC::Response
18
- raise 'Response already set from another interceptor' if @response
18
+ fail 'Response already set from another interceptor' if @response
19
19
  request = target.is_a?(LHC::Request) ? target : target.request
20
20
  @response = request.response = result
21
21
  end
@@ -0,0 +1,33 @@
1
+ class LHC::Auth < LHC::Interceptor
2
+
3
+ def before_request(request)
4
+ options = request.options[:auth] || {}
5
+ authenticate!(request, options)
6
+ end
7
+
8
+ private
9
+
10
+ def authenticate!(request, options)
11
+ if options[:bearer]
12
+ bearer_authentication!(request, options)
13
+ elsif options[:basic]
14
+ basic_authentication!(request, options)
15
+ end
16
+ end
17
+
18
+ def basic_authentication!(request, options)
19
+ auth = options[:basic]
20
+ credentials = "#{auth[:username]}:#{auth[:password]}"
21
+ set_authorization_header request, "Basic #{Base64.encode64(credentials).chomp}"
22
+ end
23
+
24
+ def bearer_authentication!(request, options)
25
+ token = options[:bearer]
26
+ token = token.call if token.is_a?(Proc)
27
+ set_authorization_header request, "Bearer #{token}"
28
+ end
29
+
30
+ def set_authorization_header(request, value)
31
+ request.headers['Authorization'] = value
32
+ end
33
+ end
@@ -0,0 +1,66 @@
1
+ class LHC::Caching < LHC::Interceptor
2
+ include ActiveSupport::Configurable
3
+
4
+ config_accessor :cache, :logger
5
+
6
+ CACHE_VERSION = '1'
7
+
8
+ # Options forwarded to the cache
9
+ FORWARDED_OPTIONS = {
10
+ cache_expires_in: :expires_in,
11
+ cache_race_condition_ttl: :race_condition_ttl
12
+ }
13
+
14
+ def before_request(request)
15
+ return unless cache
16
+ return unless request.options[:cache]
17
+ cached_response_data = cache.fetch(key(request))
18
+ return unless cached_response_data
19
+ logger.info "Served from cache: #{key(request)}" if logger
20
+ from_cache(request, cached_response_data)
21
+ end
22
+
23
+ def after_response(response)
24
+ return unless cache
25
+ request = response.request
26
+ return if !request.options[:cache] || !response.success?
27
+ cache.write(key(request), to_cache(response), options(request.options))
28
+ end
29
+
30
+ private
31
+
32
+ # converts json we read from the cache to an LHC::Response object
33
+ def from_cache(request, data)
34
+ raw = Typhoeus::Response.new(data)
35
+ request.response = LHC::Response.new(raw, request)
36
+ end
37
+
38
+ # converts a LHC::Response object to json, we store in the cache
39
+ def to_cache(response)
40
+ data = {}
41
+ data[:body] = response.body
42
+ data[:code] = response.code
43
+ # convert into a actual hash because the typhoeus headers object breaks marshaling
44
+ data[:headers] = response.headers ? Hash[response.headers] : response.headers
45
+ # return_code is quite important as Typhoeus relies on it in order to determin 'success?'
46
+ data[:return_code] = response.options[:return_code]
47
+ data
48
+ end
49
+
50
+ def key(request)
51
+ key = request.options[:cache_key]
52
+ unless key
53
+ key = "#{request.method.upcase} #{request.url}"
54
+ key += "?#{request.params.to_query}" unless request.params.blank?
55
+ end
56
+ "LHC_CACHE(v#{CACHE_VERSION}): #{key}"
57
+ end
58
+
59
+ def options(input = {})
60
+ options = {}
61
+ FORWARDED_OPTIONS.each do |k, v|
62
+ options[v] = input[k] if input.key?(k)
63
+ end
64
+ options
65
+ end
66
+ end
@@ -0,0 +1,61 @@
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 before_request(request)
13
+ return unless statsd
14
+ LHC::Monitoring.statsd.count("#{key(request)}.before_request", 1)
15
+ end
16
+
17
+ def after_request(request)
18
+ return unless statsd
19
+ LHC::Monitoring.statsd.count("#{key(request)}.count", 1)
20
+ LHC::Monitoring.statsd.count("#{key(request)}.after_request", 1)
21
+ end
22
+
23
+ def after_response(response)
24
+ return unless statsd
25
+ key = key(response)
26
+ LHC::Monitoring.statsd.timing("#{key}.time", response.time) if response.success?
27
+ key += response.timeout? ? '.timeout' : ".#{response.code}"
28
+ LHC::Monitoring.statsd.count(key, 1)
29
+ end
30
+
31
+ private
32
+
33
+ def key(target)
34
+ request = target.is_a?(LHC::Request) ? target : target.request
35
+ key = options(request.options)[:key]
36
+ return key if key.present?
37
+
38
+ url = sanitize_url(request.url)
39
+ key = [
40
+ 'lhc',
41
+ Rails.application.class.parent_name.underscore,
42
+ Rails.env,
43
+ URI.parse(url).host.gsub(/\./, '_'),
44
+ request.method
45
+ ]
46
+ key.join('.')
47
+ end
48
+
49
+ def sanitize_url(url)
50
+ return url if url.match(%r{https?://})
51
+ "http://#{url}"
52
+ end
53
+
54
+ def options(input = {})
55
+ options = {}
56
+ FORWARDED_OPTIONS.each do |k, v|
57
+ options[v] = input[k] if input.key?(k)
58
+ end
59
+ options
60
+ end
61
+ end
@@ -0,0 +1,26 @@
1
+ class LHC::Rollbar < LHC::Interceptor
2
+ include ActiveSupport::Configurable
3
+
4
+ def after_response(response)
5
+ return unless Object.const_defined?('Rollbar')
6
+ return if response.success?
7
+ request = response.request
8
+ additional_params = request.options.fetch(:rollbar, {})
9
+ data = {
10
+ response: {
11
+ body: response.body,
12
+ code: response.code,
13
+ headers: response.headers,
14
+ time: response.time,
15
+ timeout?: response.timeout?
16
+ },
17
+ request: {
18
+ url: request.url,
19
+ method: request.method,
20
+ headers: request.headers,
21
+ params: request.params
22
+ }
23
+ }.merge additional_params
24
+ Rollbar.warning("Status: #{response.code} URL: #{request.url}", data)
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ module LHC
2
+ class Railtie < Rails::Railtie
3
+ initializer "lhc.configure_rails_initialization" do
4
+ LHC::Caching.cache ||= Rails.cache
5
+ LHC::Caching.logger ||= Rails.logger
6
+ end
7
+ end
8
+ end
@@ -108,6 +108,6 @@ class LHC::Request
108
108
 
109
109
  def throw_error(response)
110
110
  error = LHC::Error.find(response)
111
- raise error.new(error, response)
111
+ fail error.new(error, response)
112
112
  end
113
113
  end
@@ -0,0 +1,7 @@
1
+ RSpec.configure do |config|
2
+ LHC::Caching.cache = ActiveSupport::Cache::MemoryStore.new
3
+
4
+ config.before(:each) do
5
+ LHC::Caching.cache.clear
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module LHC
2
- VERSION ||= "3.8.1"
2
+ VERSION ||= "4.0.0"
3
3
  end
File without changes
@@ -0,0 +1,15 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHC::Auth do
4
+ before(:each) do
5
+ LHC.config.interceptors = [LHC::Auth]
6
+ end
7
+
8
+ it 'adds basic auth to every request' do
9
+ options = { basic: { username: 'steve', password: 'can' } }
10
+ LHC.config.endpoint(:local, 'http://local.ch', auth: options)
11
+ stub_request(:get, 'http://local.ch')
12
+ .with(headers: { 'Authorization' => 'Basic c3RldmU6Y2Fu' })
13
+ LHC.get(:local)
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHC::Auth do
4
+ before(:each) do
5
+ LHC.config.interceptors = [LHC::Auth]
6
+ end
7
+
8
+ it 'adds the bearer token to every request' do
9
+ def bearer_token
10
+ '123456'
11
+ end
12
+ options = { bearer: -> { bearer_token } }
13
+ LHC.config.endpoint(:local, 'http://local.ch', auth: options)
14
+ stub_request(:get, 'http://local.ch').with(headers: { 'Authorization' => 'Bearer 123456' })
15
+ LHC.get(:local)
16
+ end
17
+ end
@@ -0,0 +1,60 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHC::Caching do
4
+ before(:each) do
5
+ LHC.config.interceptors = [LHC::Caching]
6
+ LHC::Caching.cache = Rails.cache
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(
17
+ "LHC_CACHE(v#{LHC::Caching::CACHE_VERSION}): GET http://local.ch",
18
+ {
19
+ body: 'The Website',
20
+ code: 200,
21
+ headers: nil,
22
+ return_code: nil
23
+ }, { expires_in: 5.minutes }
24
+ )
25
+ .and_call_original
26
+ original_response = LHC.get(:local)
27
+ cached_response = LHC.get(:local)
28
+ expect(original_response.body).to eq cached_response.body
29
+ expect(original_response.code).to eq cached_response.code
30
+ expect(original_response.headers).to eq cached_response.headers
31
+ expect(original_response.headers).to eq cached_response.headers
32
+ assert_requested stub, times: 1
33
+ end
34
+
35
+ it 'does not serve from cache if option is not set' do
36
+ LHC.config.endpoint(:local, 'http://local.ch')
37
+ expect(Rails.cache).not_to receive(:write)
38
+ expect(Rails.cache).not_to receive(:fetch)
39
+ stub
40
+ 2.times { LHC.get(:local) }
41
+ assert_requested stub, times: 2
42
+ end
43
+
44
+ it 'lets you configure the cache key that will be used' do
45
+ LHC.config.endpoint(:local, 'http://local.ch', cache: true, cache_key: 'STATICKEY')
46
+ expect(Rails.cache).to receive(:fetch).with("LHC_CACHE(v#{LHC::Caching::CACHE_VERSION}): STATICKEY").and_call_original
47
+ expect(Rails.cache).to receive(:write).with("LHC_CACHE(v#{LHC::Caching::CACHE_VERSION}): STATICKEY", anything, anything).and_call_original
48
+ stub
49
+ LHC.get(:local)
50
+ end
51
+
52
+ it 'does not store server errors in cache' do
53
+ LHC.config.endpoint(:local, 'http://local.ch', cache: true)
54
+ stub_request(:get, 'http://local.ch').to_return(status: 500, body: 'ERROR')
55
+ expect { LHC.get(:local) }.to raise_error LHC::ServerError
56
+ stub
57
+ expect(Rails.cache).to receive(:write).once
58
+ LHC.get(:local)
59
+ end
60
+ end
@@ -0,0 +1,22 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHC::Caching do
4
+ context 'parameters' do
5
+ before(:each) do
6
+ LHC.config.interceptors = [LHC::Caching]
7
+ Rails.cache.clear
8
+ end
9
+
10
+ it 'considers parameters when writing/reading from cache' do
11
+ LHC.config.endpoint(:local, 'http://local.ch', cache: true, cache_expires_in: 5.minutes)
12
+ stub_request(:get, 'http://local.ch').to_return(status: 200, body: 'The Website')
13
+ stub_request(:get, 'http://local.ch?location=zuerich').to_return(status: 200, body: 'The Website for Zuerich')
14
+ expect(
15
+ LHC.get(:local).body
16
+ ).to eq 'The Website'
17
+ expect(
18
+ LHC.get(:local, params: { location: 'zuerich' }).body
19
+ ).to eq 'The Website for Zuerich'
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHC::Caching do
4
+ before(:each) do
5
+ LHC.config.interceptors = [LHC::Caching]
6
+ LHC.config.endpoint(:local, 'http://local.ch', cache: true, cache_expires_in: 5.minutes)
7
+ Rails.cache.clear
8
+ # leverage the Typhoeus internal mock attribute in order to get Typhoeus evaluate the return_code
9
+ # lib/typhoeus/response/status.rb:48
10
+ allow_any_instance_of(Typhoeus::Response).to receive(:mock).and_return(false)
11
+ end
12
+
13
+ let!(:stub) { stub_request(:get, 'http://local.ch').to_return(status: 200, body: 'The Website') }
14
+
15
+ it 'provides the correct response status for responses from cache' do
16
+ stub
17
+ # the real request provides the return_code
18
+ allow_any_instance_of(Typhoeus::Response).to receive(:options)
19
+ .and_return(code: 200, status_message: '', body: 'The Website', headers: nil, return_code: :ok)
20
+ response = LHC.get(:local)
21
+ expect(response.success?).to eq true
22
+ # the cached response should get it from the cache
23
+ allow_any_instance_of(Typhoeus::Response).to receive(:options).and_call_original
24
+ cached_response = LHC.get(:local)
25
+ expect(cached_response.success?).to eq true
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHC::Caching do
4
+ context 'to_cache' do
5
+ it 'returns a marshalable object to store in the cache' do
6
+ expect do
7
+ response = Typhoeus::Response.new(headers: { 'Accept' => 'application/json' })
8
+ Marshal.dump(
9
+ LHC::Caching.new.send(:to_cache, response)
10
+ )
11
+ end.not_to raise_error
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,82 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHC::Monitoring do
4
+ let(:stub) { stub_request(:get, 'http://local.ch').to_return(status: 200, body: 'The Website') }
5
+ let(:endpoint_configuration) { LHC.config.endpoint(:local, 'http://local.ch') }
6
+
7
+ module Statsd
8
+ def self.count(_path, _value)
9
+ end
10
+
11
+ def self.timing(_path, _value)
12
+ end
13
+ end
14
+
15
+ before(:each) do
16
+ LHC.config.interceptors = [LHC::Monitoring]
17
+ LHC::Monitoring.statsd = Statsd
18
+ Rails.cache.clear
19
+ endpoint_configuration
20
+ end
21
+
22
+ it 'does not report anything if no statsd is configured' do
23
+ stub
24
+ LHC.get(:local) # and also does not crash ;)
25
+ end
26
+
27
+ context 'statsd configured' do
28
+ it 'reports trial, response and timing by default ' do
29
+ stub
30
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.before_request', 1)
31
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.after_request', 1)
32
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.count', 1)
33
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.200', 1)
34
+ expect(Statsd).to receive(:timing).with('lhc.dummy.test.local_ch.get.time', anything)
35
+ LHC.get(:local)
36
+ end
37
+
38
+ it 'does not report timing when response failed' do
39
+ stub_request(:get, 'http://local.ch').to_return(status: 500)
40
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.before_request', 1)
41
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.after_request', 1)
42
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.count', 1)
43
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.500', 1)
44
+ expect(Statsd).not_to receive(:timing)
45
+ expect { LHC.get(:local) }.to raise_error LHC::ServerError
46
+ end
47
+
48
+ it 'reports timeout instead of status code if response timed out' do
49
+ stub_request(:get, 'http://local.ch').to_timeout
50
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.before_request', 1)
51
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.after_request', 1)
52
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.count', 1)
53
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.timeout', 1)
54
+ expect(Statsd).not_to receive(:timing)
55
+ expect { LHC.get(:local) }.to raise_error LHC::Timeout
56
+ end
57
+
58
+ it 'allows to set the stats key for request' do
59
+ stub
60
+ expect(Statsd).to receive(:count).with('defined_key.before_request', 1)
61
+ expect(Statsd).to receive(:count).with('defined_key.after_request', 1)
62
+ expect(Statsd).to receive(:count).with('defined_key.count', 1)
63
+ expect(Statsd).to receive(:count).with('defined_key.200', 1)
64
+ expect(Statsd).to receive(:timing).with('defined_key.time', anything)
65
+ LHC.get(:local, monitoring_key: 'defined_key')
66
+ end
67
+ end
68
+
69
+ context 'without protocol' do
70
+ let(:endpoint_configuration) { LHC.config.endpoint(:local, 'local.ch') }
71
+
72
+ it 'reports trial, response and timing by default ' do
73
+ stub
74
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.before_request', 1)
75
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.after_request', 1)
76
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.count', 1)
77
+ expect(Statsd).to receive(:count).with('lhc.dummy.test.local_ch.get.200', 1)
78
+ expect(Statsd).to receive(:timing).with('lhc.dummy.test.local_ch.get.time', anything)
79
+ LHC.get(:local)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,50 @@
1
+ require 'rails_helper'
2
+
3
+ describe LHC::Rollbar do
4
+ before(:each) do
5
+ LHC.config.interceptors = [LHC::Rollbar]
6
+ end
7
+
8
+ it 'does not report if Rollbar is not defined' do
9
+ stub_request(:get, 'http://local.ch').to_return(status: 400)
10
+ expect(-> { LHC.get('http://local.ch') })
11
+ .to raise_error LHC::BadRequest
12
+ end
13
+
14
+ context 'Rollbar is defined' do
15
+ before(:each) do
16
+ class Rollbar; end
17
+ allow(::Rollbar).to receive(:warning)
18
+ end
19
+
20
+ it 'does report errors to rollbar' do
21
+ stub_request(:get, 'http://local.ch').to_return(status: 400)
22
+ expect(-> { LHC.get('http://local.ch') })
23
+ .to raise_error LHC::BadRequest
24
+ expect(::Rollbar).to have_received(:warning)
25
+ .with(
26
+ 'Status: 400 URL: http://local.ch',
27
+ response: hash_including(body: anything, code: anything, headers: anything, time: anything, timeout?: anything),
28
+ request: hash_including(url: anything, method: anything, headers: anything, params: anything)
29
+ )
30
+ end
31
+
32
+ context 'additional params' do
33
+ it 'does report errors to rollbar with additional data' do
34
+ stub_request(:get, 'http://local.ch')
35
+ .to_return(status: 400)
36
+ expect(-> { LHC.get('http://local.ch', rollbar: { additional: 'data' }) })
37
+ .to raise_error LHC::BadRequest
38
+ expect(::Rollbar).to have_received(:warning)
39
+ .with(
40
+ 'Status: 400 URL: http://local.ch',
41
+ hash_including(
42
+ response: anything,
43
+ request: anything,
44
+ additional: 'data'
45
+ )
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,4 +1,4 @@
1
1
  require 'spec_helper'
2
- require File.expand_path("../dummy/config/environment", __FILE__)
3
-
4
2
  ENV["RAILS_ENV"] ||= 'test'
3
+
4
+ require File.expand_path("../dummy/config/environment", __FILE__)
@@ -1,5 +1,4 @@
1
1
  require 'pry'
2
2
  require 'webmock/rspec'
3
- require 'lhc'
4
3
 
5
4
  Dir[File.join(__dir__, "support/**/*.rb")].each { |f| require f }
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: 3.8.1
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - local.ch
7
+ - https://github.com/local-ch/lhc/contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-19 00:00:00.000000000 Z
11
+ date: 2017-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: typhoeus
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '4.1'
33
+ version: '4.2'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '4.1'
40
+ version: '4.2'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec-rails
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +58,14 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 4.1.1
61
+ version: '4.2'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 4.1.1
68
+ version: '4.2'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: webmock
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -112,16 +112,16 @@ dependencies:
112
112
  name: rubocop
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - ">="
115
+ - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '0'
117
+ version: 0.36.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - ">="
122
+ - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '0'
124
+ version: 0.36.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: rubocop-rspec
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +164,10 @@ files:
164
164
  - docs/configuration.md
165
165
  - docs/exceptions.md
166
166
  - docs/interceptors.md
167
+ - docs/interceptors/authentication.md
168
+ - docs/interceptors/caching.md
169
+ - docs/interceptors/monitoring.md
170
+ - docs/interceptors/rollbar.md
167
171
  - docs/request.md
168
172
  - docs/response.md
169
173
  - lhc.gemspec
@@ -181,11 +185,16 @@ files:
181
185
  - lib/lhc/formats/json.rb
182
186
  - lib/lhc/interceptor.rb
183
187
  - lib/lhc/interceptor_processor.rb
188
+ - lib/lhc/interceptors/auth.rb
189
+ - lib/lhc/interceptors/caching.rb
190
+ - lib/lhc/interceptors/monitoring.rb
191
+ - lib/lhc/interceptors/rollbar.rb
192
+ - lib/lhc/railtie.rb
184
193
  - lib/lhc/request.rb
185
194
  - lib/lhc/response.rb
186
195
  - lib/lhc/response/data.rb
196
+ - lib/lhc/test/cache_helper.rb
187
197
  - lib/lhc/version.rb
188
- - non_rails_spec/request/request_spec.rb
189
198
  - script/ci/build.sh
190
199
  - spec/basic_methods/delete_spec.rb
191
200
  - spec/basic_methods/get_spec.rb
@@ -233,6 +242,7 @@ files:
233
242
  - spec/dummy/public/422.html
234
243
  - spec/dummy/public/500.html
235
244
  - spec/dummy/public/favicon.ico
245
+ - spec/dummy/tmp/cache/.gitkeep
236
246
  - spec/endpoint/compile_spec.rb
237
247
  - spec/endpoint/match_spec.rb
238
248
  - spec/endpoint/placeholders_spec.rb
@@ -245,18 +255,27 @@ files:
245
255
  - spec/formats/json_spec.rb
246
256
  - spec/interceptors/after_request_spec.rb
247
257
  - spec/interceptors/after_response_spec.rb
258
+ - spec/interceptors/auth/basic_auth_spec.rb
259
+ - spec/interceptors/auth/bearer_spec.rb
248
260
  - spec/interceptors/before_request_spec.rb
249
261
  - spec/interceptors/before_response_spec.rb
262
+ - spec/interceptors/caching/main_spec.rb
263
+ - spec/interceptors/caching/parameters_spec.rb
264
+ - spec/interceptors/caching/response_status_spec.rb
265
+ - spec/interceptors/caching/to_cache_spec.rb
250
266
  - spec/interceptors/default_interceptors_spec.rb
251
267
  - spec/interceptors/define_spec.rb
268
+ - spec/interceptors/monitoring/main_spec.rb
252
269
  - spec/interceptors/response_competition_spec.rb
253
270
  - spec/interceptors/return_response_spec.rb
271
+ - spec/interceptors/rollbar/main_spec.rb
254
272
  - spec/rails_helper.rb
255
273
  - spec/request/encoding_spec.rb
256
274
  - spec/request/error_handling_spec.rb
257
275
  - spec/request/headers_spec.rb
258
276
  - spec/request/option_dup_spec.rb
259
277
  - spec/request/parallel_requests_spec.rb
278
+ - spec/request/request_without_rails_spec.rb
260
279
  - spec/request/url_patterns_spec.rb
261
280
  - spec/response/body_spec.rb
262
281
  - spec/response/code_spec.rb
@@ -287,16 +306,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
287
306
  requirements:
288
307
  - - ">="
289
308
  - !ruby/object:Gem::Version
290
- version: 1.9.2
309
+ version: 2.0.0
291
310
  required_rubygems_version: !ruby/object:Gem::Requirement
292
311
  requirements:
293
312
  - - ">="
294
313
  - !ruby/object:Gem::Version
295
314
  version: '0'
296
315
  requirements:
297
- - Ruby >= 1.9.2
316
+ - Ruby >= 2.0.0
298
317
  rubyforge_project:
299
- rubygems_version: 2.6.8
318
+ rubygems_version: 2.6.10
300
319
  signing_key:
301
320
  specification_version: 4
302
321
  summary: LocalHttpClient
@@ -347,6 +366,7 @@ test_files:
347
366
  - spec/dummy/public/422.html
348
367
  - spec/dummy/public/500.html
349
368
  - spec/dummy/public/favicon.ico
369
+ - spec/dummy/tmp/cache/.gitkeep
350
370
  - spec/endpoint/compile_spec.rb
351
371
  - spec/endpoint/match_spec.rb
352
372
  - spec/endpoint/placeholders_spec.rb
@@ -359,18 +379,27 @@ test_files:
359
379
  - spec/formats/json_spec.rb
360
380
  - spec/interceptors/after_request_spec.rb
361
381
  - spec/interceptors/after_response_spec.rb
382
+ - spec/interceptors/auth/basic_auth_spec.rb
383
+ - spec/interceptors/auth/bearer_spec.rb
362
384
  - spec/interceptors/before_request_spec.rb
363
385
  - spec/interceptors/before_response_spec.rb
386
+ - spec/interceptors/caching/main_spec.rb
387
+ - spec/interceptors/caching/parameters_spec.rb
388
+ - spec/interceptors/caching/response_status_spec.rb
389
+ - spec/interceptors/caching/to_cache_spec.rb
364
390
  - spec/interceptors/default_interceptors_spec.rb
365
391
  - spec/interceptors/define_spec.rb
392
+ - spec/interceptors/monitoring/main_spec.rb
366
393
  - spec/interceptors/response_competition_spec.rb
367
394
  - spec/interceptors/return_response_spec.rb
395
+ - spec/interceptors/rollbar/main_spec.rb
368
396
  - spec/rails_helper.rb
369
397
  - spec/request/encoding_spec.rb
370
398
  - spec/request/error_handling_spec.rb
371
399
  - spec/request/headers_spec.rb
372
400
  - spec/request/option_dup_spec.rb
373
401
  - spec/request/parallel_requests_spec.rb
402
+ - spec/request/request_without_rails_spec.rb
374
403
  - spec/request/url_patterns_spec.rb
375
404
  - spec/response/body_spec.rb
376
405
  - spec/response/code_spec.rb
@@ -389,4 +418,3 @@ test_files:
389
418
  - spec/support/reset_config.rb
390
419
  - spec/timeouts/no_signal_spec.rb
391
420
  - spec/timeouts/timings_spec.rb
392
- - non_rails_spec/request/request_spec.rb