mss-sdk 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/.yardopts +9 -0
- data/LICENSE.txt +0 -0
- data/README.md +192 -0
- data/bin/mss-rb +178 -0
- data/ca-bundle.crt +3554 -0
- data/lib/mss/core/async_handle.rb +89 -0
- data/lib/mss/core/cacheable.rb +76 -0
- data/lib/mss/core/client.rb +786 -0
- data/lib/mss/core/collection/simple.rb +81 -0
- data/lib/mss/core/collection/with_limit_and_next_token.rb +70 -0
- data/lib/mss/core/collection/with_next_token.rb +96 -0
- data/lib/mss/core/collection.rb +262 -0
- data/lib/mss/core/configuration.rb +527 -0
- data/lib/mss/core/credential_providers.rb +653 -0
- data/lib/mss/core/data.rb +251 -0
- data/lib/mss/core/deprecations.rb +83 -0
- data/lib/mss/core/endpoints.rb +36 -0
- data/lib/mss/core/http/connection_pool.rb +374 -0
- data/lib/mss/core/http/curb_handler.rb +150 -0
- data/lib/mss/core/http/handler.rb +88 -0
- data/lib/mss/core/http/net_http_handler.rb +144 -0
- data/lib/mss/core/http/patch.rb +98 -0
- data/lib/mss/core/http/request.rb +258 -0
- data/lib/mss/core/http/response.rb +80 -0
- data/lib/mss/core/indifferent_hash.rb +87 -0
- data/lib/mss/core/inflection.rb +55 -0
- data/lib/mss/core/ini_parser.rb +41 -0
- data/lib/mss/core/json_client.rb +46 -0
- data/lib/mss/core/json_parser.rb +75 -0
- data/lib/mss/core/json_request_builder.rb +34 -0
- data/lib/mss/core/json_response_parser.rb +78 -0
- data/lib/mss/core/lazy_error_classes.rb +107 -0
- data/lib/mss/core/log_formatter.rb +426 -0
- data/lib/mss/core/managed_file.rb +31 -0
- data/lib/mss/core/meta_utils.rb +44 -0
- data/lib/mss/core/model.rb +61 -0
- data/lib/mss/core/naming.rb +29 -0
- data/lib/mss/core/option_grammar.rb +737 -0
- data/lib/mss/core/options/json_serializer.rb +81 -0
- data/lib/mss/core/options/validator.rb +154 -0
- data/lib/mss/core/options/xml_serializer.rb +117 -0
- data/lib/mss/core/page_result.rb +74 -0
- data/lib/mss/core/policy.rb +938 -0
- data/lib/mss/core/query_client.rb +40 -0
- data/lib/mss/core/query_error_parser.rb +23 -0
- data/lib/mss/core/query_request_builder.rb +46 -0
- data/lib/mss/core/query_response_parser.rb +34 -0
- data/lib/mss/core/region.rb +84 -0
- data/lib/mss/core/region_collection.rb +79 -0
- data/lib/mss/core/resource.rb +412 -0
- data/lib/mss/core/resource_cache.rb +39 -0
- data/lib/mss/core/response.rb +214 -0
- data/lib/mss/core/response_cache.rb +49 -0
- data/lib/mss/core/rest_error_parser.rb +23 -0
- data/lib/mss/core/rest_json_client.rb +39 -0
- data/lib/mss/core/rest_request_builder.rb +153 -0
- data/lib/mss/core/rest_response_parser.rb +65 -0
- data/lib/mss/core/rest_xml_client.rb +46 -0
- data/lib/mss/core/service_interface.rb +82 -0
- data/lib/mss/core/signers/base.rb +45 -0
- data/lib/mss/core/signers/cloud_front.rb +55 -0
- data/lib/mss/core/signers/s3.rb +158 -0
- data/lib/mss/core/signers/version_2.rb +71 -0
- data/lib/mss/core/signers/version_3.rb +85 -0
- data/lib/mss/core/signers/version_3_https.rb +60 -0
- data/lib/mss/core/signers/version_4/chunk_signed_stream.rb +190 -0
- data/lib/mss/core/signers/version_4.rb +227 -0
- data/lib/mss/core/uri_escape.rb +43 -0
- data/lib/mss/core/xml/frame.rb +245 -0
- data/lib/mss/core/xml/frame_stack.rb +84 -0
- data/lib/mss/core/xml/grammar.rb +306 -0
- data/lib/mss/core/xml/parser.rb +69 -0
- data/lib/mss/core/xml/root_frame.rb +64 -0
- data/lib/mss/core/xml/sax_handlers/libxml.rb +46 -0
- data/lib/mss/core/xml/sax_handlers/nokogiri.rb +55 -0
- data/lib/mss/core/xml/sax_handlers/ox.rb +40 -0
- data/lib/mss/core/xml/sax_handlers/rexml.rb +46 -0
- data/lib/mss/core/xml/stub.rb +122 -0
- data/lib/mss/core.rb +602 -0
- data/lib/mss/errors.rb +161 -0
- data/lib/mss/rails.rb +194 -0
- data/lib/mss/s3/access_control_list.rb +262 -0
- data/lib/mss/s3/acl_object.rb +263 -0
- data/lib/mss/s3/acl_options.rb +200 -0
- data/lib/mss/s3/bucket.rb +757 -0
- data/lib/mss/s3/bucket_collection.rb +161 -0
- data/lib/mss/s3/bucket_lifecycle_configuration.rb +472 -0
- data/lib/mss/s3/bucket_region_cache.rb +51 -0
- data/lib/mss/s3/bucket_tag_collection.rb +110 -0
- data/lib/mss/s3/bucket_version_collection.rb +78 -0
- data/lib/mss/s3/cipher_io.rb +119 -0
- data/lib/mss/s3/client/xml.rb +265 -0
- data/lib/mss/s3/client.rb +2076 -0
- data/lib/mss/s3/config.rb +60 -0
- data/lib/mss/s3/cors_rule.rb +107 -0
- data/lib/mss/s3/cors_rule_collection.rb +193 -0
- data/lib/mss/s3/data_options.rb +190 -0
- data/lib/mss/s3/encryption_utils.rb +145 -0
- data/lib/mss/s3/errors.rb +93 -0
- data/lib/mss/s3/multipart_upload.rb +353 -0
- data/lib/mss/s3/multipart_upload_collection.rb +75 -0
- data/lib/mss/s3/object_collection.rb +355 -0
- data/lib/mss/s3/object_metadata.rb +102 -0
- data/lib/mss/s3/object_upload_collection.rb +76 -0
- data/lib/mss/s3/object_version.rb +153 -0
- data/lib/mss/s3/object_version_collection.rb +88 -0
- data/lib/mss/s3/paginated_collection.rb +74 -0
- data/lib/mss/s3/policy.rb +73 -0
- data/lib/mss/s3/prefix_and_delimiter_collection.rb +46 -0
- data/lib/mss/s3/prefixed_collection.rb +84 -0
- data/lib/mss/s3/presign_v4.rb +135 -0
- data/lib/mss/s3/presigned_post.rb +574 -0
- data/lib/mss/s3/region_detection.rb +75 -0
- data/lib/mss/s3/request.rb +61 -0
- data/lib/mss/s3/s3_object.rb +1795 -0
- data/lib/mss/s3/tree/branch_node.rb +67 -0
- data/lib/mss/s3/tree/child_collection.rb +103 -0
- data/lib/mss/s3/tree/leaf_node.rb +93 -0
- data/lib/mss/s3/tree/node.rb +21 -0
- data/lib/mss/s3/tree/parent.rb +86 -0
- data/lib/mss/s3/tree.rb +115 -0
- data/lib/mss/s3/uploaded_part.rb +81 -0
- data/lib/mss/s3/uploaded_part_collection.rb +83 -0
- data/lib/mss/s3/website_configuration.rb +101 -0
- data/lib/mss/s3.rb +161 -0
- data/lib/mss/version.rb +16 -0
- data/lib/mss-sdk.rb +2 -0
- data/lib/mss.rb +14 -0
- data/rails/init.rb +14 -0
- metadata +201 -0
@@ -0,0 +1,786 @@
|
|
1
|
+
# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
5
|
+
# the License is located at
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# or in the "license" file accompanying this file. This file is
|
9
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
10
|
+
# ANY KIND, either express or implied. See the License for the specific
|
11
|
+
# language governing permissions and limitations under the License.
|
12
|
+
|
13
|
+
require 'json'
|
14
|
+
require 'set'
|
15
|
+
require 'yaml'
|
16
|
+
require 'uri'
|
17
|
+
|
18
|
+
module MSS
|
19
|
+
module Core
|
20
|
+
|
21
|
+
# Base client class for all of the Amazon MSS service clients.
|
22
|
+
class Client
|
23
|
+
|
24
|
+
extend Deprecations
|
25
|
+
|
26
|
+
# Raised when a request failed due to a networking issue (e.g.
|
27
|
+
# EOFError, IOError, Errno::ECONNRESET, Errno::EPIPE,
|
28
|
+
# Timeout::Error, etc)
|
29
|
+
class NetworkError < StandardError; end
|
30
|
+
|
31
|
+
extend Naming
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
CACHEABLE_REQUESTS = Set[]
|
35
|
+
|
36
|
+
# Creates a new low-level client.
|
37
|
+
# @param [Hash] options
|
38
|
+
# @option options [Core::Configuration] :config (MSS.config)
|
39
|
+
# The base configuration object to use. All other options
|
40
|
+
# are merged with this. Defaults to the MSS.config.
|
41
|
+
# @option (see MSS.config)
|
42
|
+
def initialize options = {}
|
43
|
+
|
44
|
+
options = options.dup # so we don't modify the options passed in
|
45
|
+
|
46
|
+
@service_ruby_name = self.class.service_ruby_name
|
47
|
+
|
48
|
+
# translate these into service specific configuration options,
|
49
|
+
# e.g. :endpoint into :s3_endpoint
|
50
|
+
[:endpoint, :region, :port, :signature_version].each do |opt|
|
51
|
+
if options[opt]
|
52
|
+
options[:"#{service_ruby_name}_#{opt}"] = options.delete(opt)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
@config = (options.delete(:config) || MSS.config)
|
57
|
+
@config = @config.with(options)
|
58
|
+
|
59
|
+
@region = @config.send(:"#{service_ruby_name}_region")
|
60
|
+
|
61
|
+
@credential_provider = @config.credential_provider
|
62
|
+
@http_handler = @config.http_handler
|
63
|
+
@endpoint = config.send(:"#{service_ruby_name}_endpoint")
|
64
|
+
@port = config.send(:"#{service_ruby_name}_port")
|
65
|
+
|
66
|
+
# deprecated attributes
|
67
|
+
@http_read_timeout = @config.http_read_timeout
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Configuration] This clients configuration.
|
71
|
+
attr_reader :config
|
72
|
+
|
73
|
+
# @return [CredentialProviders::Provider] Returns the credential
|
74
|
+
# provider for this client.
|
75
|
+
# @api private
|
76
|
+
attr_reader :credential_provider
|
77
|
+
|
78
|
+
# @return [String] The snake-cased ruby name for the service
|
79
|
+
# (e.g. 's3', 'iam', 'dynamo_db', etc).
|
80
|
+
# @api private
|
81
|
+
attr_reader :service_ruby_name
|
82
|
+
|
83
|
+
# @return [Integer] What port this client makes requests via.
|
84
|
+
# @api private
|
85
|
+
attr_reader :port
|
86
|
+
|
87
|
+
# @return [Integer] The number of seconds before requests made by
|
88
|
+
# this client should timeout if they have not received a response.
|
89
|
+
# @api private
|
90
|
+
attr_reader :http_read_timeout
|
91
|
+
deprecated :http_read_timeout, :use => 'config.http_read_timeout'
|
92
|
+
|
93
|
+
# @return [String] Returns the service endpoint (hostname) this client
|
94
|
+
# makes requests against.
|
95
|
+
# @api private
|
96
|
+
attr_reader :endpoint
|
97
|
+
|
98
|
+
# @return (see Client.operations)
|
99
|
+
def operations
|
100
|
+
self.class.operations
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns a copy of the client with a different HTTP handler.
|
104
|
+
# You can pass an object like BuiltinHttpHandler or you can
|
105
|
+
# use a block; for example:
|
106
|
+
#
|
107
|
+
# s3_with_logging = s3.with_http_handler do |request, response|
|
108
|
+
# $stderr.puts request.inspect
|
109
|
+
# super(request, response)
|
110
|
+
# $stderr.puts response.inspect
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# The block executes in the context of an HttpHandler
|
114
|
+
# instance, and `super` delegates to the HTTP handler used by
|
115
|
+
# this client. This provides an easy way to spy on requests
|
116
|
+
# and responses. See HttpHandler, HttpRequest, and
|
117
|
+
# HttpResponse for more details on how to implement a fully
|
118
|
+
# functional HTTP handler using a different HTTP library than
|
119
|
+
# the one that ships with Ruby.
|
120
|
+
# @param handler (nil) A new http handler. Leave blank and pass a
|
121
|
+
# block to wrap the current handler with the block.
|
122
|
+
# @return [Core::Client] Returns a new instance of the client class with
|
123
|
+
# the modified or wrapped http handler.
|
124
|
+
def with_http_handler(handler = nil, &blk)
|
125
|
+
handler ||= Http::Handler.new(@http_handler, &blk)
|
126
|
+
with_options(:http_handler => handler)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns a new client with the passed configuration options
|
130
|
+
# merged with the current configuration options.
|
131
|
+
#
|
132
|
+
# no_retry_client = client.with_options(:max_retries => 0)
|
133
|
+
#
|
134
|
+
# @param [Hash] options
|
135
|
+
# @option (see MSS.config)
|
136
|
+
# @return [Client]
|
137
|
+
def with_options options
|
138
|
+
with_config(config.with(options))
|
139
|
+
end
|
140
|
+
|
141
|
+
# @param [Configuration] config The configuration object to use.
|
142
|
+
# @return [Core::Client] Returns a new client object with the given
|
143
|
+
# configuration.
|
144
|
+
# @api private
|
145
|
+
def with_config config
|
146
|
+
self.class.new(:config => config)
|
147
|
+
end
|
148
|
+
|
149
|
+
# The stub returned is memoized.
|
150
|
+
# @see new_stub_for
|
151
|
+
# @api private
|
152
|
+
def stub_for method_name
|
153
|
+
@stubs ||= {}
|
154
|
+
@stubs[method_name] ||= new_stub_for(method_name)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Primarily used for testing, this method returns an empty pseudo
|
158
|
+
# service response without making a request. Its used primarily for
|
159
|
+
# testing the lighter level service interfaces.
|
160
|
+
# @api private
|
161
|
+
def new_stub_for method_name
|
162
|
+
response = Response.new(Http::Request.new, Http::Response.new)
|
163
|
+
response.request_type = method_name
|
164
|
+
response.request_options = {}
|
165
|
+
send("simulate_#{method_name}_response", response)
|
166
|
+
response.signal_success
|
167
|
+
response
|
168
|
+
end
|
169
|
+
|
170
|
+
# Logs the warning to the configured logger, otherwise to stderr.
|
171
|
+
# @param [String] warning
|
172
|
+
# @return [nil]
|
173
|
+
def log_warning warning
|
174
|
+
message = '[mss-sdk-gem-warning] ' + warning
|
175
|
+
if config.logger
|
176
|
+
config.logger.warn(message)
|
177
|
+
else
|
178
|
+
$stderr.puts(message)
|
179
|
+
end
|
180
|
+
nil
|
181
|
+
end
|
182
|
+
|
183
|
+
# @api private
|
184
|
+
def inspect
|
185
|
+
"#<#{self.class.name}>"
|
186
|
+
end
|
187
|
+
|
188
|
+
# @api private
|
189
|
+
def to_yaml_properties
|
190
|
+
skip = %w(@config @credential_provider @http_handler)
|
191
|
+
instance_variables.map(&:to_s) - skip
|
192
|
+
end
|
193
|
+
|
194
|
+
protected
|
195
|
+
|
196
|
+
# @api private
|
197
|
+
def new_request
|
198
|
+
Http::Request.new
|
199
|
+
end
|
200
|
+
|
201
|
+
def new_response(*args, &block)
|
202
|
+
resp = Response.new(*args, &block)
|
203
|
+
resp.config = config
|
204
|
+
resp.api_version = self.class::API_VERSION
|
205
|
+
resp
|
206
|
+
end
|
207
|
+
|
208
|
+
def make_async_request response
|
209
|
+
|
210
|
+
pauses = async_request_with_retries(response, response.http_request)
|
211
|
+
|
212
|
+
response
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
def async_request_with_retries response, http_request, retry_delays = nil
|
217
|
+
|
218
|
+
response.http_response = Http::Response.new
|
219
|
+
|
220
|
+
handle = Object.new
|
221
|
+
handle.extend AsyncHandle
|
222
|
+
handle.on_complete do |status|
|
223
|
+
case status
|
224
|
+
when :failure
|
225
|
+
response.error = StandardError.new("failed to contact the service")
|
226
|
+
response.signal_failure
|
227
|
+
when :success
|
228
|
+
populate_error(response)
|
229
|
+
retry_delays ||= sleep_durations(response)
|
230
|
+
if should_retry?(response) and !retry_delays.empty?
|
231
|
+
rebuild_http_request(response)
|
232
|
+
@http_handler.sleep_with_callback(retry_delays.shift) do
|
233
|
+
async_request_with_retries(response, response.http_request, retry_delays)
|
234
|
+
end
|
235
|
+
else
|
236
|
+
response.error ?
|
237
|
+
response.signal_failure :
|
238
|
+
response.signal_success
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
@http_handler.handle_async(http_request, response.http_response, handle)
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
def make_sync_request response, &read_block
|
248
|
+
retry_server_errors do
|
249
|
+
|
250
|
+
response.http_response = Http::Response.new
|
251
|
+
|
252
|
+
@http_handler.handle(
|
253
|
+
response.http_request,
|
254
|
+
response.http_response,
|
255
|
+
&read_block)
|
256
|
+
|
257
|
+
if
|
258
|
+
block_given? and
|
259
|
+
response.http_response.status < 300 and
|
260
|
+
response.http_response.body
|
261
|
+
then
|
262
|
+
|
263
|
+
msg = ":http_handler read the entire http response body into "
|
264
|
+
msg << "memory, it should have instead yielded chunks"
|
265
|
+
log_warning(msg)
|
266
|
+
|
267
|
+
# go ahead and yield the body on behalf of the handler
|
268
|
+
yield(response.http_response.body)
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
populate_error(response)
|
273
|
+
response.signal_success unless response.error
|
274
|
+
response
|
275
|
+
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
def retry_server_errors &block
|
280
|
+
|
281
|
+
response = yield
|
282
|
+
|
283
|
+
sleeps = sleep_durations(response)
|
284
|
+
while should_retry?(response)
|
285
|
+
break if sleeps.empty?
|
286
|
+
Kernel.sleep(sleeps.shift)
|
287
|
+
rebuild_http_request(response)
|
288
|
+
response = yield
|
289
|
+
end
|
290
|
+
|
291
|
+
response
|
292
|
+
|
293
|
+
end
|
294
|
+
|
295
|
+
def rebuild_http_request response
|
296
|
+
credential_provider.refresh if expired_credentials?(response)
|
297
|
+
response.rebuild_request
|
298
|
+
if redirected?(response)
|
299
|
+
loc = URI.parse(response.http_response.headers['location'].first)
|
300
|
+
MSS::Core::MetaUtils.extend_method(response.http_request, :host) do
|
301
|
+
loc.host
|
302
|
+
end
|
303
|
+
response.http_request.host = loc.host
|
304
|
+
response.http_request.port = loc.port
|
305
|
+
response.http_request.uri = loc.path
|
306
|
+
end
|
307
|
+
response.retry_count += 1
|
308
|
+
end
|
309
|
+
|
310
|
+
def sleep_durations response
|
311
|
+
if expired_credentials?(response)
|
312
|
+
[0]
|
313
|
+
else
|
314
|
+
factor = scaling_factor(response)
|
315
|
+
Array.new(config.max_retries) {|n| (2 ** n) * factor }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def scaling_factor response
|
320
|
+
throttled?(response) ? (0.5 + Kernel.rand * 0.1) : 0.3
|
321
|
+
end
|
322
|
+
|
323
|
+
def should_retry? response
|
324
|
+
if retryable_error?(response)
|
325
|
+
response.safe_to_retry?
|
326
|
+
else
|
327
|
+
false
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def retryable_error? response
|
332
|
+
expired_credentials?(response) or
|
333
|
+
response.network_error? or
|
334
|
+
throttled?(response) or
|
335
|
+
redirected?(response) or
|
336
|
+
response.error.kind_of?(Errors::ServerError)
|
337
|
+
end
|
338
|
+
|
339
|
+
# @return [Boolean] Returns `true` if the response contains an
|
340
|
+
# error message that indicates credentials have expired.
|
341
|
+
def expired_credentials? response
|
342
|
+
response.error and
|
343
|
+
response.error.respond_to?(:code) and
|
344
|
+
(
|
345
|
+
response.error.code.to_s.match(/expired/i) or # session credentials
|
346
|
+
response.error.code == 'InvalidClientTokenId' or # query services
|
347
|
+
response.error.code == 'UnrecognizedClientException' or # json services
|
348
|
+
response.error.code == 'InvalidAccessKeyId' or # s3
|
349
|
+
response.error.code == 'AuthFailure' # ec2
|
350
|
+
)
|
351
|
+
end
|
352
|
+
|
353
|
+
def throttled? response
|
354
|
+
response.error and
|
355
|
+
response.error.respond_to?(:code) and
|
356
|
+
(
|
357
|
+
response.error.code.to_s.match(/throttl/i) or
|
358
|
+
#response.error.code == 'Throttling' or # most query services
|
359
|
+
#response.error.code == 'ThrottlingException' or # json services
|
360
|
+
#response.error.code == 'RequestThrottled' or # sqs
|
361
|
+
response.error.code == 'ProvisionedThroughputExceededException' or # ddb
|
362
|
+
response.error.code == 'RequestLimitExceeded' or # ec2
|
363
|
+
response.error.code == 'BandwidthLimitExceeded' # cloud search
|
364
|
+
)
|
365
|
+
end
|
366
|
+
|
367
|
+
def redirected? response
|
368
|
+
response.http_response.status == 307
|
369
|
+
end
|
370
|
+
|
371
|
+
def return_or_raise options, &block
|
372
|
+
response = yield
|
373
|
+
unless options[:async]
|
374
|
+
raise response.error if response.error
|
375
|
+
end
|
376
|
+
response
|
377
|
+
end
|
378
|
+
|
379
|
+
# Yields to the given block (which should be making a
|
380
|
+
# request and returning a {Response} object). The results of the
|
381
|
+
# request/response are logged.
|
382
|
+
#
|
383
|
+
# @param [Hash] options
|
384
|
+
# @option options [Boolean] :async
|
385
|
+
# @return [Response]
|
386
|
+
def log_client_request options, &block
|
387
|
+
|
388
|
+
# time the request, retries and all
|
389
|
+
start = Time.now
|
390
|
+
response = yield
|
391
|
+
response.duration = Time.now - start
|
392
|
+
|
393
|
+
if options[:async]
|
394
|
+
response.on_complete { log_response(response) }
|
395
|
+
else
|
396
|
+
log_response(response)
|
397
|
+
end
|
398
|
+
|
399
|
+
response
|
400
|
+
|
401
|
+
end
|
402
|
+
|
403
|
+
# Logs the response to the configured logger.
|
404
|
+
# @param [Response] response
|
405
|
+
# @return [nil]
|
406
|
+
def log_response response
|
407
|
+
if config.logger
|
408
|
+
message = config.log_formatter.format(response)
|
409
|
+
config.logger.send(config.log_level, message)
|
410
|
+
end
|
411
|
+
nil
|
412
|
+
end
|
413
|
+
|
414
|
+
def populate_error response
|
415
|
+
|
416
|
+
status = response.http_response.status
|
417
|
+
|
418
|
+
error_code, error_message = extract_error_details(response)
|
419
|
+
|
420
|
+
error_args = [
|
421
|
+
response.http_request,
|
422
|
+
response.http_response,
|
423
|
+
error_code,
|
424
|
+
error_message
|
425
|
+
]
|
426
|
+
|
427
|
+
response.error =
|
428
|
+
case
|
429
|
+
when response.network_error? then response.http_response.network_error
|
430
|
+
when error_code then error_class(error_code).new(*error_args)
|
431
|
+
when status >= 500 then Errors::ServerError.new(*error_args)
|
432
|
+
when status >= 300 then Errors::ClientError.new(*error_args)
|
433
|
+
else nil # no error
|
434
|
+
end
|
435
|
+
|
436
|
+
end
|
437
|
+
|
438
|
+
# Extracts the error code and error message from a response
|
439
|
+
# if it contains an error. Returns nil otherwise. Should be defined
|
440
|
+
# in sub-classes (e.g. QueryClient, RESTClient, etc).
|
441
|
+
# @param [Response] response
|
442
|
+
# @return [Array<Code,Message>,nil] Should return an array with an
|
443
|
+
# error code and message, or `nil`.
|
444
|
+
def extract_error_details response
|
445
|
+
raise NotImplementedError
|
446
|
+
end
|
447
|
+
|
448
|
+
# Given an error code string, this method will return an error class.
|
449
|
+
#
|
450
|
+
# MSS::EC2::Client.new.send(:error_code, 'InvalidInstanceId')
|
451
|
+
# #=> MSS::EC2::Errors::InvalidInstanceId
|
452
|
+
#
|
453
|
+
# @param [String] error_code The error code string as returned by
|
454
|
+
# the service. If this class contains periods, they will be
|
455
|
+
# converted into namespaces (e.g. 'Foo.Bar' becomes Errors::Foo::Bar).
|
456
|
+
#
|
457
|
+
# @return [Class]
|
458
|
+
#
|
459
|
+
def error_class error_code
|
460
|
+
errors_module.error_class(error_code)
|
461
|
+
end
|
462
|
+
|
463
|
+
# Returns the ::Errors module for the current client.
|
464
|
+
#
|
465
|
+
# MSS::S3::Client.new.errors_module
|
466
|
+
# #=> MSS::S3::Errors
|
467
|
+
#
|
468
|
+
# @return [Module]
|
469
|
+
#
|
470
|
+
def errors_module
|
471
|
+
MSS.const_get(self.class.to_s[/(\w+)::Client/, 1])::Errors
|
472
|
+
end
|
473
|
+
|
474
|
+
def client_request name, options, &read_block
|
475
|
+
return_or_raise(options) do
|
476
|
+
log_client_request(options) do
|
477
|
+
|
478
|
+
if config.stub_requests?
|
479
|
+
|
480
|
+
response = stub_for(name)
|
481
|
+
response.http_request = build_request(name, options)
|
482
|
+
response.request_options = options
|
483
|
+
response
|
484
|
+
|
485
|
+
else
|
486
|
+
|
487
|
+
client = self
|
488
|
+
|
489
|
+
response = new_response do
|
490
|
+
req = client.send(:build_request, name, options)
|
491
|
+
client.send(:sign_request, req)
|
492
|
+
req
|
493
|
+
end
|
494
|
+
|
495
|
+
response.request_type = name
|
496
|
+
response.request_options = options
|
497
|
+
|
498
|
+
if
|
499
|
+
cacheable_request?(name, options) and
|
500
|
+
cache = MSS.response_cache and
|
501
|
+
cached_response = cache.cached(response)
|
502
|
+
then
|
503
|
+
cached_response.cached = true
|
504
|
+
cached_response
|
505
|
+
else
|
506
|
+
|
507
|
+
# process the http request
|
508
|
+
options[:async] ?
|
509
|
+
make_async_request(response, &read_block) :
|
510
|
+
make_sync_request(response, &read_block)
|
511
|
+
|
512
|
+
# process the http response
|
513
|
+
response.on_success do
|
514
|
+
send("process_#{name}_response", response)
|
515
|
+
if cache = MSS.response_cache
|
516
|
+
cache.add(response)
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
# close files we opened
|
521
|
+
response.on_complete do
|
522
|
+
if response.http_request.body_stream.is_a?(ManagedFile)
|
523
|
+
response.http_request.body_stream.close
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
response
|
528
|
+
|
529
|
+
end
|
530
|
+
|
531
|
+
end
|
532
|
+
|
533
|
+
end
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
def cacheable_request? name, options
|
538
|
+
self.class::CACHEABLE_REQUESTS.include?(name)
|
539
|
+
end
|
540
|
+
|
541
|
+
def build_request name, options
|
542
|
+
|
543
|
+
# we dont want to pass the async option to the configure block
|
544
|
+
opts = options.dup
|
545
|
+
opts.delete(:async)
|
546
|
+
|
547
|
+
http_request = new_request
|
548
|
+
http_request.access_key_id = credential_provider.access_key_id
|
549
|
+
http_request.service = self.class.name.split('::')[1]
|
550
|
+
|
551
|
+
# configure the http request
|
552
|
+
http_request.service_ruby_name = service_ruby_name
|
553
|
+
http_request.host = endpoint
|
554
|
+
http_request.port = port
|
555
|
+
http_request.region = @region
|
556
|
+
http_request.use_ssl = config.use_ssl?
|
557
|
+
http_request.read_timeout = config.http_read_timeout
|
558
|
+
|
559
|
+
send("configure_#{name}_request", http_request, opts)
|
560
|
+
|
561
|
+
http_request.headers["user-agent"] = user_agent_string
|
562
|
+
|
563
|
+
if
|
564
|
+
@config.http_continue_threshold and
|
565
|
+
http_request.headers['content-length'] and
|
566
|
+
http_request.headers['content-length'].to_i > @config.http_continue_threshold
|
567
|
+
then
|
568
|
+
http_request.headers["expect"] = "100-continue"
|
569
|
+
http_request.continue_timeout = @config.http_continue_timeout
|
570
|
+
else
|
571
|
+
http_request.continue_timeout = nil
|
572
|
+
end
|
573
|
+
|
574
|
+
http_request
|
575
|
+
|
576
|
+
end
|
577
|
+
|
578
|
+
# @param [Http::Request] req
|
579
|
+
# @return [Http::Request]
|
580
|
+
# @api private
|
581
|
+
def sign_request req
|
582
|
+
req
|
583
|
+
end
|
584
|
+
|
585
|
+
def user_agent_string
|
586
|
+
engine = (RUBY_ENGINE rescue nil or "ruby")
|
587
|
+
user_agent = "%s mss-sdk-ruby/#{VERSION} %s/%s %s" %
|
588
|
+
[config.user_agent_prefix, engine, RUBY_VERSION, RUBY_PLATFORM]
|
589
|
+
user_agent.strip!
|
590
|
+
if MSS.memoizing?
|
591
|
+
user_agent << " memoizing"
|
592
|
+
end
|
593
|
+
user_agent
|
594
|
+
end
|
595
|
+
|
596
|
+
class << self
|
597
|
+
|
598
|
+
# @return [Array<Symbol>] Returns a list of service operations as
|
599
|
+
# method names supported by this client.
|
600
|
+
# @api private
|
601
|
+
def operations(options = {})
|
602
|
+
if name.match(/V\d{8}$/)
|
603
|
+
@operations ||= []
|
604
|
+
else
|
605
|
+
client_class(options).operations
|
606
|
+
end
|
607
|
+
end
|
608
|
+
|
609
|
+
# @api private
|
610
|
+
def request_builders
|
611
|
+
@request_builders ||= {}
|
612
|
+
end
|
613
|
+
|
614
|
+
# @api private
|
615
|
+
def response_parsers
|
616
|
+
@response_parsers ||= {}
|
617
|
+
end
|
618
|
+
|
619
|
+
# @api private
|
620
|
+
def new(*args, &block)
|
621
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
622
|
+
client = client_class(options).allocate
|
623
|
+
client.send(:initialize, *args, &block)
|
624
|
+
client
|
625
|
+
end
|
626
|
+
|
627
|
+
private
|
628
|
+
|
629
|
+
def client_class(options)
|
630
|
+
if name =~ /Client::V\d+$/
|
631
|
+
self
|
632
|
+
else
|
633
|
+
const_get("V#{client_api_version(options).gsub(/-/, '')}")
|
634
|
+
end
|
635
|
+
end
|
636
|
+
|
637
|
+
def client_api_version(options)
|
638
|
+
api_version = options[:api_version]
|
639
|
+
api_version ||= configured_version(options[:config]) if options[:config]
|
640
|
+
api_version ||= configured_version(MSS.config)
|
641
|
+
api_version || const_get(:API_VERSION)
|
642
|
+
end
|
643
|
+
|
644
|
+
def configured_version(config = MSS.config)
|
645
|
+
svc_opt = MSS::SERVICES[name.split('::')[1]].method_name
|
646
|
+
config.send(svc_opt)[:api_version]
|
647
|
+
end
|
648
|
+
|
649
|
+
protected
|
650
|
+
|
651
|
+
# Define this in sub-classes (e.g. QueryClient, RESTClient, etc)
|
652
|
+
def request_builder_for api_config, operation
|
653
|
+
raise NotImplementedError
|
654
|
+
end
|
655
|
+
|
656
|
+
# Define this in sub-classes (e.g. QueryClient, RESTClient, etc)
|
657
|
+
def response_parser_for api_config, operation
|
658
|
+
raise NotImplementedError
|
659
|
+
end
|
660
|
+
|
661
|
+
# Adds a single method to the current client class. This method
|
662
|
+
# yields a request method builder that allows you to specify how:
|
663
|
+
#
|
664
|
+
# * the request is built
|
665
|
+
# * the response is processed
|
666
|
+
# * the response is stubbed for testing
|
667
|
+
#
|
668
|
+
def add_client_request_method method_name, options = {}, &block
|
669
|
+
|
670
|
+
operations << method_name
|
671
|
+
|
672
|
+
ClientRequestMethodBuilder.new(self, method_name, &block)
|
673
|
+
|
674
|
+
method_def = <<-METHOD
|
675
|
+
def #{method_name}(*args, &block)
|
676
|
+
options = args.first ? args.first : {}
|
677
|
+
client_request(#{method_name.inspect}, options, &block)
|
678
|
+
end
|
679
|
+
METHOD
|
680
|
+
|
681
|
+
module_eval(method_def)
|
682
|
+
|
683
|
+
end
|
684
|
+
|
685
|
+
# Loads the API configuration for the given API version.
|
686
|
+
# @param [String] api_version The API version date string
|
687
|
+
# (e.g. '2012-01-05').
|
688
|
+
# @return [Hash]
|
689
|
+
def load_api_config api_version
|
690
|
+
lib = File.dirname(File.dirname(__FILE__))
|
691
|
+
path = "#{lib}/api_config/#{service_name}-#{api_version}.yml"
|
692
|
+
YAML.load(File.read(path))
|
693
|
+
end
|
694
|
+
|
695
|
+
# @param [Symbol] version
|
696
|
+
# @param [String,nil] service_signing_name Required for `:Version4`
|
697
|
+
# @api private
|
698
|
+
def signature_version version, service_signing_name = nil
|
699
|
+
define_method(:sign_request) do |req|
|
700
|
+
@signer ||= begin
|
701
|
+
signer_class = MSS::Core::Signers.const_get(version)
|
702
|
+
signer_args = (version == :Version4) ?
|
703
|
+
[credential_provider, service_signing_name, req.region] :
|
704
|
+
[credential_provider]
|
705
|
+
signer_class.new(*signer_args)
|
706
|
+
end
|
707
|
+
@signer.sign_request(req)
|
708
|
+
req
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
712
|
+
# Defines one method for each service operation described in
|
713
|
+
# the API configuration.
|
714
|
+
# @param [String] api_version
|
715
|
+
def define_client_methods api_version
|
716
|
+
|
717
|
+
const_set(:API_VERSION, api_version)
|
718
|
+
|
719
|
+
api_config = load_api_config(api_version)
|
720
|
+
|
721
|
+
api_config[:operations].each do |operation|
|
722
|
+
|
723
|
+
builder = request_builder_for(api_config, operation)
|
724
|
+
parser = response_parser_for(api_config, operation)
|
725
|
+
|
726
|
+
define_client_method(operation[:method], builder, parser)
|
727
|
+
|
728
|
+
end
|
729
|
+
end
|
730
|
+
|
731
|
+
def define_client_method method_name, builder, parser
|
732
|
+
|
733
|
+
request_builders[method_name] = builder
|
734
|
+
response_parsers[method_name] = parser
|
735
|
+
|
736
|
+
add_client_request_method(method_name) do
|
737
|
+
|
738
|
+
configure_request do |request, request_options|
|
739
|
+
builder.populate_request(request, request_options)
|
740
|
+
end
|
741
|
+
|
742
|
+
process_response do |response|
|
743
|
+
response.data = parser.extract_data(response)
|
744
|
+
end
|
745
|
+
|
746
|
+
simulate_response do |response|
|
747
|
+
response.data = parser.simulate
|
748
|
+
end
|
749
|
+
|
750
|
+
end
|
751
|
+
end
|
752
|
+
|
753
|
+
end
|
754
|
+
|
755
|
+
# @api private
|
756
|
+
class ClientRequestMethodBuilder
|
757
|
+
|
758
|
+
def initialize client_class, method_name, &block
|
759
|
+
@client_class = client_class
|
760
|
+
@method_name = method_name
|
761
|
+
configure_request {|request, options|}
|
762
|
+
process_response {|response|}
|
763
|
+
simulate_response {|response|}
|
764
|
+
instance_eval(&block)
|
765
|
+
end
|
766
|
+
|
767
|
+
def configure_request options = {}, &block
|
768
|
+
name = "configure_#{@method_name}_request"
|
769
|
+
MetaUtils.class_extend_method(@client_class, name, &block)
|
770
|
+
end
|
771
|
+
|
772
|
+
def process_response &block
|
773
|
+
name = "process_#{@method_name}_response"
|
774
|
+
MetaUtils.class_extend_method(@client_class, name, &block)
|
775
|
+
end
|
776
|
+
|
777
|
+
def simulate_response &block
|
778
|
+
name = "simulate_#{@method_name}_response"
|
779
|
+
MetaUtils.class_extend_method(@client_class, name, &block)
|
780
|
+
end
|
781
|
+
|
782
|
+
end
|
783
|
+
|
784
|
+
end
|
785
|
+
end
|
786
|
+
end
|