lhc 3.8.1 → 4.0.0

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