em-amazon-sqs 0.1.0

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/example/server.rb ADDED
@@ -0,0 +1,27 @@
1
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..'))
2
+ require 'rubygems'
3
+ require 'lib/simple_qs'
4
+
5
+ unless ENV['AWS_ACCESS_KEY_ID'] && ENV['AWS_SECRET_ACCESS_KEY'] && ENV['AWS_ACCOUNT_ID']
6
+ abort "Please set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_ACCOUNT_ID environment variables"
7
+ end
8
+
9
+ SimpleQS.access_key_id = ENV['AWS_ACCESS_KEY_ID']
10
+ SimpleQS.secret_access_key = ENV['AWS_SECRET_ACCESS_KEY']
11
+ SimpleQS.account_id = ENV['AWS_ACCOUNT_ID']
12
+
13
+ @queue = SimpleQS::Queue.create('testMessageBus')
14
+
15
+ while true do
16
+ messages = @queue.receive_messages
17
+ unless messages.empty?
18
+ message = messages.first
19
+ message.delete
20
+ if message.body == 'quit'
21
+ puts "Quitting..."
22
+ break
23
+ else
24
+ puts "[MESSAGE]: #{message.body}"
25
+ end
26
+ end
27
+ end
data/lib/simple_qs.rb ADDED
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+
3
+ $: << File.join(File.dirname(__FILE__))
4
+
5
+ module SimpleQS
6
+
7
+ API_VERSION = '2009-02-01'
8
+
9
+ SQS_HOSTS = {
10
+ :us_east_1 => 'sqs.us-east-1.amazonaws.com',
11
+ :us_west_1 => 'sqs.us-west-1.amazonaws.com',
12
+ :eu_west_1 => 'sqs.eu-west-1.amazonaws.com',
13
+ :ap_southeast_1 => 'sqs.ap-southeast-1.amazonaws.com'
14
+ }
15
+
16
+ autoload :Message, 'simple_qs/message'
17
+ autoload :Queue, 'simple_qs/queue'
18
+ autoload :Request, 'simple_qs/request'
19
+ autoload :Responce, 'simple_qs/responce'
20
+
21
+ class << self
22
+ attr_accessor :access_key_id, :secret_access_key
23
+
24
+ def account_id=(value)
25
+ @account_id = value.gsub(/[^0-9]/, '')
26
+ end
27
+ attr_reader :account_id
28
+
29
+ def host=(value)
30
+ raise ArgumentError, 'Expected value to be one of: :us_east_1, :us_west_1, :eu_west_1, :ap_southeast_1' unless SQS_HOSTS.key?(value)
31
+ @host = value
32
+ end
33
+
34
+ def host
35
+ @host ||= :us_east_1
36
+ SQS_HOSTS[@host]
37
+ end
38
+ end
39
+ end
40
+
41
+ require 'version'
@@ -0,0 +1,135 @@
1
+ module SimpleQS
2
+ class Message
3
+
4
+ class DoubleSendError < StandardError; end
5
+ class NotReceivedError < StandardError; end
6
+
7
+ attr_accessor :queue
8
+ attr_reader :message_id, :receipt_handle, :body, :md5_of_body
9
+ attr_reader :sender_id, :sent_timestamp, :approximate_receive_count, :approximate_first_receive_timestamp
10
+
11
+ def initialize(queue, params = nil)
12
+ @queue = queue
13
+
14
+ @body = params if params.class == String
15
+ _from_responce(params) if params.class == SimpleQS::Responce
16
+ _from_hash(params) if params.class == Hash
17
+ end
18
+
19
+ def send
20
+ raise DoubleSendError, "Cann't send already sent message. Use resend() method." if message_id
21
+ raise DoubleSendError, "Cann't send received message. Use resend() method." if receipt_handle
22
+
23
+ params = {
24
+ 'Action' => 'SendMessage',
25
+ 'MessageBody' => body
26
+ }
27
+ request = queue.build_request(:post, params)
28
+ responce = request.perform
29
+ raise responce.to_error unless responce.successful?
30
+ _from_responce(responce)
31
+
32
+ self
33
+ end
34
+
35
+ def resend
36
+ dup.send
37
+ end
38
+
39
+ def delete
40
+ raise NotReceivedError, "Cann't delete message that was not received" unless receipt_handle
41
+ params = {
42
+ 'Action' => 'DeleteMessage',
43
+ 'ReceiptHandle' => receipt_handle
44
+ }
45
+ request = queue.build_request(:get, params)
46
+ responce = request.perform
47
+ raise responce.to_error unless responce.successful?
48
+ end
49
+ alias_method :destroy, :delete
50
+
51
+ def change_visibility(visibility_timeout)
52
+ SimpleQS::Queue.check_visibility_timeout(visibility_timeout)
53
+ raise NotReceivedError, "Cann't change visibility timeout for message that was not received" unless receipt_handle
54
+
55
+ params = {
56
+ 'Action' => 'ChangeMessageVisibility',
57
+ 'ReceiptHandle' => receipt_handle,
58
+ 'VisibilityTimeout' => visibility_timeout
59
+ }
60
+
61
+ request = queue.build_request(:get, params)
62
+ responce = request.perform
63
+
64
+ raise responce.to_error unless responce.successful?
65
+ end
66
+
67
+ def dup
68
+ self.class.new(queue, body)
69
+ end
70
+
71
+ def ==(other)
72
+ message_id == other.message_id && receipt_handle == other.receipt_handle
73
+ end
74
+
75
+ class << self
76
+ def send(queue, message_body)
77
+ new(queue, message_body).send
78
+ end
79
+
80
+ def receive(queue, attributes = nil, max_number_of_messages = nil, visibility_timeout = nil)
81
+
82
+ SimpleQS::Queue.check_visibility_timeout(visibility_timeout) if visibility_timeout
83
+ if max_number_of_messages && !(1..10).include?(max_number_of_messages)
84
+ raise ArgumentError, "Maximum number of messages should be in 1..10 range"
85
+ end
86
+
87
+ params = {
88
+ 'Action' => 'ReceiveMessage'
89
+ }
90
+
91
+ if attributes
92
+ attributes = [attributes] unless attributes.class == Array
93
+ attributes.uniq!
94
+ unless (attributes - [:All, :SenderId, :SentTimestamp, :ApproximateReceiveCount, :ApproximateFirstReceiveTimestamp]).empty?
95
+ raise ArgumentError,\
96
+ "Allowed attributes: :All, :SenderId, :SentTimestamp, :ApproximateReceiveCount, :ApproximateFirstReceiveTimestamp"
97
+ end
98
+ attributes.each_index do |i|
99
+ params["AttributeName.#{i + 1}"] = attributes[i]
100
+ end
101
+ end
102
+ params['MaxNumberOfMessages'] = max_number_of_messages if max_number_of_messages
103
+ params['VisibilityTimeout'] = visibility_timeout if visibility_timeout
104
+
105
+ request = queue.build_request(:get, params)
106
+ responce = request.perform
107
+ if responce.respond_to?(:message)
108
+ messages = (responce.message.class == Array ? responce.message : [responce.message])
109
+ messages.map {|message| new(queue, message)}
110
+ else
111
+ []
112
+ end
113
+ end
114
+ end
115
+
116
+ protected
117
+
118
+ def _from_responce(responce)
119
+ @message_id = responce.message_id
120
+ @md5_of_body = responce.md5_of_message_body
121
+ end
122
+
123
+ def _from_hash(message)
124
+ attributes = message.delete('Attribute')
125
+ attributes.each do |attr|
126
+ message[attr['Name']] = attr['Value']
127
+ end if attributes
128
+ message.each do |key, value|
129
+ key = key.gsub(/([A-Z]+)/, '_\1').downcase.gsub(/^_/, '')
130
+ instance_variable_set("@#{key}".to_sym, value) if respond_to?(key.to_sym)
131
+ end
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,298 @@
1
+ module SimpleQS
2
+ class Queue
3
+
4
+ class MaxVisibilityError < StandardError; end
5
+ class MaxMessageSizeError < StandardError; end
6
+ class MessageRetentionPeriodError < StandardError; end
7
+
8
+ MAX_VISIBILITY_TIMEOUT = 43200
9
+
10
+ attr_accessor :queue_url
11
+
12
+ # Initializes new SimpleQS::Queue object
13
+ # Parameters:
14
+ # queue_url_or_name - String, either queue url or queue name
15
+ def initialize(queue_url_or_name)
16
+ begin
17
+ self.class.check_queue_name(queue_url_or_name)
18
+ @name = queue_url_or_name
19
+ @queue_url = "http://#{SimpleQS.host}/#{SimpleQS.account_id}/#{@name}"
20
+ rescue ArgumentError
21
+ @queue_url = queue_url_or_name
22
+ end
23
+ end
24
+
25
+ # Get queue name
26
+ # Returns:
27
+ # String - queue name
28
+ def name
29
+ @name ||= @queue_url.split(/\//).last
30
+ end
31
+
32
+ # Is this queue deleted?
33
+ # Returns:
34
+ # Bool - true if queue is deleted
35
+ def deleted
36
+ @deleted ||= false
37
+ end
38
+ alias_method :deleted?, :deleted
39
+
40
+
41
+ # Deletes this queue
42
+ # Returns:
43
+ # Bool - true, if successful
44
+ # Raises:
45
+ # SimpleQS::Responce::Error
46
+ def delete
47
+ params = {
48
+ 'Action' => 'DeleteQueue'
49
+ }
50
+
51
+ request = build_request(:get, params)
52
+ responce = request.perform
53
+
54
+ raise responce.to_error unless responce.successful?
55
+
56
+ @deleted = true
57
+ end
58
+ alias_method :destroy, :delete
59
+
60
+ def send_message(body)
61
+ SimpleQS::Message.send(self, body)
62
+ end
63
+
64
+ def receive_messages(attributes = nil, max_number_of_messages = nil, visibility_timeout = nil)
65
+ SimpleQS::Message.receive(self, attributes, max_number_of_messages, visibility_timeout)
66
+ end
67
+
68
+ def get_attributes(attribute_or_array = nil)
69
+ params = {
70
+ 'Action' => 'GetQueueAttributes'
71
+ }
72
+
73
+ if attribute_or_array
74
+ attribute_or_array = [attribute_or_array] unless attribute_or_array.class == Array
75
+ attribute_or_array.uniq!
76
+ unless (attribute_or_array - [:All, :ApproximateNumberOfMessages, :ApproximateNumberOfMessagesNotVisible,
77
+ :VisibilityTimeout, :CreatedTimestamp, :LastModifiedTimestamp, :Policy]).empty?
78
+ raise ArgumentError,\
79
+ "Allowed attributes: :All, :ApproximateNumberOfMessages, :ApproximateNumberOfMessagesNotVisible, " <<
80
+ ":VisibilityTimeout, :CreatedTimestamp, :LastModifiedTimestamp, :Policy"
81
+ end
82
+ attribute_or_array.each_index do |i|
83
+ params["AttributeName.#{i + 1}"] = attribute_or_array[i]
84
+ end
85
+ end
86
+
87
+ request = build_request(:get, params)
88
+ responce = request.perform
89
+ raise responce.to_error unless responce.successful?
90
+
91
+ if responce.respond_to?(:attribute)
92
+ responce.attribute.inject({}) do |result, key_value|
93
+ result[key_value['Name']] = key_value['Value']
94
+ result
95
+ end
96
+ else
97
+ {}
98
+ end
99
+ end
100
+
101
+ def set_visibility_timeout(visibility_timeout)
102
+ set_attributes({:VisibilityTimeout => visibility_timeout})
103
+ end
104
+
105
+ def set_policy(policy)
106
+ set_attributes({:Policy => policy})
107
+ end
108
+
109
+ def set_maximum_message_size(message_size)
110
+ set_attributes({:MaximumMessageSize => message_size})
111
+ end
112
+
113
+ def set_message_retention_period(period)
114
+ set_attributes({:MessageRetentionPeriod => period})
115
+ end
116
+
117
+ ALLOWED_ATTRIBUTES = [
118
+ :VisibilityTimeout,
119
+ :Policy,
120
+ :MaximumMessageSize,
121
+ :MessageRetentionPeriod
122
+ ]
123
+
124
+ def set_attributes(attributes)
125
+ unless (attributes.keys - ALLOWED_ATTRIBUTES).empty?
126
+ raise ArgumentError, "Allowed attributes: #{ALLOWED_ATTRIBUTES.map {|attr| ":" << attr.to_s}.join(", ")}"
127
+ end
128
+ self.class.check_visibility_timeout(attributes[:VisibilityTimeout]) if attributes[:VisibilityTimeout]
129
+ if attributes[:MaximumMessageSize] && !(1024..65536).include?(attributes[:MaximumMessageSize])
130
+ raise MaxMessageSizeError, 'MaximumMessageSize should be between 1024 and 65536'
131
+ end
132
+ if attributes[:MessageRetentionPeriod] && !(3600..1209600).include?(attributes[:MessageRetentionPeriod])
133
+ raise MessageRetentionPeriodError, 'MessageRetentionPeriod should be between 3600 and 1209600'
134
+ end
135
+
136
+ params = {
137
+ 'Action' => 'SetQueueAttributes'
138
+ }
139
+
140
+ i = 1
141
+ attributes.each do |key, value|
142
+ params["Attribute.#{i}.Name"] = key.to_s
143
+ params["Attribute.#{i}.Value"] = value
144
+ i += 1
145
+ end
146
+
147
+ request = build_request(:get, params)
148
+ responce = request.perform
149
+
150
+ raise responce.to_error unless responce.successful?
151
+ end
152
+
153
+ # label - String, identifier for permissions
154
+ # permissions - Array of Hashes, [{:account_id => '125074342641', :action => 'SendMessage'}, ...]
155
+ def add_permissions(label, permissions)
156
+ self.class.check_queue_name(label)
157
+
158
+ params = {
159
+ 'Action' => 'AddPermission',
160
+ 'Label' => label
161
+ }
162
+
163
+ i = 1
164
+ permissions.each do |permission_hash|
165
+ raise ArgumentError, "invalid action: #{permission_hash[:action]}"\
166
+ unless ['*', 'SendMessage', 'ReceiveMessage', 'DeleteMessage',
167
+ 'ChangeMessageVisibility', 'GetQueueAttributes'].include?(permission_hash[:action])
168
+
169
+ params["AWSAccountId.#{i}"] = permission_hash[:account_id]
170
+ params["ActionName.#{i}"] = permission_hash[:action]
171
+ i += 1
172
+ end
173
+
174
+ request = build_request(:post, params)
175
+ responce = request.perform
176
+
177
+ raise responce.to_error unless responce.successful?
178
+ end
179
+
180
+ def remove_permissions(label)
181
+ params = {
182
+ 'Action' => 'RemovePermission',
183
+ 'Label' => label
184
+ }
185
+
186
+ request = build_request(:get, params)
187
+ responce = request.perform
188
+
189
+ raise responce.to_error unless responce.successful?
190
+ end
191
+
192
+ def build_request(method, params)
193
+ request = SimpleQS::Request.build(method, params)
194
+ request.query_string = [SimpleQS.account_id, name]
195
+ request
196
+ end
197
+
198
+ class << self
199
+ # Create new queue
200
+ # Parameters:
201
+ # name - String, name of queue. Should be not longer than 80 chars and contain only a-zA-Z0-9\-\_
202
+ # default_visibility_timeout - Fixnum, visibility timeout [Optional]. In range 0 to MAX_VISIBILITY_TIMEOUT.
203
+ # If nil, then default is used (30 seconds).
204
+ # &block - if given, then newly created queue object passed to it
205
+ # Returns:
206
+ # SimpleQS::Queue
207
+ # Raises:
208
+ # ArgumentError
209
+ # SimpleQS::Queue::MaxVisibilityError
210
+ # SimpleQS::Responce::Error
211
+ def create(name, default_visibility_timeout = nil, &block)
212
+
213
+ check_queue_name(name)
214
+ check_visibility_timeout(default_visibility_timeout)
215
+
216
+ params = {
217
+ 'Action' => 'CreateQueue',
218
+ 'QueueName' => name
219
+ }
220
+ params['DefaultVisibilityTimeout'] = default_visibility_timeout if default_visibility_timeout
221
+
222
+ request = SimpleQS::Request.build(:get, params)
223
+ responce = request.perform
224
+ if responce.successful?
225
+ queue = new(responce.queue_url)
226
+ yield queue if block_given?
227
+ queue
228
+ else
229
+ raise responce.to_error
230
+ end
231
+ end
232
+
233
+ # List queues
234
+ # Parameters:
235
+ # pattern - String, first letterns of queues names [Optional].
236
+ # Returns:
237
+ # Array - array of SimpleQS::Queue or empty if nothing found
238
+ # Rises:
239
+ # ArgumentError
240
+ # SimpleQS::Responce::Error
241
+ def list(pattern = nil)
242
+ params = {
243
+ 'Action' => 'ListQueues'
244
+ }
245
+
246
+ check_queue_name(pattern) if pattern
247
+ params['QueueNamePrefix'] = pattern if pattern
248
+
249
+ request = SimpleQS::Request.build(:get, params)
250
+ responce = request.perform
251
+ raise responce.to_error unless responce.successful?
252
+
253
+ begin
254
+ queues = responce.queue_url.class == Array ? responce.queue_url : [responce.queue_url]
255
+ queues.map {|queue_url| new(queue_url)}
256
+ rescue NoMethodError
257
+ []
258
+ end
259
+ end
260
+
261
+ def exists?(queue_name)
262
+ list(queue_name).any? {|queue| queue.name == queue_name}
263
+ end
264
+
265
+ def delete(queue_name)
266
+ new(queue_name).delete
267
+ end
268
+ alias_method :destroy, :delete
269
+
270
+ # Performs checks on queue name. Raises ArgumentError with message in case of
271
+ # constraint violation
272
+ def check_queue_name(name)
273
+ raise ArgumentError, "expected to be String, but got #{name.class.to_s}" unless name.class == String
274
+ raise(
275
+ ArgumentError,
276
+ "should be maximum 80 characters long"
277
+ ) if name.length > 80
278
+ raise(
279
+ ArgumentError,
280
+ "should contain only alphanumeric characters, hyphens (-), and underscores (_)"
281
+ ) if name =~ /[^a-zA-Z0-9\_\-]/
282
+ end
283
+
284
+ # Performs checks on visibility timeout. Raises ArgumentError or SimpleQS::Queue::MaxVisibilityError
285
+ # with message in case of constraint violation
286
+ def check_visibility_timeout(default_visibility_timeout)
287
+ raise(
288
+ ArgumentError,
289
+ "expected visibility timeout to respon to to_i, but #{default_visibility_timeout.class.to_s} doesn't"
290
+ ) if default_visibility_timeout && default_visibility_timeout.class != Fixnum
291
+ raise(
292
+ MaxVisibilityError,
293
+ "expected visibility timeout to be in 0...#{MAX_VISIBILITY_TIMEOUT} range, got #{default_visibility_timeout}"
294
+ ) if default_visibility_timeout && !(0...MAX_VISIBILITY_TIMEOUT).include?(default_visibility_timeout)
295
+ end
296
+ end
297
+ end
298
+ end