sqs_async 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/sqs.rb +162 -0
- data/lib/sqs_attributes.rb +38 -0
- data/lib/sqs_message.rb +22 -0
- data/lib/sqs_queue.rb +18 -0
- data/spec/fixtures/delete_message.xml +8 -0
- data/spec/fixtures/error_response.xml +18 -0
- data/spec/fixtures/list_queues.xml +13 -0
- data/spec/fixtures/queue_attributes.xml +40 -0
- data/spec/fixtures/receive_message.xml +40 -0
- data/spec/spec_helper.rb +100 -0
- data/spec/sqs_attributes_spec.rb +23 -0
- data/spec/sqs_message_spec.rb +20 -0
- data/spec/sqs_queue_spec.rb +18 -0
- data/spec/sqs_spec.rb +152 -0
- metadata +240 -0
data/lib/sqs.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
require 'hmac'
|
4
|
+
require 'hmac-sha2'
|
5
|
+
require 'base64'
|
6
|
+
require 'net/http'
|
7
|
+
require 'time'
|
8
|
+
require 'nokogiri'
|
9
|
+
require 'json'
|
10
|
+
require 'eventmachine'
|
11
|
+
require 'em-http-request'
|
12
|
+
require 'sqs_message'
|
13
|
+
require 'sqs_queue'
|
14
|
+
require 'sqs_attributes'
|
15
|
+
require 'logger'
|
16
|
+
|
17
|
+
module SQS
|
18
|
+
attr_accessor :aws_key, :aws_secret, :regions, :default_parameters, :post_options
|
19
|
+
|
20
|
+
def list_queues(options={})
|
21
|
+
prefix = options.delete(:prefix)
|
22
|
+
match = options.delete(:match)
|
23
|
+
|
24
|
+
options.merge!( "Action" => "ListQueues" )
|
25
|
+
options.merge!( "QueueNamePrefix" => encode(prefix) ) if prefix
|
26
|
+
|
27
|
+
call_amazon(options) do |req|
|
28
|
+
queues = SQSQueue.parse(req.response)
|
29
|
+
queues.select!{|q| q.queue_url.path.match(match) } if match
|
30
|
+
queues
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def receive_message(options={})
|
35
|
+
raise "no target queue specified" unless options[:queue]
|
36
|
+
options.merge!("Action" => "ReceiveMessage", "MaxNumberOfMessages" => 10 )
|
37
|
+
call_amazon(options){ |req| SQSMessage.parse(req.response) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete_message(options={})
|
41
|
+
raise "no Message specified" unless options[:message]
|
42
|
+
options.merge!("Action" => "DeleteMessage", "ReceiptHandle" => options.delete(:message).receipt_handle)
|
43
|
+
call_amazon(options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_queue_attributes(options={})
|
47
|
+
raise "no target queue specified" unless options[:queue]
|
48
|
+
options.merge!( "Action" => "GetQueueAttributes", "AttributeName" => "All" )
|
49
|
+
call_amazon(options){ |req| SQSAttributes.parse(req.response) }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def call_amazon(options)
|
55
|
+
endpoint = (options[:queue] != nil) ? options.delete(:queue).queue_url : "http://" << ( options.delete(:host) || region_host(:us_east) )
|
56
|
+
callbacks = options.delete(:callbacks) || {:success=>nil, :failure =>nil }
|
57
|
+
|
58
|
+
params = sign_params( endpoint, options )
|
59
|
+
req = EM::HttpRequest.new("#{endpoint}?#{params}").get
|
60
|
+
req.callback do |req|
|
61
|
+
if(req.response.to_s.match(/<ErrorResponse>/i))
|
62
|
+
on_failure(req, callbacks)
|
63
|
+
else
|
64
|
+
result = req
|
65
|
+
result = yield req if block_given?
|
66
|
+
callbacks[:success].call(result) if callbacks[:success]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
req.errback do |req|
|
70
|
+
on_failure(req, callbacks)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def on_failure(req, callbacks)
|
75
|
+
result = (req.error != nil) ? req.error : req.response
|
76
|
+
log result
|
77
|
+
callbacks[:failure].call(result) if callbacks[:failure]
|
78
|
+
end
|
79
|
+
|
80
|
+
def sign_params(uri, opts)
|
81
|
+
uri = URI.parse(uri) if uri.kind_of? String
|
82
|
+
opts = default_paramters.merge(opts)
|
83
|
+
|
84
|
+
sorted_params = opts.sort {|x,y| x[0] <=> y[0]}
|
85
|
+
encoded_params = sorted_params.collect do |p|
|
86
|
+
encode(p[0].to_s) << "=" << encode(p[1].to_s)
|
87
|
+
end
|
88
|
+
params_string = encoded_params.join("&")
|
89
|
+
|
90
|
+
req_desc = ["GET", uri.host.downcase, uri.request_uri, params_string].join("\n")
|
91
|
+
params_string << "&Signature=" << generate_signature(req_desc)
|
92
|
+
end
|
93
|
+
|
94
|
+
def generate_signature(request_description)
|
95
|
+
hmac = HMAC::SHA256.new(aws_secret)
|
96
|
+
hmac.update(request_description)
|
97
|
+
encode(Base64.encode64(hmac.digest).chomp)
|
98
|
+
end
|
99
|
+
|
100
|
+
def encoding_exclusions
|
101
|
+
/[^\w\d\-\_\.\~]/
|
102
|
+
end
|
103
|
+
|
104
|
+
def encode(val)
|
105
|
+
URI.encode(val, encoding_exclusions)
|
106
|
+
end
|
107
|
+
|
108
|
+
def region_host(key)
|
109
|
+
regions[key][:uri]
|
110
|
+
end
|
111
|
+
|
112
|
+
def regions
|
113
|
+
@regions ||= Regions
|
114
|
+
@regions
|
115
|
+
end
|
116
|
+
|
117
|
+
def default_paramters
|
118
|
+
@default_paramters ||= Parameters.merge("AWSAccessKeyId" => aws_key)
|
119
|
+
@default_paramters.merge("Expires" => (Time.now+(60*30)).utc.iso8601)
|
120
|
+
end
|
121
|
+
|
122
|
+
def post_options
|
123
|
+
@post_options ||= PostOptions
|
124
|
+
@post_options
|
125
|
+
end
|
126
|
+
|
127
|
+
def logger
|
128
|
+
return @logger if @logger
|
129
|
+
|
130
|
+
@logger = Logger.new @log_path || "./sqs_async.log"
|
131
|
+
@logger.level = @log_level || Logger::WARN
|
132
|
+
@logger
|
133
|
+
end
|
134
|
+
|
135
|
+
def log(msg)
|
136
|
+
log_msg = ["SERVICE ERROR"]
|
137
|
+
log_msg << caller[0..7].join("\n\t")
|
138
|
+
log_msg << "-".ljust(80, "-")
|
139
|
+
log_msg << msg
|
140
|
+
log_msg << "-".ljust(80, "-")
|
141
|
+
logger.error(log_msg.join("\n"))
|
142
|
+
end
|
143
|
+
|
144
|
+
Regions = {
|
145
|
+
:us_east => { :name => "US-East (Northern Virginia) Region", :uri => "sqs.us-east-1.amazonaws.com"},
|
146
|
+
:us_west => { :name => "US-West (Northern California) Region", :uri => "sqs.us-west-1.amazonaws.com"},
|
147
|
+
:eu => { :name => "EU (Ireland) Region", :uri => "sqs.eu-west-1.amazonaws.com"},
|
148
|
+
:asia_singapore => { :name => "Asia Pacific (Singapore) Region", :uri => "sqs.ap-southeast-1.amazonaws.com"},
|
149
|
+
:asia_tokyo => { :name => "Asia Pacific (Tokyo) Region", :uri => "sqs.ap-northeast-1.amazonaws.com"}
|
150
|
+
}
|
151
|
+
|
152
|
+
|
153
|
+
Parameters = {
|
154
|
+
"Version" => "2009-02-01",
|
155
|
+
"SignatureVersion"=>"2",
|
156
|
+
"SignatureMethod"=>"HmacSHA256",
|
157
|
+
}
|
158
|
+
|
159
|
+
PostOptions = {
|
160
|
+
"Content-Type" => "application/x-www-form-urlencoded"
|
161
|
+
}
|
162
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'json'
|
3
|
+
require 'base64'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
class SQSAttributes
|
7
|
+
attr_accessor :approximate_number_of_messages,
|
8
|
+
:approximate_number_of_messages_not_visible,
|
9
|
+
:visibility_timeout,
|
10
|
+
:create_timestamp,
|
11
|
+
:last_modified_timestamp,
|
12
|
+
:policy,
|
13
|
+
:maximum_message_size,
|
14
|
+
:message_retention_period,
|
15
|
+
:queue_arn
|
16
|
+
|
17
|
+
def self.parse(xml)
|
18
|
+
doc = Nokogiri::XML(xml)
|
19
|
+
queue_data = SQSAttributes.new
|
20
|
+
doc.search("Attribute").each do |attribute|
|
21
|
+
meth = underscore(attribute.at("Name").text)
|
22
|
+
if queue_data.respond_to? meth.to_sym
|
23
|
+
queue_data.send "#{meth}=", attribute.at("Value").text
|
24
|
+
end
|
25
|
+
end
|
26
|
+
queue_data
|
27
|
+
end
|
28
|
+
|
29
|
+
# Taken from ActiveSupport. License information can be found at rubyonrails.org
|
30
|
+
def self.underscore(camel_cased_word)
|
31
|
+
word = camel_cased_word.to_s.dup
|
32
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
33
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
34
|
+
word.tr!("-", "_")
|
35
|
+
word.downcase!
|
36
|
+
word
|
37
|
+
end
|
38
|
+
end
|
data/lib/sqs_message.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'json'
|
3
|
+
require 'base64'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
class SQSMessage
|
7
|
+
attr_accessor :body, :md5_of_body, :message_id, :receipt_handle
|
8
|
+
|
9
|
+
def self.parse(xml)
|
10
|
+
doc = Nokogiri::XML(xml)
|
11
|
+
messages = []
|
12
|
+
doc.search("Message").each do |message_element|
|
13
|
+
s = SQSMessage.new
|
14
|
+
s.body = message_element.at("Body").text
|
15
|
+
s.md5_of_body = message_element.at("MD5OfBody").text
|
16
|
+
s.message_id = message_element.at("MessageId").text
|
17
|
+
s.receipt_handle = message_element.at("ReceiptHandle").text.strip
|
18
|
+
messages << s
|
19
|
+
end
|
20
|
+
messages
|
21
|
+
end
|
22
|
+
end
|
data/lib/sqs_queue.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'json'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
class SQSQueue
|
6
|
+
attr_accessor :queue_url
|
7
|
+
|
8
|
+
def self.parse(xml)
|
9
|
+
doc = Nokogiri::XML(xml)
|
10
|
+
queues = []
|
11
|
+
doc.search("QueueUrl").each do |element|
|
12
|
+
s = SQSQueue.new
|
13
|
+
s.queue_url = URI.parse(element.text)
|
14
|
+
queues << s
|
15
|
+
end
|
16
|
+
queues
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<ErrorResponse>
|
3
|
+
<Error>
|
4
|
+
<Type>
|
5
|
+
Sender
|
6
|
+
</Type>
|
7
|
+
<Code>
|
8
|
+
AuthFailure
|
9
|
+
</Code>
|
10
|
+
<Message>
|
11
|
+
The provided signature is not valid for this access token
|
12
|
+
</Message>
|
13
|
+
<Detail/>
|
14
|
+
</Error>
|
15
|
+
<RequestId>
|
16
|
+
ef3aba6a-dc84-4937-91bf-cef2ddd6775a
|
17
|
+
</RequestId>
|
18
|
+
</ErrorResponse>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<ListQueuesResponse>
|
3
|
+
<ListQueuesResult>
|
4
|
+
<QueueUrl>
|
5
|
+
http://sqs.us-east-1.amazonaws.com/123456789012/testQueue
|
6
|
+
</QueueUrl>
|
7
|
+
</ListQueuesResult>
|
8
|
+
<ResponseMetadata>
|
9
|
+
<RequestId>
|
10
|
+
725275ae-0b9b-4762-b238-436d7c65a1ac
|
11
|
+
</RequestId>
|
12
|
+
</ResponseMetadata>
|
13
|
+
</ListQueuesResponse>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<GetQueueAttributesResponse>
|
3
|
+
<GetQueueAttributesResult>
|
4
|
+
<Attribute>
|
5
|
+
<Name>VisibilityTimeout</Name>
|
6
|
+
<Value>30</Value>
|
7
|
+
</Attribute>
|
8
|
+
<Attribute>
|
9
|
+
<Name>ApproximateNumberOfMessages</Name>
|
10
|
+
<Value>0</Value>
|
11
|
+
</Attribute>
|
12
|
+
<Attribute>
|
13
|
+
<Name>ApproximateNumberOfMessagesNotVisible</Name>
|
14
|
+
<Value>0</Value>
|
15
|
+
</Attribute>
|
16
|
+
<Attribute>
|
17
|
+
<Name>CreatedTimestamp</Name>
|
18
|
+
<Value>1286771522</Value>
|
19
|
+
</Attribute>
|
20
|
+
<Attribute>
|
21
|
+
<Name>LastModifiedTimestamp</Name>
|
22
|
+
<Value>1286771522</Value>
|
23
|
+
</Attribute>
|
24
|
+
<Attribute>
|
25
|
+
<Name>QueueArn</Name>
|
26
|
+
<Value>arn:aws:sqs:us-east-1:123456789012:qfoo</Value>
|
27
|
+
</Attribute>
|
28
|
+
<Attribute>
|
29
|
+
<Name>MaximumMessageSize</Name>
|
30
|
+
<Value>8192</Value>
|
31
|
+
</Attribute>
|
32
|
+
<Attribute>
|
33
|
+
<Name>MessageRetentionPeriod</Name>
|
34
|
+
<Value>345600</Value>
|
35
|
+
</Attribute>
|
36
|
+
</GetQueueAttributesResult>
|
37
|
+
<ResponseMetadata>
|
38
|
+
<RequestId>1ea71be5-b5a2-4f9d-b85a-945d8d08cd0b</RequestId>
|
39
|
+
</ResponseMetadata>
|
40
|
+
</GetQueueAttributesResponse>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<ReceiveMessageResponse>
|
3
|
+
<ReceiveMessageResult>
|
4
|
+
<Message>
|
5
|
+
<MessageId>
|
6
|
+
5fea7756-0ea4-451a-a703-a558b933e274
|
7
|
+
</MessageId>
|
8
|
+
<ReceiptHandle>
|
9
|
+
MbZj6wDWli+JvwwJaBV+3dcjk2YW2vA3+STFFljTM8tJJg6HRG6PYSasuWXPJB+Cw
|
10
|
+
Lj1FjgXUv1uSj1gUPAWV66FU/WeR4mq2OKpEGYWbnLmpRCJVAyeMjeU5ZBdtcQ+QE
|
11
|
+
auMZc8ZRv37sIW2iJKq3M9MFx1YvV11A2x/KSbkJ0=
|
12
|
+
</ReceiptHandle>
|
13
|
+
<MD5OfBody>
|
14
|
+
fafb00f5732ab283681e124bf8747ed1
|
15
|
+
</MD5OfBody>
|
16
|
+
<Body>This is a test message</Body>
|
17
|
+
<Attribute>
|
18
|
+
<Name>SenderId</Name>
|
19
|
+
<Value>195004372649</Value>
|
20
|
+
</Attribute>
|
21
|
+
<Attribute>
|
22
|
+
<Name>SentTimestamp</Name>
|
23
|
+
<Value>1238099229000</Value>
|
24
|
+
</Attribute>
|
25
|
+
<Attribute>
|
26
|
+
<Name>ApproximateReceiveCount</Name>
|
27
|
+
<Value>5</Value>
|
28
|
+
</Attribute>
|
29
|
+
<Attribute>
|
30
|
+
<Name>ApproximateFirstReceiveTimestamp</Name>
|
31
|
+
<Value>1250700979248</Value>
|
32
|
+
</Attribute>
|
33
|
+
</Message>
|
34
|
+
</ReceiveMessageResult>
|
35
|
+
<ResponseMetadata>
|
36
|
+
<RequestId>
|
37
|
+
b6633655-283d-45b4-aee4-4e84e0ae6afa
|
38
|
+
</RequestId>
|
39
|
+
</ResponseMetadata>
|
40
|
+
</ReceiveMessageResponse>
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
ENV["BUBBLE_ENV"] = "test"
|
2
|
+
|
3
|
+
$:.unshift(File.dirname(__FILE__))
|
4
|
+
%w{ ../ lib config vendor/sqs_async/lib }.each do |d|
|
5
|
+
$:.unshift(File.dirname(__FILE__)+"/#{d}/")
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'bundler/setup'
|
9
|
+
require 'rspec'
|
10
|
+
require 'timecop'
|
11
|
+
require 'em-http-request'
|
12
|
+
|
13
|
+
require 'sqs'
|
14
|
+
require 'sqs_message'
|
15
|
+
require 'sqs_queue'
|
16
|
+
require 'sqs_attributes'
|
17
|
+
|
18
|
+
|
19
|
+
RSpec.configure do |config|
|
20
|
+
config.mock_framework = :mocha
|
21
|
+
|
22
|
+
def xml_fixture(name)
|
23
|
+
File.read(File.dirname(__FILE__)+"/fixtures/#{name}.xml")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module EventMachine
|
28
|
+
class MockHttpRequest
|
29
|
+
def initialize *args
|
30
|
+
@callbacks = []
|
31
|
+
@errbacks = []
|
32
|
+
end
|
33
|
+
|
34
|
+
def get *args
|
35
|
+
return self
|
36
|
+
end
|
37
|
+
|
38
|
+
def post *args
|
39
|
+
return self
|
40
|
+
end
|
41
|
+
|
42
|
+
def callback &block
|
43
|
+
@callbacks << block
|
44
|
+
end
|
45
|
+
|
46
|
+
def errback &block
|
47
|
+
@errbacks << block
|
48
|
+
end
|
49
|
+
|
50
|
+
def succeed(response)
|
51
|
+
@callbacks.each {|c| c.call(response)}
|
52
|
+
reset
|
53
|
+
end
|
54
|
+
|
55
|
+
def error(response)
|
56
|
+
@errbacks.each {|c| c.call(response)}
|
57
|
+
reset
|
58
|
+
end
|
59
|
+
|
60
|
+
def reset
|
61
|
+
@callbacks = []
|
62
|
+
@errbacks = []
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class HttpRequest
|
67
|
+
def self.new *args
|
68
|
+
request = MockHttpRequest.new
|
69
|
+
connection_instances << request
|
70
|
+
request
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.connection_instances
|
74
|
+
@connection_instances ||= []
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.succeed(response)
|
78
|
+
connection_instances.each do |conn|
|
79
|
+
conn.succeed(response)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.error(response)
|
84
|
+
connection_instances.each do |conn|
|
85
|
+
conn.error(response)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class MockResponse
|
91
|
+
def initialize(body)
|
92
|
+
@body = body
|
93
|
+
end
|
94
|
+
|
95
|
+
def response
|
96
|
+
@body
|
97
|
+
end
|
98
|
+
alias :error :response
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "SQSAttributes" do
|
4
|
+
describe ".parse" do
|
5
|
+
let(:attributes_obj) { SQSAttributes.parse(xml_fixture(:queue_attributes)) }
|
6
|
+
|
7
|
+
it "returns SQSAttributes objects" do
|
8
|
+
attributes_obj.should be_a_kind_of(SQSAttributes)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "parses attributes" do
|
12
|
+
attributes_obj.approximate_number_of_messages.should == "0"
|
13
|
+
attributes_obj.approximate_number_of_messages_not_visible.should == "0"
|
14
|
+
attributes_obj.visibility_timeout.should == "30"
|
15
|
+
attributes_obj.create_timestamp.should == nil
|
16
|
+
attributes_obj.last_modified_timestamp.should == "1286771522"
|
17
|
+
attributes_obj.policy.should == nil
|
18
|
+
attributes_obj.maximum_message_size.should == "8192"
|
19
|
+
attributes_obj.message_retention_period.should == "345600"
|
20
|
+
attributes_obj.queue_arn.should == "arn:aws:sqs:us-east-1:123456789012:qfoo"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "SQSMessage" do
|
4
|
+
describe ".parse" do
|
5
|
+
let(:messages) { SQSMessage.parse(xml_fixture(:receive_message)) }
|
6
|
+
|
7
|
+
it "returns SQSMessage objects" do
|
8
|
+
messages.length.should == 1
|
9
|
+
messages.first.should be_a_kind_of(SQSMessage)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "parses attributes" do
|
13
|
+
message = messages.first
|
14
|
+
message.body.should == "This is a test message"
|
15
|
+
message.receipt_handle.should match /MbZj6wDWli.+?/im
|
16
|
+
message.md5_of_body.should == "\n fafb00f5732ab283681e124bf8747ed1\n "
|
17
|
+
message.message_id.should == "\n 5fea7756-0ea4-451a-a703-a558b933e274\n "
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sqs_message'
|
3
|
+
|
4
|
+
describe "SQSQueue" do
|
5
|
+
describe ".parse" do
|
6
|
+
let(:queues) { SQSQueue.parse(xml_fixture(:list_queues)) }
|
7
|
+
|
8
|
+
it "returns SQSQueue objects" do
|
9
|
+
queues.length.should == 1
|
10
|
+
queues.first.should be_a_kind_of(SQSQueue)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "parses attributes" do
|
14
|
+
queue = queues.first
|
15
|
+
queue.queue_url.should == URI.parse("http://sqs.us-east-1.amazonaws.com/123456789012/testQueue")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/spec/sqs_spec.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'sqs'
|
3
|
+
|
4
|
+
class MySQSClient
|
5
|
+
include SQS
|
6
|
+
def initialize
|
7
|
+
self.aws_key = 'adsf'
|
8
|
+
self.aws_secret = 'adsffasdf'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "SQS" do
|
13
|
+
let(:client) { MySQSClient.new }
|
14
|
+
let(:queue) { SQSQueue.new.tap { |q| q.queue_url = URI.parse("http://sqs.us-east-1.amazonaws.com/123456789012/testQueue") } }
|
15
|
+
let(:message) { SQSMessage.new.tap {|msg| msg.receipt_handle = "foo" } }
|
16
|
+
|
17
|
+
# we are testing private members because the
|
18
|
+
# interaction with Amazon is stubbed. Therefore
|
19
|
+
# we want to ensure that these perform as expected
|
20
|
+
# even though they would normally be encapsulated by
|
21
|
+
# calls to amazon.
|
22
|
+
#
|
23
|
+
# Leon and John 5-31-11
|
24
|
+
context "Private member testing" do
|
25
|
+
|
26
|
+
before(:all) do
|
27
|
+
MySQSClient.send :public, :sign_params
|
28
|
+
MySQSClient.send :public, :generate_signature
|
29
|
+
end
|
30
|
+
|
31
|
+
after(:all) do
|
32
|
+
MySQSClient.send :private, :sign_params
|
33
|
+
MySQSClient.send :private, :generate_signature
|
34
|
+
end
|
35
|
+
|
36
|
+
it "signs requests prior to sending" do
|
37
|
+
Timecop.freeze(Time.parse("2011-04-20T00:00:00")) do
|
38
|
+
client.sign_params("http://edgecase.com/mysevice", {}).should == "AWSAccessKeyId=adsf&Expires=2011-04-20T04%3A30%3A00Z&SignatureMethod=HmacSHA256&SignatureVersion=2&Version=2009-02-01&Signature=oKFwSahHQIMKiSOabVZqwcZEFowOdqyyj2gamyjJ3oU%3D"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "generates valid signatures based on params" do
|
43
|
+
req_desc = ["GET", 'foo', '/', "foo=bar&baz=fellini"].join("\n")
|
44
|
+
client.generate_signature(req_desc).should == "WHmv1xv6iqPMw6kaw0sXVlqXfmoqkFpkKqBi2ONpAa4%3D"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "calls Amazon Endpoints asynchronously to" do
|
49
|
+
it "list available queues" do
|
50
|
+
client.list_queues(
|
51
|
+
:callbacks => {
|
52
|
+
:success => lambda{|queues|
|
53
|
+
queues.length.should == 1
|
54
|
+
queues.first.queue_url.to_s.should == "http://sqs.us-east-1.amazonaws.com/123456789012/testQueue"
|
55
|
+
}
|
56
|
+
}
|
57
|
+
)
|
58
|
+
EM::HttpRequest.succeed(EM::MockResponse.new(xml_fixture(:list_queues)))
|
59
|
+
end
|
60
|
+
|
61
|
+
it "pull a message from the queue" do
|
62
|
+
queue.queue_url = URI.parse("http://sqs.us-east-1.amazonaws.com/123456789012/testQueue")
|
63
|
+
client.receive_message(
|
64
|
+
:queue => queue,
|
65
|
+
:callbacks => {
|
66
|
+
:success => lambda{|messages|
|
67
|
+
messages.kind_of?(Enumerable).should == true
|
68
|
+
messages.length.should == 1
|
69
|
+
messages.first.kind_of?(SQSMessage).should == true
|
70
|
+
}
|
71
|
+
}
|
72
|
+
)
|
73
|
+
|
74
|
+
EM::HttpRequest.succeed(EM::MockResponse.new(xml_fixture(:receive_message)))
|
75
|
+
end
|
76
|
+
|
77
|
+
it "delete a message from the queue" do
|
78
|
+
mock_obj = mock();
|
79
|
+
mock_obj.expects(:call).once
|
80
|
+
client.delete_message(
|
81
|
+
:queue => queue,
|
82
|
+
:message => message,
|
83
|
+
:callbacks => { :success => mock_obj }
|
84
|
+
)
|
85
|
+
|
86
|
+
EM::HttpRequest.succeed(EM::MockResponse.new(xml_fixture(:delete_message)))
|
87
|
+
end
|
88
|
+
|
89
|
+
it "gets the queue's attributes" do
|
90
|
+
client.get_queue_attributes(
|
91
|
+
:queue => queue,
|
92
|
+
:callbacks => {
|
93
|
+
:success => lambda{|attr_obj|
|
94
|
+
attr_obj.kind_of?(SQSAttributes).should == true
|
95
|
+
}
|
96
|
+
}
|
97
|
+
)
|
98
|
+
|
99
|
+
EM::HttpRequest.succeed(EM::MockResponse.new(xml_fixture(:queue_attributes)))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
context "error result" do
|
103
|
+
let(:fail_mock) do
|
104
|
+
fail_mock = mock
|
105
|
+
fail_mock.expects(:call).once
|
106
|
+
fail_mock
|
107
|
+
end
|
108
|
+
|
109
|
+
let(:error_result) do
|
110
|
+
EM::MockResponse.new(xml_fixture(:error_response))
|
111
|
+
end
|
112
|
+
|
113
|
+
it "list_queues failure" do
|
114
|
+
client.list_queues(
|
115
|
+
:callbacks => {
|
116
|
+
:failure => fail_mock
|
117
|
+
}
|
118
|
+
)
|
119
|
+
EM::HttpRequest.succeed(error_result)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "list_queues failure on misc http error not related to amazon" do
|
123
|
+
client.list_queues(
|
124
|
+
:callbacks => {
|
125
|
+
:failure => fail_mock
|
126
|
+
}
|
127
|
+
)
|
128
|
+
EM::HttpRequest.error(error_result)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "receive_message failure" do
|
132
|
+
client.receive_message(
|
133
|
+
:queue => queue,
|
134
|
+
:callbacks => {
|
135
|
+
:failure => fail_mock
|
136
|
+
}
|
137
|
+
)
|
138
|
+
EM::HttpRequest.succeed(error_result)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "delete_message failure" do
|
142
|
+
client.delete_message(
|
143
|
+
:queue => queue,
|
144
|
+
:message => message,
|
145
|
+
:callbacks => {
|
146
|
+
:failure => fail_mock
|
147
|
+
}
|
148
|
+
)
|
149
|
+
EM::HttpRequest.succeed(error_result)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
metadata
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sqs_async
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- EdgeCase <contact@edgecase.com>
|
14
|
+
- John Andrews <john@edgecase.com>
|
15
|
+
- Leon Gersing <leon@edgecase.com>
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
|
20
|
+
date: 2011-05-31 00:00:00 -04:00
|
21
|
+
default_executable:
|
22
|
+
dependencies:
|
23
|
+
- !ruby/object:Gem::Dependency
|
24
|
+
name: eventmachine
|
25
|
+
prerelease: false
|
26
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ~>
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
hash: 62196357
|
32
|
+
segments:
|
33
|
+
- 1
|
34
|
+
- 0
|
35
|
+
- 0
|
36
|
+
- beta
|
37
|
+
- 3
|
38
|
+
version: 1.0.0.beta.3
|
39
|
+
type: :runtime
|
40
|
+
version_requirements: *id001
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: em-http-request
|
43
|
+
prerelease: false
|
44
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ~>
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
hash: 62196363
|
50
|
+
segments:
|
51
|
+
- 1
|
52
|
+
- 0
|
53
|
+
- 0
|
54
|
+
- beta
|
55
|
+
- 4
|
56
|
+
version: 1.0.0.beta.4
|
57
|
+
type: :runtime
|
58
|
+
version_requirements: *id002
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: nokogiri
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
type: :runtime
|
72
|
+
version_requirements: *id003
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: json
|
75
|
+
prerelease: false
|
76
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
hash: 3
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
type: :runtime
|
86
|
+
version_requirements: *id004
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: ruby-hmac
|
89
|
+
prerelease: false
|
90
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
hash: 3
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
version: "0"
|
99
|
+
type: :runtime
|
100
|
+
version_requirements: *id005
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: rspec
|
103
|
+
prerelease: false
|
104
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 23
|
110
|
+
segments:
|
111
|
+
- 2
|
112
|
+
- 6
|
113
|
+
- 0
|
114
|
+
version: 2.6.0
|
115
|
+
type: :development
|
116
|
+
version_requirements: *id006
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: rspec-core
|
119
|
+
prerelease: false
|
120
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
hash: 3
|
126
|
+
segments:
|
127
|
+
- 0
|
128
|
+
version: "0"
|
129
|
+
type: :development
|
130
|
+
version_requirements: *id007
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: rspec-expectations
|
133
|
+
prerelease: false
|
134
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
135
|
+
none: false
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
hash: 3
|
140
|
+
segments:
|
141
|
+
- 0
|
142
|
+
version: "0"
|
143
|
+
type: :development
|
144
|
+
version_requirements: *id008
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: mocha
|
147
|
+
prerelease: false
|
148
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
hash: 3
|
154
|
+
segments:
|
155
|
+
- 0
|
156
|
+
version: "0"
|
157
|
+
type: :development
|
158
|
+
version_requirements: *id009
|
159
|
+
- !ruby/object:Gem::Dependency
|
160
|
+
name: timecop
|
161
|
+
prerelease: false
|
162
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
163
|
+
none: false
|
164
|
+
requirements:
|
165
|
+
- - ">="
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
hash: 3
|
168
|
+
segments:
|
169
|
+
- 0
|
170
|
+
version: "0"
|
171
|
+
type: :development
|
172
|
+
version_requirements: *id010
|
173
|
+
description: A simple library that leverages Event Machine to issue requests to the Amazon SQS service while blocking as little as possible
|
174
|
+
email: contact@edgecase.com
|
175
|
+
executables: []
|
176
|
+
|
177
|
+
extensions: []
|
178
|
+
|
179
|
+
extra_rdoc_files: []
|
180
|
+
|
181
|
+
files:
|
182
|
+
- lib/sqs.rb
|
183
|
+
- lib/sqs_attributes.rb
|
184
|
+
- lib/sqs_message.rb
|
185
|
+
- lib/sqs_queue.rb
|
186
|
+
- spec/fixtures/delete_message.xml
|
187
|
+
- spec/fixtures/error_response.xml
|
188
|
+
- spec/fixtures/list_queues.xml
|
189
|
+
- spec/fixtures/queue_attributes.xml
|
190
|
+
- spec/fixtures/receive_message.xml
|
191
|
+
- spec/spec_helper.rb
|
192
|
+
- spec/sqs_attributes_spec.rb
|
193
|
+
- spec/sqs_message_spec.rb
|
194
|
+
- spec/sqs_queue_spec.rb
|
195
|
+
- spec/sqs_spec.rb
|
196
|
+
has_rdoc: true
|
197
|
+
homepage: https://github.com/edgecase/sqs_async
|
198
|
+
licenses: []
|
199
|
+
|
200
|
+
post_install_message:
|
201
|
+
rdoc_options: []
|
202
|
+
|
203
|
+
require_paths:
|
204
|
+
- lib
|
205
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
206
|
+
none: false
|
207
|
+
requirements:
|
208
|
+
- - ">="
|
209
|
+
- !ruby/object:Gem::Version
|
210
|
+
hash: 3
|
211
|
+
segments:
|
212
|
+
- 0
|
213
|
+
version: "0"
|
214
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
215
|
+
none: false
|
216
|
+
requirements:
|
217
|
+
- - ">="
|
218
|
+
- !ruby/object:Gem::Version
|
219
|
+
hash: 3
|
220
|
+
segments:
|
221
|
+
- 0
|
222
|
+
version: "0"
|
223
|
+
requirements: []
|
224
|
+
|
225
|
+
rubyforge_project: sqs_async
|
226
|
+
rubygems_version: 1.5.0
|
227
|
+
signing_key:
|
228
|
+
specification_version: 3
|
229
|
+
summary: Non-Blocking SQS library.
|
230
|
+
test_files:
|
231
|
+
- spec/fixtures/delete_message.xml
|
232
|
+
- spec/fixtures/error_response.xml
|
233
|
+
- spec/fixtures/list_queues.xml
|
234
|
+
- spec/fixtures/queue_attributes.xml
|
235
|
+
- spec/fixtures/receive_message.xml
|
236
|
+
- spec/spec_helper.rb
|
237
|
+
- spec/sqs_attributes_spec.rb
|
238
|
+
- spec/sqs_message_spec.rb
|
239
|
+
- spec/sqs_queue_spec.rb
|
240
|
+
- spec/sqs_spec.rb
|