aws-sdk-core 3.168.4 → 3.224.1
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 +4 -4
- data/CHANGELOG.md +719 -0
- data/VERSION +1 -1
- data/lib/aws-defaults/default_configuration.rb +5 -6
- data/lib/aws-defaults.rb +4 -1
- data/lib/aws-sdk-core/arn.rb +1 -3
- data/lib/aws-sdk-core/assume_role_credentials.rb +13 -5
- data/lib/aws-sdk-core/assume_role_web_identity_credentials.rb +14 -7
- data/lib/aws-sdk-core/binary/decode_handler.rb +3 -9
- data/lib/aws-sdk-core/binary/encode_handler.rb +1 -1
- data/lib/aws-sdk-core/binary/event_builder.rb +34 -37
- data/lib/aws-sdk-core/binary/event_stream_decoder.rb +1 -0
- data/lib/aws-sdk-core/binary/event_stream_encoder.rb +4 -3
- data/lib/aws-sdk-core/cbor/decoder.rb +308 -0
- data/lib/aws-sdk-core/cbor/encoder.rb +243 -0
- data/lib/aws-sdk-core/cbor.rb +53 -0
- data/lib/aws-sdk-core/client_side_monitoring.rb +9 -0
- data/lib/aws-sdk-core/client_stubs.rb +33 -55
- data/lib/aws-sdk-core/credential_provider.rb +8 -1
- data/lib/aws-sdk-core/credential_provider_chain.rb +39 -11
- data/lib/aws-sdk-core/credentials.rb +19 -6
- data/lib/aws-sdk-core/ec2_metadata.rb +1 -1
- data/lib/aws-sdk-core/ecs_credentials.rb +179 -53
- data/lib/aws-sdk-core/endpoints/condition.rb +5 -0
- data/lib/aws-sdk-core/endpoints/endpoint.rb +3 -1
- data/lib/aws-sdk-core/endpoints/endpoint_rule.rb +5 -1
- data/lib/aws-sdk-core/endpoints/error_rule.rb +5 -0
- data/lib/aws-sdk-core/endpoints/function.rb +5 -0
- data/lib/aws-sdk-core/endpoints/matchers.rb +19 -18
- data/lib/aws-sdk-core/endpoints/reference.rb +5 -0
- data/lib/aws-sdk-core/endpoints/rule.rb +5 -0
- data/lib/aws-sdk-core/endpoints/rule_set.rb +5 -0
- data/lib/aws-sdk-core/endpoints/rules_provider.rb +5 -0
- data/lib/aws-sdk-core/endpoints/templater.rb +6 -0
- data/lib/aws-sdk-core/endpoints/tree_rule.rb +5 -0
- data/lib/aws-sdk-core/endpoints/url.rb +1 -0
- data/lib/aws-sdk-core/endpoints.rb +79 -19
- data/lib/aws-sdk-core/error_handler.rb +41 -0
- data/lib/aws-sdk-core/errors.rb +14 -5
- data/lib/aws-sdk-core/event_emitter.rb +0 -16
- data/lib/aws-sdk-core/ini_parser.rb +7 -0
- data/lib/aws-sdk-core/instance_profile_credentials.rb +56 -32
- data/lib/aws-sdk-core/json/builder.rb +8 -1
- data/lib/aws-sdk-core/json/error_handler.rb +30 -14
- data/lib/aws-sdk-core/json/handler.rb +13 -6
- data/lib/aws-sdk-core/json/json_engine.rb +3 -1
- data/lib/aws-sdk-core/json/oj_engine.rb +7 -1
- data/lib/aws-sdk-core/json/parser.rb +33 -3
- data/lib/aws-sdk-core/json.rb +43 -14
- data/lib/aws-sdk-core/log/formatter.rb +6 -0
- data/lib/aws-sdk-core/log/param_filter.rb +2 -2
- data/lib/aws-sdk-core/log/param_formatter.rb +7 -3
- data/lib/aws-sdk-core/log.rb +10 -0
- data/lib/aws-sdk-core/lru_cache.rb +75 -0
- data/lib/aws-sdk-core/pageable_response.rb +3 -1
- data/lib/aws-sdk-core/param_validator.rb +9 -4
- data/lib/aws-sdk-core/plugins/bearer_authorization.rb +2 -0
- data/lib/aws-sdk-core/plugins/checksum_algorithm.rb +333 -168
- data/lib/aws-sdk-core/plugins/client_metrics_plugin.rb +1 -1
- data/lib/aws-sdk-core/plugins/client_metrics_send_plugin.rb +14 -2
- data/lib/aws-sdk-core/plugins/credentials_configuration.rb +9 -3
- data/lib/aws-sdk-core/plugins/endpoint_pattern.rb +40 -32
- data/lib/aws-sdk-core/plugins/global_configuration.rb +8 -9
- data/lib/aws-sdk-core/plugins/http_checksum.rb +3 -8
- data/lib/aws-sdk-core/plugins/invocation_id.rb +1 -11
- data/lib/aws-sdk-core/plugins/logging.rb +2 -0
- data/lib/aws-sdk-core/plugins/protocols/api_gateway.rb +3 -1
- data/lib/aws-sdk-core/plugins/protocols/ec2.rb +2 -24
- data/lib/aws-sdk-core/plugins/protocols/json_rpc.rb +6 -8
- data/lib/aws-sdk-core/plugins/protocols/query.rb +4 -2
- data/lib/aws-sdk-core/plugins/protocols/rest_json.rb +3 -15
- data/lib/aws-sdk-core/plugins/protocols/rest_xml.rb +3 -0
- data/lib/aws-sdk-core/plugins/protocols/rpc_v2.rb +17 -0
- data/lib/aws-sdk-core/plugins/regional_endpoint.rb +162 -37
- data/lib/aws-sdk-core/plugins/request_compression.rb +226 -0
- data/lib/aws-sdk-core/plugins/retry_errors.rb +12 -3
- data/lib/aws-sdk-core/plugins/sign.rb +44 -17
- data/lib/aws-sdk-core/plugins/signature_v2.rb +2 -1
- data/lib/aws-sdk-core/plugins/signature_v4.rb +2 -1
- data/lib/aws-sdk-core/plugins/stub_responses.rb +53 -9
- data/lib/aws-sdk-core/plugins/telemetry.rb +75 -0
- data/lib/aws-sdk-core/plugins/transfer_encoding.rb +16 -9
- data/lib/aws-sdk-core/plugins/user_agent.rb +191 -14
- data/lib/aws-sdk-core/plugins.rb +39 -0
- data/lib/aws-sdk-core/process_credentials.rb +48 -29
- data/lib/aws-sdk-core/query/ec2_handler.rb +27 -0
- data/lib/aws-sdk-core/query/ec2_param_builder.rb +5 -7
- data/lib/aws-sdk-core/query/handler.rb +4 -4
- data/lib/aws-sdk-core/query/param_builder.rb +2 -2
- data/lib/aws-sdk-core/query.rb +2 -1
- data/lib/aws-sdk-core/refreshing_credentials.rb +12 -12
- data/lib/aws-sdk-core/resources.rb +8 -0
- data/lib/aws-sdk-core/rest/content_type_handler.rb +60 -0
- data/lib/aws-sdk-core/rest/handler.rb +3 -4
- data/lib/aws-sdk-core/rest/request/body.rb +32 -5
- data/lib/aws-sdk-core/rest/request/endpoint.rb +24 -4
- data/lib/aws-sdk-core/rest/request/headers.rb +15 -7
- data/lib/aws-sdk-core/rest/request/querystring_builder.rb +62 -36
- data/lib/aws-sdk-core/rest/response/body.rb +15 -1
- data/lib/aws-sdk-core/rest/response/header_list_parser.rb +79 -0
- data/lib/aws-sdk-core/rest/response/headers.rb +8 -3
- data/lib/aws-sdk-core/rest.rb +1 -0
- data/lib/aws-sdk-core/rpc_v2/builder.rb +62 -0
- data/lib/aws-sdk-core/rpc_v2/cbor_engine.rb +18 -0
- data/lib/aws-sdk-core/rpc_v2/content_type_handler.rb +47 -0
- data/lib/aws-sdk-core/rpc_v2/error_handler.rb +85 -0
- data/lib/aws-sdk-core/rpc_v2/handler.rb +79 -0
- data/lib/aws-sdk-core/rpc_v2/parser.rb +90 -0
- data/lib/aws-sdk-core/rpc_v2.rb +69 -0
- data/lib/aws-sdk-core/shared_config.rb +125 -39
- data/lib/aws-sdk-core/shared_credentials.rb +1 -7
- data/lib/aws-sdk-core/sso_credentials.rb +5 -2
- data/lib/aws-sdk-core/stubbing/protocols/ec2.rb +12 -11
- data/lib/aws-sdk-core/stubbing/protocols/json.rb +11 -10
- data/lib/aws-sdk-core/stubbing/protocols/query.rb +7 -6
- data/lib/aws-sdk-core/stubbing/protocols/rest.rb +2 -1
- data/lib/aws-sdk-core/stubbing/protocols/rest_json.rb +9 -8
- data/lib/aws-sdk-core/stubbing/protocols/rest_xml.rb +6 -5
- data/lib/aws-sdk-core/stubbing/protocols/rpc_v2.rb +39 -0
- data/lib/aws-sdk-core/stubbing/stub_data.rb +11 -0
- data/lib/aws-sdk-core/stubbing.rb +22 -0
- data/lib/aws-sdk-core/telemetry/base.rb +177 -0
- data/lib/aws-sdk-core/telemetry/no_op.rb +70 -0
- data/lib/aws-sdk-core/telemetry/otel.rb +235 -0
- data/lib/aws-sdk-core/telemetry/span_kind.rb +22 -0
- data/lib/aws-sdk-core/telemetry/span_status.rb +59 -0
- data/lib/aws-sdk-core/telemetry.rb +78 -0
- data/lib/aws-sdk-core/util.rb +39 -0
- data/lib/aws-sdk-core/waiters/poller.rb +12 -5
- data/lib/aws-sdk-core/xml/builder.rb +17 -9
- data/lib/aws-sdk-core/xml/error_handler.rb +32 -42
- data/lib/aws-sdk-core/xml/parser/frame.rb +4 -20
- data/lib/aws-sdk-core/xml/parser/{engines/oga.rb → oga_engine.rb} +2 -0
- data/lib/aws-sdk-core/xml/parser/stack.rb +2 -0
- data/lib/aws-sdk-core/xml/parser.rb +2 -6
- data/lib/aws-sdk-core.rb +82 -107
- data/lib/aws-sdk-sso/client.rb +185 -79
- data/lib/aws-sdk-sso/client_api.rb +7 -0
- data/lib/aws-sdk-sso/endpoint_parameters.rb +9 -6
- data/lib/aws-sdk-sso/endpoint_provider.rb +37 -96
- data/lib/aws-sdk-sso/endpoints.rb +3 -54
- data/lib/aws-sdk-sso/plugins/endpoints.rb +23 -22
- data/lib/aws-sdk-sso/types.rb +1 -0
- data/lib/aws-sdk-sso.rb +15 -11
- data/lib/aws-sdk-ssooidc/client.rb +592 -112
- data/lib/aws-sdk-ssooidc/client_api.rb +89 -1
- data/lib/aws-sdk-ssooidc/endpoint_parameters.rb +9 -6
- data/lib/aws-sdk-ssooidc/endpoint_provider.rb +37 -95
- data/lib/aws-sdk-ssooidc/endpoints.rb +3 -40
- data/lib/aws-sdk-ssooidc/errors.rb +52 -0
- data/lib/aws-sdk-ssooidc/plugins/endpoints.rb +23 -20
- data/lib/aws-sdk-ssooidc/types.rb +407 -53
- data/lib/aws-sdk-ssooidc.rb +15 -11
- data/lib/aws-sdk-sts/client.rb +516 -238
- data/lib/aws-sdk-sts/client_api.rb +48 -11
- data/lib/aws-sdk-sts/customizations.rb +5 -1
- data/lib/aws-sdk-sts/endpoint_parameters.rb +10 -9
- data/lib/aws-sdk-sts/endpoint_provider.rb +91 -213
- data/lib/aws-sdk-sts/endpoints.rb +3 -118
- data/lib/aws-sdk-sts/errors.rb +16 -0
- data/lib/aws-sdk-sts/plugins/endpoints.rb +23 -30
- data/lib/aws-sdk-sts/presigner.rb +1 -1
- data/lib/aws-sdk-sts/types.rb +217 -36
- data/lib/aws-sdk-sts.rb +15 -11
- data/lib/seahorse/client/async_base.rb +4 -5
- data/lib/seahorse/client/async_response.rb +19 -0
- data/lib/seahorse/client/base.rb +18 -21
- data/lib/seahorse/client/configuration.rb +0 -4
- data/lib/seahorse/client/h2/connection.rb +25 -31
- data/lib/seahorse/client/h2/handler.rb +14 -3
- data/lib/seahorse/client/handler.rb +1 -1
- data/lib/seahorse/client/http/response.rb +1 -1
- data/lib/seahorse/client/net_http/connection_pool.rb +13 -11
- data/lib/seahorse/client/net_http/handler.rb +21 -9
- data/lib/seahorse/client/net_http/patches.rb +1 -4
- data/lib/seahorse/client/networking_error.rb +1 -1
- data/lib/seahorse/client/plugin.rb +9 -0
- data/lib/seahorse/client/plugins/endpoint.rb +0 -1
- data/lib/seahorse/client/plugins/h2.rb +4 -4
- data/lib/seahorse/client/plugins/net_http.rb +57 -16
- data/lib/seahorse/client/plugins/request_callback.rb +31 -0
- data/lib/seahorse/client/request_context.rb +8 -1
- data/lib/seahorse/client/response.rb +8 -0
- data/lib/seahorse/model/operation.rb +3 -0
- data/lib/seahorse/model/shapes.rb +2 -2
- data/sig/aws-sdk-core/async_client_stubs.rbs +21 -0
- data/sig/aws-sdk-core/client_stubs.rbs +10 -0
- data/sig/aws-sdk-core/errors.rbs +22 -0
- data/sig/aws-sdk-core/resources/collection.rbs +21 -0
- data/sig/aws-sdk-core/structure.rbs +4 -0
- data/sig/aws-sdk-core/telemetry/base.rbs +46 -0
- data/sig/aws-sdk-core/telemetry/otel.rbs +22 -0
- data/sig/aws-sdk-core/telemetry/span_kind.rbs +15 -0
- data/sig/aws-sdk-core/telemetry/span_status.rbs +24 -0
- data/sig/aws-sdk-core/waiters/errors.rbs +20 -0
- data/sig/aws-sdk-core.rbs +7 -0
- data/sig/seahorse/client/async_base.rbs +18 -0
- data/sig/seahorse/client/base.rbs +25 -0
- data/sig/seahorse/client/handler_builder.rbs +16 -0
- data/sig/seahorse/client/response.rbs +61 -0
- metadata +92 -23
- /data/lib/aws-sdk-core/xml/parser/{engines/libxml.rb → libxml_engine.rb} +0 -0
- /data/lib/aws-sdk-core/xml/parser/{engines/nokogiri.rb → nokogiri_engine.rb} +0 -0
- /data/lib/aws-sdk-core/xml/parser/{engines/ox.rb → ox_engine.rb} +0 -0
- /data/lib/aws-sdk-core/xml/parser/{engines/rexml.rb → rexml_engine.rb} +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aws
|
|
4
|
+
module Plugins
|
|
5
|
+
# @api private
|
|
6
|
+
class RequestCompression < Seahorse::Client::Plugin
|
|
7
|
+
DEFAULT_MIN_COMPRESSION_SIZE = 10_240
|
|
8
|
+
MIN_COMPRESSION_SIZE_LIMIT = 10_485_760
|
|
9
|
+
SUPPORTED_ENCODINGS = %w[gzip].freeze
|
|
10
|
+
CHUNK_SIZE = 1 * 1024 * 1024 # one MB
|
|
11
|
+
|
|
12
|
+
option(
|
|
13
|
+
:disable_request_compression,
|
|
14
|
+
default: false,
|
|
15
|
+
doc_type: 'Boolean',
|
|
16
|
+
docstring: <<-DOCS) do |cfg|
|
|
17
|
+
When set to 'true' the request body will not be compressed
|
|
18
|
+
for supported operations.
|
|
19
|
+
DOCS
|
|
20
|
+
resolve_disable_request_compression(cfg)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
option(
|
|
24
|
+
:request_min_compression_size_bytes,
|
|
25
|
+
default: 10_240,
|
|
26
|
+
doc_type: 'Integer',
|
|
27
|
+
docstring: <<-DOCS) do |cfg|
|
|
28
|
+
The minimum size in bytes that triggers compression for request
|
|
29
|
+
bodies. The value must be non-negative integer value between 0
|
|
30
|
+
and 10485780 bytes inclusive.
|
|
31
|
+
DOCS
|
|
32
|
+
resolve_request_min_compression_size_bytes(cfg)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def after_initialize(client)
|
|
36
|
+
validate_disable_request_compression_input(client.config)
|
|
37
|
+
validate_request_min_compression_size_bytes_input(client.config)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def validate_disable_request_compression_input(cfg)
|
|
41
|
+
unless [true, false].include?(cfg.disable_request_compression)
|
|
42
|
+
raise ArgumentError,
|
|
43
|
+
'Must provide either `true` or `false` for the '\
|
|
44
|
+
'`disable_request_compression` configuration option.'
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def validate_request_min_compression_size_bytes_input(cfg)
|
|
49
|
+
value = Integer(cfg.request_min_compression_size_bytes)
|
|
50
|
+
unless value.between?(0, MIN_COMPRESSION_SIZE_LIMIT)
|
|
51
|
+
raise ArgumentError,
|
|
52
|
+
'Must provide a non-negative integer value between '\
|
|
53
|
+
'`0` and `10485760` bytes inclusive for the '\
|
|
54
|
+
'`request_min_compression_size_bytes` configuration option.'
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def add_handlers(handlers, _config)
|
|
59
|
+
# priority set to ensure compression happens BEFORE checksum
|
|
60
|
+
handlers.add(CompressionHandler, priority: 16, step: :build)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def resolve_disable_request_compression(cfg)
|
|
67
|
+
value = ENV['AWS_DISABLE_REQUEST_COMPRESSION'] ||
|
|
68
|
+
Aws.shared_config.disable_request_compression(profile: cfg.profile) ||
|
|
69
|
+
'false'
|
|
70
|
+
Aws::Util.str_2_bool(value)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def resolve_request_min_compression_size_bytes(cfg)
|
|
74
|
+
value = ENV['AWS_REQUEST_MIN_COMPRESSION_SIZE_BYTES'] ||
|
|
75
|
+
Aws.shared_config.request_min_compression_size_bytes(profile: cfg.profile) ||
|
|
76
|
+
DEFAULT_MIN_COMPRESSION_SIZE.to_s
|
|
77
|
+
Integer(value)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @api private
|
|
82
|
+
class CompressionHandler < Seahorse::Client::Handler
|
|
83
|
+
def call(context)
|
|
84
|
+
if should_compress?(context)
|
|
85
|
+
selected_encoding = request_encoding_selection(context)
|
|
86
|
+
if selected_encoding
|
|
87
|
+
if streaming?(context.operation.input)
|
|
88
|
+
process_streaming_compression(selected_encoding, context)
|
|
89
|
+
elsif context.http_request.body.size >= context.config.request_min_compression_size_bytes
|
|
90
|
+
process_compression(selected_encoding, context)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
with_metric(selected_encoding) { @handler.call(context) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def with_metric(encoding, &block)
|
|
100
|
+
case encoding
|
|
101
|
+
when 'gzip'
|
|
102
|
+
Aws::Plugins::UserAgent.metric('GZIP_REQUEST_COMPRESSION', &block)
|
|
103
|
+
else
|
|
104
|
+
block.call
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def request_encoding_selection(context)
|
|
109
|
+
encoding_list = context.operation.request_compression['encodings']
|
|
110
|
+
encoding_list.find { |encoding| RequestCompression::SUPPORTED_ENCODINGS.include?(encoding) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def update_content_encoding(encoding, context)
|
|
114
|
+
headers = context.http_request.headers
|
|
115
|
+
if headers['Content-Encoding']
|
|
116
|
+
headers['Content-Encoding'] += ", #{encoding}"
|
|
117
|
+
else
|
|
118
|
+
headers['Content-Encoding'] = encoding
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def should_compress?(context)
|
|
123
|
+
context.operation.request_compression &&
|
|
124
|
+
!context.config.disable_request_compression
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def streaming?(input)
|
|
128
|
+
if payload = input[:payload_member] # checking ref and shape
|
|
129
|
+
payload['streaming'] || payload.shape['streaming']
|
|
130
|
+
else
|
|
131
|
+
false
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def process_compression(encoding, context)
|
|
136
|
+
case encoding
|
|
137
|
+
when 'gzip'
|
|
138
|
+
gzip_compress(context)
|
|
139
|
+
else
|
|
140
|
+
raise StandardError, "We currently do not support #{encoding} encoding"
|
|
141
|
+
end
|
|
142
|
+
update_content_encoding(encoding, context)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def gzip_compress(context)
|
|
146
|
+
compressed = StringIO.new
|
|
147
|
+
compressed.binmode
|
|
148
|
+
gzip_writer = Zlib::GzipWriter.new(compressed)
|
|
149
|
+
if context.http_request.body.respond_to?(:read)
|
|
150
|
+
update_in_chunks(gzip_writer, context.http_request.body)
|
|
151
|
+
else
|
|
152
|
+
gzip_writer.write(context.http_request.body)
|
|
153
|
+
end
|
|
154
|
+
gzip_writer.close
|
|
155
|
+
new_body = StringIO.new(compressed.string)
|
|
156
|
+
context.http_request.body = new_body
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def update_in_chunks(compressor, io)
|
|
160
|
+
loop do
|
|
161
|
+
chunk = io.read(CHUNK_SIZE)
|
|
162
|
+
break unless chunk
|
|
163
|
+
|
|
164
|
+
compressor.write(chunk)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def process_streaming_compression(encoding, context)
|
|
169
|
+
case encoding
|
|
170
|
+
when 'gzip'
|
|
171
|
+
context.http_request.body = GzipIO.new(context.http_request.body)
|
|
172
|
+
else
|
|
173
|
+
raise StandardError, "We currently do not support #{encoding} encoding"
|
|
174
|
+
end
|
|
175
|
+
update_content_encoding(encoding, context)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @api private
|
|
179
|
+
class GzipIO
|
|
180
|
+
def initialize(body)
|
|
181
|
+
@body = body
|
|
182
|
+
@buffer = ChunkBuffer.new
|
|
183
|
+
@gzip_writer = Zlib::GzipWriter.new(@buffer)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def read(length, buff = nil)
|
|
187
|
+
if @gzip_writer.closed?
|
|
188
|
+
# an empty string to signify an end as
|
|
189
|
+
# there will be nothing remaining to be read
|
|
190
|
+
StringIO.new('').read(length, buff)
|
|
191
|
+
return
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
chunk = @body.read(length)
|
|
195
|
+
if !chunk || chunk.empty?
|
|
196
|
+
# closing the writer will write one last chunk
|
|
197
|
+
# with a trailer (to be read from the @buffer)
|
|
198
|
+
@gzip_writer.close
|
|
199
|
+
else
|
|
200
|
+
# flush happens first to ensure that header fields
|
|
201
|
+
# are being sent over since write will override
|
|
202
|
+
@gzip_writer.flush
|
|
203
|
+
@gzip_writer.write(chunk)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
StringIO.new(@buffer.last_chunk).read(length, buff)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# @api private
|
|
211
|
+
class ChunkBuffer
|
|
212
|
+
def initialize
|
|
213
|
+
@last_chunk = nil
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
attr_reader :last_chunk
|
|
217
|
+
|
|
218
|
+
def write(data)
|
|
219
|
+
@last_chunk = data
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
@@ -73,6 +73,7 @@ is only used in the `legacy` retry mode.
|
|
|
73
73
|
:retry_jitter,
|
|
74
74
|
default: :none,
|
|
75
75
|
doc_type: Symbol,
|
|
76
|
+
rbs_type: '(:none | :equal | :full | ^(Integer) -> Integer)',
|
|
76
77
|
docstring: <<-DOCS)
|
|
77
78
|
A delay randomiser function used by the default backoff function.
|
|
78
79
|
Some predefined functions can be referenced by name - :none, :equal, :full,
|
|
@@ -97,6 +98,7 @@ This option is only used in the `legacy` retry mode.
|
|
|
97
98
|
:retry_mode,
|
|
98
99
|
default: 'legacy',
|
|
99
100
|
doc_type: String,
|
|
101
|
+
rbs_type: '("legacy" | "standard" | "adaptive")',
|
|
100
102
|
docstring: <<-DOCS) do |cfg|
|
|
101
103
|
Specifies which retry algorithm to use. Values are:
|
|
102
104
|
|
|
@@ -111,7 +113,6 @@ Specifies which retry algorithm to use. Values are:
|
|
|
111
113
|
functionality of `standard` mode along with automatic client side
|
|
112
114
|
throttling. This is a provisional mode that may change behavior
|
|
113
115
|
in the future.
|
|
114
|
-
|
|
115
116
|
DOCS
|
|
116
117
|
resolve_retry_mode(cfg)
|
|
117
118
|
end
|
|
@@ -233,7 +234,7 @@ a clock skew correction and retry requests with skewed client clocks.
|
|
|
233
234
|
|
|
234
235
|
get_send_token(config)
|
|
235
236
|
add_retry_headers(context)
|
|
236
|
-
response = @handler.call(context)
|
|
237
|
+
response = with_metric(config.retry_mode) { @handler.call(context) }
|
|
237
238
|
error_inspector = Retries::ErrorInspector.new(
|
|
238
239
|
response.error, response.context.http_response.status_code
|
|
239
240
|
)
|
|
@@ -270,6 +271,10 @@ a clock skew correction and retry requests with skewed client clocks.
|
|
|
270
271
|
|
|
271
272
|
private
|
|
272
273
|
|
|
274
|
+
def with_metric(retry_mode, &block)
|
|
275
|
+
Aws::Plugins::UserAgent.metric("RETRY_MODE_#{retry_mode.upcase}", &block)
|
|
276
|
+
end
|
|
277
|
+
|
|
273
278
|
def get_send_token(config)
|
|
274
279
|
# either fail fast or block until a token becomes available
|
|
275
280
|
# must be configurable
|
|
@@ -357,7 +362,7 @@ a clock skew correction and retry requests with skewed client clocks.
|
|
|
357
362
|
class LegacyHandler < Seahorse::Client::Handler
|
|
358
363
|
|
|
359
364
|
def call(context)
|
|
360
|
-
response = @handler.call(context)
|
|
365
|
+
response = with_metric { @handler.call(context) }
|
|
361
366
|
if response.error
|
|
362
367
|
error_inspector = Retries::ErrorInspector.new(
|
|
363
368
|
response.error, response.context.http_response.status_code
|
|
@@ -376,6 +381,10 @@ a clock skew correction and retry requests with skewed client clocks.
|
|
|
376
381
|
|
|
377
382
|
private
|
|
378
383
|
|
|
384
|
+
def with_metric(&block)
|
|
385
|
+
Aws::Plugins::UserAgent.metric('RETRY_MODE_LEGACY', &block)
|
|
386
|
+
end
|
|
387
|
+
|
|
379
388
|
def retry_if_possible(response, error_inspector)
|
|
380
389
|
context = response.context
|
|
381
390
|
if should_retry?(context, error_inspector)
|
|
@@ -13,8 +13,7 @@ module Aws
|
|
|
13
13
|
option(:sigv4_region)
|
|
14
14
|
option(:unsigned_operations, default: [])
|
|
15
15
|
|
|
16
|
-
supported_auth_types = %w[sigv4 bearer none]
|
|
17
|
-
supported_auth_types += ['sigv4a'] if Aws::Sigv4::Signer.use_crt?
|
|
16
|
+
supported_auth_types = %w[sigv4 bearer sigv4-s3express sigv4a none]
|
|
18
17
|
SUPPORTED_AUTH_TYPES = supported_auth_types.freeze
|
|
19
18
|
|
|
20
19
|
def add_handlers(handlers, cfg)
|
|
@@ -24,10 +23,14 @@ module Aws
|
|
|
24
23
|
|
|
25
24
|
# @api private
|
|
26
25
|
# Return a signer with the `sign(context)` method
|
|
27
|
-
def self.signer_for(auth_scheme, config,
|
|
26
|
+
def self.signer_for(auth_scheme, config, sigv4_region_override = nil, sigv4_credentials_override = nil)
|
|
28
27
|
case auth_scheme['name']
|
|
29
|
-
when 'sigv4', 'sigv4a'
|
|
30
|
-
|
|
28
|
+
when 'sigv4', 'sigv4a', 'sigv4-s3express'
|
|
29
|
+
sigv4_overrides = {
|
|
30
|
+
region: sigv4_region_override,
|
|
31
|
+
credentials: sigv4_credentials_override
|
|
32
|
+
}
|
|
33
|
+
SignatureV4.new(auth_scheme, config, sigv4_overrides)
|
|
31
34
|
when 'bearer'
|
|
32
35
|
Bearer.new
|
|
33
36
|
else
|
|
@@ -38,19 +41,28 @@ module Aws
|
|
|
38
41
|
class Handler < Seahorse::Client::Handler
|
|
39
42
|
def call(context)
|
|
40
43
|
# Skip signing if using sigv2 signing from s3_signer in S3
|
|
44
|
+
credentials = nil
|
|
41
45
|
unless v2_signing?(context.config)
|
|
42
46
|
signer = Sign.signer_for(
|
|
43
47
|
context[:auth_scheme],
|
|
44
48
|
context.config,
|
|
45
|
-
context[:sigv4_region]
|
|
49
|
+
context[:sigv4_region],
|
|
50
|
+
context[:sigv4_credentials]
|
|
46
51
|
)
|
|
52
|
+
credentials = signer.credentials if signer.is_a?(SignatureV4)
|
|
47
53
|
signer.sign(context)
|
|
48
54
|
end
|
|
49
|
-
@handler.call(context)
|
|
55
|
+
with_metrics(credentials) { @handler.call(context) }
|
|
50
56
|
end
|
|
51
57
|
|
|
52
58
|
private
|
|
53
59
|
|
|
60
|
+
def with_metrics(credentials, &block)
|
|
61
|
+
return block.call unless credentials&.respond_to?(:metrics)
|
|
62
|
+
|
|
63
|
+
Aws::Plugins::UserAgent.metric(*credentials.metrics, &block)
|
|
64
|
+
end
|
|
65
|
+
|
|
54
66
|
def v2_signing?(config)
|
|
55
67
|
# 's3' is legacy signing, 'v4' is default
|
|
56
68
|
config.respond_to?(:signature_version) &&
|
|
@@ -88,27 +100,30 @@ module Aws
|
|
|
88
100
|
|
|
89
101
|
# @api private
|
|
90
102
|
class SignatureV4
|
|
91
|
-
|
|
103
|
+
attr_reader :signer
|
|
104
|
+
|
|
105
|
+
def initialize(auth_scheme, config, sigv4_overrides = {})
|
|
92
106
|
scheme_name = auth_scheme['name']
|
|
93
107
|
|
|
94
|
-
unless %w[sigv4 sigv4a].include?(scheme_name)
|
|
108
|
+
unless %w[sigv4 sigv4a sigv4-s3express].include?(scheme_name)
|
|
95
109
|
raise ArgumentError,
|
|
96
|
-
"Expected sigv4 or
|
|
110
|
+
"Expected sigv4, sigv4a, or sigv4-s3express auth scheme, got #{scheme_name}"
|
|
97
111
|
end
|
|
98
112
|
|
|
99
113
|
region = if scheme_name == 'sigv4a'
|
|
100
|
-
auth_scheme['signingRegionSet'].
|
|
114
|
+
auth_scheme['signingRegionSet'].join(',')
|
|
101
115
|
else
|
|
102
116
|
auth_scheme['signingRegion']
|
|
103
117
|
end
|
|
104
118
|
begin
|
|
105
|
-
@signer = Aws::Sigv4::Signer.new(
|
|
119
|
+
@signer = config.sigv4_signer || Aws::Sigv4::Signer.new(
|
|
106
120
|
service: config.sigv4_name || auth_scheme['signingName'],
|
|
107
|
-
region:
|
|
108
|
-
credentials_provider: config.credentials,
|
|
121
|
+
region: sigv4_overrides[:region] || config.sigv4_region || region,
|
|
122
|
+
credentials_provider: sigv4_overrides[:credentials] || config.credentials,
|
|
109
123
|
signing_algorithm: scheme_name.to_sym,
|
|
110
124
|
uri_escape_path: !!!auth_scheme['disableDoubleEncoding'],
|
|
111
|
-
|
|
125
|
+
normalize_path: !!!auth_scheme['disableNormalizePath'],
|
|
126
|
+
unsigned_headers: %w[content-length user-agent x-amzn-trace-id expect transfer-encoding connection]
|
|
112
127
|
)
|
|
113
128
|
rescue Aws::Sigv4::Errors::MissingCredentialsError
|
|
114
129
|
raise Aws::Errors::MissingCredentialsError
|
|
@@ -150,15 +165,27 @@ module Aws
|
|
|
150
165
|
@signer.sign_event(*args)
|
|
151
166
|
end
|
|
152
167
|
|
|
168
|
+
def credentials
|
|
169
|
+
@signer.credentials_provider
|
|
170
|
+
end
|
|
171
|
+
|
|
153
172
|
private
|
|
154
173
|
|
|
155
174
|
def apply_authtype(context, req)
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
# only used for event streaming at input
|
|
176
|
+
if context[:input_event_emitter]
|
|
177
|
+
req.headers['X-Amz-Content-Sha256'] = 'STREAMING-AWS4-HMAC-SHA256-EVENTS'
|
|
178
|
+
elsif unsigned_payload?(context, req)
|
|
158
179
|
req.headers['X-Amz-Content-Sha256'] ||= 'UNSIGNED-PAYLOAD'
|
|
159
180
|
end
|
|
160
181
|
end
|
|
161
182
|
|
|
183
|
+
def unsigned_payload?(context, req)
|
|
184
|
+
(context.operation['unsignedPayload'] ||
|
|
185
|
+
context.operation['authtype'] == 'v4-unsigned-body') &&
|
|
186
|
+
req.endpoint.scheme == 'https'
|
|
187
|
+
end
|
|
188
|
+
|
|
162
189
|
def reset_signature(req)
|
|
163
190
|
# in case this request is being re-signed
|
|
164
191
|
req.headers.delete('Authorization')
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
module Aws
|
|
4
4
|
module Plugins
|
|
5
5
|
# @api private
|
|
6
|
-
#
|
|
6
|
+
# Deprecated - does not look at new traits like `auth` and `unsignedPayload`
|
|
7
|
+
# Necessary to exist after endpoints 2.0 for old service clients + new core
|
|
7
8
|
class SignatureV2 < Seahorse::Client::Plugin
|
|
8
9
|
|
|
9
10
|
option(:v2_signer) do |cfg|
|
|
@@ -5,7 +5,8 @@ require 'aws-sigv4'
|
|
|
5
5
|
module Aws
|
|
6
6
|
module Plugins
|
|
7
7
|
# @api private
|
|
8
|
-
#
|
|
8
|
+
# Deprecated - does not look at new traits like `auth` and `unsignedPayload`
|
|
9
|
+
# Necessary to exist after endpoints 2.0 for old service clients + new core
|
|
9
10
|
class SignatureV4 < Seahorse::Client::Plugin
|
|
10
11
|
|
|
11
12
|
V4_AUTH = %w[v4 v4-unsigned-payload v4-unsigned-body]
|
|
@@ -8,6 +8,7 @@ module Aws
|
|
|
8
8
|
option(:stub_responses,
|
|
9
9
|
default: false,
|
|
10
10
|
doc_type: 'Boolean',
|
|
11
|
+
rbs_type: 'untyped',
|
|
11
12
|
docstring: <<-DOCS)
|
|
12
13
|
Causes the client to return stubbed responses. By default
|
|
13
14
|
fake responses are generated and returned. You can specify
|
|
@@ -28,8 +29,16 @@ requests are made, and retries are disabled.
|
|
|
28
29
|
end
|
|
29
30
|
end
|
|
30
31
|
|
|
32
|
+
option(:stubs) { {} }
|
|
33
|
+
option(:stubs_mutex) { Mutex.new }
|
|
34
|
+
option(:api_requests) { [] }
|
|
35
|
+
option(:api_requests_mutex) { Mutex.new }
|
|
36
|
+
|
|
31
37
|
def add_handlers(handlers, config)
|
|
32
|
-
|
|
38
|
+
return unless config.stub_responses
|
|
39
|
+
|
|
40
|
+
handlers.add(ApiRequestsHandler)
|
|
41
|
+
handlers.add(StubbingHandler, step: :send)
|
|
33
42
|
end
|
|
34
43
|
|
|
35
44
|
def after_initialize(client)
|
|
@@ -45,20 +54,43 @@ requests are made, and retries are disabled.
|
|
|
45
54
|
end
|
|
46
55
|
end
|
|
47
56
|
|
|
48
|
-
class
|
|
57
|
+
class ApiRequestsHandler < Seahorse::Client::Handler
|
|
58
|
+
def call(context)
|
|
59
|
+
context.config.api_requests_mutex.synchronize do
|
|
60
|
+
context.config.api_requests << {
|
|
61
|
+
operation_name: context.operation_name,
|
|
62
|
+
params: context.params,
|
|
63
|
+
context: context
|
|
64
|
+
}
|
|
65
|
+
end
|
|
66
|
+
@handler.call(context)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
49
69
|
|
|
70
|
+
class StubbingHandler < Seahorse::Client::Handler
|
|
50
71
|
def call(context)
|
|
51
|
-
|
|
72
|
+
span_wrapper(context) do
|
|
73
|
+
stub_responses(context)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def stub_responses(context)
|
|
52
80
|
resp = Seahorse::Client::Response.new(context: context)
|
|
53
81
|
async_mode = context.client.is_a? Seahorse::Client::AsyncBase
|
|
54
|
-
|
|
55
|
-
|
|
82
|
+
stub = context.client.next_stub(context)
|
|
83
|
+
stub[:mutex].synchronize { apply_stub(stub, resp, async_mode) }
|
|
84
|
+
|
|
85
|
+
if async_mode
|
|
86
|
+
Seahorse::Client::AsyncResponse.new(
|
|
87
|
+
context: context,
|
|
88
|
+
stream: context[:input_event_stream_handler].event_emitter.stream,
|
|
89
|
+
sync_queue: Queue.new
|
|
90
|
+
)
|
|
56
91
|
else
|
|
57
|
-
|
|
92
|
+
resp
|
|
58
93
|
end
|
|
59
|
-
|
|
60
|
-
async_mode ? Seahorse::Client::AsyncResponse.new(
|
|
61
|
-
context: context, stream: context[:input_event_stream_handler].event_emitter.stream, sync_queue: Queue.new) : resp
|
|
62
94
|
end
|
|
63
95
|
|
|
64
96
|
def apply_stub(stub, response, async_mode = false)
|
|
@@ -98,6 +130,18 @@ requests are made, and retries are disabled.
|
|
|
98
130
|
http_resp.signal_done
|
|
99
131
|
end
|
|
100
132
|
|
|
133
|
+
def span_wrapper(context, &block)
|
|
134
|
+
context.tracer.in_span(
|
|
135
|
+
'Handler.StubResponses',
|
|
136
|
+
attributes: Aws::Telemetry.http_request_attrs(context)
|
|
137
|
+
) do |span|
|
|
138
|
+
block.call.tap do
|
|
139
|
+
span.add_attributes(
|
|
140
|
+
Aws::Telemetry.http_response_attrs(context)
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
101
145
|
end
|
|
102
146
|
end
|
|
103
147
|
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aws
|
|
4
|
+
module Plugins
|
|
5
|
+
# @api private
|
|
6
|
+
class Telemetry < Seahorse::Client::Plugin
|
|
7
|
+
option(
|
|
8
|
+
:telemetry_provider,
|
|
9
|
+
default: Aws::Telemetry::NoOpTelemetryProvider,
|
|
10
|
+
doc_type: Aws::Telemetry::TelemetryProviderBase,
|
|
11
|
+
rbs_type: Aws::Telemetry::TelemetryProviderBase,
|
|
12
|
+
docstring: <<-DOCS) do |_cfg|
|
|
13
|
+
Allows you to provide a telemetry provider, which is used to
|
|
14
|
+
emit telemetry data. By default, uses `NoOpTelemetryProvider` which
|
|
15
|
+
will not record or emit any telemetry data. The SDK supports the
|
|
16
|
+
following telemetry providers:
|
|
17
|
+
|
|
18
|
+
* OpenTelemetry (OTel) - To use the OTel provider, install and require the
|
|
19
|
+
`opentelemetry-sdk` gem and then, pass in an instance of a
|
|
20
|
+
`Aws::Telemetry::OTelProvider` for telemetry provider.
|
|
21
|
+
DOCS
|
|
22
|
+
Aws::Telemetry::NoOpTelemetryProvider.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def after_initialize(client)
|
|
26
|
+
validate_telemetry_provider(client.config)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def validate_telemetry_provider(config)
|
|
30
|
+
unless config.telemetry_provider.is_a?(Aws::Telemetry::TelemetryProviderBase)
|
|
31
|
+
raise ArgumentError,
|
|
32
|
+
'Must provide a telemetry provider for the '\
|
|
33
|
+
'`telemetry_provider` configuration option.'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Handler < Seahorse::Client::Handler
|
|
38
|
+
def call(context)
|
|
39
|
+
span_wrapper(context) { @handler.call(context) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def span_wrapper(context, &block)
|
|
45
|
+
service_id = service_id(context)
|
|
46
|
+
attributes = {
|
|
47
|
+
'rpc.system' => 'aws-api',
|
|
48
|
+
'rpc.service' => service_id,
|
|
49
|
+
'rpc.method' => context.operation.name,
|
|
50
|
+
'code.function' => context.operation_name.to_s,
|
|
51
|
+
'code.namespace' => 'Aws::Plugins::Telemetry'
|
|
52
|
+
}
|
|
53
|
+
context.tracer.in_span(
|
|
54
|
+
parent_span_name(context, service_id),
|
|
55
|
+
attributes: attributes,
|
|
56
|
+
kind: Aws::Telemetry::SpanKind::CLIENT,
|
|
57
|
+
&block
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def service_id(context)
|
|
62
|
+
context.config.api.metadata['serviceId'] ||
|
|
63
|
+
context.config.api.metadata['serviceAbbreviation'] ||
|
|
64
|
+
context.config.api.metadata['serviceFullName']
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def parent_span_name(context, service_id)
|
|
68
|
+
"#{service_id}.#{context.operation.name}".delete(' ')
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
handler(Handler, step: :initialize, priority: 99)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -5,7 +5,8 @@ module Aws
|
|
|
5
5
|
|
|
6
6
|
# For Streaming Input Operations, when `requiresLength` is enabled
|
|
7
7
|
# checking whether `Content-Length` header can be set,
|
|
8
|
-
# for `v4-unsigned-body` operations,
|
|
8
|
+
# for `unsignedPayload` and `v4-unsigned-body` operations,
|
|
9
|
+
# set `Transfer-Encoding` header.
|
|
9
10
|
class TransferEncoding < Seahorse::Client::Plugin
|
|
10
11
|
|
|
11
12
|
# @api private
|
|
@@ -16,8 +17,8 @@ module Aws
|
|
|
16
17
|
unless context.http_request.body.respond_to?(:size)
|
|
17
18
|
if requires_length?(context.operation.input)
|
|
18
19
|
# if size of the IO is not available but required
|
|
19
|
-
raise Aws::Errors::MissingContentLength
|
|
20
|
-
elsif context.operation
|
|
20
|
+
raise Aws::Errors::MissingContentLength
|
|
21
|
+
elsif unsigned_payload?(context.operation)
|
|
21
22
|
context.http_request.headers['Transfer-Encoding'] = 'chunked'
|
|
22
23
|
end
|
|
23
24
|
end
|
|
@@ -29,18 +30,24 @@ module Aws
|
|
|
29
30
|
private
|
|
30
31
|
|
|
31
32
|
def streaming?(ref)
|
|
32
|
-
if payload = ref[:payload_member]
|
|
33
|
-
payload[
|
|
34
|
-
payload.shape["streaming"]
|
|
33
|
+
if (payload = ref[:payload_member])
|
|
34
|
+
payload['streaming'] || payload.shape['streaming']
|
|
35
35
|
else
|
|
36
36
|
false
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
def unsigned_payload?(operation)
|
|
41
|
+
operation['unsignedPayload'] ||
|
|
42
|
+
operation['authtype'] == 'v4-unsigned-body'
|
|
43
|
+
end
|
|
44
|
+
|
|
40
45
|
def requires_length?(ref)
|
|
41
|
-
payload = ref[:payload_member]
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
if (payload = ref[:payload_member])
|
|
47
|
+
payload['requiresLength'] || payload.shape['requiresLength']
|
|
48
|
+
else
|
|
49
|
+
false
|
|
50
|
+
end
|
|
44
51
|
end
|
|
45
52
|
|
|
46
53
|
end
|