Class: Amazon::AWS::Search::Request

Inherits:
Object
  • Object
show all
Includes:
REXML
Defined in:
lib/ruby-paa/aws/search.rb

Direct Known Subclasses

Amazon::AWS::ShoppingCart::Cart

Defined Under Namespace

Classes: AccessKeyIdError, LocaleError

Constant Summary collapse

DIGEST_SUPPORT =

Do we have support for the SHA-256 Secure Hash Algorithm?

Note that Module#constants returns Strings in Ruby 1.8 and Symbols in 1.9.

OpenSSL::Digest.constants.include?( 'SHA256' ) || OpenSSL::Digest.constants.include?( :SHA256 )
DIGEST =

Requests are authenticated using the SHA-256 Secure Hash Algorithm.

OpenSSL::Digest::Digest.new( 'sha256' )

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key_id = nil, associate = nil, locale = nil, cache = nil, user_agent = USER_AGENT) ⇒ Request

This method is used to generate an AWS search request object.

key_id is your AWS access key ID. Note that your secret key, used for signing requests, can be specified only in your ~/.amazonrc configuration file.

associate is your Associates tag (if any), locale is the locale in which you which to work (us for amazon.com, uk for amazon.co.uk, etc.), cache is whether or not you wish to utilise a response cache, and user_agent is the client name to pass when performing calls to AWS. By default, user_agent will be set to a string identifying the Ruby/AWS library and its version number.

locale and cache can also be set later, if you wish to change the current behaviour.

Example:

req = Request.new( '0Y44V8FAFNM119CX4TR2', 'calibanorg-20' )


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/ruby-paa/aws/search.rb', line 70

def initialize(key_id=nil, associate=nil, locale=nil, cache=nil, user_agent=USER_AGENT)

   	  @config ||= Amazon::Config.new

   	  def_locale = locale
   	  locale = 'us' unless locale
   	  locale.downcase!

   	  key_id ||= @config['key_id']
   	  cache = @config['cache'] if cache.nil?

   	  # Take locale from config file if no locale was passed to method.
   	  #
   	  if @config.key?( 'locale' ) && ! def_locale
   	    locale = @config['locale']
   	  end
   	  validate_locale( locale )

   	  if key_id.nil?
   	    raise AccessKeyIdError, 'key_id may not be nil'
   	  end

   	  @key_id     = key_id
   	  @tag	      = associate || @config['associate'] || DEF_ASSOC[locale]
   	  @user_agent = user_agent
   	  @cache      = unless cache == 'false' || cache == false
		  Amazon::AWS::Cache.new( @config['cache_dir'] )
		else
		  nil
		end

  # Set the following two variables from the config file. Will be
  # *nil* if not present in config file.
  #
  @api	      = @config['api']
  @encoding   = @config['encoding']

  self.locale = locale
end

Instance Attribute Details

#cacheObject

If @cache has simply been assigned true at some point in time, assign a proper cache object to it when it is referenced. Otherwise, just return its value.



145
146
147
148
149
150
151
# File 'lib/ruby-paa/aws/search.rb', line 145

def cache  # :nodoc:
  if @cache == true
    @cache = Amazon::AWS::Cache.new( @config['cache_dir'] )
  else
    @cache
  end
end

#configObject (readonly)

Returns the value of attribute config.



42
43
44
# File 'lib/ruby-paa/aws/search.rb', line 42

def config
  @config
end

#connObject (readonly)

Returns the value of attribute conn.



42
43
44
# File 'lib/ruby-paa/aws/search.rb', line 42

def conn
  @conn
end

#encodingObject

Returns the value of attribute encoding.



44
45
46
# File 'lib/ruby-paa/aws/search.rb', line 44

def encoding
  @encoding
end

#localeObject

Returns the value of attribute locale.



42
43
44
# File 'lib/ruby-paa/aws/search.rb', line 42

def locale
  @locale
end

#queryObject (readonly)

Returns the value of attribute query.



42
43
44
# File 'lib/ruby-paa/aws/search.rb', line 42

def query
  @query
end

#user_agentObject (readonly)

Returns the value of attribute user_agent.



42
43
44
# File 'lib/ruby-paa/aws/search.rb', line 42

