lhc 12.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +37 -0
- data/.rubocop.localch.yml +325 -0
- data/.rubocop.yml +61 -0
- data/.ruby-version +1 -0
- data/Gemfile +13 -0
- data/Gemfile.activesupport4 +4 -0
- data/Gemfile.activesupport5 +4 -0
- data/Gemfile.activesupport6 +4 -0
- data/LICENSE +674 -0
- data/README.md +984 -0
- data/Rakefile +25 -0
- data/cider-ci.yml +6 -0
- data/cider-ci/bin/bundle +51 -0
- data/cider-ci/bin/ruby_install +8 -0
- data/cider-ci/bin/ruby_version +25 -0
- data/cider-ci/jobs/rspec-activesupport-4.yml +28 -0
- data/cider-ci/jobs/rspec-activesupport-5.yml +27 -0
- data/cider-ci/jobs/rspec-activesupport-6.yml +28 -0
- data/cider-ci/jobs/rubocop.yml +18 -0
- data/cider-ci/task_components/bundle.yml +22 -0
- data/cider-ci/task_components/rspec.yml +36 -0
- data/cider-ci/task_components/rubocop.yml +29 -0
- data/cider-ci/task_components/ruby.yml +15 -0
- data/friday.yml +3 -0
- data/lhc.gemspec +39 -0
- data/lib/core_ext/hash/deep_transform_values.rb +48 -0
- data/lib/lhc.rb +136 -0
- data/lib/lhc/concerns/lhc/basic_methods_concern.rb +42 -0
- data/lib/lhc/concerns/lhc/configuration_concern.rb +20 -0
- data/lib/lhc/concerns/lhc/fix_invalid_encoding_concern.rb +42 -0
- data/lib/lhc/concerns/lhc/formats_concern.rb +25 -0
- data/lib/lhc/concerns/lhc/request/user_agent_concern.rb +25 -0
- data/lib/lhc/config.rb +47 -0
- data/lib/lhc/endpoint.rb +119 -0
- data/lib/lhc/error.rb +80 -0
- data/lib/lhc/errors/client_error.rb +73 -0
- data/lib/lhc/errors/parser_error.rb +4 -0
- data/lib/lhc/errors/server_error.rb +28 -0
- data/lib/lhc/errors/timeout.rb +4 -0
- data/lib/lhc/errors/unknown_error.rb +4 -0
- data/lib/lhc/format.rb +18 -0
- data/lib/lhc/formats.rb +8 -0
- data/lib/lhc/formats/form.rb +45 -0
- data/lib/lhc/formats/json.rb +55 -0
- data/lib/lhc/formats/multipart.rb +45 -0
- data/lib/lhc/formats/plain.rb +42 -0
- data/lib/lhc/interceptor.rb +32 -0
- data/lib/lhc/interceptors.rb +26 -0
- data/lib/lhc/interceptors/auth.rb +98 -0
- data/lib/lhc/interceptors/caching.rb +127 -0
- data/lib/lhc/interceptors/default_timeout.rb +16 -0
- data/lib/lhc/interceptors/logging.rb +37 -0
- data/lib/lhc/interceptors/monitoring.rb +63 -0
- data/lib/lhc/interceptors/prometheus.rb +51 -0
- data/lib/lhc/interceptors/retry.rb +41 -0
- data/lib/lhc/interceptors/rollbar.rb +36 -0
- data/lib/lhc/interceptors/throttle.rb +81 -0
- data/lib/lhc/interceptors/zipkin.rb +110 -0
- data/lib/lhc/railtie.rb +10 -0
- data/lib/lhc/request.rb +157 -0
- data/lib/lhc/response.rb +60 -0
- data/lib/lhc/response/data.rb +28 -0
- data/lib/lhc/response/data/base.rb +22 -0
- data/lib/lhc/response/data/collection.rb +16 -0
- data/lib/lhc/response/data/item.rb +29 -0
- data/lib/lhc/rspec.rb +12 -0
- data/lib/lhc/test/cache_helper.rb +3 -0
- data/lib/lhc/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 +80 -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/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/options_spec.rb +89 -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 +29 -0
- data/spec/interceptors/dup_spec.rb +19 -0
- data/spec/interceptors/logging/main_spec.rb +37 -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 +41 -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 +106 -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 +37 -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 +61 -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 +8 -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 +7 -0
- data/spec/support/zipkin_mock.rb +113 -0
- data/spec/timeouts/no_signal_spec.rb +13 -0
- data/spec/timeouts/timings_spec.rb +55 -0
- metadata +534 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::ServerError < LHC::Error
|
4
|
+
end
|
5
|
+
|
6
|
+
class LHC::InternalServerError < LHC::ServerError
|
7
|
+
end
|
8
|
+
|
9
|
+
class LHC::NotImplemented < LHC::ServerError
|
10
|
+
end
|
11
|
+
|
12
|
+
class LHC::BadGateway < LHC::ServerError
|
13
|
+
end
|
14
|
+
|
15
|
+
class LHC::ServiceUnavailable < LHC::ServerError
|
16
|
+
end
|
17
|
+
|
18
|
+
class LHC::GatewayTimeout < LHC::ServerError
|
19
|
+
end
|
20
|
+
|
21
|
+
class LHC::HttpVersionNotSupported < LHC::ServerError
|
22
|
+
end
|
23
|
+
|
24
|
+
class LHC::InsufficientStorage < LHC::ServerError
|
25
|
+
end
|
26
|
+
|
27
|
+
class LHC::NotExtended < LHC::ServerError
|
28
|
+
end
|
data/lib/lhc/format.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::Format
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def no_content_type_header!(options)
|
8
|
+
return if (options[:headers].keys & [:'Content-Type', 'Content-Type']).blank?
|
9
|
+
|
10
|
+
raise 'Content-Type header is not allowed for formatted requests!\nSee https://github.com/local-ch/lhc#formats for more information.'
|
11
|
+
end
|
12
|
+
|
13
|
+
def no_accept_header!(options)
|
14
|
+
return if (options[:headers].keys & [:Accept, 'Accept']).blank?
|
15
|
+
|
16
|
+
raise 'Accept header is not allowed for formatted requests!\nSee https://github.com/local-ch/lhc#formats for more information.'
|
17
|
+
end
|
18
|
+
end
|
data/lib/lhc/formats.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LHC::Formats
|
4
|
+
class Form < LHC::Format
|
5
|
+
include LHC::BasicMethodsConcern
|
6
|
+
|
7
|
+
def self.request(options)
|
8
|
+
options[:format] = new
|
9
|
+
super(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_options(options)
|
13
|
+
options[:headers] ||= {}
|
14
|
+
no_content_type_header!(options)
|
15
|
+
options[:headers]['Content-Type'] = 'application/x-www-form-urlencoded'
|
16
|
+
options
|
17
|
+
end
|
18
|
+
|
19
|
+
def as_json(input)
|
20
|
+
parse(input)
|
21
|
+
end
|
22
|
+
|
23
|
+
def as_open_struct(input)
|
24
|
+
parse(input)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_body(input)
|
28
|
+
input
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
'form'
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_sym
|
36
|
+
to_s.to_sym
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def parse(input)
|
42
|
+
input
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LHC::Formats
|
4
|
+
class JSON < LHC::Format
|
5
|
+
include LHC::BasicMethodsConcern
|
6
|
+
|
7
|
+
def self.request(options)
|
8
|
+
options[:format] = new
|
9
|
+
super(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_options(options)
|
13
|
+
options[:headers] ||= {}
|
14
|
+
no_content_type_header!(options)
|
15
|
+
no_accept_header!(options)
|
16
|
+
|
17
|
+
options[:headers]['Content-Type'] = 'application/json; charset=utf-8'
|
18
|
+
options[:headers]['Accept'] = 'application/json,application/vnd.api+json'
|
19
|
+
options[:headers]['Accept-Charset'] = 'utf-8'
|
20
|
+
options
|
21
|
+
end
|
22
|
+
|
23
|
+
def as_json(input)
|
24
|
+
parse(input, Hash)
|
25
|
+
end
|
26
|
+
|
27
|
+
def as_open_struct(input)
|
28
|
+
parse(input, OpenStruct)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_body(input)
|
32
|
+
if input.is_a?(String)
|
33
|
+
input
|
34
|
+
else
|
35
|
+
input.to_json
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
'json'
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_sym
|
44
|
+
to_s.to_sym
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def parse(input, object_class)
|
50
|
+
::JSON.parse(input, object_class: object_class)
|
51
|
+
rescue ::JSON::ParserError => e
|
52
|
+
raise LHC::ParserError.new(e.message, input)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LHC::Formats
|
4
|
+
class Multipart < LHC::Format
|
5
|
+
include LHC::BasicMethodsConcern
|
6
|
+
|
7
|
+
def self.request(options)
|
8
|
+
options[:format] = new
|
9
|
+
super(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_options(options)
|
13
|
+
options[:headers] ||= {}
|
14
|
+
no_content_type_header!(options)
|
15
|
+
options[:headers]['Content-Type'] = 'multipart/form-data'
|
16
|
+
options
|
17
|
+
end
|
18
|
+
|
19
|
+
def as_json(input)
|
20
|
+
parse(input)
|
21
|
+
end
|
22
|
+
|
23
|
+
def as_open_struct(input)
|
24
|
+
parse(input)
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_body(input)
|
28
|
+
input
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
'multipart'
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_sym
|
36
|
+
to_s.to_sym
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def parse(input)
|
42
|
+
input
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LHC::Formats
|
4
|
+
class Plain < LHC::Format
|
5
|
+
include LHC::BasicMethodsConcern
|
6
|
+
|
7
|
+
def self.request(options)
|
8
|
+
options[:format] = new
|
9
|
+
super(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_options(options)
|
13
|
+
options
|
14
|
+
end
|
15
|
+
|
16
|
+
def as_json(input)
|
17
|
+
parse(input)
|
18
|
+
end
|
19
|
+
|
20
|
+
def as_open_struct(input)
|
21
|
+
parse(input)
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_body(input)
|
25
|
+
input
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
'plain'
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_sym
|
33
|
+
to_s.to_sym
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def parse(input)
|
39
|
+
input
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::Interceptor
|
4
|
+
|
5
|
+
attr_reader :request
|
6
|
+
|
7
|
+
def initialize(request)
|
8
|
+
@request = request
|
9
|
+
end
|
10
|
+
|
11
|
+
def response
|
12
|
+
@request.response
|
13
|
+
end
|
14
|
+
|
15
|
+
def before_raw_request; end
|
16
|
+
|
17
|
+
def before_request; end
|
18
|
+
|
19
|
+
def after_request; end
|
20
|
+
|
21
|
+
def before_response; end
|
22
|
+
|
23
|
+
def after_response; end
|
24
|
+
|
25
|
+
# Prevent Interceptors from beeing duplicated!
|
26
|
+
# Their classes have flag-character.
|
27
|
+
# When duplicated you can't check for their class name anymore:
|
28
|
+
# e.g. options.deep_dup[:interceptors].include?(LHC::Caching) # false
|
29
|
+
def self.dup
|
30
|
+
self
|
31
|
+
end
|
32
|
+
end
|
@@ -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 LHC::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] || LHC.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? LHC::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,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::Auth < LHC::Interceptor
|
4
|
+
include ActiveSupport::Configurable
|
5
|
+
config_accessor :refresh_client_token
|
6
|
+
|
7
|
+
def before_request
|
8
|
+
authenticate!
|
9
|
+
end
|
10
|
+
|
11
|
+
def after_response
|
12
|
+
return unless configuration_correct?
|
13
|
+
return unless reauthenticate?
|
14
|
+
reauthenticate!
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def authenticate!
|
20
|
+
if auth_options[:bearer]
|
21
|
+
bearer_authentication!
|
22
|
+
elsif auth_options[:basic]
|
23
|
+
basic_authentication!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def basic_authentication!
|
28
|
+
auth = auth_options[:basic]
|
29
|
+
credentials = "#{auth[:username]}:#{auth[:password]}"
|
30
|
+
set_authorization_header("Basic #{Base64.encode64(credentials).chomp}")
|
31
|
+
end
|
32
|
+
|
33
|
+
def bearer_authentication!
|
34
|
+
token = auth_options[:bearer]
|
35
|
+
token = token.call if token.is_a?(Proc)
|
36
|
+
set_bearer_authorization_header(token)
|
37
|
+
end
|
38
|
+
|
39
|
+
# rubocop:disable Style/AccessorMethodName
|
40
|
+
def set_authorization_header(value)
|
41
|
+
request.headers['Authorization'] = value
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_bearer_authorization_header(token)
|
45
|
+
set_authorization_header("Bearer #{token}")
|
46
|
+
end
|
47
|
+
# rubocop:enable Style/AccessorMethodName
|
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 LHC::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
|
+
LHC::Error.find(response) == LHC::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 all_interceptor_classes
|
77
|
+
@all_interceptors ||= LHC::Interceptors.new(request).all.map(&:class)
|
78
|
+
end
|
79
|
+
|
80
|
+
def auth_options
|
81
|
+
@auth_options ||= request.options[:auth].dup || {}
|
82
|
+
end
|
83
|
+
|
84
|
+
def configuration_correct?
|
85
|
+
# warn user about configs, only if refresh_client_token_option is set at all
|
86
|
+
refresh_client_token_option && refresh_client_token? && retry_interceptor?
|
87
|
+
end
|
88
|
+
|
89
|
+
def refresh_client_token?
|
90
|
+
return true if refresh_client_token_option.is_a?(Proc)
|
91
|
+
warn("[WARNING] The given refresh_client_token must be a Proc for reauthentication.")
|
92
|
+
end
|
93
|
+
|
94
|
+
def retry_interceptor?
|
95
|
+
return true if all_interceptor_classes.include?(LHC::Retry) && all_interceptor_classes.index(LHC::Retry) > all_interceptor_classes.index(self.class)
|
96
|
+
warn("[WARNING] Your interceptors must include LHC::Retry after LHC::Auth.")
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LHC::Caching < LHC::Interceptor
|
4
|
+
include ActiveSupport::Configurable
|
5
|
+
|
6
|
+
config_accessor :cache, :logger
|
7
|
+
|
8
|
+
CACHE_VERSION = '1'
|
9
|
+
|
10
|
+
# Options forwarded to the cache
|
11
|
+
FORWARDED_OPTIONS = [:expires_in, :race_condition_ttl]
|
12
|
+
|
13
|
+
def before_request
|
14
|
+
return unless cache?(request)
|
15
|
+
deprecation_warning(request.options)
|
16
|
+
options = options(request.options)
|
17
|
+
key = key(request, options[:key])
|
18
|
+
response_data = cache_for(options).fetch(key)
|
19
|
+
return unless response_data
|
20
|
+
logger&.info "Served from cache: #{key}"
|
21
|
+
from_cache(request, response_data)
|
22
|
+
end
|
23
|
+
|
24
|
+
def after_response
|
25
|
+
return unless response.success?
|
26
|
+
request = response.request
|
27
|
+
return unless cache?(request)
|
28
|
+
options = options(request.options)
|
29
|
+
cache_for(options).write(
|
30
|
+
key(request, options[:key]),
|
31
|
+
to_cache(response),
|
32
|
+
cache_options(options)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# return the cache for the given options
|
39
|
+
def cache_for(options)
|
40
|
+
options.fetch(:use, cache)
|
41
|
+
end
|
42
|
+
|
43
|
+
# do we even need to bother with this interceptor?
|
44
|
+
# based on the options, this method will
|
45
|
+
# return false if this interceptor cannot work
|
46
|
+
def cache?(request)
|
47
|
+
return false unless request.options[:cache]
|
48
|
+
options = options(request.options)
|
49
|
+
cache_for(options) &&
|
50
|
+
cached_method?(request.method, options[:methods])
|
51
|
+
end
|
52
|
+
|
53
|
+
# returns the request_options
|
54
|
+
# will map deprecated options to the new format
|
55
|
+
def options(request_options)
|
56
|
+
options = (request_options[:cache] == true) ? {} : request_options[:cache].dup
|
57
|
+
map_deprecated_options!(request_options, options)
|
58
|
+
options
|
59
|
+
end
|
60
|
+
|
61
|
+
# maps `cache_key` -> `key`, `cache_expires_in` -> `expires_in` and so on
|
62
|
+
def map_deprecated_options!(request_options, options)
|
63
|
+
deprecated_keys(request_options).each do |deprecated_key|
|
64
|
+
new_key = deprecated_key.to_s.gsub(/^cache_/, '').to_sym
|
65
|
+
options[new_key] = request_options[deprecated_key]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# converts json we read from the cache to an LHC::Response object
|
70
|
+
def from_cache(request, data)
|
71
|
+
raw = Typhoeus::Response.new(data)
|
72
|
+
response = LHC::Response.new(raw, request, from_cache: true)
|
73
|
+
request.response = response
|
74
|
+
response
|
75
|
+
end
|
76
|
+
|
77
|
+
# converts a LHC::Response object to json, we store in the cache
|
78
|
+
def to_cache(response)
|
79
|
+
data = {}
|
80
|
+
data[:body] = response.body
|
81
|
+
data[:code] = response.code
|
82
|
+
# convert into a actual hash because the typhoeus headers object breaks marshaling
|
83
|
+
data[:headers] = response.headers ? Hash[response.headers] : response.headers
|
84
|
+
# return_code is quite important as Typhoeus relies on it in order to determin 'success?'
|
85
|
+
data[:return_code] = response.options[:return_code]
|
86
|
+
# in a test scenario typhoeus uses mocks and not return_code to determine 'success?'
|
87
|
+
data[:mock] = response.mock
|
88
|
+
data
|
89
|
+
end
|
90
|
+
|
91
|
+
def key(request, key)
|
92
|
+
unless key
|
93
|
+
key = "#{request.method.upcase} #{request.url}"
|
94
|
+
key += "?#{request.params.to_query}" unless request.params.blank?
|
95
|
+
end
|
96
|
+
"LHC_CACHE(v#{CACHE_VERSION}): #{key}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Checks if the provided method should be cached
|
100
|
+
# in regards of the provided options.
|
101
|
+
def cached_method?(method, cached_methods)
|
102
|
+
(cached_methods || [:get]).include?(method)
|
103
|
+
end
|
104
|
+
|
105
|
+
# extracts the options that should be forwarded to
|
106
|
+
# the cache
|
107
|
+
def cache_options(input = {})
|
108
|
+
input.each_with_object({}) do |(key, value), result|
|
109
|
+
result[key] = value if key.in? FORWARDED_OPTIONS
|
110
|
+
result
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# grabs the deprecated keys from the request options
|
115
|
+
def deprecated_keys(request_options)
|
116
|
+
request_options.keys.select { |k| k =~ /^cache_.*/ }.sort
|
117
|
+
end
|
118
|
+
|
119
|
+
# emits a deprecation warning if necessary
|
120
|
+
def deprecation_warning(request_options)
|
121
|
+
unless deprecated_keys(request_options).empty?
|
122
|
+
ActiveSupport::Deprecation.warn(
|
123
|
+
"Cache options have changed! #{deprecated_keys(request_options).join(', ')} are deprecated and will be removed in future versions."
|
124
|
+
)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|