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,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DHC::Retry < DHC::Interceptor
|
|
4
|
+
attr_accessor :retries, :current_retry
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :max, :all
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def after_response
|
|
11
|
+
response.request.options[:retries] ||= 0
|
|
12
|
+
return unless retry?(response.request)
|
|
13
|
+
response.request.options[:retries] += 1
|
|
14
|
+
current_retry = response.request.options[:retries]
|
|
15
|
+
begin
|
|
16
|
+
response.request.run!
|
|
17
|
+
rescue DHC::Error
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
response.request.response if current_retry == response.request.options[:retries]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def retry?(request)
|
|
26
|
+
return false if request.response.success?
|
|
27
|
+
return false if request.error_ignored?
|
|
28
|
+
return false if !request.options.dig(:retry) && !DHC::Retry.all
|
|
29
|
+
request.options[:retries] < max(request)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def max(request)
|
|
33
|
+
options(request).is_a?(Hash) ? options(request).fetch(:max, DHC::Retry.max) : DHC::Retry.max
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def options(request)
|
|
37
|
+
@options ||= request.options.dig(:retry)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
DHC::Retry.max = 3
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'core_ext/hash/deep_transform_values'
|
|
4
|
+
|
|
5
|
+
class DHC::Rollbar < DHC::Interceptor
|
|
6
|
+
include ActiveSupport::Configurable
|
|
7
|
+
include DHC::FixInvalidEncodingConcern
|
|
8
|
+
|
|
9
|
+
def after_response
|
|
10
|
+
return unless Object.const_defined?('Rollbar')
|
|
11
|
+
return if response.success?
|
|
12
|
+
request = response.request
|
|
13
|
+
additional_params = request.options.fetch(:rollbar, {})
|
|
14
|
+
data = {
|
|
15
|
+
response: {
|
|
16
|
+
body: response.body,
|
|
17
|
+
code: response.code,
|
|
18
|
+
headers: response.headers,
|
|
19
|
+
time: response.time,
|
|
20
|
+
timeout?: response.timeout?
|
|
21
|
+
},
|
|
22
|
+
request: {
|
|
23
|
+
url: request.url,
|
|
24
|
+
method: request.method,
|
|
25
|
+
headers: request.headers,
|
|
26
|
+
params: request.params
|
|
27
|
+
}
|
|
28
|
+
}.merge additional_params
|
|
29
|
+
begin
|
|
30
|
+
Rollbar.warning("Status: #{response.code} URL: #{request.url}", data)
|
|
31
|
+
rescue Encoding::UndefinedConversionError
|
|
32
|
+
sanitized_data = data.deep_transform_values { |value| self.class.fix_invalid_encoding(value) }
|
|
33
|
+
Rollbar.warning("Status: #{response.code} URL: #{request.url}", sanitized_data)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/duration'
|
|
4
|
+
|
|
5
|
+
class DHC::Throttle < DHC::Interceptor
|
|
6
|
+
class OutOfQuota < StandardError
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :track
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def before_request
|
|
14
|
+
options = request.options.dig(:throttle)
|
|
15
|
+
return unless options
|
|
16
|
+
break_options = options.dig(:break)
|
|
17
|
+
return unless break_options
|
|
18
|
+
break_when_quota_reached! if break_options.match('%')
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def after_response
|
|
22
|
+
options = response.request.options.dig(:throttle)
|
|
23
|
+
return unless throttle?(options)
|
|
24
|
+
self.class.track ||= {}
|
|
25
|
+
self.class.track[options.dig(:provider)] = {
|
|
26
|
+
limit: limit(options: options[:limit], response: response),
|
|
27
|
+
remaining: remaining(options: options[:remaining], response: response),
|
|
28
|
+
expires: expires(options: options[:expires], response: response)
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def throttle?(options)
|
|
35
|
+
[options&.dig(:track), response.headers].none?(&:blank?)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def break_when_quota_reached!
|
|
39
|
+
options = request.options.dig(:throttle)
|
|
40
|
+
track = (self.class.track || {}).dig(options[:provider])
|
|
41
|
+
return if track.blank? || track[:remaining].blank? || track[:limit].blank? || track[:expires].blank?
|
|
42
|
+
return if Time.zone.now > track[:expires]
|
|
43
|
+
# avoid floats by multiplying with 100
|
|
44
|
+
remaining = track[:remaining] * 100
|
|
45
|
+
limit = track[:limit]
|
|
46
|
+
quota = 100 - options[:break].to_i
|
|
47
|
+
raise(OutOfQuota, "Reached predefined quota for #{options[:provider]}") if remaining < quota * limit
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def limit(options:, response:)
|
|
51
|
+
@limit ||=
|
|
52
|
+
if options.is_a?(Proc)
|
|
53
|
+
options.call(response)
|
|
54
|
+
elsif options.is_a?(Integer)
|
|
55
|
+
options
|
|
56
|
+
elsif options.is_a?(Hash) && options[:header]
|
|
57
|
+
response.headers[options[:header]]&.to_i
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def remaining(options:, response:)
|
|
62
|
+
@remaining ||=
|
|
63
|
+
begin
|
|
64
|
+
if options.is_a?(Proc)
|
|
65
|
+
options.call(response)
|
|
66
|
+
elsif options.is_a?(Hash) && options[:header]
|
|
67
|
+
response.headers[options[:header]]&.to_i
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def expires(options:, response:)
|
|
73
|
+
@expires ||= convert_expires(read_expire_option(options, response))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def read_expire_option(options, response)
|
|
77
|
+
(options.is_a?(Hash) && options[:header]) ? response.headers[options[:header]] : options
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def convert_expires(value)
|
|
81
|
+
return if value.blank?
|
|
82
|
+
return value.call(response) if value.is_a?(Proc)
|
|
83
|
+
return Time.parse(value) if value.match(/GMT/)
|
|
84
|
+
Time.zone.at(value.to_i).to_datetime
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class DHC::Zipkin < DHC::Interceptor
|
|
4
|
+
B3_HEADERS = {
|
|
5
|
+
trace_id: 'X-B3-TraceId',
|
|
6
|
+
parent_id: 'X-B3-ParentSpanId',
|
|
7
|
+
span_id: 'X-B3-SpanId',
|
|
8
|
+
sampled: 'X-B3-Sampled',
|
|
9
|
+
flags: 'X-B3-Flags'
|
|
10
|
+
}.freeze
|
|
11
|
+
TRUE = '1' # true in binary annotation
|
|
12
|
+
|
|
13
|
+
def before_request
|
|
14
|
+
return if !dependencies? || !tracing?
|
|
15
|
+
ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
|
|
16
|
+
# add headers even if the current trace_id should not be sampled
|
|
17
|
+
B3_HEADERS.each { |method, header| request.headers[header] = trace_id.send(method).to_s }
|
|
18
|
+
# only sample the current call if we're instructed to do so
|
|
19
|
+
start_trace! if ::Trace.tracer && trace_id.sampled?
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def after_response
|
|
24
|
+
# only sample the current call if we're instructed to do so
|
|
25
|
+
return unless dependencies? && trace_id.sampled?
|
|
26
|
+
end_trace!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def tracing?
|
|
32
|
+
ZipkinTracer::TraceContainer.current
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def start_trace!
|
|
36
|
+
record_path
|
|
37
|
+
record_remote_endpoint
|
|
38
|
+
record_local_endpoint
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def end_trace!
|
|
42
|
+
record_status
|
|
43
|
+
record_error unless response.success?
|
|
44
|
+
record_end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def trace_id
|
|
48
|
+
@trace_id ||= ZipkinTracer::TraceGenerator.new.next_trace_id
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# register a new span with zipkin tracer
|
|
52
|
+
def span
|
|
53
|
+
@span ||= ::Trace.tracer.start_span(trace_id, url.path)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def url
|
|
57
|
+
@url ||= URI(request.raw.url)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def status
|
|
61
|
+
@status ||= response.code.to_s
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def service_name
|
|
65
|
+
@service_name ||= url.host
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def record_local_endpoint
|
|
69
|
+
span.record(::Trace::Annotation::CLIENT_SEND, local_endpoint)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def record_remote_endpoint
|
|
73
|
+
span.record_tag(::Trace::BinaryAnnotation::SERVER_ADDRESS, TRUE, ::Trace::BinaryAnnotation::Type::BOOL, remote_endpoint)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def record_path
|
|
77
|
+
span.record_tag(::Trace::BinaryAnnotation::PATH, url.path, ::Trace::BinaryAnnotation::Type::STRING, local_endpoint)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def record_end
|
|
81
|
+
span.record(::Trace::Annotation::CLIENT_RECV, local_endpoint)
|
|
82
|
+
::Trace.tracer.end_span(span)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def record_error
|
|
86
|
+
span.record_tag(::Trace::BinaryAnnotation::ERROR, status, ::Trace::BinaryAnnotation::Type::STRING, local_endpoint)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def record_status
|
|
90
|
+
span.record_tag(::Trace::BinaryAnnotation::STATUS, status, ::Trace::BinaryAnnotation::Type::STRING, local_endpoint)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def local_endpoint
|
|
94
|
+
@local_endpoint ||= ::Trace.default_endpoint
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def remote_endpoint
|
|
98
|
+
@remote_endpoint ||= ::Trace::Endpoint.remote_endpoint(url, service_name, local_endpoint.ip_format)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def dependencies?
|
|
102
|
+
(
|
|
103
|
+
defined?(ZipkinTracer::TraceContainer) &&
|
|
104
|
+
defined?(::Trace) &&
|
|
105
|
+
defined?(::Trace::Annotation) &&
|
|
106
|
+
defined?(::Trace::BinaryAnnotation) &&
|
|
107
|
+
defined?(::Trace::Endpoint)
|
|
108
|
+
) || warn('[WARNING] Zipkin interceptor is enabled but dependencies are not found. See: https://github.com/DePayFi/dhc#zipkin')
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/dhc/railtie.rb
ADDED
data/lib/dhc/request.rb
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'uri'
|
|
4
|
+
require 'active_support/core_ext/object/deep_dup'
|
|
5
|
+
require 'dhc/concerns/dhc/request/user_agent_concern'
|
|
6
|
+
|
|
7
|
+
# The request is doing an http-request using typhoeus.
|
|
8
|
+
# It provides functionalities to access and alter request data
|
|
9
|
+
# and it communicates with interceptors.
|
|
10
|
+
class DHC::Request
|
|
11
|
+
include UserAgentConcern
|
|
12
|
+
|
|
13
|
+
TYPHOEUS_OPTIONS ||= %i[params method body headers follow_location params_encoding].freeze
|
|
14
|
+
|
|
15
|
+
attr_accessor :response, :options, :raw, :format, :error_handler, :errors_ignored, :source
|
|
16
|
+
|
|
17
|
+
def initialize(options, self_executing = true)
|
|
18
|
+
self.errors_ignored = (options.fetch(:ignore, []) || []).to_a.compact
|
|
19
|
+
self.source = options&.dig(:source)
|
|
20
|
+
self.options = format!(options.deep_dup || {})
|
|
21
|
+
self.error_handler = options.delete :rescue
|
|
22
|
+
use_configured_endpoint!
|
|
23
|
+
generate_url_from_template!
|
|
24
|
+
self.interceptors = DHC::Interceptors.new(self)
|
|
25
|
+
interceptors.intercept(:before_raw_request)
|
|
26
|
+
self.raw = create_request
|
|
27
|
+
interceptors.intercept(:before_request)
|
|
28
|
+
if self_executing && !response
|
|
29
|
+
run!
|
|
30
|
+
elsif response
|
|
31
|
+
on_complete(response)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def url
|
|
36
|
+
raw.base_url || options[:url]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def method
|
|
40
|
+
(raw.options[:method] || options[:method] || :get).to_sym
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def headers
|
|
44
|
+
raw.options.fetch(:headers, nil) || raw.options[:headers] = {}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def params
|
|
48
|
+
raw.options.fetch(:params, nil) || raw.options[:params] = {}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def error_ignored?
|
|
52
|
+
ignore_error?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def run!
|
|
56
|
+
raw.run
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
attr_accessor :interceptors
|
|
62
|
+
|
|
63
|
+
def format!(options)
|
|
64
|
+
self.format = options.delete(:format) || DHC::Formats::JSON.new
|
|
65
|
+
format.format_options(options)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def optionally_encoded_url(options)
|
|
69
|
+
return options[:url] unless options.fetch(:url_encoding, true)
|
|
70
|
+
encode_url(options[:url])
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def create_request
|
|
74
|
+
request = Typhoeus::Request.new(
|
|
75
|
+
optionally_encoded_url(options),
|
|
76
|
+
translate_body(typhoeusize(options))
|
|
77
|
+
)
|
|
78
|
+
request.on_headers do
|
|
79
|
+
interceptors.intercept(:after_request)
|
|
80
|
+
interceptors.intercept(:before_response)
|
|
81
|
+
end
|
|
82
|
+
request.on_complete { |response| on_complete(response) }
|
|
83
|
+
request
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def translate_body(options)
|
|
87
|
+
return options if options.fetch(:body, nil).blank?
|
|
88
|
+
options[:body] = format.to_body(options[:body])
|
|
89
|
+
options
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def encode_url(url)
|
|
93
|
+
return url if url.nil?
|
|
94
|
+
Addressable::URI.escape(url)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def typhoeusize(options)
|
|
98
|
+
options = options.deep_dup
|
|
99
|
+
easy = Ethon::Easy.new
|
|
100
|
+
options.delete(:url)
|
|
101
|
+
options.each do |key, _v|
|
|
102
|
+
next if TYPHOEUS_OPTIONS.include? key
|
|
103
|
+
method = "#{key}="
|
|
104
|
+
options.delete key unless easy.respond_to?(method)
|
|
105
|
+
end
|
|
106
|
+
options
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Get configured endpoint and use it for doing the request.
|
|
110
|
+
# Explicit request options are overriding configured options.
|
|
111
|
+
def use_configured_endpoint!
|
|
112
|
+
endpoint = DHC.config.endpoints[options[:url]]
|
|
113
|
+
return unless endpoint
|
|
114
|
+
# explicit options override endpoint options
|
|
115
|
+
new_options = endpoint.options.deep_merge(options)
|
|
116
|
+
# set new options
|
|
117
|
+
self.options = new_options
|
|
118
|
+
options[:url] = endpoint.url
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Generates URL from a URL template
|
|
122
|
+
def generate_url_from_template!
|
|
123
|
+
endpoint = DHC::Endpoint.new(options[:url])
|
|
124
|
+
params =
|
|
125
|
+
if format && options[:body].present? && options[:body].respond_to?(:as_json) && options[:body].as_json.is_a?(Hash)
|
|
126
|
+
options[:body].as_json.merge(options[:params] || {}).deep_symbolize_keys
|
|
127
|
+
else
|
|
128
|
+
options[:params]
|
|
129
|
+
end
|
|
130
|
+
options[:url] = endpoint.compile(params)
|
|
131
|
+
endpoint.remove_interpolated_params!(options[:params])
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def on_complete(response)
|
|
135
|
+
self.response = response.is_a?(DHC::Response) ? response : DHC::Response.new(response, self)
|
|
136
|
+
interceptors.intercept(:after_response)
|
|
137
|
+
handle_error(self.response) unless self.response.success?
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def handle_error(response)
|
|
141
|
+
return if ignore_error?
|
|
142
|
+
throw_error(response) unless error_handler
|
|
143
|
+
response.body_replacement = error_handler.call(response)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def ignore_error?
|
|
147
|
+
@ignore_error ||= begin
|
|
148
|
+
errors_ignored.detect do |ignored_error|
|
|
149
|
+
error <= ignored_error
|
|
150
|
+
end.present?
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def error
|
|
155
|
+
@error ||= DHC::Error.find(response)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def throw_error(response)
|
|
159
|
+
raise error.new(error, response)
|
|
160
|
+
end
|
|
161
|
+
end
|