def user_agent
  @user_agent
end

Instance Method Details

#reconnectObject

Reconnect to the server if our connection has been lost (due to a time-out, etc.).



185
186
187
188
# File 'lib/ruby-paa/aws/search.rb', line 185

def reconnect  # :nodoc:
  connect( self.locale )
  self
end

#search(operation, nr_pages = 1) ⇒ Object

Perform a search of the AWS database, returning an AWSObject.

operation is an object of a subclass of Operation, such as ItemSearch, ItemLookup, etc. It may also be a MultipleOperation object.

In versions of Ruby/AWS up to prior to 0.8.0, the second parameter to this method was response_group. This way of passing response groups has been deprecated since 0.7.0 and completely removed in 0.8.0. To pair a set of response groups with an operation, assign directly to the operation’s @response_group attribute.

nr_pages is the number of results pages to return. It defaults to 1. If a higher number is given, pages 1 to nr_pages will be returned. If the special value :ALL_PAGES is given, all results pages will be returned.

Note that ItemLookup operations can use several different pagination parameters. An ItemLookup will typically return just one results page containing a single product, but :ALL_PAGES can still be used to apply the OfferPage parameter to paginate through multiple pages of offers.

Similarly, a single product may have multiple pages of reviews available. In such a case, it is up to the user to manually supply the ReviewPage parameter and an appropriate value.

In the same vein, variations can be returned by using the VariationPage parameter.

The pagination parameters supported by each type of operation, together with the maximum page number that can be retrieved for each type of data, are # documented in the AWS Developer’s Guide:

docs.amazonwebservices.com/AWSECommerceService/2009-11-01/DG/index.html?MaximumNumberofPages.html

The pagination parameter used by :ALL_PAGES can be looked up in the Amazon::AWS::PAGINATION hash.

If operation is of class MultipleOperation, the operations encapsulated within will return only the first page of results, regardless of whether a higher number of pages is requested.

If a block is passed to this method, each successive page of results will be yielded to the block.



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/ruby-paa/aws/search.rb', line 291

