vacuum 0.1.3 → 0.2.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +69 -27
- data/lib/vacuum/endpoint/base.rb +58 -0
- data/lib/vacuum/endpoint/mws.rb +59 -0
- data/lib/vacuum/endpoint/product_advertising.rb +34 -0
- data/lib/vacuum/mws.rb +8 -0
- data/lib/vacuum/product_advertising.rb +7 -0
- data/lib/vacuum/request/base.rb +96 -0
- data/lib/vacuum/request/mws.rb +24 -0
- data/lib/vacuum/request/product_advertising.rb +90 -0
- data/lib/vacuum/request/signature/authentication.rb +32 -0
- data/lib/vacuum/request/signature/builder.rb +70 -0
- data/lib/vacuum/request/utils.rb +24 -0
- data/lib/vacuum/response/base.rb +57 -0
- data/lib/vacuum/response/mws.rb +7 -0
- data/lib/vacuum/response/product_advertising.rb +19 -0
- data/lib/vacuum/response/utils.rb +48 -0
- data/lib/vacuum/version.rb +1 -1
- data/lib/vacuum.rb +31 -11
- data/spec/fixtures/{http_response → product_advertising} +0 -0
- data/spec/spec_helper.rb +5 -4
- data/spec/support/shared_examples/endpoint.rb +43 -0
- data/spec/support/shared_examples/request.rb +95 -0
- data/spec/support/shared_examples/response.rb +51 -0
- data/spec/vacuum/endpoint/base_spec.rb +19 -0
- data/spec/vacuum/endpoint/mws_spec.rb +47 -0
- data/spec/vacuum/endpoint/product_advertising_spec.rb +25 -0
- data/spec/vacuum/request/base_spec.rb +22 -0
- data/spec/vacuum/request/mws_spec.rb +26 -0
- data/spec/vacuum/request/product_advertising_spec.rb +104 -0
- data/spec/vacuum/request/signature/authentication_spec.rb +30 -0
- data/spec/vacuum/request/signature/builder_spec.rb +76 -0
- data/spec/vacuum/request/utils_spec.rb +23 -0
- data/spec/vacuum/response/base_spec.rb +38 -0
- data/spec/vacuum/response/product_advertising_spec.rb +57 -0
- data/spec/vacuum/response/utils_spec.rb +42 -0
- metadata +107 -21
- data/lib/vacuum/em.rb +0 -21
- data/lib/vacuum/request.rb +0 -119
- data/lib/vacuum/response.rb +0 -63
- data/spec/vacuum/request_spec.rb +0 -130
- data/spec/vacuum/response_spec.rb +0 -80
data/README.md
CHANGED
@@ -2,42 +2,84 @@
|
|
2
2
|
|
3
3
|
[![travis] [1]] [2]
|
4
4
|
|
5
|
-
|
5
|
+
Vacuum is a Ruby wrapper to [various Amazon Web Services (AWS) APIs] [3].
|
6
6
|
|
7
|
-
|
7
|
+
![vacuum] [4]
|
8
8
|
|
9
|
-
|
9
|
+
## Amazon Product Advertising API
|
10
|
+
|
11
|
+
Vacuum knows the [Amazon Product Advertising API] [5] [inside out] [6].
|
10
12
|
|
11
13
|
```ruby
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
#
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
14
|
+
request = Vacuum.new(:product_advertising) do |config|
|
15
|
+
config.locale 'US'
|
16
|
+
|
17
|
+
config.key 'key'
|
18
|
+
config.secret 'secret'
|
19
|
+
config.tag 'tag'
|
20
|
+
end
|
21
|
+
|
22
|
+
# Use an alternative Faraday adapter.
|
23
|
+
# request.connection do |builder|
|
24
|
+
# builder.adapter :typhoeus
|
25
|
+
# end
|
26
|
+
|
27
|
+
# A barebone search request.
|
28
|
+
request.build operation: 'ItemSearch',
|
29
|
+
search_index: 'Books',
|
30
|
+
keywords: 'Deleuze'
|
31
|
+
response = request.get
|
32
|
+
|
33
|
+
# A less verbose search.
|
34
|
+
request.search :books, 'Deleuze'
|
35
|
+
|
36
|
+
if response.valid?
|
37
|
+
# response.code
|
38
|
+
# response.body
|
39
|
+
# response.errors
|
40
|
+
# response.xml # The Nokogiri XML doc
|
41
|
+
# response.to_hash
|
42
|
+
response.find('Item') do |item|
|
32
43
|
p item['ASIN']
|
33
44
|
end
|
34
45
|
end
|
35
46
|
```
|
47
|
+
## Amazon Marketplace Web Services API
|
48
|
+
|
49
|
+
The wrapper to the [Amazon Marketplace Web Services API] [7] is a
|
50
|
+
work-in-progress.
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
request = Vacuum.new(:mws_products) do |config|
|
54
|
+
config.locale 'US'
|
55
|
+
|
56
|
+
config.key 'key'
|
57
|
+
config.secret 'secret'
|
58
|
+
config.marketplace 'marketplace'
|
59
|
+
config.seller 'seller'
|
60
|
+
end
|
61
|
+
|
62
|
+
request.build 'Action' => 'GetLowestOfferListingsForASIN',
|
63
|
+
'ASINList.ASIN.1' => '0231081596'
|
64
|
+
offers = request.get.find 'GetLowestOfferListingsForASINResult'
|
65
|
+
```
|
66
|
+
|
67
|
+
## Other AWS APIs
|
68
|
+
|
69
|
+
Vacuum should work with EC2, S3, IAM, SimpleDB, SQS, SNS, SES, ELB, CW, and so
|
70
|
+
on. Implement and send a pull request.
|
71
|
+
|
72
|
+
# Addendum
|
73
|
+
|
74
|
+
![vacuums] [8]
|
36
75
|
|
37
|
-
|
76
|
+
> Workers queuing to crawl AWS.
|
38
77
|
|
39
78
|
[1]: https://secure.travis-ci.org/hakanensari/vacuum.png
|
40
79
|
[2]: http://travis-ci.org/hakanensari/vacuum
|
41
|
-
[3]: http://
|
42
|
-
[4]:
|
43
|
-
[5]: https://
|
80
|
+
[3]: http://aws.amazon.com/
|
81
|
+
[4]: http://f.cl.ly/items/2k2X0e2u0G3k1c260D2u/vacuum.png
|
82
|
+
[5]: https://affiliate-program.amazon.co.uk/gp/advertising/api/detail/main.html
|
83
|
+
[6]: https://github.com/hakanensari/vacuum/blob/master/examples/product_advertising/
|
84
|
+
[7]: https://developer.amazonservices.com/gp/mws/docs.html
|
85
|
+
[8]: http://f.cl.ly/items/1Q3W372A0H3M0w2H1e0W/hoover.jpeg
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Endpoint
|
3
|
+
# An Amazon Web Services (AWS) API endpoint.
|
4
|
+
class Base
|
5
|
+
LOCALES = %w(CA CN DE ES FR IT JP UK US)
|
6
|
+
|
7
|
+
# Raises a Not Implemented Error.
|
8
|
+
#
|
9
|
+
# When implemented, this should return a String AWS API host.
|
10
|
+
def host
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the String AWS access key ID.
|
15
|
+
#
|
16
|
+
# Raises a Missing Key error if key is missing.
|
17
|
+
def key
|
18
|
+
@key or raise MissingKey
|
19
|
+
end
|
20
|
+
|
21
|
+
# Sets the String AWS access key ID.
|
22
|
+
attr_writer :key
|
23
|
+
|
24
|
+
# Returns the String AWS API locale (default: US).
|
25
|
+
#
|
26
|
+
# Raises a Bad Locale error if locale is not valid.
|
27
|
+
def locale
|
28
|
+
@locale ||= 'US'
|
29
|
+
LOCALES.include? @locale or raise BadLocale
|
30
|
+
|
31
|
+
@locale
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets the String AWS API locale.
|
35
|
+
attr_writer :locale
|
36
|
+
|
37
|
+
# Returns the String AWS access secret key.
|
38
|
+
#
|
39
|
+
# Raises a Missing Secret error if secret is missing.
|
40
|
+
def secret
|
41
|
+
@secret or raise MissingSecret
|
42
|
+
end
|
43
|
+
|
44
|
+
# Sets the String AWS access secret key.
|
45
|
+
attr_writer :secret
|
46
|
+
|
47
|
+
# Returns a String user agent for the AWS API request.
|
48
|
+
def user_agent
|
49
|
+
engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
|
50
|
+
language = [engine, RUBY_VERSION, "p#{RUBY_PATCHLEVEL}"].join ' '
|
51
|
+
hostname = `hostname`.chomp
|
52
|
+
version = Vacuum::VERSION
|
53
|
+
|
54
|
+
"Vacuum/#{version} (Language=#{language}; Host=#{hostname})"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Endpoint
|
3
|
+
# A Marketplace Web Services (MWS) API endpoint.
|
4
|
+
class MWS < Base
|
5
|
+
# A list of MWS API hosts.
|
6
|
+
HOSTS = {
|
7
|
+
'CA' => 'mws.amazonservices.ca',
|
8
|
+
'CN' => 'mws.amazonservices.com.cn',
|
9
|
+
'DE' => 'mws-eu.amazonservices.com',
|
10
|
+
'ES' => 'mws-eu.amazonservices.com',
|
11
|
+
'FR' => 'mws-eu.amazonservices.com',
|
12
|
+
'IT' => 'mws-eu.amazonservices.com',
|
13
|
+
'JP' => 'mws.amazonservices.jp',
|
14
|
+
'UK' => 'mws-eu.amazonservices.com',
|
15
|
+
'US' => 'mws.amazonservices.com'
|
16
|
+
}
|
17
|
+
|
18
|
+
# Internal: Gets/Sets the Symbol MWS API type.
|
19
|
+
attr_accessor :api
|
20
|
+
|
21
|
+
# Returns a String MWS API host.
|
22
|
+
def host
|
23
|
+
HOSTS[locale]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sets the String marketplace ID.
|
27
|
+
#
|
28
|
+
# Raises a Missing Marketplace error if marketplace ID is missing.
|
29
|
+
def marketplace
|
30
|
+
@marketplace or raise MissingMarketplace
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets the String marketplace ID tag.
|
34
|
+
attr_writer :marketplace
|
35
|
+
|
36
|
+
# Returns a String MWS API URL path.
|
37
|
+
#
|
38
|
+
# Raises a Not Implemented Error if API is not implemented.
|
39
|
+
def path
|
40
|
+
case api
|
41
|
+
when :products
|
42
|
+
'/Products/2011-10-01'
|
43
|
+
else
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sets the String seller ID.
|
49
|
+
#
|
50
|
+
# Raises a Missing Seller error if seller ID is missing.
|
51
|
+
def seller
|
52
|
+
@seller or raise MissingSeller
|
53
|
+
end
|
54
|
+
|
55
|
+
# Sets the String seller ID tag.
|
56
|
+
attr_writer :seller
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Endpoint
|
3
|
+
# A Product Advertising API endpoint.
|
4
|
+
class ProductAdvertising < Base
|
5
|
+
# A list of Product Advertising API hosts.
|
6
|
+
HOSTS = {
|
7
|
+
'CA' => 'ecs.amazonaws.ca',
|
8
|
+
'CN' => 'webservices.amazon.cn',
|
9
|
+
'DE' => 'ecs.amazonaws.de',
|
10
|
+
'ES' => 'webservices.amazon.es',
|
11
|
+
'FR' => 'ecs.amazonaws.fr',
|
12
|
+
'IT' => 'webservices.amazon.it',
|
13
|
+
'JP' => 'ecs.amazonaws.jp',
|
14
|
+
'UK' => 'ecs.amazonaws.co.uk',
|
15
|
+
'US' => 'ecs.amazonaws.com'
|
16
|
+
}
|
17
|
+
|
18
|
+
# Returns a String Product Advertising API host.
|
19
|
+
def host
|
20
|
+
HOSTS[locale]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Sets the String Associate tag.
|
24
|
+
#
|
25
|
+
# Raises a Missing Tag error if tag is missing.
|
26
|
+
def tag
|
27
|
+
@tag or raise MissingTag
|
28
|
+
end
|
29
|
+
|
30
|
+
# Sets the String Associate tag.
|
31
|
+
attr_writer :tag
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/vacuum/mws.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Request
|
3
|
+
# An Amazon Web Services (AWS) API request.
|
4
|
+
class Base
|
5
|
+
# Returns the AWS API Endpoint.
|
6
|
+
attr :endpoint
|
7
|
+
|
8
|
+
# Creates a new request for given locale and credentials.
|
9
|
+
#
|
10
|
+
# Yields the AWS API endpoint if a block is given.
|
11
|
+
def initialize(&blk)
|
12
|
+
@parameters = {}
|
13
|
+
@endpoint = Endpoint.const_get(class_basename).new
|
14
|
+
|
15
|
+
configure &blk if block_given?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Adds given parameters to the request.
|
19
|
+
#
|
20
|
+
# hsh - A Hash of parameter key and value pairs.
|
21
|
+
#
|
22
|
+
# Returns self.
|
23
|
+
def build(hsh)
|
24
|
+
hsh.each do |k, v|
|
25
|
+
k = Utils.camelize k.to_s if k.is_a? Symbol
|
26
|
+
@parameters[k] = v.is_a?(Array) ? v.join(',') : v.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Resets the request to the given parameters.
|
33
|
+
#
|
34
|
+
# hsh - A Hash of parameter key and value pairs.
|
35
|
+
#
|
36
|
+
# Returns self.
|
37
|
+
def build!(hsh = {})
|
38
|
+
@parameters = {}
|
39
|
+
build hsh
|
40
|
+
end
|
41
|
+
|
42
|
+
# Yields the AWS API Endpoint.
|
43
|
+
#
|
44
|
+
# Returns nothing.
|
45
|
+
def configure(&blk)
|
46
|
+
yield @endpoint
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns a Faraday::Connection.
|
50
|
+
#
|
51
|
+
# Yields a Faraday::Builder to configure the connection if a block is
|
52
|
+
# given.
|
53
|
+
def connection
|
54
|
+
@connection ||= Faraday.new do |builder|
|
55
|
+
builder.use Signature::Authentication, endpoint.secret
|
56
|
+
|
57
|
+
yield builder if block_given?
|
58
|
+
|
59
|
+
unless builder.handlers.any? { |h| h.name.include? ':Adapter:' }
|
60
|
+
builder.adapter Faraday.default_adapter
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Performs the AWS API request.
|
66
|
+
#
|
67
|
+
# Returns a Vacuum::Response::Base or a subclass thereof.
|
68
|
+
def get
|
69
|
+
res = connection.get url
|
70
|
+
Response.const_get(class_basename).new res.body, res.status
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the Hash parameters of the AWS API request.
|
74
|
+
def parameters
|
75
|
+
default_parameters.merge @parameters
|
76
|
+
end
|
77
|
+
|
78
|
+
# Raises a Not Implemented Error.
|
79
|
+
#
|
80
|
+
# When implemented, this should return an Addressable::URI.
|
81
|
+
def url
|
82
|
+
raise NotImplementedError
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def class_basename
|
88
|
+
self.class.name.split('::').last
|
89
|
+
end
|
90
|
+
|
91
|
+
def default_parameters
|
92
|
+
{ 'AWSAccessKeyId' => endpoint.key }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Request
|
3
|
+
# A Marketplace Web Services (MWS) API request.
|
4
|
+
class MWS < Base
|
5
|
+
# Returns the Addressable::URI URL of the MWS API request.
|
6
|
+
def url
|
7
|
+
Addressable::URI.new :scheme => 'https',
|
8
|
+
:host => endpoint.host,
|
9
|
+
:path => endpoint.path,
|
10
|
+
:query_values => parameters
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def default_parameters
|
16
|
+
super.merge 'MarketplaceId' => endpoint.marketplace,
|
17
|
+
'SellerId' => endpoint.seller,
|
18
|
+
'Service' => 'AWSECommerceService',
|
19
|
+
'SignatureMethod' => 'HmacSHA256',
|
20
|
+
'SignatureVersion' => 2
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Request
|
3
|
+
# A Product Advertising API request.
|
4
|
+
class ProductAdvertising < Base
|
5
|
+
# Looks up attributes of up to twenty items.
|
6
|
+
#
|
7
|
+
# item_ids - Splat Array of item IDs. The last element may optionally
|
8
|
+
# specify a Hash of parameter key and value pairs.
|
9
|
+
#
|
10
|
+
# Examples
|
11
|
+
#
|
12
|
+
# request.find '0679753354', response_group: 'Images'
|
13
|
+
#
|
14
|
+
# Returns a Vacuum::Response.
|
15
|
+
def find(*item_ids)
|
16
|
+
given_params = item_ids.last.is_a?(Hash) ? item_ids.pop : {}
|
17
|
+
|
18
|
+
params =
|
19
|
+
case item_ids.size
|
20
|
+
when 1..10
|
21
|
+
{
|
22
|
+
'Operation' => 'ItemLookup',
|
23
|
+
'ItemId' => item_ids
|
24
|
+
}.merge given_params
|
25
|
+
when 11..20
|
26
|
+
default = {
|
27
|
+
'Operation' => 'ItemLookup',
|
28
|
+
'ItemId.1.ItemId' => item_ids.shift(10),
|
29
|
+
'ItemId.2.ItemId' => item_ids
|
30
|
+
}
|
31
|
+
given_params.reduce(default) do |a, (k, v)|
|
32
|
+
a.merge "ItemLookup.Shared.#{Utils.camelize k.to_s}" => v
|
33
|
+
end
|
34
|
+
else
|
35
|
+
raise ArgumentError, "Can't look up #{item_ids.size} items"
|
36
|
+
end
|
37
|
+
build! params
|
38
|
+
|
39
|
+
get
|
40
|
+
end
|
41
|
+
|
42
|
+
# Searches for items that satisfy the given criteria, including one or
|
43
|
+
# more search indices.
|
44
|
+
#
|
45
|
+
# index - Symbol search index
|
46
|
+
# query_or_params - String keyword query or Hash of parameter key and
|
47
|
+
# value pairs (default: nil).
|
48
|
+
#
|
49
|
+
# Examples
|
50
|
+
#
|
51
|
+
# # Search the entire Amazon catalog for Deleuze.
|
52
|
+
# request.search :all, 'Deleuze'
|
53
|
+
#
|
54
|
+
# # Search books for non-fiction titles authored by Deleuze and sort
|
55
|
+
# # results by relevance.
|
56
|
+
# request.search :books, power: 'author:lacan and not fiction',
|
57
|
+
# sort: 'relevancerank'
|
58
|
+
#
|
59
|
+
def search(index, query_or_params = nil)
|
60
|
+
params = case query_or_params
|
61
|
+
when String
|
62
|
+
{ 'Keywords' => query_or_params }
|
63
|
+
else
|
64
|
+
query_or_params
|
65
|
+
end
|
66
|
+
build! params.merge! 'Operation' => 'ItemSearch',
|
67
|
+
'SearchIndex' => Utils.camelize(index.to_s)
|
68
|
+
|
69
|
+
get
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns the Addressable::URI URL of the Product Advertising API
|
73
|
+
# request.
|
74
|
+
def url
|
75
|
+
Addressable::URI.new :scheme => 'http',
|
76
|
+
:host => endpoint.host,
|
77
|
+
:path => '/onca/xml',
|
78
|
+
:query_values => parameters
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def default_parameters
|
84
|
+
super.merge 'AssociateTag' => endpoint.tag,
|
85
|
+
'Service' => 'AWSECommerceService',
|
86
|
+
'Version' => '2011-08-01'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Request
|
3
|
+
module Signature
|
4
|
+
# Internal: Middleware that signs REST requests to various Amazon API
|
5
|
+
# endpoints.
|
6
|
+
class Authentication < Faraday::Middleware
|
7
|
+
# Initializes the middleware.
|
8
|
+
#
|
9
|
+
# app - An Object that responds to `call` and returns a
|
10
|
+
# Faraday::Response.
|
11
|
+
# secret - The String Amazon AWS access secret key.
|
12
|
+
def initialize(app, secret)
|
13
|
+
@secret = secret
|
14
|
+
super app
|
15
|
+
end
|
16
|
+
|
17
|
+
# Signs the request.
|
18
|
+
#
|
19
|
+
# env - A Hash that contains info about the request.
|
20
|
+
#
|
21
|
+
# Returns an Object that responds to `call` and returns a
|
22
|
+
# Faraday::Response.
|
23
|
+
def call(env)
|
24
|
+
builder = Builder.new env, @secret
|
25
|
+
builder.timestamp.sort_query.sign
|
26
|
+
|
27
|
+
@app.call builder.env
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Request
|
3
|
+
module Signature
|
4
|
+
# Internal: Signs a request to an Amazon API with an HMAC-SHA256
|
5
|
+
# signature.
|
6
|
+
class Builder
|
7
|
+
# Returns a Hash that contains info about the request.
|
8
|
+
attr :env
|
9
|
+
|
10
|
+
# Returns the String Amazon AWS access secret key.
|
11
|
+
attr :secret
|
12
|
+
|
13
|
+
# Initializes a new Builder.
|
14
|
+
#
|
15
|
+
# env - A Hash that contains info about the request.
|
16
|
+
def initialize(env, secret)
|
17
|
+
env[:url] = Addressable::URI.parse env[:url]
|
18
|
+
@env, @secret = env, secret
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the String name of the HTTP method used by the request.
|
22
|
+
def method
|
23
|
+
env[:method].to_s.upcase
|
24
|
+
end
|
25
|
+
|
26
|
+
# Signs the request.
|
27
|
+
#
|
28
|
+
# Returns self.
|
29
|
+
def sign
|
30
|
+
url.query = url.query.to_s + "&Signature=#{Utils.encode signature}"
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns a String signature.
|
35
|
+
def signature
|
36
|
+
sha256 = OpenSSL::Digest::SHA256.new
|
37
|
+
hash = OpenSSL::HMAC.digest sha256, secret, string_to_sign
|
38
|
+
|
39
|
+
Base64.encode64(hash).chomp
|
40
|
+
end
|
41
|
+
|
42
|
+
# Sorts the URL query values of the request.
|
43
|
+
#
|
44
|
+
# Returns self.
|
45
|
+
def sort_query
|
46
|
+
url.query_values = url.query_values
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns a String to sign based on pseudo-grammar specified by Amazon.
|
51
|
+
def string_to_sign
|
52
|
+
[method, url.host, url.path, url.query].join "\n"
|
53
|
+
end
|
54
|
+
|
55
|
+
# Timestamps the request.
|
56
|
+
#
|
57
|
+
# Returns self.
|
58
|
+
def timestamp
|
59
|
+
url.query = url.query.to_s + "&Timestamp=#{Utils.encode Time.now.utc.iso8601}"
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the Addressable::URI URL of the request.
|
64
|
+
def url
|
65
|
+
env[:url]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Request
|
3
|
+
module Utils
|
4
|
+
# Camelizes a value.
|
5
|
+
#
|
6
|
+
# val - A String value.
|
7
|
+
#
|
8
|
+
# Returns an upper-camelcased String.
|
9
|
+
def self.camelize(val)
|
10
|
+
val.split('_').map(&:capitalize).join
|
11
|
+
end
|
12
|
+
|
13
|
+
# Percent encodes a URI component.
|
14
|
+
#
|
15
|
+
# component - The String URI component to encode.
|
16
|
+
#
|
17
|
+
# Returns the String encoded component.
|
18
|
+
def self.encode(component)
|
19
|
+
Addressable::URI.encode_component \
|
20
|
+
component, Addressable::URI::CharacterClasses::UNRESERVED
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Vacuum
|
2
|
+
module Response
|
3
|
+
# An Amazon Web Services (AWS) API response.
|
4
|
+
class Base
|
5
|
+
# Gets/Sets the String response body.
|
6
|
+
attr_accessor :body
|
7
|
+
|
8
|
+
# Gets/Sets the Integer HTTP response code.
|
9
|
+
attr_accessor :code
|
10
|
+
|
11
|
+
# Initializes a new Response.
|
12
|
+
#
|
13
|
+
# body - The String response body.
|
14
|
+
# code - An HTTP response code that responds to to_i.
|
15
|
+
def initialize(body, code)
|
16
|
+
@body, @code = body, code.to_i
|
17
|
+
end
|
18
|
+
|
19
|
+
# Queries the response.
|
20
|
+
#
|
21
|
+
# query - String attribute to be queried.
|
22
|
+
#
|
23
|
+
# Yields matching nodes to a given block if one is given.
|
24
|
+
#
|
25
|
+
# Returns an Array of matching nodes or the return values of the yielded
|
26
|
+
# block if latter was given.
|
27
|
+
def find(query)
|
28
|
+
path = if xml.namespaces.empty?
|
29
|
+
"//#{query}"
|
30
|
+
else
|
31
|
+
"//xmlns:#{query}"
|
32
|
+
end
|
33
|
+
|
34
|
+
xml.xpath(path).map do |node|
|
35
|
+
hsh = Utils.xml_to_hash node
|
36
|
+
block_given? ? yield(hsh) : hsh
|
37
|
+
end
|
38
|
+
end
|
39
|
+
alias [] find
|
40
|
+
|
41
|
+
# Returns a Hash representation of the response.
|
42
|
+
def to_hash
|
43
|
+
Utils.xml_to_hash xml
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns whether the HTTP response is OK.
|
47
|
+
def valid?
|
48
|
+
code == 200
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns an XML document.
|
52
|
+
def xml
|
53
|
+
@xml ||= Nokogiri::XML.parse @body
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|