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.
Files changed (131) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +9 -0
  3. data/LICENSE.txt +0 -0
  4. data/README.md +192 -0
  5. data/bin/mss-rb +178 -0
  6. data/ca-bundle.crt +3554 -0
  7. data/lib/mss/core/async_handle.rb +89 -0
  8. data/lib/mss/core/cacheable.rb +76 -0
  9. data/lib/mss/core/client.rb +786 -0
  10. data/lib/mss/core/collection/simple.rb +81 -0
  11. data/lib/mss/core/collection/with_limit_and_next_token.rb +70 -0
  12. data/lib/mss/core/collection/with_next_token.rb +96 -0
  13. data/lib/mss/core/collection.rb +262 -0
  14. data/lib/mss/core/configuration.rb +527 -0
  15. data/lib/mss/core/credential_providers.rb +653 -0
  16. data/lib/mss/core/data.rb +251 -0
  17. data/lib/mss/core/deprecations.rb +83 -0
  18. data/lib/mss/core/endpoints.rb +36 -0
  19. data/lib/mss/core/http/connection_pool.rb +374 -0
  20. data/lib/mss/core/http/curb_handler.rb +150 -0
  21. data/lib/mss/core/http/handler.rb +88 -0
  22. data/lib/mss/core/http/net_http_handler.rb +144 -0
  23. data/lib/mss/core/http/patch.rb +98 -0
  24. data/lib/mss/core/http/request.rb +258 -0
  25. data/lib/mss/core/http/response.rb +80 -0
  26. data/lib/mss/core/indifferent_hash.rb +87 -0
  27. data/lib/mss/core/inflection.rb +55 -0
  28. data/lib/mss/core/ini_parser.rb +41 -0
  29. data/lib/mss/core/json_client.rb +46 -0
  30. data/lib/mss/core/json_parser.rb +75 -0
  31. data/lib/mss/core/json_request_builder.rb +34 -0
  32. data/lib/mss/core/json_response_parser.rb +78 -0
  33. data/lib/mss/core/lazy_error_classes.rb +107 -0
  34. data/lib/mss/core/log_formatter.rb +426 -0
  35. data/lib/mss/core/managed_file.rb +31 -0
  36. data/lib/mss/core/meta_utils.rb +44 -0
  37. data/lib/mss/core/model.rb +61 -0
  38. data/lib/mss/core/naming.rb +29 -0
  39. data/lib/mss/core/option_grammar.rb +737 -0
  40. data/lib/mss/core/options/json_serializer.rb +81 -0
  41. data/lib/mss/core/options/validator.rb +154 -0
  42. data/lib/mss/core/options/xml_serializer.rb +117 -0
  43. data/lib/mss/core/page_result.rb +74 -0
  44. data/lib/mss/core/policy.rb +938 -0
  45. data/lib/mss/core/query_client.rb +40 -0
  46. data/lib/mss/core/query_error_parser.rb +23 -0
  47. data/lib/mss/core/query_request_builder.rb +46 -0
  48. data/lib/mss/core/query_response_parser.rb +34 -0
  49. data/lib/mss/core/region.rb +84 -0
  50. data/lib/mss/core/region_collection.rb +79 -0
  51. data/lib/mss/core/resource.rb +412 -0
  52. data/lib/mss/core/resource_cache.rb +39 -0
  53. data/lib/mss/core/response.rb +214 -0
  54. data/lib/mss/core/response_cache.rb +49 -0
  55. data/lib/mss/core/rest_error_parser.rb +23 -0
  56. data/lib/mss/core/rest_json_client.rb +39 -0
  57. data/lib/mss/core/rest_request_builder.rb +153 -0
  58. data/lib/mss/core/rest_response_parser.rb +65 -0
  59. data/lib/mss/core/rest_xml_client.rb +46 -0
  60. data/lib/mss/core/service_interface.rb +82 -0
  61. data/lib/mss/core/signers/base.rb +45 -0
  62. data/lib/mss/core/signers/cloud_front.rb +55 -0
  63. data/lib/mss/core/signers/s3.rb +158 -0
  64. data/lib/mss/core/signers/version_2.rb +71 -0
  65. data/lib/mss/core/signers/version_3.rb +85 -0
  66. data/lib/mss/core/signers/version_3_https.rb +60 -0
  67. data/lib/mss/core/signers/version_4/chunk_signed_stream.rb +190 -0
  68. data/lib/mss/core/signers/version_4.rb +227 -0
  69. data/lib/mss/core/uri_escape.rb +43 -0
  70. data/lib/mss/core/xml/frame.rb +245 -0
  71. data/lib/mss/core/xml/frame_stack.rb +84 -0
  72. data/lib/mss/core/xml/grammar.rb +306 -0
  73. data/lib/mss/core/xml/parser.rb +69 -0
  74. data/lib/mss/core/xml/root_frame.rb +64 -0
  75. data/lib/mss/core/xml/sax_handlers/libxml.rb +46 -0
  76. data/lib/mss/core/xml/sax_handlers/nokogiri.rb +55 -0
  77. data/lib/mss/core/xml/sax_handlers/ox.rb +40 -0
  78. data/lib/mss/core/xml/sax_handlers/rexml.rb +46 -0
  79. data/lib/mss/core/xml/stub.rb +122 -0
  80. data/lib/mss/core.rb +602 -0
  81. data/lib/mss/errors.rb +161 -0
  82. data/lib/mss/rails.rb +194 -0
  83. data/lib/mss/s3/access_control_list.rb +262 -0
  84. data/lib/mss/s3/acl_object.rb +263 -0
  85. data/lib/mss/s3/acl_options.rb +200 -0
  86. data/lib/mss/s3/bucket.rb +757 -0
  87. data/lib/mss/s3/bucket_collection.rb +161 -0
  88. data/lib/mss/s3/bucket_lifecycle_configuration.rb +472 -0
  89. data/lib/mss/s3/bucket_region_cache.rb +51 -0
  90. data/lib/mss/s3/bucket_tag_collection.rb +110 -0
  91. data/lib/mss/s3/bucket_version_collection.rb +78 -0
  92. data/lib/mss/s3/cipher_io.rb +119 -0
  93. data/lib/mss/s3/client/xml.rb +265 -0
  94. data/lib/mss/s3/client.rb +2076 -0
  95. data/lib/mss/s3/config.rb +60 -0
  96. data/lib/mss/s3/cors_rule.rb +107 -0
  97. data/lib/mss/s3/cors_rule_collection.rb +193 -0
  98. data/lib/mss/s3/data_options.rb +190 -0
  99. data/lib/mss/s3/encryption_utils.rb +145 -0
  100. data/lib/mss/s3/errors.rb +93 -0
  101. data/lib/mss/s3/multipart_upload.rb +353 -0
  102. data/lib/mss/s3/multipart_upload_collection.rb +75 -0
  103. data/lib/mss/s3/object_collection.rb +355 -0
  104. data/lib/mss/s3/object_metadata.rb +102 -0
  105. data/lib/mss/s3/object_upload_collection.rb +76 -0
  106. data/lib/mss/s3/object_version.rb +153 -0
  107. data/lib/mss/s3/object_version_collection.rb +88 -0
  108. data/lib/mss/s3/paginated_collection.rb +74 -0
  109. data/lib/mss/s3/policy.rb +73 -0
  110. data/lib/mss/s3/prefix_and_delimiter_collection.rb +46 -0
  111. data/lib/mss/s3/prefixed_collection.rb +84 -0
  112. data/lib/mss/s3/presign_v4.rb +135 -0
  113. data/lib/mss/s3/presigned_post.rb +574 -0
  114. data/lib/mss/s3/region_detection.rb +75 -0
  115. data/lib/mss/s3/request.rb +61 -0
  116. data/lib/mss/s3/s3_object.rb +1795 -0
  117. data/lib/mss/s3/tree/branch_node.rb +67 -0
  118. data/lib/mss/s3/tree/child_collection.rb +103 -0
  119. data/lib/mss/s3/tree/leaf_node.rb +93 -0
  120. data/lib/mss/s3/tree/node.rb +21 -0
  121. data/lib/mss/s3/tree/parent.rb +86 -0
  122. data/lib/mss/s3/tree.rb +115 -0
  123. data/lib/mss/s3/uploaded_part.rb +81 -0
  124. data/lib/mss/s3/uploaded_part_collection.rb +83 -0
  125. data/lib/mss/s3/website_configuration.rb +101 -0
  126. data/lib/mss/s3.rb +161 -0
  127. data/lib/mss/version.rb +16 -0
  128. data/lib/mss-sdk.rb +2 -0
  129. data/lib/mss.rb +14 -0
  130. data/rails/init.rb +14 -0
  131. 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