sqs 0.1.1

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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jakub Kuźma
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,57 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = "sqs"
10
+ gem.summary = %Q{Simple Queue Service accessing library}
11
+ gem.email = "qoobaa@gmail.com"
12
+ gem.homepage = "http://github.com/qoobaa/sqs"
13
+ gem.authors = ["Jakub Kuźma"]
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/*_test.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/*_test.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :default => :test
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ if File.exist?('VERSION.yml')
46
+ config = YAML.load(File.read('VERSION.yml'))
47
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
48
+ else
49
+ version = ""
50
+ end
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "sqs #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
57
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,114 @@
1
+ module Sqs
2
+
3
+ # Class responsible for handling connections to amazon hosts
4
+ class Connection
5
+ include Parser
6
+
7
+ attr_accessor :access_key_id, :secret_access_key, :use_ssl, :timeout, :debug
8
+ alias :use_ssl? :use_ssl
9
+
10
+ # ==== Parameters:
11
+ # +options+:: Hash of options
12
+ #
13
+ # ==== Options:
14
+ # +access_key_id+:: access key id
15
+ # +secret_access_key+:: secret access key
16
+ # +use_ssl+:: optional, defaults to false
17
+ # +debug+:: optional, defaults to false
18
+ # +timeout+:: optional, for Net::HTTP
19
+ def initialize(options = {})
20
+ @access_key_id = options[:access_key_id]
21
+ @secret_access_key = options[:secret_access_key]
22
+ @use_ssl = options[:use_ssl] || false
23
+ @debug = options[:debug]
24
+ @timeout = options[:timeout]
25
+ end
26
+
27
+ # Makes request with given HTTP method, sets missing parameters,
28
+ # adds signature to request header and returns response object
29
+ # (Net::HTTPResponse)
30
+ #
31
+ # ==== Parameters:
32
+ # +options+:: hash of options
33
+ #
34
+ # ==== Options:
35
+ # +host+:: hostname to connect to, optional, defaults to queue.amazonaws.com
36
+ # +path+:: path to send request to, required, throws ArgumentError if not given
37
+ #
38
+ # ==== Returns:
39
+ # Net::HTTPResponse object -- response from remote server
40
+ def request(options)
41
+ host = options.delete(:host) || HOST
42
+ path = options.delete(:path) or raise ArgumentError, "No path given"
43
+
44
+ request = Net::HTTP::Post.new(path)
45
+
46
+ response = http(host).start do |http|
47
+ add_common_options!(options)
48
+ add_timestamp!(options)
49
+ add_signature!(host, path, options)
50
+ set_form_data!(request, options)
51
+ http.request(request)
52
+ end
53
+
54
+ handle_response(response)
55
+ end
56
+
57
+ private
58
+
59
+ def set_form_data!(request, options)
60
+ request.set_form_data(options)
61
+ request.content_type = "application/x-www-form-urlencoded; charset=utf-8"
62
+ end
63
+
64
+ def add_common_options!(options)
65
+ options.merge!("AWSAccessKeyId" => access_key_id,
66
+ "SignatureMethod" => "HmacSHA256",
67
+ "SignatureVersion" => "2",
68
+ "Version" => "2009-02-01")
69
+ end
70
+
71
+ def add_timestamp!(options)
72
+ options["Timestamp"] = Time.now.utc.iso8601 if options["Timestamp"].nil? and options["Expires"].nil?
73
+ end
74
+
75
+ def add_signature!(host, path, options)
76
+ options["Signature"] = Signature.generate(:method => :post,
77
+ :host => host,
78
+ :path => path,
79
+ :access_key_id => access_key_id,
80
+ :secret_access_key => secret_access_key,
81
+ :params => options)
82
+ end
83
+
84
+ def port
85
+ use_ssl ? 443 : 80
86
+ end
87
+
88
+ def http(host)
89
+ http = Net::HTTP.new(host, port)
90
+ http.set_debug_output(STDOUT) if @debug
91
+ http.use_ssl = @use_ssl
92
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @use_ssl
93
+ http.read_timeout = @timeout if @timeout
94
+ http
95
+ end
96
+
97
+ def handle_response(response)
98
+ case response.code.to_i
99
+ when 200...300
100
+ response
101
+ when 300...600
102
+ if response.body.nil? || response.body.empty?
103
+ raise Error::ResponseError.new(nil, response)
104
+ else
105
+ code, message = parse_error(response.body)
106
+ raise Error::ResponseError.exception(code).new(message, response)
107
+ end
108
+ else
109
+ raise ConnectionError.new(response, "Unknown response code: #{response.code}")
110
+ end
111
+ response
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,97 @@
1
+ module Sqs
2
+ module Error
3
+
4
+ # All responses with a code between 300 and 599 that contain an
5
+ # <Error></Error> body are wrapped in an ErrorResponse which
6
+ # contains an Error object. This Error class generates a custom
7
+ # exception with the name of the xml Error and its message. All
8
+ # such runtime generated exception classes descend from
9
+ # ResponseError and contain the ErrorResponse object so that all
10
+ # code that makes a request can rescue ResponseError and get
11
+ # access to the ErrorResponse.
12
+ class ResponseError < StandardError
13
+ attr_reader :response
14
+
15
+ # ==== Parameters:
16
+ # +message+:: what went wrong
17
+ # +response+:: Net::HTTPResponse object or nil
18
+ def initialize(message, response)
19
+ @response = response
20
+ super(message)
21
+ end
22
+
23
+ # Factory for all other Exception classes in module, each for every
24
+ # error response available from AmazonAWS
25
+ #
26
+ # ==== Parameters:
27
+ # +code+:: code name of exception
28
+ #
29
+ # ==== Returns:
30
+ # Descendant of ResponseError suitable for that exception code or ResponseError class
31
+ # if no class found
32
+ def self.exception(code)
33
+ Sqs::Error.const_get(code)
34
+ rescue NameError
35
+ ResponseError
36
+ end
37
+ end
38
+
39
+ #:stopdoc:
40
+
41
+ class AccessDenied < ResponseError; end
42
+ class AuthFailure < ResponseError; end
43
+ class ConflictingQueryParameter < ResponseError; end
44
+ class ElementNotSigned < ResponseError; end
45
+ class InternalError < ResponseError; end
46
+ class InvalidAccessKeyId < ResponseError; end
47
+ class InvalidAction < ResponseError; end
48
+ class InvalidAddress < ResponseError; end
49
+ class InvalidAttributeName < ResponseError; end
50
+ class InvalidHttpRequest < ResponseError; end
51
+ class InvalidMessageContents < ResponseError; end
52
+ class InvalidParameterCombination < ResponseError; end
53
+ class InvalidParameterValue < ResponseError; end
54
+ class InvalidQueryParameter < ResponseError; end
55
+ class InvalidRequest < ResponseError; end
56
+ class InvalidSecurity < ResponseError; end
57
+ class InvalidSecurityToken < ResponseError; end
58
+ class MalformedSOAPSignature < ResponseError; end
59
+ class MalformedVersion < ResponseError; end
60
+ class MessageTooLong < ResponseError; end
61
+ class MissingClientTokenId < ResponseError; end
62
+ class MissingCredentials < ResponseError; end
63
+ class MissingParameter < ResponseError; end
64
+ class NoSuchVersion < ResponseError; end
65
+ class NonExistentQueue < ResponseError; end
66
+ class NotAuthorizedToUseVersion < ResponseError; end
67
+ class QueueDeletedRecently < ResponseError; end
68
+ class QueueNameExists < ResponseError; end
69
+ class ReadCountOutOfRange < ResponseError; end
70
+ class RequestExpired < ResponseError; end
71
+ class RequestThrottled < ResponseError; end
72
+ class SOAP11IncorrectDateFormat < ResponseError; end
73
+ class SOAP11MissingAction < ResponseError; end
74
+ class ServiceUnavailable < ResponseError; end
75
+ class SignatureDoesNotMatch < ResponseError; end
76
+ class SoapBodyMissing < ResponseError; end
77
+ class SoapEnvelopeMissing < ResponseError; end
78
+ class SoapEnvelopeParseError < ResponseError; end
79
+ class UnknownEnvelopeNamespace < ResponseError; end
80
+ class WSSecurityCorruptSignedInfo < ResponseError; end
81
+ class WSSecurityCreatedDateIncorrectFormat < ResponseError; end
82
+ class WSSecurityEncodingTypeError < ResponseError; end
83
+ class WSSecurityExpiresDateIncorrectFormat < ResponseError; end
84
+ class WSSecurityIncorrectValuetype < ResponseError; end
85
+ class WSSecurityMissingValuetype < ResponseError; end
86
+ class WSSecurityMultipleCredentialError < ResponseError; end
87
+ class WSSecurityMultipleX509Error < ResponseError; end
88
+ class WSSecuritySignatureError < ResponseError; end
89
+ class WSSecuritySignatureMissing < ResponseError; end
90
+ class WSSecuritySignedInfoMissing < ResponseError; end
91
+ class WSSecurityTimestampExpired < ResponseError; end
92
+ class WSSecurityTimestampExpiresMissing < ResponseError; end
93
+ class WSSecurityTimestampMissing < ResponseError; end
94
+ class WSSecurityX509CertCredentialError < ResponseError; end
95
+ class X509ParseError < ResponseError; end
96
+ end
97
+ end
@@ -0,0 +1,36 @@
1
+ module Sqs
2
+ class Message
3
+ extend Forwardable
4
+
5
+ def_instance_delegators :queue, :name, :queue_request
6
+ attr_reader :queue, :id, :body, :body_md5, :receipt_handle
7
+ private_class_method :new
8
+
9
+ def ==(other)
10
+ self.id == other.id and self.queue == other.queue
11
+ end
12
+
13
+ def destroy
14
+ delete_message
15
+ true
16
+ end
17
+
18
+ def inspect #:nodoc:
19
+ "#<#{self.class}:#{name}/#{id}>"
20
+ end
21
+
22
+ private
23
+
24
+ def initialize(queue, options)
25
+ @queue = queue
26
+ @id = options[:id]
27
+ @body = options[:body]
28
+ @body_md5 = options[:body_md5]
29
+ @receipt_handle = options[:receipt_handle]
30
+ end
31
+
32
+ def delete_message
33
+ queue_request("Action" => "DeleteMessage", "ReceiptHandle" => receipt_handle)
34
+ end
35
+ end
36
+ end
data/lib/sqs/parser.rb ADDED
@@ -0,0 +1,48 @@
1
+ module Sqs
2
+ module Parser
3
+ include REXML
4
+
5
+ def rexml_document(xml)
6
+ xml.force_encoding(Encoding::UTF_8) if xml.respond_to? :force_encoding
7
+ Document.new(xml)
8
+ end
9
+
10
+ def parse_list_queues_result(xml)
11
+ urls = []
12
+ rexml_document(xml).elements.each("ListQueuesResponse/ListQueuesResult/QueueUrl") { |e| urls << e.text }
13
+ urls
14
+ end
15
+
16
+ def parse_create_queue_result(xml)
17
+ rexml_document(xml).elements["CreateQueueResponse/CreateQueueResult/QueueUrl"].text
18
+ end
19
+
20
+ def parse_get_queue_attributes_result(xml)
21
+ attributes = {}
22
+ rexml_document(xml).elements.each("GetQueueAttributesResponse/GetQueueAttributesResult/Attribute") do |e|
23
+ name = e.elements["Name"].text
24
+ value = e.elements["Value"].text
25
+ attributes[name] = value
26
+ end
27
+ attributes
28
+ end
29
+
30
+ def parse_receive_message_result(xml)
31
+ messages = []
32
+ rexml_document(xml).elements.each("ReceiveMessageResponse/ReceiveMessageResult/Message") do |e|
33
+ messages << {
34
+ :id => e.elements["MessageId"].text,
35
+ :receipt_handle => e.elements["ReceiptHandle"].text,
36
+ :body_md5 => e.elements["MD5OfBody"].text,
37
+ :body => e.elements["Body"].text
38
+ }
39
+ end
40
+ messages
41
+ end
42
+
43
+ def parse_error(xml)
44
+ document = rexml_document(xml)
45
+ [document.elements["ErrorResponse/Error/Code"].text.split(".").last, document.elements["ErrorResponse/Error/Message"].text]
46
+ end
47
+ end
48
+ end
data/lib/sqs/queue.rb ADDED
@@ -0,0 +1,95 @@
1
+ module Sqs
2
+ class Queue
3
+ extend Forwardable
4
+ include Parser
5
+
6
+ attr_reader :path, :name, :service
7
+ def_instance_delegators :service, :service_request
8
+ private_class_method :new
9
+
10
+ def ==(other)
11
+ self.name == other.name and self.service == other.service
12
+ end
13
+
14
+ def destroy
15
+ delete_queue
16
+ true
17
+ end
18
+
19
+ def attributes
20
+ get_queue_attributes
21
+ end
22
+
23
+ def update_attributes(attributes)
24
+ set_queue_attributes(attributes)
25
+ true
26
+ end
27
+
28
+ def create_message(body)
29
+ send_message("MessageBody" => body)
30
+ true
31
+ end
32
+
33
+ def message(visibility_timeout = nil)
34
+ options = {}
35
+ options["VisibilityTimeout"] = visibility_timeout.to_s if visibility_timeout
36
+ receive_message(options).first
37
+ end
38
+
39
+ def inspect #:nodoc:
40
+ "#<#{self.class}:#{name}>"
41
+ end
42
+
43
+ private
44
+
45
+ def initialize(service, url) #:nodoc:
46
+ self.service = service
47
+ self.url = url
48
+ end
49
+
50
+ attr_writer :service, :path
51
+
52
+ def url=(url)
53
+ parsed_url = URI.parse(url)
54
+ self.path = parsed_url.path[1..-1]
55
+ self.name = parsed_url.path.split("/").last
56
+ end
57
+
58
+ def name=(name)
59
+ @name = name
60
+ end
61
+
62
+ def queue_request(options = {})
63
+ service_request(options.merge(:path => path))
64
+ end
65
+
66
+ def delete_queue
67
+ queue_request("Action" => "DeleteQueue")
68
+ end
69
+
70
+ def get_queue_attributes
71
+ response = queue_request("Action" => "GetQueueAttributes", "AttributeName" => "All")
72
+ parse_get_queue_attributes_result(response.body)
73
+ end
74
+
75
+ def set_queue_attributes(options)
76
+ attributes = {}
77
+ options.each_with_index do |attribute, i|
78
+ attributes["Attribute.#{i + 1}.Name"] = attribute.first.to_s
79
+ attributes["Attribute.#{i + 1}.Value"] = attribute.last.to_s
80
+ end
81
+ queue_request(attributes.merge("Action" => "SetQueueAttributes"))
82
+ end
83
+
84
+ def send_message(options)
85
+ queue_request(options.merge("Action" => "SendMessage"))
86
+ end
87
+
88
+ def receive_message(options)
89
+ response = queue_request(options.merge("Action" => "ReceiveMessage"))
90
+ parse_receive_message_result(response.body).map do |message_attributes|
91
+ Message.send(:new, self, message_attributes)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,58 @@
1
+ # Copyright (c) 2008 Ryan Daigle
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ module Sqs
24
+ module Roxy # :nodoc:all
25
+ module Moxie
26
+ # Set up this class to proxy on the given name
27
+ def proxy(name, options = {}, &block)
28
+
29
+ # Make sure args are OK
30
+ original_method = method_defined?(name) ? instance_method(name) : nil
31
+ raise "Cannot proxy an existing method, \"#{name}\", and also have a :to option. Please use one or the other." if
32
+ original_method and options[:to]
33
+
34
+ # If we're proxying an existing method, we need to store
35
+ # the original method and move it out of the way so
36
+ # we can take over
37
+ if original_method
38
+ new_method = "proxied_#{name}"
39
+ alias_method new_method, "#{name}"
40
+ options[:to] = original_method
41
+ end
42
+
43
+ # Thanks to Jerry for this simplification of my original class_eval approach
44
+ # http://ryandaigle.com/articles/2008/11/10/implement-ruby-proxy-objects-with-roxy/comments/8059#comment-8059
45
+ if !original_method or original_method.arity == 0
46
+ define_method name do
47
+ @proxy_for ||= {}
48
+ @proxy_for[name] ||= Proxy.new(self, options, nil, &block)
49
+ end
50
+ else
51
+ define_method name do |*args|
52
+ Proxy.new(self, options, args, &block)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,72 @@
1
+ # Copyright (c) 2008 Ryan Daigle
2
+
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation files
5
+ # (the "Software"), to deal in the Software without restriction,
6
+ # including without limitation the rights to use, copy, modify, merge,
7
+ # publish, distribute, sublicense, and/or sell copies of the Software,
8
+ # and to permit persons to whom the Software is furnished to do so,
9
+ # subject to the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
18
+ # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
19
+ # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ module Sqs
24
+ module Roxy # :nodoc:all
25
+ # The very simple proxy class that provides a basic pass-through
26
+ # mechanism between the proxy owner and the proxy target.
27
+ class Proxy
28
+
29
+ alias :proxy_instance_eval :instance_eval
30
+ alias :proxy_extend :extend
31
+
32
+ # Make sure the proxy is as dumb as it can be.
33
+ # Blatanly taken from Jim Wierich's BlankSlate post:
34
+ # http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
35
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^proxy_|^object_id)/ }
36
+
37
+ def initialize(owner, options, args, &block)
38
+ @owner = owner
39
+ @target = options[:to]
40
+ @args = args
41
+
42
+ # Adorn with user-provided proxy methods
43
+ [options[:extend]].flatten.each { |ext| proxy_extend(ext) } if options[:extend]
44
+ proxy_instance_eval &block if block_given?
45
+ end
46
+
47
+ def proxy_owner
48
+ @owner
49
+ end
50
+
51
+ def proxy_target
52
+ if @target.is_a?(Proc)
53
+ @target.call(@owner)
54
+ elsif @target.is_a?(UnboundMethod)
55
+ bound_method = @target.bind(proxy_owner)
56
+ bound_method.arity == 0 ? bound_method.call : bound_method.call(*@args)
57
+ else
58
+ @target
59
+ end
60
+ end
61
+
62
+ # def inspect
63
+ # "#<S3::Roxy::Proxy:0x#{object_id.to_s(16)}>"
64
+ # end
65
+
66
+ # Delegate all method calls we don't know about to target object
67
+ def method_missing(sym, *args, &block)
68
+ proxy_target.__send__(sym, *args, &block)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,118 @@
1
+ module Sqs
2
+ class Service
3
+ extend Roxy::Moxie
4
+ include Parser
5
+
6
+ attr_reader :access_key_id, :secret_access_key, :use_ssl
7
+
8
+ # Compares service to other, by access_key_id and secret_access_key
9
+ def ==(other)
10
+ self.access_key_id == other.access_key_id and self.secret_access_key == other.secret_access_key
11
+ end
12
+
13
+ # ==== Parameters:
14
+ # +options+:: a hash of options described below
15
+ #
16
+ # ==== Options:
17
+ # +access_key_id+:: Amazon access key id, required
18
+ # +secret_access_key+:: Amazon secret access key, required
19
+ # +use_ssl+:: true if use ssl in connection, otherwise false
20
+ # +timeout+:: parameter for Net::HTTP module
21
+ # +debug+:: prints the raw requests to STDOUT
22
+ def initialize(options)
23
+ @access_key_id = options[:access_key_id] or raise ArgumentError, "No access key id given"
24
+ @secret_access_key = options[:secret_access_key] or raise ArgumentError, "No secret access key given"
25
+ @use_ssl = options[:use_ssl]
26
+ @timeout = options[:timeout]
27
+ @debug = options[:debug]
28
+ end
29
+
30
+ # Returns all queues in the service and caches the result (see reload)
31
+ def queues(reload = false)
32
+ if reload or @queues.nil?
33
+ @queues = list_queues
34
+ else
35
+ @queues
36
+ end
37
+ end
38
+
39
+ proxy :queues do
40
+ # Builds new queue with given name
41
+ def create(name, default_visibility_timeout = nil)
42
+ options = { "QueueName" => name }
43
+ options["DefaultVisibilityTimeout"] = default_visibility_timeout.to_s if default_visibility_timeout
44
+ proxy_owner.send(:create_queue, options)
45
+ end
46
+
47
+ # Finds the queue with given name
48
+ def find_first(name)
49
+ find_all(name).first
50
+ end
51
+ alias :find :find_first
52
+
53
+ # Find all queues in the service
54
+ def find_all(name)
55
+ if name and not name.empty?
56
+ proxy_owner.send(:list_queues, "QueueNamePrefix" => name)
57
+ else
58
+ proxy_target
59
+ end
60
+ end
61
+
62
+ # Reloads the queue list (clears the cache)
63
+ def reload
64
+ proxy_owner.queues(true)
65
+ end
66
+
67
+ # Destroy all queues in the service (USE WITH CARE!).
68
+ def destroy_all
69
+ proxy_target.each { |queue| queue.destroy }
70
+ true
71
+ end
72
+ end
73
+
74
+ def inspect #:nodoc:
75
+ "#<#{self.class}:#@access_key_id>"
76
+ end
77
+
78
+ private
79
+
80
+ def list_queues(options = {})
81
+ response = service_request(options.merge("Action" => "ListQueues"))
82
+
83
+ parse_list_queues_result(response.body).map do |url|
84
+ Queue.send(:new, self, url)
85
+ end
86
+ end
87
+
88
+ def create_queue(options)
89
+ name = options["QueueName"]
90
+ raise ArgumentError, "Invalid queue name: #{name}" unless queue_name_valid?(name)
91
+
92
+ response = service_request(options.merge("Action" => "CreateQueue"))
93
+
94
+ url = parse_create_queue_result(response.body)
95
+ Queue.send(:new, self, url)
96
+ end
97
+
98
+ def queue_name_valid?(name)
99
+ name =~ /\A[a-zA-Z0-9_-]{1,80}\Z/
100
+ end
101
+
102
+ def service_request(options = {})
103
+ connection.request(options.merge(:path => "/#{options[:path]}"))
104
+ end
105
+
106
+ def connection
107
+ if @connection.nil?
108
+ @connection = Connection.new
109
+ @connection.access_key_id = @access_key_id
110
+ @connection.secret_access_key = @secret_access_key
111
+ @connection.use_ssl = @use_ssl
112
+ @connection.timeout = @timeout
113
+ @connection.debug = @debug
114
+ end
115
+ @connection
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,60 @@
1
+ module Sqs
2
+
3
+ # Class responsible for generating signatures to requests.
4
+ #
5
+ # Implements algorithm defined by Amazon Web Services to sign
6
+ # request with secret private credentials
7
+
8
+ class Signature
9
+ UNSAFE_REGEXP = /[^a-zA-Z0-9_.-]/
10
+
11
+ # Generates signature for given parameters
12
+ #
13
+ # ==== Parameters:
14
+ # +options+: a hash that contains options listed below
15
+ #
16
+ # ==== Options:
17
+ # +host+: hostname
18
+ # +access_key_id+: access key id
19
+ # +secret_access_key+: secret access key
20
+ # +method+: method of the request ("GET" or "POST")
21
+ # +params+: request parameters hash
22
+ # +path+: request path
23
+ #
24
+ # ==== Returns:
25
+ # Generated signature for given hostname and request
26
+ def self.generate(options)
27
+ host = options[:host]
28
+ access_key_id = options[:access_key_id]
29
+ secret_access_key = options[:secret_access_key]
30
+ method = options[:method]
31
+ params = options[:params]
32
+ path = options[:path]
33
+
34
+ string_to_sign = ""
35
+ string_to_sign << method.to_s.upcase
36
+ string_to_sign << "\n"
37
+ string_to_sign << host.to_s.downcase
38
+ string_to_sign << "\n"
39
+ string_to_sign << path
40
+ string_to_sign << "\n"
41
+ string_to_sign << canonicalized_query_string(params)
42
+
43
+ digest = OpenSSL::Digest::Digest.new("sha256")
44
+ hmac = OpenSSL::HMAC.digest(digest, secret_access_key, string_to_sign)
45
+ base64 = Base64.encode64(hmac)
46
+ base64.chomp
47
+ end
48
+
49
+ private
50
+
51
+ def self.canonicalized_query_string(params)
52
+ results = params.sort.map do |param|
53
+ urlencoded_key = URI.encode(param.first, UNSAFE_REGEXP)
54
+ urlencoded_value = URI.encode(param.last, UNSAFE_REGEXP)
55
+ "#{urlencoded_key}=#{urlencoded_value}"
56
+ end
57
+ results.join("&")
58
+ end
59
+ end
60
+ end
data/lib/sqs.rb ADDED
@@ -0,0 +1,26 @@
1
+ # stdlibs
2
+ require "base64"
3
+ require "forwardable"
4
+ require "net/http"
5
+ require "net/https"
6
+ require "openssl"
7
+ require "rexml/document"
8
+ require "time"
9
+
10
+ # proxy stuff
11
+ require "sqs/roxy/moxie"
12
+ require "sqs/roxy/proxy"
13
+
14
+ # sqs stuff
15
+ require "sqs/parser"
16
+ require "sqs/connection"
17
+ require "sqs/exceptions"
18
+ require "sqs/message"
19
+ require "sqs/queue"
20
+ require "sqs/service"
21
+ require "sqs/signature"
22
+
23
+ module Sqs
24
+ # Default (and only) host serving SQS stuff
25
+ HOST = "queue.amazonaws.com"
26
+ end
data/sqs.gemspec ADDED
@@ -0,0 +1,56 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{sqs}
8
+ s.version = "0.1.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Jakub Kuźma"]
12
+ s.date = %q{2009-08-17}
13
+ s.email = %q{qoobaa@gmail.com}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE"
16
+ ]
17
+ s.files = [
18
+ ".document",
19
+ ".gitignore",
20
+ "LICENSE",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "lib/sqs.rb",
24
+ "lib/sqs/connection.rb",
25
+ "lib/sqs/exceptions.rb",
26
+ "lib/sqs/message.rb",
27
+ "lib/sqs/parser.rb",
28
+ "lib/sqs/queue.rb",
29
+ "lib/sqs/roxy/moxie.rb",
30
+ "lib/sqs/roxy/proxy.rb",
31
+ "lib/sqs/service.rb",
32
+ "lib/sqs/signature.rb",
33
+ "sqs.gemspec",
34
+ "test/signature_test.rb",
35
+ "test/test_helper.rb"
36
+ ]
37
+ s.homepage = %q{http://github.com/qoobaa/sqs}
38
+ s.rdoc_options = ["--charset=UTF-8"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.5}
41
+ s.summary = %q{Simple Queue Service accessing library}
42
+ s.test_files = [
43
+ "test/signature_test.rb",
44
+ "test/test_helper.rb"
45
+ ]
46
+
47
+ if s.respond_to? :specification_version then
48
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
49
+ s.specification_version = 3
50
+
51
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
52
+ else
53
+ end
54
+ else
55
+ end
56
+ end
@@ -0,0 +1,33 @@
1
+ require "test_helper"
2
+
3
+ class SignatureTest < Test::Unit::TestCase
4
+ def setup
5
+ @options = {}
6
+ @params = {}
7
+ @options[:host] = "queue.amazonaws.com"
8
+ @options[:path] = "/"
9
+ @options[:method] = :get
10
+ @options[:access_key_id] = "0PN5J17HBGZHT7JJ3X82"
11
+ @options[:secret_access_key] = "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"
12
+ @params["SignatureMethod"] = "HmacSHA256"
13
+ @params["SignatureVersion"] = "2"
14
+ @params["Version"] = "2009-02-01"
15
+ @params["Timestamp"] = "2009-08-17T08:56:12Z"
16
+ @params["AWSAccessKeyId"] = @options[:access_key_id]
17
+ @options[:params] = @params
18
+ end
19
+
20
+ def test_signature_for_get
21
+ @options[:method] = :get
22
+ expected = "lQcH/YUdHHpo4hshdHZrGbX9CLvksKmD9atmrCroyAo="
23
+ actual = Sqs::Signature.generate(@options)
24
+ assert_equal expected, actual
25
+ end
26
+
27
+ def test_signagure_for_post
28
+ @options[:method] = :post
29
+ expected = "st9O4GTVytI+5BjbjfRSPRB8xKOxel52F7Sle706BcA="
30
+ actual = Sqs::Signature.generate(@options)
31
+ assert_equal expected, actual
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'rr'
4
+
5
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ require 'sqs'
8
+
9
+ class Test::Unit::TestCase
10
+ include RR::Adapters::TestUnit
11
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sqs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - "Jakub Ku\xC5\xBAma"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-08-17 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: qoobaa@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ files:
25
+ - .document
26
+ - .gitignore
27
+ - LICENSE
28
+ - Rakefile
29
+ - VERSION
30
+ - lib/sqs.rb
31
+ - lib/sqs/connection.rb
32
+ - lib/sqs/exceptions.rb
33
+ - lib/sqs/message.rb
34
+ - lib/sqs/parser.rb
35
+ - lib/sqs/queue.rb
36
+ - lib/sqs/roxy/moxie.rb
37
+ - lib/sqs/roxy/proxy.rb
38
+ - lib/sqs/service.rb
39
+ - lib/sqs/signature.rb
40
+ - sqs.gemspec
41
+ - test/signature_test.rb
42
+ - test/test_helper.rb
43
+ has_rdoc: true
44
+ homepage: http://github.com/qoobaa/sqs
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options:
49
+ - --charset=UTF-8
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.5
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: Simple Queue Service accessing library
71
+ test_files:
72
+ - test/signature_test.rb
73
+ - test/test_helper.rb