Permalink
Browse files

Basic connections work

  • Loading branch information...
1 parent ee43486 commit 7cc01c4bbfc8089f957c449ded5957fa0d94761e @SFEley committed Feb 9, 2012
View
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
# specify any dependencies here; for example:
s.add_runtime_dependency "eventmachine"
s.add_runtime_dependency "em-http-request"
+ s.add_runtime_dependency "nokogiri"
s.add_development_dependency "rspec"
s.add_development_dependency "webmock"
View
@@ -0,0 +1,33 @@
+module EventMachine
+ module AWS
+
+ # Utility methods to convert names between AWS CamelCaseConventions and Ruby :snake_case_conventions.
+ module Inflections
+ private
+
+ def snakecase(name)
+ # Adapted from ActiveSupport's #underscore method
+ name.to_s.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
+ end
+
+ def symbolize(name)
+ snakecase(name).to_sym
+ end
+
+ def camelcase(name)
+ # Adapted from ActiveSupport's #camelize method
+ name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }
+ end
+
+ # Camelize key names for Amazon conventions
+ def camelkeys(hash)
+ out = {}
+ hash.each do |k, v|
+ out[camelcase(k)] = v
+ end
+ out
+ end
+ end
+
+ end
+end
@@ -1,64 +0,0 @@
-require 'uri'
-require 'openssl'
-require 'base64'
-
-module EventMachine
- module AWS
-
- # Encapsulates the logic to add authentication attributes to AWS requests.
- class SignatureV2
- attr_reader :access_key, :secret_key, :method, :host, :path
-
- def initialize(access_key, secret_key, method, endpoint)
- @access_key = access_key
- @secret_key = secret_key
- @method = method
- if endpoint =~ %r!^(https?://)?([^/]+)(/[^?]*)!
- @host, @path = $2, $3
- end
- end
-
- # Adds the Query Protocol authentication headers
- def signature(params)
- signature_params = {
- AWSAccessKeyId: access_key,
- SignatureVersion: 2,
- SignatureMethod: 'HmacSHA256'
- }
- param_string = signable_params params.merge(signature_params)
- signature_params[:Signature] = hmac_sign param_string
- signature_params
- end
-
- # Implements the AWS signing method, version 2 (HMAC digest then Base64 encoded) with the secret key
- # See: http://docs.amazonwebservices.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/Query_QueryAuth.html
- def hmac_sign(str)
- hmac = OpenSSL::HMAC.digest('sha256', secret_key, str)
- Base64.strict_encode64 hmac
- end
-
- # Lines up and encodes our parameters nicely and neatly in sorted order
- def signable_params(params)
- if method == :get
- param_array = params.collect{|pair| "#{uri_escape(pair[0])}=#{uri_escape(pair[1])}"}
- else
- param_array = params.collect{|pair| "#{form_escape(pair[0])}=#{form_escape(pair[1])}"}
- end
- param_string = param_array.sort.join('&')
- "#{method.to_s.upcase}\n#{host}\n#{path}\n#{param_string}"
- end
-
- private
-
- # # URI escape according to the character set that's legal in RFC 3986
- # (and the AWS EC2 User Guide, v2011-05-15, p.338)
- def uri_escape(val)
- URI.escape(val.to_s, /[^A-Za-z0-9\-_.~]/)
- end
-
- def form_escape(val)
- URI.encode_www_form_component(val.to_s)
- end
- end
- end
-end
View
@@ -1,5 +1,9 @@
+require 'fiber'
require 'em-http'
-require 'em-aws/protocol/signature_v2'
+require 'em-aws/inflections'
+require 'em-aws/query/signature_v2'
+require 'em-aws/query/response'
+require 'em-aws/query/response_parser'
module EventMachine
module AWS
@@ -8,11 +12,12 @@ module AWS
# extract parameters from the response.
class Query
API_VERSION = nil # Subclasses should override this
- SIGNER_CLASS = AWS::SignatureV2
+ SIGNER_CLASS = SignatureV2
+
+ include Inflections
attr_reader :aws_access_key_id,
:aws_secret_access_key,
- :connection,
:region,
:ssl,
:method,
@@ -46,39 +51,38 @@ def endpoint
end
def call(action, params = {}, &block)
+
query = {
'Action' => camelcase(action),
'Version' => self.class::API_VERSION,
'Timestamp' => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
}
query.merge! camelkeys(params)
query.merge! @signer.signature(query) if @signer
- if method == :get
- connection.get query: query
- else
- connection.send method, body: query
- end
+
+ send_request(query, &block)
end
protected
- def snakecase(name)
- # Adapted from ActiveSupport's #underscore method
- name.to_s.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').downcase
- end
-
- def camelcase(name)
- # Adapted from ActiveSupport's #camelize method
- name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }
- end
-
- # Camelize key names for Amazon conventions
- def camelkeys(hash)
- out = {}
- hash.each do |k, v|
- out[camelcase(k)] = v
+ def send_request(params, &block)
+ if method == :get
+ request = EventMachine::HttpRequest.new(endpoint).get query: params
+ else
+ request = EventMachine::HttpRequest.new(endpoint).send method, body: params
+ end
+ request.errback do |raw_response|
+ puts raw_response.response_header
+ puts raw_response.response
+ end
+
+ if block
+ request.callback do |raw_response|
+ # raw_response is the object returned by EM::HttpClient.
+ block.call Response.new(raw_response)
+ end
end
- out
+ request
end
end
@@ -0,0 +1,48 @@
+require 'em-aws/query/response_parser'
+
+module EventMachine
+ module AWS
+ class Query
+
+ # Exposes the result values from an Amazon Query Protocol XML response as a hash or
+ # as dynamic methods.
+ class Response
+
+ attr_reader :header, :xml, :result, :metadata
+
+ def initialize(http_response)
+ @header = http_response.response_header
+ @xml = http_response.response
+ @result, @metadata = {}, {}
+ parse @xml unless @xml.empty?
+ end
+
+ # Returns the specified key from the inner 'SomeActionResults' data.
+ def [](val)
+ @result[val]
+ end
+
+ def action
+ metadata[:action]
+ end
+
+ def request_id
+ metadata[:request_id]
+ end
+
+ def method_missing(name, *args, &block)
+ @result[name] or super
+ end
+
+ protected
+
+ def parse(xml)
+ parser = Nokogiri::XML::SAX::Parser.new ResponseParser.new(@result, @metadata)
+ parser.parse xml
+ end
+
+ end
+
+ end
+ end
+end
@@ -0,0 +1,93 @@
+require 'nokogiri'
+require 'em-aws/inflections'
+
+module EventMachine
+ module AWS
+ class Query
+ class Response
+
+ # Used for parsing Amazon's XML responses with Nokogiri.
+ # Implements the SAX model for fast flexible processing,
+ # and populates the relevant hashes in the "parent" response.
+ class ResponseParser < Nokogiri::XML::SAX::Document
+ include Inflections
+
+ def initialize(result, metadata)
+ @result, @metadata = result, metadata
+ end
+
+ def start_document
+ @stack = []
+ @current_string = ''
+ end
+
+ def start_element(name, attrs=[])
+ case name
+ when /(.+)Response$/
+ @metadata[:action] = $1
+ when /(.+)Result$/
+ @stack.push @result
+ when 'ResponseMetadata'
+ @stack.push @metadata
+ when 'member'
+ @stack.push Array.new unless @stack.last.is_a?(Array)
+ @stack.push :member
+ else
+ @stack.push Hash.new unless @stack.last.is_a?(Hash)
+ @stack.push symbolize(name)
+ end
+
+ # puts "Just started #{name} - #{@stack}"
+ end
+
+ def characters(str)
+ @current_string << str
+ end
+
+ def cdata_block(str)
+ @current_string << str
+ end
+
+
+ def end_element(name)
+ value = coerce_value(@current_string)
+ @current_string = ''
+ collapse_stack(value)
+ # puts "Just finished #{name} - #{@stack}"
+ end
+
+ private
+
+ def collapse_stack(value)
+ case element = @stack.pop
+ when :member # Add to the array
+ @stack.last << value
+ when :entry # Add 'key'/'value' elements to the hash
+ @stack.last[value[:key]] = value[:value]
+ when Symbol
+ if @stack.last.is_a?(Hash)
+ @stack.last[element] = value
+ else
+ raise "I don't know how to add #{element} to #{@stack}"
+ end
+ else
+ collapse_stack element unless @stack.empty?
+ end
+ end
+
+ def coerce_value(val)
+ case val.strip!
+ when '' then nil
+ when /\A[-+]?\d+\Z/ then val.to_i
+ when /\A[-+]?\d+\.\d+\Z/ then val.to_f
+ else val
+ end
+ end
+
+
+
+ end
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit 7cc01c4

Please sign in to comment.