def search(operation, nr_pages=1)
  parameters = Amazon::AWS::SERVICE.
		 merge( { 'AWSAccessKeyId' => @key_id,
			  'AssociateTag'   => @tag } ).
		 merge( operation.query_parameters )

  if nr_pages.is_a? Amazon::AWS::ResponseGroup
    raise ObsolescenceError, 'Request#search method no longer accepts response_group parameter.'
  end

  # Pre-0.8.0 user code may have passed *nil* as the second parameter,
  # in order to use the @response_group of the operation.
  #
  nr_pages ||= 1

  # Check to see whether a particular version of the API has been
  # requested. If so, overwrite Version with the new value.
  #
  parameters.merge!( { 'Version' => @api } ) if @api

  @query = Amazon::AWS.assemble_query( parameters, @encoding )
  page = Amazon::AWS.get_page( self )

  # Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
  #
  page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'

  doc = Document.new( page )

  # Some errors occur at the very top level of the XML. For example,
  # when no Operation parameter is given. This should not be possible
  # with user code, but occurred during debugging of this library.
  #
  error_check( doc )

  # Another possible error results in a document containing nothing
  # but <Result>Internal Error</Result>. This occurs when a specific
  # version of the AWS API is requested, in combination with an
  # operation that did not yet exist in that version of the API.
  #
  # For example:
  #
  # http://ecs.amazonaws.com/onca/xml?AWSAccessKeyId=foo&Operation=VehicleSearch&Year=2008&ResponseGroup=VehicleMakes&Service=AWSECommerceService&Version=2008-03-03
  #
  if xml = doc.elements['Result']
    raise Amazon::AWS::Error::AWSError, xml.text
  end

  # Fundamental errors happen at the OperationRequest level. For
  # example, if an invalid AWSAccessKeyId is used.
  #
  error_check( doc.elements['*/OperationRequest'] )

  # Check for parameter and value errors deeper down, inside Request.
  #
  if operation.kind == 'MultipleOperation'

    # Everything is a level deeper, because of the
    # <MultiOperationResponse> container.
    #
    # Check for errors in the first operation.
    #
    error_check( doc.elements['*/*/*/Request'] )

    # Check for errors in the second operation.
    #
    error_check( doc.elements['*/*[3]/*/Request'] )

    # If second operation is batched, check for errors in its 2nd set
    # of results.
    #
    if batched = doc.elements['*/*[3]/*[2]/Request']
      error_check( batched )
    end
  else
    error_check( doc.elements['*/*/Request'] )

    # If operation is batched, check for errors in its 2nd set of
    # results.
    #
    if batched = doc.elements['*/*[3]/Request']
      error_check( batched )
    end
  end

  if doc.elements['*/*[2]/TotalPages']
    total_pages = doc.elements['*/*[2]/TotalPages'].text.to_i

  # FIXME: ListLookup and MultipleOperation (and possibly others) have
  # TotalPages nested one level deeper. I should take some time to
  # ensure that all operations that can return multiple results pages
  # are covered by either the 'if' above or the 'elsif' here.
  #
  elsif doc.elements['*/*[2]/*[2]/TotalPages']
    total_pages = doc.elements['*/*[2]/*[2]/TotalPages'].text.to_i
  else
    total_pages = 1
  end

  # Create a root AWS object and walk the XML response tree.
  #
  aws = AWS::AWSObject.new( operation )
  aws.walk( doc )
  result = aws

  # If only one page has been requested or only one page is available,
  # we can stop here. First yield to the block, if given.
  #
  if nr_pages == 1 || ( tp = total_pages ) == 1
     yield result if block_given?
     return result
  end

  # Limit the number of pages to the maximum number available.
  #
  nr_pages = tp.to_i if nr_pages == :ALL_PAGES || nr_pages > tp.to_i

  if PAGINATION.key? operation.kind
    page_parameter = PAGINATION[operation.kind]['parameter']
    max_pages = PAGINATION[operation.kind]['max_page']
  else
    page_parameter = 'ItemPage'
    max_pages = 400
  end

  # Iterate over pages 2 and higher, but go no higher than MAX_PAGES.
  #
  2.upto( nr_pages < max_pages ? nr_pages : max_pages ) do |page_nr|
    @query = Amazon::AWS.assemble_query(
	      parameters.merge( { page_parameter => page_nr } ),
	      @encoding)
    page = Amazon::AWS.get_page( self )

    # Ruby 1.9 needs to know that the page is UTF-8, not ASCII-8BIT.
    #
    page.force_encoding( 'utf-8' ) if RUBY_VERSION >= '1.9.0'

    doc = Document.new( page )

    # Check for errors.
    #
    error_check( doc.elements['*/OperationRequest'] )
    error_check( doc.elements['*/*/Request'] )

    # Create a new AWS object and walk the XML response tree.
    #
    aws = AWS::AWSObject.new( operation )
    aws.walk( doc )

    # When dealing with multiple pages, we return not just an
    # AWSObject, but an array of them.
    #
    result = [ result ] unless result.is_a? Array

    # Append the new object to the array.
    #
    result << aws
  end

  # Yield each object to the block, if given.
  #
  result.each { |r| yield r } if block_given?

  result
end

#signObject

Add a signature to a request object’s query string. This implicitly also adds a timestamp.



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/ruby-paa/aws/search.rb', line 215

def sign # :nodoc:
  
  return false unless DIGEST_SUPPORT

  timestamp
  params = @query[1..-1].split( '&' ).sort.join( '&' )
 
  sign_str = "GET\n%s\n%s\n%s" % [ ENDPOINT[@locale].host,
				   ENDPOINT[@locale].path,
				   params ]

  Amazon.dprintf( 'Calculating SHA256 HMAC of "%s"...', sign_str )

  hmac = OpenSSL::HMAC.digest( DIGEST,
			       @config['secret_key_id'],
			       sign_str )
  Amazon.dprintf( 'SHA256 HMAC is "%s"', hmac.inspect )

  base64_hmac = [ hmac ].pack( 'm' ).chomp
  Amazon.dprintf( 'Base64-encoded HMAC is "%s".', base64_hmac )

  signature = Amazon.url_encode( base64_hmac )

  params << '&Signature=%s' % [ signature ]
  @query = '?' + params

  true
end