hearth 1.0.0.pre1 → 1.0.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -4
- data/VERSION +1 -1
- data/lib/hearth/api_error.rb +15 -1
- data/lib/hearth/auth_option.rb +21 -0
- data/lib/hearth/auth_schemes/anonymous.rb +21 -0
- data/lib/hearth/auth_schemes/http_api_key.rb +16 -0
- data/lib/hearth/auth_schemes/http_basic.rb +16 -0
- data/lib/hearth/auth_schemes/http_bearer.rb +16 -0
- data/lib/hearth/auth_schemes/http_digest.rb +16 -0
- data/lib/hearth/auth_schemes.rb +32 -0
- data/lib/hearth/checksums.rb +31 -0
- data/lib/hearth/client_stubs.rb +130 -0
- data/lib/hearth/config/env_provider.rb +53 -0
- data/lib/hearth/config/resolver.rb +52 -0
- data/lib/hearth/configuration.rb +15 -0
- data/lib/hearth/connection_pool.rb +77 -0
- data/lib/hearth/context.rb +28 -4
- data/lib/hearth/dns/host_address.rb +23 -0
- data/lib/hearth/dns/host_resolver.rb +92 -0
- data/lib/hearth/dns.rb +48 -0
- data/lib/hearth/http/api_error.rb +4 -8
- data/lib/hearth/http/client.rb +208 -59
- data/lib/hearth/http/error_inspector.rb +85 -0
- data/lib/hearth/http/error_parser.rb +18 -20
- data/lib/hearth/http/field.rb +64 -0
- data/lib/hearth/http/fields.rb +117 -0
- data/lib/hearth/http/middleware/content_length.rb +5 -2
- data/lib/hearth/http/middleware/content_md5.rb +31 -0
- data/lib/hearth/http/middleware/request_compression.rb +157 -0
- data/lib/hearth/http/middleware.rb +12 -0
- data/lib/hearth/http/networking_error.rb +1 -14
- data/lib/hearth/http/request.rb +83 -56
- data/lib/hearth/http/response.rb +42 -13
- data/lib/hearth/http.rb +14 -5
- data/lib/hearth/identities/anonymous.rb +8 -0
- data/lib/hearth/identities/http_api_key.rb +16 -0
- data/lib/hearth/identities/http_bearer.rb +16 -0
- data/lib/hearth/identities/http_login.rb +20 -0
- data/lib/hearth/identities.rb +21 -0
- data/lib/hearth/identity_resolver.rb +17 -0
- data/lib/hearth/interceptor.rb +506 -0
- data/lib/hearth/interceptor_context.rb +36 -0
- data/lib/hearth/interceptor_list.rb +48 -0
- data/lib/hearth/interceptors.rb +75 -0
- data/lib/hearth/middleware/auth.rb +100 -0
- data/lib/hearth/middleware/build.rb +32 -0
- data/lib/hearth/middleware/host_prefix.rb +10 -6
- data/lib/hearth/middleware/initialize.rb +58 -0
- data/lib/hearth/middleware/parse.rb +45 -6
- data/lib/hearth/middleware/retry.rb +97 -23
- data/lib/hearth/middleware/send.rb +137 -25
- data/lib/hearth/middleware/sign.rb +65 -0
- data/lib/hearth/middleware/validate.rb +11 -1
- data/lib/hearth/middleware.rb +19 -8
- data/lib/hearth/middleware_stack.rb +1 -43
- data/lib/hearth/networking_error.rb +18 -0
- data/lib/hearth/number_helper.rb +2 -2
- data/lib/hearth/output.rb +8 -4
- data/lib/hearth/plugin_list.rb +53 -0
- data/lib/hearth/query/param.rb +52 -0
- data/lib/hearth/query/param_list.rb +54 -0
- data/lib/hearth/query/param_matcher.rb +32 -0
- data/lib/hearth/refreshing_identity_resolver.rb +63 -0
- data/lib/hearth/request.rb +22 -0
- data/lib/hearth/response.rb +33 -0
- data/lib/hearth/retry/adaptive.rb +60 -0
- data/lib/hearth/retry/capacity_not_available_error.rb +9 -0
- data/lib/hearth/retry/client_rate_limiter.rb +143 -0
- data/lib/hearth/retry/exponential_backoff.rb +15 -0
- data/lib/hearth/retry/retry_quota.rb +56 -0
- data/lib/hearth/retry/standard.rb +46 -0
- data/lib/hearth/retry/strategy.rb +20 -0
- data/lib/hearth/retry.rb +16 -0
- data/lib/hearth/signers/anonymous.rb +16 -0
- data/lib/hearth/signers/http_api_key.rb +29 -0
- data/lib/hearth/signers/http_basic.rb +23 -0
- data/lib/hearth/signers/http_bearer.rb +19 -0
- data/lib/hearth/signers/http_digest.rb +19 -0
- data/lib/hearth/signers.rb +23 -0
- data/lib/hearth/stubs.rb +30 -0
- data/lib/hearth/time_helper.rb +5 -3
- data/lib/hearth/validator.rb +44 -5
- data/lib/hearth/waiters/poller.rb +6 -7
- data/lib/hearth/waiters/waiter.rb +17 -4
- data/lib/hearth/xml/formatter.rb +11 -2
- data/lib/hearth/xml/node.rb +2 -2
- data/lib/hearth.rb +32 -5
- data/sig/lib/hearth/aliases.rbs +4 -0
- data/sig/lib/hearth/api_error.rbs +13 -0
- data/sig/lib/hearth/auth_option.rbs +11 -0
- data/sig/lib/hearth/auth_schemes/anonymous.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_api_key.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_basic.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_bearer.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_digest.rbs +7 -0
- data/sig/lib/hearth/auth_schemes.rbs +13 -0
- data/sig/lib/hearth/block_io.rbs +9 -0
- data/sig/lib/hearth/client_stubs.rbs +5 -0
- data/sig/lib/hearth/configuration.rbs +7 -0
- data/sig/lib/hearth/dns/host_address.rbs +13 -0
- data/sig/lib/hearth/dns/host_resolver.rbs +19 -0
- data/sig/lib/hearth/http/api_error.rbs +13 -0
- data/sig/lib/hearth/http/client.rbs +9 -0
- data/sig/lib/hearth/http/field.rbs +19 -0
- data/sig/lib/hearth/http/fields.rbs +43 -0
- data/sig/lib/hearth/http/request.rbs +25 -0
- data/sig/lib/hearth/http/response.rbs +21 -0
- data/sig/lib/hearth/identities/anonymous.rbs +6 -0
- data/sig/lib/hearth/identities/http_api_key.rbs +9 -0
- data/sig/lib/hearth/identities/http_bearer.rbs +9 -0
- data/sig/lib/hearth/identities/http_login.rbs +11 -0
- data/sig/lib/hearth/identities.rbs +9 -0
- data/sig/lib/hearth/identity_resolver.rbs +7 -0
- data/sig/lib/hearth/interceptor.rbs +9 -0
- data/sig/lib/hearth/interceptor_context.rbs +15 -0
- data/sig/lib/hearth/interceptor_list.rbs +16 -0
- data/sig/lib/hearth/interfaces.rbs +65 -0
- data/sig/lib/hearth/output.rbs +11 -0
- data/sig/lib/hearth/plugin_list.rbs +15 -0
- data/sig/lib/hearth/query/param.rbs +17 -0
- data/sig/lib/hearth/query/param_list.rbs +25 -0
- data/sig/lib/hearth/request.rbs +9 -0
- data/sig/lib/hearth/response.rbs +11 -0
- data/sig/lib/hearth/retry/adaptive.rbs +13 -0
- data/sig/lib/hearth/retry/exponential_backoff.rbs +7 -0
- data/sig/lib/hearth/retry/standard.rbs +13 -0
- data/sig/lib/hearth/retry/strategy.rbs +11 -0
- data/sig/lib/hearth/retry.rbs +9 -0
- data/sig/lib/hearth/signers/anonymous.rbs +9 -0
- data/sig/lib/hearth/signers/http_api_key.rbs +9 -0
- data/sig/lib/hearth/signers/http_basic.rbs +9 -0
- data/sig/lib/hearth/signers/http_bearer.rbs +9 -0
- data/sig/lib/hearth/signers/http_digest.rbs +9 -0
- data/sig/lib/hearth/signers.rbs +9 -0
- data/sig/lib/hearth/structure.rbs +7 -0
- data/sig/lib/hearth/union.rbs +5 -0
- data/sig/lib/hearth/waiters/waiter.rbs +17 -0
- metadata +132 -22
- data/lib/hearth/http/headers.rb +0 -70
- data/lib/hearth/middleware/around_handler.rb +0 -24
- data/lib/hearth/middleware/request_handler.rb +0 -24
- data/lib/hearth/middleware/response_handler.rb +0 -25
- data/lib/hearth/middleware_builder.rb +0 -246
- data/lib/hearth/stubbing/client_stubs.rb +0 -115
- data/lib/hearth/stubbing/stubs.rb +0 -32
- data/lib/hearth/waiters/errors.rb +0 -15
- data/sig/lib/seahorse/api_error.rbs +0 -10
- data/sig/lib/seahorse/document.rbs +0 -2
- data/sig/lib/seahorse/http/api_error.rbs +0 -21
- data/sig/lib/seahorse/http/headers.rbs +0 -47
- data/sig/lib/seahorse/http/response.rbs +0 -21
- data/sig/lib/seahorse/simple_delegator.rbs +0 -3
- data/sig/lib/seahorse/structure.rbs +0 -18
- data/sig/lib/seahorse/stubbing/client_stubs.rbs +0 -103
- data/sig/lib/seahorse/stubbing/stubs.rbs +0 -14
- data/sig/lib/seahorse/union.rbs +0 -6
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A middleware that resolves identities for signing requests.
|
6
|
+
# @api private
|
7
|
+
class Auth
|
8
|
+
include Middleware::Logging
|
9
|
+
|
10
|
+
# @param [Class] app The next middleware in the stack.
|
11
|
+
# @param [#resolve(auth_params)] auth_resolver A class that responds to a
|
12
|
+
# `resolve(auth_params)` method where `auth_params` is a struct with an
|
13
|
+
# operation_name. For a given operation_name, the method must return an
|
14
|
+
# ordered list of {Hearth::AuthOption} objects to be considered for
|
15
|
+
# authentication.
|
16
|
+
# @param [Struct] auth_params A struct with an operation_name and other
|
17
|
+
# parameters that may be used to resolve auth options.
|
18
|
+
# @param [Array<Hearth::AuthScheme::Base>] auth_schemes A list of
|
19
|
+
# auth schemes to consider for authentication.
|
20
|
+
def initialize(app, auth_resolver:, auth_params:, auth_schemes:, **kwargs)
|
21
|
+
@app = app
|
22
|
+
@auth_resolver = auth_resolver
|
23
|
+
@auth_params = auth_params
|
24
|
+
@auth_schemes = auth_schemes.to_h { |s| [s.scheme_id, s] }
|
25
|
+
|
26
|
+
@identity_resolvers = {}
|
27
|
+
kwargs.each do |key, value|
|
28
|
+
next unless key.superclass == Hearth::Identities::Base
|
29
|
+
|
30
|
+
@identity_resolvers[key] = value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param input
|
35
|
+
# @param context
|
36
|
+
# @return [Output]
|
37
|
+
def call(input, context)
|
38
|
+
log_debug(context, 'Resolving auth')
|
39
|
+
auth_options = @auth_resolver.resolve(@auth_params)
|
40
|
+
log_debug(context, "Resolved auth options: #{auth_options}")
|
41
|
+
context.auth = resolve_auth(auth_options)
|
42
|
+
log_debug(context, "Resolved auth: #{context.auth}")
|
43
|
+
@app.call(input, context)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
ResolvedAuth = Struct.new(
|
49
|
+
:signer,
|
50
|
+
:signer_properties,
|
51
|
+
:identity,
|
52
|
+
:identity_properties,
|
53
|
+
keyword_init: true
|
54
|
+
)
|
55
|
+
|
56
|
+
def resolve_auth(auth_options)
|
57
|
+
failures = []
|
58
|
+
|
59
|
+
auth_options.each do |auth_option|
|
60
|
+
auth_scheme = @auth_schemes[auth_option.scheme_id]
|
61
|
+
resolved_auth = try_load_auth_scheme(
|
62
|
+
auth_option,
|
63
|
+
auth_scheme,
|
64
|
+
failures
|
65
|
+
)
|
66
|
+
|
67
|
+
return resolved_auth if resolved_auth
|
68
|
+
end
|
69
|
+
|
70
|
+
raise failures.join("\n")
|
71
|
+
end
|
72
|
+
|
73
|
+
def try_load_auth_scheme(auth_option, auth_scheme, failures)
|
74
|
+
scheme_id = auth_option.scheme_id
|
75
|
+
unless auth_scheme
|
76
|
+
failures << "Auth scheme #{scheme_id} was not enabled " \
|
77
|
+
'for this request'
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
identity_resolver = auth_scheme.identity_resolver(@identity_resolvers)
|
82
|
+
unless identity_resolver
|
83
|
+
failures << "Auth scheme #{scheme_id} did not have an " \
|
84
|
+
'identity resolver configured'
|
85
|
+
return
|
86
|
+
end
|
87
|
+
|
88
|
+
identity_properties = auth_option.identity_properties
|
89
|
+
identity = identity_resolver.identity(identity_properties)
|
90
|
+
|
91
|
+
ResolvedAuth.new(
|
92
|
+
identity: identity,
|
93
|
+
identity_properties: auth_option.identity_properties,
|
94
|
+
signer: auth_scheme.signer,
|
95
|
+
signer_properties: auth_option.signer_properties
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -5,6 +5,8 @@ module Hearth
|
|
5
5
|
# A middleware that builds a request object.
|
6
6
|
# @api private
|
7
7
|
class Build
|
8
|
+
include Middleware::Logging
|
9
|
+
|
8
10
|
# @param [Class] app The next middleware in the stack.
|
9
11
|
# @param [Class] builder A builder object responsible for building the
|
10
12
|
# request. It must respond to #build and take the request and input as
|
@@ -18,7 +20,37 @@ module Hearth
|
|
18
20
|
# @param context
|
19
21
|
# @return [Output]
|
20
22
|
def call(input, context)
|
23
|
+
interceptor_error = Interceptors.invoke(
|
24
|
+
hook: Interceptor::MODIFY_BEFORE_SERIALIZATION,
|
25
|
+
input: input,
|
26
|
+
context: context,
|
27
|
+
output: nil,
|
28
|
+
aggregate_errors: false
|
29
|
+
)
|
30
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
31
|
+
|
32
|
+
interceptor_error = Interceptors.invoke(
|
33
|
+
hook: Interceptor::READ_BEFORE_SERIALIZATION,
|
34
|
+
input: input,
|
35
|
+
context: context,
|
36
|
+
output: nil,
|
37
|
+
aggregate_errors: false
|
38
|
+
)
|
39
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
40
|
+
|
41
|
+
log_debug(context, "Building request with: #{input}")
|
21
42
|
@builder.build(context.request, input: input)
|
43
|
+
log_debug(context, "Built request: #{context.request.inspect}")
|
44
|
+
|
45
|
+
interceptor_error = Interceptors.invoke(
|
46
|
+
hook: Interceptor::READ_AFTER_SERIALIZATION,
|
47
|
+
input: input,
|
48
|
+
context: context,
|
49
|
+
output: nil,
|
50
|
+
aggregate_errors: false
|
51
|
+
)
|
52
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
53
|
+
|
22
54
|
@app.call(input, context)
|
23
55
|
end
|
24
56
|
end
|
@@ -5,6 +5,8 @@ module Hearth
|
|
5
5
|
# A middleware that prefixes the host.
|
6
6
|
# @api private
|
7
7
|
class HostPrefix
|
8
|
+
include Middleware::Logging
|
9
|
+
|
8
10
|
# @param [Class] app The next middleware in the stack.
|
9
11
|
# @param [Boolean] disable_host_prefix If true, this option will not
|
10
12
|
# modify the host url.
|
@@ -21,25 +23,27 @@ module Hearth
|
|
21
23
|
# @param context
|
22
24
|
# @return [Output]
|
23
25
|
def call(input, context)
|
24
|
-
unless @disable_host_prefix
|
25
|
-
prefix = apply_labels(@host_prefix, input)
|
26
|
-
context.request.prefix_host(prefix)
|
27
|
-
end
|
26
|
+
prefix_host(input, context) unless @disable_host_prefix
|
28
27
|
@app.call(input, context)
|
29
28
|
end
|
30
29
|
|
31
30
|
private
|
32
31
|
|
32
|
+
def prefix_host(input, context)
|
33
|
+
log_debug(context, "Prefixing host with #{@host_prefix}")
|
34
|
+
prefix = apply_labels(@host_prefix, input)
|
35
|
+
context.request.prefix_host(prefix)
|
36
|
+
log_debug(context, "Prefixed host: #{context.request.uri.host}")
|
37
|
+
end
|
38
|
+
|
33
39
|
def apply_labels(host_prefix, input)
|
34
40
|
host_prefix.gsub(/\{.+?\}/) do |host_label|
|
35
41
|
key = host_label.delete('{}')
|
36
42
|
value = input[key.to_sym]
|
37
|
-
|
38
43
|
if value.nil? || value.empty?
|
39
44
|
raise ArgumentError,
|
40
45
|
"Host label #{key} cannot be nil or empty."
|
41
46
|
end
|
42
|
-
|
43
47
|
value
|
44
48
|
end
|
45
49
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A middleware used to initialize the request, called first in the stack
|
6
|
+
# @api private
|
7
|
+
class Initialize
|
8
|
+
include Middleware::Logging
|
9
|
+
|
10
|
+
# @param [Class] app The next middleware in the stack.
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param input
|
16
|
+
# @param context
|
17
|
+
# @return [Output]
|
18
|
+
def call(input, context)
|
19
|
+
interceptor_error = Interceptors.invoke(
|
20
|
+
hook: Interceptor::READ_BEFORE_EXECUTION,
|
21
|
+
input: input,
|
22
|
+
context: context,
|
23
|
+
output: nil,
|
24
|
+
aggregate_errors: true
|
25
|
+
)
|
26
|
+
|
27
|
+
output =
|
28
|
+
if interceptor_error
|
29
|
+
Hearth::Output.new(error: interceptor_error)
|
30
|
+
else
|
31
|
+
log_debug(context, "Initializing request with #{input}")
|
32
|
+
@app.call(input, context)
|
33
|
+
end
|
34
|
+
log_debug(context, 'Finished request')
|
35
|
+
|
36
|
+
interceptor_error = Interceptors.invoke(
|
37
|
+
hook: Interceptor::MODIFY_BEFORE_COMPLETION,
|
38
|
+
input: input,
|
39
|
+
context: context,
|
40
|
+
output: output,
|
41
|
+
aggregate_errors: false
|
42
|
+
)
|
43
|
+
output.error = interceptor_error if interceptor_error
|
44
|
+
|
45
|
+
interceptor_error = Interceptors.invoke(
|
46
|
+
hook: Interceptor::READ_AFTER_EXECUTION,
|
47
|
+
input: input,
|
48
|
+
context: context,
|
49
|
+
output: output,
|
50
|
+
aggregate_errors: true
|
51
|
+
)
|
52
|
+
output.error = interceptor_error if interceptor_error
|
53
|
+
|
54
|
+
output
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -5,6 +5,8 @@ module Hearth
|
|
5
5
|
# A middleware that parses a response object.
|
6
6
|
# @api private
|
7
7
|
class Parse
|
8
|
+
include Middleware::Logging
|
9
|
+
|
8
10
|
# @param [Class] app The next middleware in the stack.
|
9
11
|
# @param [Class] error_parser A parser object responsible for parsing the
|
10
12
|
# response if there is an error. It must respond to #parse and take the
|
@@ -23,19 +25,56 @@ module Hearth
|
|
23
25
|
# @return [Output]
|
24
26
|
def call(input, context)
|
25
27
|
output = @app.call(input, context)
|
26
|
-
|
27
|
-
|
28
|
+
|
29
|
+
interceptor_error = Interceptors.invoke(
|
30
|
+
hook: Interceptor::MODIFY_BEFORE_DESERIALIZATION,
|
31
|
+
input: input,
|
32
|
+
context: context,
|
33
|
+
output: output,
|
34
|
+
aggregate_errors: false
|
35
|
+
)
|
36
|
+
if interceptor_error
|
37
|
+
output.error = interceptor_error
|
38
|
+
return output
|
39
|
+
end
|
40
|
+
|
41
|
+
interceptor_error = Interceptors.invoke(
|
42
|
+
hook: Interceptor::READ_BEFORE_DESERIALIZATION,
|
43
|
+
input: input,
|
44
|
+
context: context,
|
45
|
+
output: output,
|
46
|
+
aggregate_errors: false
|
47
|
+
)
|
48
|
+
if interceptor_error
|
49
|
+
output.error = interceptor_error
|
50
|
+
return output
|
51
|
+
end
|
52
|
+
|
53
|
+
parse_error(context, output) unless output.error
|
54
|
+
parse_data(context, output) unless output.error
|
55
|
+
|
56
|
+
interceptor_error = Interceptors.invoke(
|
57
|
+
hook: Interceptor::READ_AFTER_DESERIALIZATION,
|
58
|
+
input: input,
|
59
|
+
context: context,
|
60
|
+
output: output,
|
61
|
+
aggregate_errors: false
|
62
|
+
)
|
63
|
+
output.error = interceptor_error if interceptor_error
|
64
|
+
|
28
65
|
output
|
29
66
|
end
|
30
67
|
|
31
68
|
private
|
32
69
|
|
33
|
-
def parse_error(
|
34
|
-
output.error = @error_parser.parse(response)
|
70
|
+
def parse_error(context, output)
|
71
|
+
output.error = @error_parser.parse(context.response, output.metadata)
|
72
|
+
log_debug(context, "Parsed error: #{output.error}") if output.error
|
35
73
|
end
|
36
74
|
|
37
|
-
def parse_data(
|
38
|
-
output.data = @data_parser.parse(response)
|
75
|
+
def parse_data(context, output)
|
76
|
+
output.data = @data_parser.parse(context.response)
|
77
|
+
log_debug(context, "Parsed data: #{output.data}") if output.data
|
39
78
|
end
|
40
79
|
end
|
41
80
|
end
|
@@ -2,41 +2,115 @@
|
|
2
2
|
|
3
3
|
module Hearth
|
4
4
|
module Middleware
|
5
|
-
# A middleware that retries the request.
|
5
|
+
# A middleware that retries the request using a retry strategy.
|
6
6
|
# @api private
|
7
7
|
class Retry
|
8
|
+
include Middleware::Logging
|
9
|
+
|
8
10
|
# @param [Class] app The next middleware in the stack.
|
9
|
-
# @param [
|
10
|
-
#
|
11
|
-
#
|
12
|
-
|
11
|
+
# @param [Strategy] retry_strategy (Standard) The retry strategy
|
12
|
+
# to use. Hearth has two built in classes, Standard and Adaptive.
|
13
|
+
# * `Retry::Standard` - A standardized set of retry rules across
|
14
|
+
# the AWS SDKs. This includes support for retry quotas, which limit
|
15
|
+
# the number of unsuccessful retries a client can make.
|
16
|
+
# * `Retry::Adaptive` - An experimental retry mode that includes
|
17
|
+
# all the functionality of `standard` mode along with automatic
|
18
|
+
# client side throttling. This is a provisional mode that may change
|
19
|
+
# behavior in the future.
|
20
|
+
def initialize(app, retry_strategy:, error_inspector_class:)
|
13
21
|
@app = app
|
14
|
-
@
|
15
|
-
@
|
22
|
+
@retry_strategy = retry_strategy
|
23
|
+
@error_inspector_class = error_inspector_class
|
24
|
+
|
25
|
+
@retries = 0
|
16
26
|
end
|
17
27
|
|
18
|
-
# @param input
|
19
|
-
# @param context
|
20
|
-
# @return [Output]
|
21
28
|
def call(input, context)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
interceptor_error = Interceptors.invoke(
|
30
|
+
hook: Interceptor::MODIFY_BEFORE_RETRY_LOOP,
|
31
|
+
input: input,
|
32
|
+
context: context,
|
33
|
+
output: nil,
|
34
|
+
aggregate_errors: false
|
35
|
+
)
|
36
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
37
|
+
|
38
|
+
output = nil
|
39
|
+
token = @retry_strategy.acquire_initial_retry_token(nil)
|
40
|
+
log_debug(context, "Starting retry loop with token: #{token}")
|
41
|
+
loop do
|
42
|
+
interceptor_error = Interceptors.invoke(
|
43
|
+
hook: Interceptor::READ_BEFORE_ATTEMPT,
|
44
|
+
input: input,
|
45
|
+
context: context,
|
46
|
+
output: nil,
|
47
|
+
aggregate_errors: true
|
48
|
+
)
|
49
|
+
|
50
|
+
output =
|
51
|
+
if interceptor_error
|
52
|
+
Hearth::Output.new(error: interceptor_error)
|
53
|
+
else
|
54
|
+
log_debug(context, 'Attempting request in retry loop')
|
55
|
+
@app.call(input, context)
|
56
|
+
end
|
57
|
+
|
58
|
+
interceptor_error = Interceptors.invoke(
|
59
|
+
hook: Interceptor::MODIFY_BEFORE_ATTEMPT_COMPLETION,
|
60
|
+
input: input,
|
61
|
+
context: context,
|
62
|
+
output: output,
|
63
|
+
aggregate_errors: false
|
64
|
+
)
|
65
|
+
output.error = interceptor_error if interceptor_error
|
66
|
+
|
67
|
+
interceptor_error = Interceptors.invoke(
|
68
|
+
hook: Interceptor::READ_AFTER_ATTEMPT,
|
69
|
+
input: input,
|
70
|
+
context: context,
|
71
|
+
output: output,
|
72
|
+
aggregate_errors: true
|
73
|
+
)
|
74
|
+
output.error = interceptor_error if interceptor_error
|
75
|
+
|
76
|
+
if (error = output.error)
|
77
|
+
log_debug(context, "Request failed with error: #{error}")
|
78
|
+
error_info = @error_inspector_class.new(error, context.response)
|
79
|
+
token = @retry_strategy.refresh_retry_token(token, error_info)
|
80
|
+
break unless token
|
81
|
+
|
82
|
+
log_debug(context, "Retry token refreshed: #{token}")
|
83
|
+
log_debug(context, "Sleeping for #{token.retry_delay} seconds")
|
84
|
+
Kernel.sleep(token.retry_delay)
|
85
|
+
else
|
86
|
+
@retry_strategy.record_success(token)
|
87
|
+
log_debug(context, 'Request succeeded')
|
88
|
+
break
|
89
|
+
end
|
90
|
+
|
91
|
+
reset_request(context)
|
92
|
+
reset_response(context, output)
|
93
|
+
@retries += 1
|
31
94
|
end
|
95
|
+
log_debug(context, 'Finished retry loop')
|
96
|
+
output
|
32
97
|
end
|
33
98
|
|
34
99
|
private
|
35
100
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
101
|
+
def reset_request(context)
|
102
|
+
request = context.request
|
103
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
104
|
+
|
105
|
+
context.auth.signer.reset(
|
106
|
+
request: request,
|
107
|
+
properties: context.auth.signer_properties
|
108
|
+
)
|
109
|
+
end
|
110
|
+
|
111
|
+
def reset_response(context, output)
|
112
|
+
context.response.reset
|
113
|
+
output.error = nil
|
40
114
|
end
|
41
115
|
end
|
42
116
|
end
|
@@ -5,58 +5,170 @@ module Hearth
|
|
5
5
|
# A middleware used to send the request.
|
6
6
|
# @api private
|
7
7
|
class Send
|
8
|
+
include Middleware::Logging
|
9
|
+
|
8
10
|
# @param [Class] _app The next middleware in the stack.
|
9
11
|
# @param [Boolean] stub_responses If true, a request is not sent and a
|
10
12
|
# stubbed response is returned.
|
11
|
-
# @param [Class]
|
12
|
-
# a stubbed response. It must respond to #stub and take
|
13
|
-
# and stub data as arguments.
|
14
|
-
# @param [
|
13
|
+
# @param [Class] stub_data_class A stub object that is responsible for
|
14
|
+
# creating a stubbed data response. It must respond to #stub and take
|
15
|
+
# the response and stub data as arguments.
|
16
|
+
# @param [Array<Class>] stub_error_classes An array of error classes
|
17
|
+
# that are responsible for creating a stubbed error response. They
|
18
|
+
# must respond to #stub and take the response and stub data as
|
19
|
+
# arguments.
|
20
|
+
# @param [Stubs] stubs A {Hearth::Stubs} object containing
|
15
21
|
# stubbed data for any given operation.
|
16
|
-
def initialize(_app, client:, stub_responses:,
|
22
|
+
def initialize(_app, client:, stub_responses:,
|
23
|
+
stub_data_class:, stub_error_classes:, stubs:)
|
17
24
|
@client = client
|
18
25
|
@stub_responses = stub_responses
|
19
|
-
@
|
26
|
+
@stub_data_class = stub_data_class
|
27
|
+
@stub_error_classes = stub_error_classes
|
20
28
|
@stubs = stubs
|
21
29
|
end
|
22
30
|
|
23
|
-
# @param
|
31
|
+
# @param input
|
24
32
|
# @param context
|
25
33
|
# @return [Output]
|
26
|
-
def call(
|
34
|
+
def call(input, context)
|
35
|
+
interceptor_error = Interceptors.invoke(
|
36
|
+
hook: Interceptor::MODIFY_BEFORE_TRANSMIT,
|
37
|
+
input: input,
|
38
|
+
context: context,
|
39
|
+
output: nil,
|
40
|
+
aggregate_errors: false
|
41
|
+
)
|
42
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
43
|
+
|
44
|
+
interceptor_error = Interceptors.invoke(
|
45
|
+
hook: Interceptor::READ_BEFORE_TRANSMIT,
|
46
|
+
input: input,
|
47
|
+
context: context,
|
48
|
+
output: nil,
|
49
|
+
aggregate_errors: false
|
50
|
+
)
|
51
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
52
|
+
|
53
|
+
output = Output.new
|
27
54
|
if @stub_responses
|
28
|
-
|
29
|
-
output = Output.new
|
30
|
-
apply_stub(stub, context, output)
|
31
|
-
output
|
55
|
+
stub_response(input, context, output)
|
32
56
|
else
|
33
|
-
|
34
|
-
request: context.request,
|
35
|
-
response: context.response
|
36
|
-
)
|
37
|
-
Output.new
|
57
|
+
send_request(context, output)
|
38
58
|
end
|
59
|
+
|
60
|
+
interceptor_error = Interceptors.invoke(
|
61
|
+
hook: Interceptor::READ_AFTER_TRANSMIT,
|
62
|
+
input: input,
|
63
|
+
context: context,
|
64
|
+
output: output,
|
65
|
+
aggregate_errors: false
|
66
|
+
)
|
67
|
+
output.error = interceptor_error if interceptor_error
|
68
|
+
|
69
|
+
output
|
39
70
|
end
|
40
71
|
|
41
72
|
private
|
42
73
|
|
43
|
-
def
|
74
|
+
def stub_response(input, context, output)
|
75
|
+
stub = @stubs.next(context.operation_name)
|
76
|
+
log_debug(context, "Stubbing response with stub: #{stub}")
|
77
|
+
apply_stub(stub, input, context, output)
|
78
|
+
log_debug(context, "Stubbed response: #{context.response.inspect}")
|
79
|
+
return unless context.response.body.respond_to?(:rewind)
|
80
|
+
|
81
|
+
context.response.body.rewind
|
82
|
+
end
|
83
|
+
|
84
|
+
def send_request(context, output)
|
85
|
+
log_debug(context, "Sending request: #{context.request.inspect}")
|
86
|
+
@client.transmit(
|
87
|
+
request: context.request,
|
88
|
+
response: context.response,
|
89
|
+
logger: context.logger
|
90
|
+
)
|
91
|
+
log_debug(context, "Received response: #{context.response.inspect}")
|
92
|
+
rescue Hearth::NetworkingError => e
|
93
|
+
output.error = e
|
94
|
+
end
|
95
|
+
|
96
|
+
def apply_stub(stub, input, context, output)
|
44
97
|
case stub
|
45
98
|
when Proc
|
46
|
-
stub = stub.call(
|
47
|
-
apply_stub(stub, context, output)
|
48
|
-
when Exception
|
99
|
+
stub = stub.call(input)
|
100
|
+
apply_stub(stub, input, context, output)
|
101
|
+
when Exception, ApiError
|
49
102
|
output.error = stub
|
50
|
-
when Class
|
51
|
-
output.error = stub.new
|
52
103
|
when Hash
|
53
|
-
|
104
|
+
apply_stub_hash(stub, context)
|
54
105
|
when NilClass
|
55
|
-
|
106
|
+
apply_stub_nil(context)
|
107
|
+
when Hearth::Structure
|
108
|
+
apply_stub_hearth_structure(stub, context)
|
109
|
+
when Hearth::Response
|
110
|
+
context.response.replace(stub)
|
56
111
|
else
|
57
112
|
raise ArgumentError, 'Unsupported stub type'
|
58
113
|
end
|
59
114
|
end
|
115
|
+
|
116
|
+
def apply_stub_hash(stub, context)
|
117
|
+
if stub.key?(:error) && !stub.key?(:data)
|
118
|
+
apply_stub_hash_error(stub, context)
|
119
|
+
elsif stub.key?(:data) && !stub.key?(:error)
|
120
|
+
apply_stub_hash_data(stub, context)
|
121
|
+
else
|
122
|
+
raise ArgumentError, 'Unsupported stub hash, must be :data or :error'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def apply_stub_hash_data(stub, context)
|
127
|
+
output = @stub_data_class.build(stub[:data], context: 'stub')
|
128
|
+
@stub_data_class.validate!(output, context: 'stub')
|
129
|
+
@stub_data_class.stub(context.response, stub: output)
|
130
|
+
end
|
131
|
+
|
132
|
+
def apply_stub_hash_error(stub, context)
|
133
|
+
stub_error_class = stub_error_class(stub[:error][:class])
|
134
|
+
output = stub_error_class.build(
|
135
|
+
stub[:error][:data] || {},
|
136
|
+
context: 'stub'
|
137
|
+
)
|
138
|
+
stub_error_class.validate!(output, context: 'stub')
|
139
|
+
stub_error_class.stub(context.response, stub: output)
|
140
|
+
end
|
141
|
+
|
142
|
+
def stub_error_class(error_class)
|
143
|
+
raise ArgumentError, 'Missing stub error class' unless error_class
|
144
|
+
|
145
|
+
unless error_class.is_a?(Class)
|
146
|
+
raise ArgumentError, 'Stub error class must be a class'
|
147
|
+
end
|
148
|
+
|
149
|
+
error_base_name = error_class.name.split('::').last
|
150
|
+
stub_class = @stub_error_classes.find do |stub_error_class|
|
151
|
+
stub_base_name = stub_error_class.name.split('::').last
|
152
|
+
error_base_name == stub_base_name
|
153
|
+
end
|
154
|
+
raise ArgumentError, 'Unsupported stub error class' unless stub_class
|
155
|
+
|
156
|
+
stub_class
|
157
|
+
end
|
158
|
+
|
159
|
+
def apply_stub_nil(context)
|
160
|
+
output = @stub_data_class.build(
|
161
|
+
@stub_data_class.default,
|
162
|
+
context: 'stub'
|
163
|
+
)
|
164
|
+
@stub_data_class.validate!(output, context: 'stub')
|
165
|
+
@stub_data_class.stub(context.response, stub: output)
|
166
|
+
end
|
167
|
+
|
168
|
+
def apply_stub_hearth_structure(stub, context)
|
169
|
+
@stub_data_class.validate!(stub, context: 'stub')
|
170
|
+
@stub_data_class.stub(context.response, stub: stub)
|
171
|
+
end
|
60
172
|
end
|
61
173
|
end
|
62
174
|
end
|