dhc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/rubocop.yml +27 -0
- data/.github/workflows/test.yml +27 -0
- data/.gitignore +37 -0
- data/.rubocop.yml +105 -0
- data/.ruby-version +1 -0
- data/Gemfile +5 -0
- data/LICENSE +674 -0
- data/README.md +999 -0
- data/Rakefile +25 -0
- data/dhc.gemspec +40 -0
- data/lib/core_ext/hash/deep_transform_values.rb +48 -0
- data/lib/dhc.rb +77 -0
- data/lib/dhc/concerns/dhc/basic_methods_concern.rb +42 -0
- data/lib/dhc/concerns/dhc/configuration_concern.rb +20 -0
- data/lib/dhc/concerns/dhc/fix_invalid_encoding_concern.rb +42 -0
- data/lib/dhc/concerns/dhc/formats_concern.rb +25 -0
- data/lib/dhc/concerns/dhc/request/user_agent_concern.rb +25 -0
- data/lib/dhc/config.rb +47 -0
- data/lib/dhc/endpoint.rb +119 -0
- data/lib/dhc/error.rb +82 -0
- data/lib/dhc/errors/client_error.rb +73 -0
- data/lib/dhc/errors/parser_error.rb +4 -0
- data/lib/dhc/errors/server_error.rb +28 -0
- data/lib/dhc/errors/timeout.rb +4 -0
- data/lib/dhc/errors/unknown_error.rb +4 -0
- data/lib/dhc/format.rb +18 -0
- data/lib/dhc/formats.rb +8 -0
- data/lib/dhc/formats/form.rb +45 -0
- data/lib/dhc/formats/json.rb +55 -0
- data/lib/dhc/formats/multipart.rb +45 -0
- data/lib/dhc/formats/plain.rb +42 -0
- data/lib/dhc/interceptor.rb +36 -0
- data/lib/dhc/interceptors.rb +26 -0
- data/lib/dhc/interceptors/auth.rb +94 -0
- data/lib/dhc/interceptors/caching.rb +148 -0
- data/lib/dhc/interceptors/default_timeout.rb +16 -0
- data/lib/dhc/interceptors/logging.rb +37 -0
- data/lib/dhc/interceptors/monitoring.rb +92 -0
- data/lib/dhc/interceptors/prometheus.rb +51 -0
- data/lib/dhc/interceptors/retry.rb +41 -0
- data/lib/dhc/interceptors/rollbar.rb +36 -0
- data/lib/dhc/interceptors/throttle.rb +86 -0
- data/lib/dhc/interceptors/zipkin.rb +110 -0
- data/lib/dhc/railtie.rb +9 -0
- data/lib/dhc/request.rb +161 -0
- data/lib/dhc/response.rb +60 -0
- data/lib/dhc/response/data.rb +28 -0
- data/lib/dhc/response/data/base.rb +18 -0
- data/lib/dhc/response/data/collection.rb +16 -0
- data/lib/dhc/response/data/item.rb +29 -0
- data/lib/dhc/rspec.rb +11 -0
- data/lib/dhc/test/cache_helper.rb +3 -0
- data/lib/dhc/version.rb +5 -0
- data/script/ci/build.sh +19 -0
- data/spec/basic_methods/delete_spec.rb +34 -0
- data/spec/basic_methods/get_spec.rb +49 -0
- data/spec/basic_methods/post_spec.rb +42 -0
- data/spec/basic_methods/put_spec.rb +48 -0
- data/spec/basic_methods/request_spec.rb +19 -0
- data/spec/basic_methods/request_without_rails_spec.rb +29 -0
- data/spec/config/endpoints_spec.rb +63 -0
- data/spec/config/placeholders_spec.rb +32 -0
- data/spec/core_ext/hash/deep_transform_values_spec.rb +24 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +8 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +7 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +4 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +5 -0
- data/spec/dummy/bin/rails +6 -0
- data/spec/dummy/bin/rake +6 -0
- data/spec/dummy/config.ru +6 -0
- data/spec/dummy/config/application.rb +16 -0
- data/spec/dummy/config/boot.rb +7 -0
- data/spec/dummy/config/environment.rb +7 -0
- data/spec/dummy/config/environments/development.rb +36 -0
- data/spec/dummy/config/environments/production.rb +77 -0
- data/spec/dummy/config/environments/test.rb +41 -0
- data/spec/dummy/config/initializers/assets.rb +10 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +9 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -0
- data/spec/dummy/config/initializers/inflections.rb +18 -0
- data/spec/dummy/config/initializers/mime_types.rb +6 -0
- data/spec/dummy/config/initializers/session_store.rb +5 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +11 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/endpoint/compile_spec.rb +35 -0
- data/spec/endpoint/match_spec.rb +41 -0
- data/spec/endpoint/placeholders_spec.rb +30 -0
- data/spec/endpoint/remove_interpolated_params_spec.rb +17 -0
- data/spec/endpoint/values_as_params_spec.rb +31 -0
- data/spec/error/dup_spec.rb +12 -0
- data/spec/error/find_spec.rb +57 -0
- data/spec/error/response_spec.rb +17 -0
- data/spec/error/timeout_spec.rb +14 -0
- data/spec/error/to_s_spec.rb +85 -0
- data/spec/formats/form_spec.rb +27 -0
- data/spec/formats/json_spec.rb +66 -0
- data/spec/formats/multipart_spec.rb +26 -0
- data/spec/formats/plain_spec.rb +29 -0
- data/spec/interceptors/after_request_spec.rb +20 -0
- data/spec/interceptors/after_response_spec.rb +39 -0
- data/spec/interceptors/auth/basic_auth_spec.rb +17 -0
- data/spec/interceptors/auth/bearer_spec.rb +19 -0
- data/spec/interceptors/auth/body_spec.rb +36 -0
- data/spec/interceptors/auth/long_basic_auth_credentials_spec.rb +17 -0
- data/spec/interceptors/auth/no_instance_var_for_options_spec.rb +27 -0
- data/spec/interceptors/auth/reauthentication_configuration_spec.rb +61 -0
- data/spec/interceptors/auth/reauthentication_spec.rb +44 -0
- data/spec/interceptors/before_request_spec.rb +21 -0
- data/spec/interceptors/before_response_spec.rb +20 -0
- data/spec/interceptors/caching/hydra_spec.rb +26 -0
- data/spec/interceptors/caching/main_spec.rb +73 -0
- data/spec/interceptors/caching/methods_spec.rb +42 -0
- data/spec/interceptors/caching/multilevel_cache_spec.rb +139 -0
- data/spec/interceptors/caching/options_spec.rb +78 -0
- data/spec/interceptors/caching/parameters_spec.rb +24 -0
- data/spec/interceptors/caching/response_status_spec.rb +29 -0
- data/spec/interceptors/caching/to_cache_spec.rb +16 -0
- data/spec/interceptors/default_interceptors_spec.rb +15 -0
- data/spec/interceptors/default_timeout/main_spec.rb +34 -0
- data/spec/interceptors/define_spec.rb +30 -0
- data/spec/interceptors/dup_spec.rb +19 -0
- data/spec/interceptors/logging/main_spec.rb +37 -0
- data/spec/interceptors/monitoring/caching_spec.rb +66 -0
- data/spec/interceptors/monitoring/main_spec.rb +97 -0
- data/spec/interceptors/prometheus_spec.rb +54 -0
- data/spec/interceptors/response_competition_spec.rb +39 -0
- data/spec/interceptors/retry/main_spec.rb +73 -0
- data/spec/interceptors/return_response_spec.rb +38 -0
- data/spec/interceptors/rollbar/invalid_encoding_spec.rb +43 -0
- data/spec/interceptors/rollbar/main_spec.rb +57 -0
- data/spec/interceptors/throttle/main_spec.rb +236 -0
- data/spec/interceptors/throttle/reset_track_spec.rb +53 -0
- data/spec/interceptors/zipkin/distributed_tracing_spec.rb +135 -0
- data/spec/rails_helper.rb +6 -0
- data/spec/request/body_spec.rb +39 -0
- data/spec/request/encoding_spec.rb +38 -0
- data/spec/request/error_handling_spec.rb +88 -0
- data/spec/request/headers_spec.rb +12 -0
- data/spec/request/ignore_errors_spec.rb +73 -0
- data/spec/request/option_dup_spec.rb +13 -0
- data/spec/request/parallel_requests_spec.rb +59 -0
- data/spec/request/params_encoding_spec.rb +26 -0
- data/spec/request/request_without_rails_spec.rb +15 -0
- data/spec/request/url_patterns_spec.rb +54 -0
- data/spec/request/user_agent_spec.rb +26 -0
- data/spec/request/user_agent_without_rails_spec.rb +27 -0
- data/spec/response/body_spec.rb +16 -0
- data/spec/response/code_spec.rb +16 -0
- data/spec/response/data_accessor_spec.rb +29 -0
- data/spec/response/data_spec.rb +85 -0
- data/spec/response/effective_url_spec.rb +16 -0
- data/spec/response/headers_spec.rb +18 -0
- data/spec/response/options_spec.rb +18 -0
- data/spec/response/success_spec.rb +13 -0
- data/spec/response/time_spec.rb +21 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/fixtures/json/feedback.json +11 -0
- data/spec/support/fixtures/json/feedbacks.json +164 -0
- data/spec/support/fixtures/json/localina_content_ad.json +23 -0
- data/spec/support/load_json.rb +5 -0
- data/spec/support/reset_config.rb +8 -0
- data/spec/support/zipkin_mock.rb +114 -0
- data/spec/timeouts/no_signal_spec.rb +13 -0
- data/spec/timeouts/timings_spec.rb +55 -0
- metadata +527 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Handles interceptions during the lifecycle of a request
|
|
4
|
+
# Represents all active interceptors for a request/response.
|
|
5
|
+
class DHC::Interceptors
|
|
6
|
+
|
|
7
|
+
attr_accessor :all
|
|
8
|
+
|
|
9
|
+
# Intitalizes and determines if global or local interceptors are used
|
|
10
|
+
def initialize(request)
|
|
11
|
+
self.all = (request.options[:interceptors] || DHC.config.interceptors).map do |interceptor|
|
|
12
|
+
interceptor.new(request)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Forwards messages to interceptors and handles provided responses.
|
|
17
|
+
def intercept(name)
|
|
18
|
+
all.each do |interceptor|
|
|
19
|
+
result = interceptor.send(name)
|
|
20
|
+
if result.is_a? DHC::Response
|
|
21
|
+
raise 'Response already set from another interceptor' if @response
|
|
22
|
+
@response = interceptor.request.response = result
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DHC::Auth < DHC::Interceptor
|
|
4
|
+
include ActiveSupport::Configurable
|
|
5
|
+
config_accessor :refresh_client_token
|
|
6
|
+
|
|
7
|
+
def before_raw_request
|
|
8
|
+
body_authentication! if auth_options[:body]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def before_request
|
|
12
|
+
bearer_authentication! if auth_options[:bearer]
|
|
13
|
+
basic_authentication! if auth_options[:basic]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def after_response
|
|
17
|
+
return unless configuration_correct?
|
|
18
|
+
return unless reauthenticate?
|
|
19
|
+
reauthenticate!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def body_authentication!
|
|
25
|
+
auth = auth_options[:body]
|
|
26
|
+
request.options[:body] = (request.options[:body] || {}).merge(auth)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def basic_authentication!
|
|
30
|
+
auth = auth_options[:basic]
|
|
31
|
+
credentials = "#{auth[:username]}:#{auth[:password]}"
|
|
32
|
+
set_authorization_header("Basic #{Base64.strict_encode64(credentials).chomp}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def bearer_authentication!
|
|
36
|
+
token = auth_options[:bearer]
|
|
37
|
+
token = token.call if token.is_a?(Proc)
|
|
38
|
+
set_bearer_authorization_header(token)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def set_authorization_header(value)
|
|
42
|
+
request.headers['Authorization'] = value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def set_bearer_authorization_header(token)
|
|
46
|
+
set_authorization_header("Bearer #{token}")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reauthenticate!
|
|
50
|
+
# refresh token and update header
|
|
51
|
+
token = refresh_client_token_option.call
|
|
52
|
+
set_bearer_authorization_header(token)
|
|
53
|
+
# trigger DHC::Retry and ensure we do not trigger reauthenticate!
|
|
54
|
+
# again should it fail another time
|
|
55
|
+
new_options = request.options.dup
|
|
56
|
+
new_options = new_options.merge(retry: { max: 1 })
|
|
57
|
+
new_options = new_options.merge(auth: { reauthenticated: true })
|
|
58
|
+
request.options = new_options
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def reauthenticate?
|
|
62
|
+
!response.success? &&
|
|
63
|
+
!auth_options[:reauthenticated] &&
|
|
64
|
+
bearer_header_present? &&
|
|
65
|
+
DHC::Error.find(response) == DHC::Unauthorized
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def bearer_header_present?
|
|
69
|
+
@has_bearer_header ||= request.headers['Authorization'] =~ /^Bearer .+$/i
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def refresh_client_token_option
|
|
73
|
+
@refresh_client_token_option ||= auth_options[:refresh_client_token] || refresh_client_token
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def auth_options
|
|
77
|
+
request.options[:auth] || {}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def configuration_correct?
|
|
81
|
+
# warn user about configs, only if refresh_client_token_option is set at all
|
|
82
|
+
refresh_client_token_option && refresh_client_token? && retry_interceptor?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def refresh_client_token?
|
|
86
|
+
return true if refresh_client_token_option.is_a?(Proc)
|
|
87
|
+
warn('[WARNING] The given refresh_client_token must be a Proc for reauthentication.')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def retry_interceptor?
|
|
91
|
+
return true if all_interceptor_classes.include?(DHC::Retry) && all_interceptor_classes.index(DHC::Retry) > all_interceptor_classes.index(self.class)
|
|
92
|
+
warn('[WARNING] Your interceptors must include DHC::Retry after DHC::Auth.')
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DHC::Caching < DHC::Interceptor
|
|
4
|
+
include ActiveSupport::Configurable
|
|
5
|
+
|
|
6
|
+
config_accessor :cache, :central
|
|
7
|
+
|
|
8
|
+
# to control cache invalidation across all applications in case of
|
|
9
|
+
# breaking changes within this inteceptor
|
|
10
|
+
# that do not lead to cache invalidation otherwise
|
|
11
|
+
CACHE_VERSION = '1'
|
|
12
|
+
|
|
13
|
+
# Options forwarded to the cache
|
|
14
|
+
FORWARDED_OPTIONS = %i[expires_in race_condition_ttl].freeze
|
|
15
|
+
|
|
16
|
+
class MultilevelCache
|
|
17
|
+
|
|
18
|
+
def initialize(central: nil, local: nil)
|
|
19
|
+
@central = central
|
|
20
|
+
@local = local
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def fetch(key)
|
|
24
|
+
central_response = @central[:read].fetch(key) if @central && @central[:read].present?
|
|
25
|
+
if central_response
|
|
26
|
+
puts %([DHC] served from central cache: "#{key}")
|
|
27
|
+
return central_response
|
|
28
|
+
end
|
|
29
|
+
local_response = @local.fetch(key) if @local
|
|
30
|
+
if local_response
|
|
31
|
+
puts %([DHC] served from local cache: "#{key}")
|
|
32
|
+
return local_response
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def write(key, content, options)
|
|
37
|
+
@central[:write].write(key, content, options) if @central && @central[:write].present?
|
|
38
|
+
@local.write(key, content, options) if @local.present?
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def before_request
|
|
43
|
+
return unless cache?(request)
|
|
44
|
+
return if response_data.blank?
|
|
45
|
+
from_cache(request, response_data)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def after_response
|
|
49
|
+
return unless response.success?
|
|
50
|
+
return unless cache?(request)
|
|
51
|
+
return if response_data.present?
|
|
52
|
+
multilevel_cache.write(
|
|
53
|
+
key(request, options[:key]),
|
|
54
|
+
to_cache(response),
|
|
55
|
+
cache_options
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# from cache
|
|
62
|
+
def response_data
|
|
63
|
+
# stop calling multi-level cache if it already returned nil for this interceptor instance
|
|
64
|
+
return @response_data if defined? @response_data
|
|
65
|
+
@response_data ||= multilevel_cache.fetch(key(request, options[:key]))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# performs read/write (fetch/write) on all configured cache levels (e.g. local & central)
|
|
69
|
+
def multilevel_cache
|
|
70
|
+
MultilevelCache.new(
|
|
71
|
+
central: central_cache,
|
|
72
|
+
local: local_cache
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# returns the local cache either configured for entire DHC
|
|
77
|
+
# or configured locally for that particular request
|
|
78
|
+
def local_cache
|
|
79
|
+
options.fetch(:use, cache)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def central_cache
|
|
83
|
+
return nil if central.blank? || (central[:read].blank? && central[:write].blank?)
|
|
84
|
+
{}.tap do |options|
|
|
85
|
+
options[:read] = ActiveSupport::Cache::RedisCacheStore.new(url: central[:read]) if central[:read].present?
|
|
86
|
+
options[:write] = ActiveSupport::Cache::RedisCacheStore.new(url: central[:write]) if central[:write].present?
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# do we even need to bother with this interceptor?
|
|
91
|
+
# based on the options, this method will
|
|
92
|
+
# return false if this interceptor cannot work
|
|
93
|
+
def cache?(request)
|
|
94
|
+
return false unless request.options[:cache]
|
|
95
|
+
(local_cache || central_cache) &&
|
|
96
|
+
cached_method?(request.method, options[:methods])
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def options
|
|
100
|
+
options = (request.options[:cache] == true) ? {} : request.options[:cache].dup
|
|
101
|
+
options
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# converts json we read from the cache to an DHC::Response object
|
|
105
|
+
def from_cache(request, data)
|
|
106
|
+
raw = Typhoeus::Response.new(data)
|
|
107
|
+
response = DHC::Response.new(raw, request, from_cache: true)
|
|
108
|
+
request.response = response
|
|
109
|
+
response
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# converts a DHC::Response object to json, we store in the cache
|
|
113
|
+
def to_cache(response)
|
|
114
|
+
data = {}
|
|
115
|
+
data[:body] = response.body
|
|
116
|
+
data[:code] = response.code
|
|
117
|
+
# convert into a actual hash because the typhoeus headers object breaks marshaling
|
|
118
|
+
data[:headers] = response.headers ? Hash[response.headers] : response.headers
|
|
119
|
+
# return_code is quite important as Typhoeus relies on it in order to determin 'success?'
|
|
120
|
+
data[:return_code] = response.options[:return_code]
|
|
121
|
+
# in a test scenario typhoeus uses mocks and not return_code to determine 'success?'
|
|
122
|
+
data[:mock] = response.mock
|
|
123
|
+
data
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def key(request, key)
|
|
127
|
+
unless key
|
|
128
|
+
key = "#{request.method.upcase} #{request.url}"
|
|
129
|
+
key += "?#{request.params.to_query}" unless request.params.blank?
|
|
130
|
+
end
|
|
131
|
+
"DHC_CACHE(v#{CACHE_VERSION}): #{key}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Checks if the provided method should be cached
|
|
135
|
+
# in regards of the provided options.
|
|
136
|
+
def cached_method?(method, cached_methods)
|
|
137
|
+
(cached_methods || [:get]).include?(method)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# extracts the options that should be forwarded to
|
|
141
|
+
# the cache
|
|
142
|
+
def cache_options
|
|
143
|
+
options.each_with_object({}) do |(key, value), result|
|
|
144
|
+
result[key] = value if key.in? FORWARDED_OPTIONS
|
|
145
|
+
result
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DHC::DefaultTimeout < DHC::Interceptor
|
|
4
|
+
include ActiveSupport::Configurable
|
|
5
|
+
|
|
6
|
+
config_accessor :timeout, :connecttimeout
|
|
7
|
+
|
|
8
|
+
CONNECTTIMEOUT = 2 # seconds
|
|
9
|
+
TIMEOUT = 15 # seconds
|
|
10
|
+
|
|
11
|
+
def before_raw_request
|
|
12
|
+
request_options = (request.options || {})
|
|
13
|
+
request_options[:timeout] ||= timeout || TIMEOUT
|
|
14
|
+
request_options[:connecttimeout] ||= connecttimeout || CONNECTTIMEOUT
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DHC::Logging < DHC::Interceptor
|
|
4
|
+
|
|
5
|
+
include ActiveSupport::Configurable
|
|
6
|
+
config_accessor :logger
|
|
7
|
+
|
|
8
|
+
def before_request
|
|
9
|
+
return unless logger
|
|
10
|
+
logger.info(
|
|
11
|
+
[
|
|
12
|
+
'Before DHC request',
|
|
13
|
+
"<#{request.object_id}>",
|
|
14
|
+
request.method.upcase,
|
|
15
|
+
"#{request.url} at #{Time.now.iso8601}",
|
|
16
|
+
"Params=#{request.params}",
|
|
17
|
+
"Headers=#{request.headers}",
|
|
18
|
+
request.source ? "\nCalled from #{request.source}" : nil
|
|
19
|
+
].compact.join(' ')
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def after_response
|
|
24
|
+
return unless logger
|
|
25
|
+
logger.info(
|
|
26
|
+
[
|
|
27
|
+
'After DHC response for request',
|
|
28
|
+
"<#{request.object_id}>",
|
|
29
|
+
request.method.upcase,
|
|
30
|
+
"#{request.url} at #{Time.now.iso8601}",
|
|
31
|
+
"Time=#{response.time_ms}ms",
|
|
32
|
+
"URL=#{response.effective_url}",
|
|
33
|
+
request.source ? "\nCalled from #{request.source}" : nil
|
|
34
|
+
].compact.join(' ')
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DHC::Monitoring < DHC::Interceptor
|
|
4
|
+
|
|
5
|
+
# Options forwarded to the monitoring
|
|
6
|
+
FORWARDED_OPTIONS = {
|
|
7
|
+
monitoring_key: :key
|
|
8
|
+
}.freeze
|
|
9
|
+
|
|
10
|
+
include ActiveSupport::Configurable
|
|
11
|
+
|
|
12
|
+
config_accessor :statsd, :env
|
|
13
|
+
|
|
14
|
+
def before_request
|
|
15
|
+
return unless statsd
|
|
16
|
+
DHC::Monitoring.statsd.count("#{key}.before_request", 1)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def after_request
|
|
20
|
+
return unless statsd
|
|
21
|
+
DHC::Monitoring.statsd.count("#{key}.count", 1)
|
|
22
|
+
DHC::Monitoring.statsd.count("#{key}.after_request", 1)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def after_response
|
|
26
|
+
return unless statsd
|
|
27
|
+
monitor_time!
|
|
28
|
+
monitor_cache!
|
|
29
|
+
monitor_response!
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def monitor_time!
|
|
35
|
+
DHC::Monitoring.statsd.timing("#{key}.time", response.time) if response.success?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def monitor_cache!
|
|
39
|
+
return if request.options[:cache].blank?
|
|
40
|
+
return unless monitor_caching_configuration_check
|
|
41
|
+
if response.from_cache?
|
|
42
|
+
DHC::Monitoring.statsd.count("#{key}.cache.hit", 1)
|
|
43
|
+
else
|
|
44
|
+
DHC::Monitoring.statsd.count("#{key}.cache.miss", 1)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def monitor_caching_configuration_check
|
|
49
|
+
return true if all_interceptor_classes.include?(DHC::Caching) && all_interceptor_classes.index(self.class) > all_interceptor_classes.index(DHC::Caching)
|
|
50
|
+
warn('[WARNING] Your interceptors must include DHC::Caching and DHC::Monitoring and also in that order.')
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def monitor_response!
|
|
54
|
+
if response.timeout?
|
|
55
|
+
DHC::Monitoring.statsd.count("#{key}.timeout", 1)
|
|
56
|
+
else
|
|
57
|
+
DHC::Monitoring.statsd.count("#{key}.#{response.code}", 1)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def key
|
|
62
|
+
key = options(request.options)[:key]
|
|
63
|
+
return key if key.present?
|
|
64
|
+
|
|
65
|
+
url = sanitize_url(request.url)
|
|
66
|
+
key = [
|
|
67
|
+
'dhc',
|
|
68
|
+
module_parent_name.underscore,
|
|
69
|
+
DHC::Monitoring.env || Rails.env,
|
|
70
|
+
URI.parse(url).host.gsub(/\./, '_'),
|
|
71
|
+
request.method
|
|
72
|
+
]
|
|
73
|
+
key.join('.')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def module_parent_name
|
|
77
|
+
(ActiveSupport.gem_version >= Gem::Version.new('6.0.0')) ? Rails.application.class.module_parent_name : Rails.application.class.parent_name
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def sanitize_url(url)
|
|
81
|
+
return url if url.match(%r{https?://})
|
|
82
|
+
"http://#{url}"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def options(input = {})
|
|
86
|
+
options = {}
|
|
87
|
+
FORWARDED_OPTIONS.each do |k, v|
|
|
88
|
+
options[v] = input[k] if input.key?(k)
|
|
89
|
+
end
|
|
90
|
+
options
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DHC::Prometheus < DHC::Interceptor
|
|
4
|
+
include ActiveSupport::Configurable
|
|
5
|
+
|
|
6
|
+
config_accessor :client, :namespace
|
|
7
|
+
|
|
8
|
+
REQUEST_COUNTER_KEY = :dhc_requests
|
|
9
|
+
REQUEST_HISTOGRAM_KEY = :dhc_request_seconds
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
attr_accessor :registered
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(request)
|
|
16
|
+
super(request)
|
|
17
|
+
return if DHC::Prometheus.registered || DHC::Prometheus.client.blank?
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
DHC::Prometheus.client.registry.counter(DHC::Prometheus::REQUEST_COUNTER_KEY, 'Counter of all DHC requests.')
|
|
21
|
+
DHC::Prometheus.client.registry.histogram(DHC::Prometheus::REQUEST_HISTOGRAM_KEY, 'Request timings for all DHC requests in seconds.')
|
|
22
|
+
rescue Prometheus::Client::Registry::AlreadyRegisteredError => e
|
|
23
|
+
Rails.logger.error(e) if defined?(Rails)
|
|
24
|
+
ensure
|
|
25
|
+
DHC::Prometheus.registered = true
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def after_response
|
|
30
|
+
return if !DHC::Prometheus.registered || DHC::Prometheus.client.blank?
|
|
31
|
+
|
|
32
|
+
host = URI.parse(request.url).host
|
|
33
|
+
|
|
34
|
+
DHC::Prometheus.client.registry
|
|
35
|
+
.get(DHC::Prometheus::REQUEST_COUNTER_KEY)
|
|
36
|
+
.increment(
|
|
37
|
+
code: response.code,
|
|
38
|
+
success: response.success?,
|
|
39
|
+
timeout: response.timeout?,
|
|
40
|
+
host: host,
|
|
41
|
+
app: DHC::Prometheus.namespace
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
DHC::Prometheus.client.registry
|
|
45
|
+
.get(DHC::Prometheus::REQUEST_HISTOGRAM_KEY)
|
|
46
|
+
.observe({
|
|
47
|
+
host: host,
|
|
48
|
+
app: DHC::Prometheus.namespace
|
|
49
|
+
}, response.time)
|
|
50
|
+
end
|
|
51
|
+
end
|