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
data/Rakefile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
rescue LoadError
|
|
6
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require 'rdoc/task'
|
|
10
|
+
|
|
11
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
|
12
|
+
rdoc.rdoc_dir = 'rdoc'
|
|
13
|
+
rdoc.title = 'DHC'
|
|
14
|
+
rdoc.options << '--line-numbers'
|
|
15
|
+
rdoc.rdoc_files.include('README.rdoc')
|
|
16
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
require 'rspec/core/rake_task'
|
|
21
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
22
|
+
task default: :spec
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Bundler::GemHelper.install_tasks
|
data/dhc.gemspec
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
|
4
|
+
|
|
5
|
+
# Maintain your gem's version:
|
|
6
|
+
require 'dhc/version'
|
|
7
|
+
|
|
8
|
+
# Describe your gem and declare its dependencies:
|
|
9
|
+
Gem::Specification.new do |s|
|
|
10
|
+
s.name = 'dhc'
|
|
11
|
+
s.version = DHC::VERSION
|
|
12
|
+
s.authors = ['https://github.com/DePayFi/dhc/contributors']
|
|
13
|
+
s.email = ['engineering@depay.fi']
|
|
14
|
+
s.homepage = 'https://github.com/DePayFi/dhc'
|
|
15
|
+
s.summary = 'Advanced HTTP Client for Ruby, fueled with interceptors'
|
|
16
|
+
s.description = 'DHC is an advanced HTTP client. Implementing basic http-communication enhancements like interceptors, exception handling, format handling, accessing response data, configuring endpoints and placeholders and fully compatible, RFC-compliant URL-template support.'
|
|
17
|
+
|
|
18
|
+
s.files = `git ls-files`.split("\n")
|
|
19
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
|
20
|
+
s.require_paths = ['lib']
|
|
21
|
+
|
|
22
|
+
s.requirements << 'Ruby >= 2.0.0'
|
|
23
|
+
s.required_ruby_version = '>= 2.7.0'
|
|
24
|
+
|
|
25
|
+
s.add_dependency 'activesupport', '>= 5.2'
|
|
26
|
+
s.add_dependency 'addressable'
|
|
27
|
+
s.add_dependency 'typhoeus', '>= 0.11'
|
|
28
|
+
|
|
29
|
+
s.add_development_dependency 'prometheus-client', '~> 0.7.1'
|
|
30
|
+
s.add_development_dependency 'pry'
|
|
31
|
+
s.add_development_dependency 'rails', '>= 5.2'
|
|
32
|
+
s.add_development_dependency 'redis'
|
|
33
|
+
s.add_development_dependency 'rspec-rails', '>= 3.0.0'
|
|
34
|
+
s.add_development_dependency 'rubocop'
|
|
35
|
+
s.add_development_dependency 'rubocop-rspec'
|
|
36
|
+
s.add_development_dependency 'timecop'
|
|
37
|
+
s.add_development_dependency 'webmock'
|
|
38
|
+
|
|
39
|
+
s.license = 'GPL-3.0'
|
|
40
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This was copied from: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/hash/deep_transform_values.rb
|
|
4
|
+
class Hash
|
|
5
|
+
# Returns a new hash with all values converted by the block operation.
|
|
6
|
+
# This includes the values from the root hash and from all
|
|
7
|
+
# nested hashes and arrays.
|
|
8
|
+
#
|
|
9
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
|
10
|
+
#
|
|
11
|
+
# hash.deep_transform_values{ |value| value.to_s.upcase }
|
|
12
|
+
# # => {person: {name: "ROB", age: "28"}}
|
|
13
|
+
def deep_transform_values(&block)
|
|
14
|
+
_deep_transform_values_in_object(self, &block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Destructively converts all values by using the block operation.
|
|
18
|
+
# This includes the values from the root hash and from all
|
|
19
|
+
# nested hashes and arrays.
|
|
20
|
+
def deep_transform_values!(&block)
|
|
21
|
+
_deep_transform_values_in_object!(self, &block)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# support methods for deep transforming nested hashes and arrays
|
|
27
|
+
def _deep_transform_values_in_object(object, &block)
|
|
28
|
+
case object
|
|
29
|
+
when Hash
|
|
30
|
+
object.transform_values { |value| _deep_transform_values_in_object(value, &block) }
|
|
31
|
+
when Array
|
|
32
|
+
object.map { |e| _deep_transform_values_in_object(e, &block) }
|
|
33
|
+
else
|
|
34
|
+
yield(object)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def _deep_transform_values_in_object!(object, &block)
|
|
39
|
+
case object
|
|
40
|
+
when Hash
|
|
41
|
+
object.transform_values! { |value| _deep_transform_values_in_object!(value, &block) }
|
|
42
|
+
when Array
|
|
43
|
+
object.map! { |e| _deep_transform_values_in_object!(e, &block) }
|
|
44
|
+
else
|
|
45
|
+
yield(object)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
data/lib/dhc.rb
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'typhoeus'
|
|
4
|
+
require 'active_support/core_ext/object/blank'
|
|
5
|
+
require 'active_support/core_ext/hash/keys'
|
|
6
|
+
|
|
7
|
+
module DHC
|
|
8
|
+
autoload :BasicMethodsConcern, 'dhc/concerns/dhc/basic_methods_concern'
|
|
9
|
+
autoload :ConfigurationConcern, 'dhc/concerns/dhc/configuration_concern'
|
|
10
|
+
autoload :FixInvalidEncodingConcern, 'dhc/concerns/dhc/fix_invalid_encoding_concern'
|
|
11
|
+
autoload :FormatsConcern, 'dhc/concerns/dhc/formats_concern'
|
|
12
|
+
|
|
13
|
+
include BasicMethodsConcern
|
|
14
|
+
include ConfigurationConcern
|
|
15
|
+
include FormatsConcern
|
|
16
|
+
|
|
17
|
+
autoload :Auth, 'dhc/interceptors/auth'
|
|
18
|
+
autoload :Caching, 'dhc/interceptors/caching'
|
|
19
|
+
autoload :DefaultTimeout, 'dhc/interceptors/default_timeout'
|
|
20
|
+
autoload :Logging, 'dhc/interceptors/logging'
|
|
21
|
+
autoload :Prometheus, 'dhc/interceptors/prometheus'
|
|
22
|
+
autoload :Retry, 'dhc/interceptors/retry'
|
|
23
|
+
autoload :Throttle, 'dhc/interceptors/throttle'
|
|
24
|
+
|
|
25
|
+
autoload :Config, 'dhc/config'
|
|
26
|
+
autoload :Endpoint, 'dhc/endpoint'
|
|
27
|
+
|
|
28
|
+
autoload :Error, 'dhc/error'
|
|
29
|
+
autoload :ClientError, 'dhc/errors/client_error'
|
|
30
|
+
autoload :BadRequest, 'dhc/errors/client_error'
|
|
31
|
+
autoload :Unauthorized, 'dhc/errors/client_error'
|
|
32
|
+
autoload :PaymentRequired, 'dhc/errors/client_error'
|
|
33
|
+
autoload :Forbidden, 'dhc/errors/client_error'
|
|
34
|
+
autoload :Forbidden, 'dhc/errors/client_error'
|
|
35
|
+
autoload :NotFound, 'dhc/errors/client_error'
|
|
36
|
+
autoload :MethodNotAllowed, 'dhc/errors/client_error'
|
|
37
|
+
autoload :NotAcceptable, 'dhc/errors/client_error'
|
|
38
|
+
autoload :ProxyAuthenticationRequired, 'dhc/errors/client_error'
|
|
39
|
+
autoload :RequestTimeout, 'dhc/errors/client_error'
|
|
40
|
+
autoload :Conflict, 'dhc/errors/client_error'
|
|
41
|
+
autoload :Gone, 'dhc/errors/client_error'
|
|
42
|
+
autoload :LengthRequired, 'dhc/errors/client_error'
|
|
43
|
+
autoload :PreconditionFailed, 'dhc/errors/client_error'
|
|
44
|
+
autoload :RequestEntityTooLarge, 'dhc/errors/client_error'
|
|
45
|
+
autoload :RequestUriToLong, 'dhc/errors/client_error'
|
|
46
|
+
autoload :UnsupportedMediaType, 'dhc/errors/client_error'
|
|
47
|
+
autoload :RequestedRangeNotSatisfiable, 'dhc/errors/client_error'
|
|
48
|
+
autoload :ExpectationFailed, 'dhc/errors/client_error'
|
|
49
|
+
autoload :UnprocessableEntity, 'dhc/errors/client_error'
|
|
50
|
+
autoload :Locked, 'dhc/errors/client_error'
|
|
51
|
+
autoload :FailedDependency, 'dhc/errors/client_error'
|
|
52
|
+
autoload :UpgradeRequired, 'dhc/errors/client_error'
|
|
53
|
+
autoload :ParserError, 'dhc/errors/parser_error'
|
|
54
|
+
autoload :ServerError, 'dhc/errors/server_error'
|
|
55
|
+
autoload :InternalServerError, 'dhc/errors/server_error'
|
|
56
|
+
autoload :NotImplemented, 'dhc/errors/server_error'
|
|
57
|
+
autoload :BadGateway, 'dhc/errors/server_error'
|
|
58
|
+
autoload :ServiceUnavailable, 'dhc/errors/server_error'
|
|
59
|
+
autoload :GatewayTimeout, 'dhc/errors/server_error'
|
|
60
|
+
autoload :HttpVersionNotSupported, 'dhc/errors/server_error'
|
|
61
|
+
autoload :InsufficientStorage, 'dhc/errors/server_error'
|
|
62
|
+
autoload :NotExtended, 'dhc/errors/server_error'
|
|
63
|
+
autoload :Timeout, 'dhc/errors/timeout'
|
|
64
|
+
autoload :UnknownError, 'dhc/errors/unknown_error'
|
|
65
|
+
|
|
66
|
+
autoload :Interceptor, 'dhc/interceptor'
|
|
67
|
+
autoload :Interceptors, 'dhc/interceptors'
|
|
68
|
+
autoload :Formats, 'dhc/formats'
|
|
69
|
+
autoload :Format, 'dhc/format'
|
|
70
|
+
autoload :Monitoring, 'dhc/interceptors/monitoring'
|
|
71
|
+
autoload :Request, 'dhc/request'
|
|
72
|
+
autoload :Response, 'dhc/response'
|
|
73
|
+
autoload :Rollbar, 'dhc/interceptors/rollbar'
|
|
74
|
+
autoload :Zipkin, 'dhc/interceptors/zipkin'
|
|
75
|
+
|
|
76
|
+
require 'dhc/railtie' if defined?(Rails)
|
|
77
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support'
|
|
4
|
+
|
|
5
|
+
module DHC
|
|
6
|
+
module BasicMethodsConcern
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def request(options)
|
|
11
|
+
if options.is_a? Array
|
|
12
|
+
parallel_requests(options)
|
|
13
|
+
else
|
|
14
|
+
DHC::Request.new(options).response
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
%i[get post put delete].each do |http_method|
|
|
19
|
+
define_method(http_method) do |url, options = {}|
|
|
20
|
+
request(options.merge(
|
|
21
|
+
url: url,
|
|
22
|
+
method: http_method
|
|
23
|
+
))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def parallel_requests(options)
|
|
30
|
+
hydra = Typhoeus::Hydra.new # do not use memoization !
|
|
31
|
+
requests = []
|
|
32
|
+
options.each do |option|
|
|
33
|
+
request = DHC::Request.new(option, false)
|
|
34
|
+
requests << request
|
|
35
|
+
hydra.queue request.raw unless request.response.present?
|
|
36
|
+
end
|
|
37
|
+
hydra.run
|
|
38
|
+
requests.map(&:response)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support'
|
|
4
|
+
|
|
5
|
+
module DHC
|
|
6
|
+
module ConfigurationConcern
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def config
|
|
11
|
+
DHC::Config.instance
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def configure
|
|
15
|
+
DHC::Config.instance.reset
|
|
16
|
+
yield config
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support'
|
|
4
|
+
|
|
5
|
+
module DHC
|
|
6
|
+
module FixInvalidEncodingConcern
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# fix strings that contain non-UTF8 encoding in a forceful way
|
|
11
|
+
# should none of the fix-attempts be successful,
|
|
12
|
+
# an empty string is returned instead
|
|
13
|
+
def fix_invalid_encoding(string)
|
|
14
|
+
return string unless string.is_a?(String)
|
|
15
|
+
result = string.dup
|
|
16
|
+
|
|
17
|
+
# we assume it's ISO-8859-1 first
|
|
18
|
+
if !result.valid_encoding? || !utf8?(result)
|
|
19
|
+
result.encode!('UTF-8', 'ISO-8859-1', invalid: :replace, undef: :replace, replace: '')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# if it's still an issue, try with BINARY
|
|
23
|
+
if !result.valid_encoding? || !utf8?(result)
|
|
24
|
+
result.encode!('UTF-8', 'BINARY', invalid: :replace, undef: :replace, replace: '')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# if its STILL an issue, return an empty string :(
|
|
28
|
+
if !result.valid_encoding? || !utf8?(result)
|
|
29
|
+
result = ''
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def utf8?(string)
|
|
38
|
+
string.encoding == Encoding::UTF_8
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DHC
|
|
4
|
+
module FormatsConcern
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
def form
|
|
9
|
+
DHC::Formats::Form
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def json
|
|
13
|
+
DHC::Formats::JSON
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def multipart
|
|
17
|
+
DHC::Formats::Multipart
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def plain
|
|
21
|
+
DHC::Formats::Plain
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support'
|
|
4
|
+
require 'dhc/version'
|
|
5
|
+
|
|
6
|
+
module DHC
|
|
7
|
+
class Request
|
|
8
|
+
module UserAgentConcern
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
Typhoeus::Config.user_agent = begin
|
|
13
|
+
version = DHC::VERSION
|
|
14
|
+
application = nil
|
|
15
|
+
if defined?(Rails)
|
|
16
|
+
app_class = Rails.application.class
|
|
17
|
+
application = (ActiveSupport.gem_version >= Gem::Version.new('6.0.0')) ? app_class.module_parent_name : app_class.parent_name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
"DHC (#{[version, application].compact.join('; ')}) [https://github.com/DePayFi/dhc]"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/dhc/config.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
|
|
5
|
+
class DHC::Config
|
|
6
|
+
include Singleton
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@endpoints = {}
|
|
10
|
+
@placeholders = {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def endpoint(name, url, options = {})
|
|
14
|
+
name = name.to_sym
|
|
15
|
+
raise 'Endpoint already exists for that name' if @endpoints[name]
|
|
16
|
+
@endpoints[name] = DHC::Endpoint.new(url, options)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def endpoints
|
|
20
|
+
@endpoints.dup
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def placeholder(name, value)
|
|
24
|
+
name = name.to_sym
|
|
25
|
+
raise 'Placeholder already exists for that name' if @placeholders[name]
|
|
26
|
+
@placeholders[name] = value
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def placeholders
|
|
30
|
+
@placeholders.dup
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def interceptors
|
|
34
|
+
(@interceptors || []).dup
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def interceptors=(interceptors)
|
|
38
|
+
raise 'Default interceptors already set and can only be set once' if @interceptors
|
|
39
|
+
@interceptors = interceptors
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def reset
|
|
43
|
+
@endpoints = {}
|
|
44
|
+
@placeholders = {}
|
|
45
|
+
@interceptors = nil
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/dhc/endpoint.rb
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'addressable/template'
|
|
4
|
+
|
|
5
|
+
# An endpoint is an url that leads to a backend resource.
|
|
6
|
+
# The url can also be an url-template (https://tools.ietf.org/html/rfc6570).
|
|
7
|
+
class DHC::Endpoint
|
|
8
|
+
attr_accessor :url
|
|
9
|
+
attr_writer :options
|
|
10
|
+
|
|
11
|
+
def initialize(url, options = nil)
|
|
12
|
+
self.url = url
|
|
13
|
+
self.options = options
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def uri
|
|
17
|
+
@uri ||= Addressable::Template.new(url)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def compile(params)
|
|
21
|
+
context = DHC.config.placeholders.deep_dup
|
|
22
|
+
context.merge!(params) if params.is_a?(Hash)
|
|
23
|
+
expanded = uri.partial_expand(context)
|
|
24
|
+
|
|
25
|
+
if expanded.variables.empty?
|
|
26
|
+
expanded.pattern
|
|
27
|
+
else
|
|
28
|
+
raise("Compilation incomplete. Unable to find value for #{expanded.variables.join(', ')}.")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Endpoint options are immutable
|
|
33
|
+
def options
|
|
34
|
+
@options.deep_dup
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Removes keys from provided params hash
|
|
38
|
+
# when they are used for interpolation.
|
|
39
|
+
def remove_interpolated_params!(params)
|
|
40
|
+
params ||= {}
|
|
41
|
+
removed = params.slice(*placeholders)
|
|
42
|
+
params.except!(*placeholders)
|
|
43
|
+
|
|
44
|
+
removed
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns all placeholders found in the url-template.
|
|
48
|
+
# They are alphabetically sorted.
|
|
49
|
+
def placeholders
|
|
50
|
+
uri.variables.sort.map(&:to_sym)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Compares a concrete url with a template
|
|
54
|
+
# Returns true if concrete url is covered by the template
|
|
55
|
+
# Example: {+datastore}/contracts/{id} == http://depay.fi/contracts/1
|
|
56
|
+
def match?(url)
|
|
57
|
+
return true if url == uri.pattern
|
|
58
|
+
match_data = match_data(url)
|
|
59
|
+
return false if match_data.nil?
|
|
60
|
+
|
|
61
|
+
first = match_data.values.first
|
|
62
|
+
last = match_data.values.last
|
|
63
|
+
url_format = File.extname(url)
|
|
64
|
+
|
|
65
|
+
# eliminate false positives in form:
|
|
66
|
+
# http://host/feedbacks matches {+service}/{entry_id}/feedbacks (service: http:/, entry_id: host)
|
|
67
|
+
first&.match(%(https?:/$)).nil? &&
|
|
68
|
+
# eliminates false positives in form:
|
|
69
|
+
# http://host/entries/123.json matches {+service}/entries/{id} (service: http://host, id: 123.json)
|
|
70
|
+
(url_format.blank? || !last&.ends_with?(url_format))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Extracts the values from url and
|
|
74
|
+
# creates params according to template
|
|
75
|
+
def values_as_params(url)
|
|
76
|
+
match_data = match_data(url)
|
|
77
|
+
return if match_data.nil?
|
|
78
|
+
Hash[match_data.variables.map(&:to_sym).zip(match_data.values)]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Checks if the name has a match in the current context
|
|
82
|
+
def find_value(name, mapping)
|
|
83
|
+
context = DHC.config.placeholders.deep_dup
|
|
84
|
+
context.merge!(mapping)
|
|
85
|
+
|
|
86
|
+
context[name]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Compares a concrete url with a template
|
|
90
|
+
# Returns true if concrete url is covered by the template
|
|
91
|
+
# Example: {+datastore}/contracts/{id} == http://depay.fi/contracts/1
|
|
92
|
+
def self.match?(url, template)
|
|
93
|
+
new(template).match?(url)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns all placeholders found in the url-template.
|
|
97
|
+
# They are alphabetically sorted.
|
|
98
|
+
def self.placeholders(template)
|
|
99
|
+
new(template).placeholders
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Extracts the values from url and
|
|
103
|
+
# creates params according to template
|
|
104
|
+
def self.values_as_params(template, url)
|
|
105
|
+
raise("#{url} does not match the template: #{template}") unless match?(url, template)
|
|
106
|
+
new(template).values_as_params(url)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def match_data(url)
|
|
112
|
+
parsed = URI.parse(url)
|
|
113
|
+
parsed.query = parsed.fragment = nil
|
|
114
|
+
|
|
115
|
+
uri.match(parsed)
|
|
116
|
+
rescue URI::InvalidURIError
|
|
117
|
+
nil
|
|
118
|
+
end
|
|
119
|
+
end
|