right_aws 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,369 @@
1
+ #
2
+ # Copyright (c) 2007 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ module RightAws
25
+
26
+ #
27
+ # SQS.
28
+ #
29
+ # sqs = RightAws::Sqs.new(aws_access_key_id, aws_secret_access_key)
30
+ # queue1 = sqs.queue('my_awesome_queue')
31
+ # ...
32
+ # queue2 = RightAws::Sqs::Queue.create(sqs, 'my_cool_queue', true)
33
+ # puts queue2.size
34
+ # ...
35
+ # message1 = queue2.receive
36
+ # message1.visibility = 0
37
+ # puts message1
38
+ # ...
39
+ # queue2.clear(true)
40
+ # queue2.send_message('Ola-la!')
41
+ # message2 = queue2.pop
42
+ # ...
43
+ # grantee1 = RightAws::Sqs::Grantee.create(queue2,'one_cool_guy@email.address')
44
+ # grantee1.grant('FULLCONTROL')
45
+ # grantee1.drop
46
+ # ...
47
+ # grantee2 = queue.grantees('another_cool_guy@email.address')
48
+ # grantee2.revoke('SENDMESSAGE')
49
+ #
50
+ class Sqs
51
+ attr_reader :interface
52
+
53
+ def initialize(aws_access_key_id, aws_secret_access_key, params={})
54
+ @interface = SqsInterface.new(aws_access_key_id, aws_secret_access_key, params)
55
+ end
56
+
57
+ # Retrieves a list of queues.
58
+ # Returns an +array+ of +Queue+ instances.
59
+ #
60
+ # RightAws::Sqs.queues #=> array of queues
61
+ #
62
+ def queues(prefix=nil)
63
+ @interface.list_queues(prefix).map do |url|
64
+ Queue.new(self, url)
65
+ end
66
+ end
67
+
68
+ # Returns Queue instance by queue name.
69
+ # If the queue does not exist at Amazon SQS and +create+ then creates it.
70
+ #
71
+ # RightAws::Sqs.queue('my_awesome_queue') #=> #<RightAws::Sqs::Queue:0xb7b626e4 ... >
72
+ #
73
+ def queue(queue_name, create=true, visibility=nil)
74
+ url = @interface.queue_url_by_name(queue_name)
75
+ url = (create ? @interface.create_queue(queue_name, visibility) : nil) unless url
76
+ url ? Queue.new(self, url) : nil
77
+ end
78
+
79
+
80
+ class Queue
81
+ attr_reader :name, :url, :sqs
82
+
83
+ # Returns Queue instance by queue name.
84
+ # If the queue does not exist at Amazon SQS and +create+ then creates it.
85
+ #
86
+ # RightAws::Sqs::Queue.create(sqs, 'my_awesome_queue') #=> #<RightAws::Sqs::Queue:0xb7b626e4 ... >
87
+ #
88
+ def self.create(sqs, url_or_name, create=true, visibility=nil)
89
+ sqs.queue(url_or_name, create=true, visibility)
90
+ end
91
+
92
+ # Creates new Queue instance.
93
+ # Does not create queue at Amazon.
94
+ #
95
+ # queue = RightAws::Sqs::Queue.new(sqs, 'my_awesome_queue')
96
+ #
97
+ def initialize(sqs, url_or_name)
98
+ @sqs = sqs
99
+ @url = @sqs.interface.queue_url_by_name(url_or_name)
100
+ @name = @sqs.interface.queue_name_by_url(@url)
101
+ end
102
+
103
+ # Retrieves queue size.
104
+ #
105
+ # queue.size #=> 1
106
+ #
107
+ def size
108
+ @sqs.interface.get_queue_length(@url)
109
+ end
110
+
111
+ # Clears queue.
112
+ # Deletes only the visible messages unless +force+ is +true+.
113
+ #
114
+ # queue.clear(true) #=> true
115
+ #
116
+ # P.S. when <tt>force==true</tt> the queue deletes then creates again. This is
117
+ # the quickest method to clear big queue oe the queue with 'locked' messages. All queue
118
+ # attributes restores. But there is no way to restore grantees permissions to
119
+ # this queue. If you have no grantees except of 'root' then you have no problems.
120
+ # But if the grantees are there - better use <tt>queue.clear(false)</tt>.
121
+ #
122
+ def clear(force=false)
123
+ if force
124
+ @sqs.interface.force_clear_queue(@url)
125
+ else
126
+ @sqs.interface.clear_queue(@url)
127
+ end
128
+ end
129
+
130
+ # Deletes queue.
131
+ # Queue must be empty or +force+ must be set to +true+.
132
+ # Returns +true+.
133
+ #
134
+ # queue.delete(true) #=> true
135
+ #
136
+ def delete(force=false)
137
+ @sqs.interface.delete_queue(@url, force)
138
+ end
139
+
140
+ # Sends new message to queue.
141
+ # Returns new Message instance that has been sent to queue.
142
+ def send_message(message)
143
+ message = message.to_s
144
+ msg = Message.new(self, @sqs.interface.send_message(@url, message), message)
145
+ msg.sent_at = Time.now
146
+ msg
147
+ end
148
+ alias_method :push, :send_message
149
+
150
+ # Retrieves a bunch of message from queue.
151
+ # Returns an array of Message instances.
152
+ #
153
+ # queue.receive_messages(2,10) #=> array of messages
154
+ #
155
+ def receive_messages(number_of_messages=1, visibility=nil)
156
+ list = @sqs.interface.receive_messages(@url, number_of_messages, visibility)
157
+ list.map! do |entry|
158
+ msg = Message.new(self, entry[:id], entry[:body], visibility)
159
+ msg.received_at = Time.now
160
+ msg
161
+ end
162
+ end
163
+
164
+ # Retrieves first accessible message from queue.
165
+ # Returns Message instance or +nil+ it the queue is empty.
166
+ #
167
+ # queue.receive #=> #<RightAws::Sqs::Message:0xb7bf0884 ... >
168
+ #
169
+ def receive(visibility=nil)
170
+ list = receive_messages(1, visibility)
171
+ list.empty? ? nil : list[0]
172
+ end
173
+
174
+ # Peeks message body.
175
+ #
176
+ # queue.peek #=> #<RightAws::Sqs::Message:0xb7bf0884 ... >
177
+ #
178
+ def peek(message_id)
179
+ entry = @sqs.interface.peek_message(@url, message_id)
180
+ msg = Message.new(self, entry[:id], entry[:body])
181
+ msg.received_at = Time.now
182
+ msg
183
+ end
184
+
185
+ # Pops (and deletes) first accessible message from queue.
186
+ # Returns Message instance or +nil+ it the queue is empty.
187
+ #
188
+ # queue.pop #=> #<RightAws::Sqs::Message:0xb7bf0884 ... >
189
+ #
190
+ def pop
191
+ msg = receive
192
+ msg.delete if msg
193
+ msg
194
+ end
195
+
196
+ # Retrieves +VisibilityTimeout+ value for the queue.
197
+ # Returns new timeout value.
198
+ #
199
+ # queue.visibility #=> 30
200
+ #
201
+ def visibility
202
+ @sqs.interface.get_visibility_timeout(@url)
203
+ end
204
+
205
+ # Sets new +VisibilityTimeout+ for the queue.
206
+ # Returns new timeout value.
207
+ #
208
+ # queue.visibility #=> 30
209
+ # queue.visibility = 33
210
+ # queue.visibility #=> 33
211
+ #
212
+ def visibility=(visibility_timeout)
213
+ @sqs.interface.set_visibility_timeout(@url, visibility_timeout)
214
+ visibility_timeout
215
+ end
216
+
217
+ # Sets new queue attribute value.
218
+ # Not all attributes may be changed: +ApproximateNumberOfMessages+ (for example) is a read only attribute.
219
+ # Returns a value to be assigned to attribute.
220
+ #
221
+ # queue.set_attribute('VisibilityTimeout', '100') #=> '100'
222
+ # queue.get_attribute('VisibilityTimeout') #=> '100'
223
+ #
224
+ def set_attribute(attribute, value)
225
+ @sqs.interface.set_queue_attributes(@url, attribute, value)
226
+ value
227
+ end
228
+
229
+ # Retrieves queue attributes.
230
+ # At this moment Amazon supports +VisibilityTimeout+ and +ApproximateNumberOfMessages+ only.
231
+ # If the name of attribute is set - returns its value otherwise - returns a hash of attributes.
232
+ #
233
+ # queue.get_attribute('VisibilityTimeout') #=> '100'
234
+ #
235
+ def get_attribute(attribute='All')
236
+ attributes = @sqs.interface.get_queue_attributes(@url, attribute)
237
+ attribute=='All' ? attributes : attributes[attribute]
238
+ end
239
+
240
+ # Retrieves a list grantees.
241
+ # Returns an +array+ of Grantee instances if the +grantee_email_address+ is unset.
242
+ # Else returns Grantee instance that points to +grantee_email_address+ or +nil+.
243
+ #
244
+ # grantees = queue.grantees #=> [#<RightAws::Sqs::Grantee:0xb7bf0888 ... >, ...]
245
+ # ...
246
+ # grantee = queue.grantees('cool_guy@email.address') #=> nil | #<RightAws::Sqs::Grantee:0xb7bf0888 ... >
247
+ #
248
+ def grantees(grantee_email_address=nil, permission = nil)
249
+ hash = @sqs.interface.list_grants(@url, grantee_email_address, permission)
250
+ grantees = []
251
+ hash.each do |key, value|
252
+ grantees << Grantee.new(self, grantee_email_address, key, value[:name], value[:perms])
253
+ end
254
+ if grantee_email_address
255
+ grantees.blank? ? nil : grantees.shift
256
+ else
257
+ grantees
258
+ end
259
+ end
260
+ end
261
+
262
+
263
+ class Message
264
+ attr_reader :queue, :id, :body, :visibility
265
+ attr_accessor :sent_at, :received_at
266
+
267
+ def initialize(queue, id=nil, body=nil, visibility=nil)
268
+ @queue = queue
269
+ @id = id
270
+ @body = body
271
+ @visibility = visibility
272
+ @sent_at = nil
273
+ @received_at = nil
274
+ end
275
+
276
+ # Returns +Message+ instance body.
277
+ def to_s
278
+ @body
279
+ end
280
+
281
+ # Changes +VisibilityTimeout+ for current message.
282
+ # Returns new +VisibilityTimeout+ value.
283
+ def visibility=(visibility_timeout)
284
+ @queue.sqs.interface.change_message_visibility(@queue.url, @id, visibility_timeout)
285
+ @visibility = visibility_timeout
286
+ end
287
+
288
+ # Removes message from queue.
289
+ # Returns +true+.
290
+ def delete
291
+ @queue.sqs.interface.delete_message(@queue.url, @id)
292
+ end
293
+ end
294
+
295
+
296
+ class Grantee
297
+ attr_accessor :queue, :id, :name, :perms, :email
298
+
299
+ # Creates new Grantee instance.
300
+ # To create new grantee for queue use:
301
+ #
302
+ # grantee = Grantee.new(queue, grantee@email.address)
303
+ # grantee.grant('FULLCONTROL')
304
+ #
305
+ def initialize(queue, email=nil, id=nil, name=nil, perms=[])
306
+ @queue = queue
307
+ @id = id
308
+ @name = name
309
+ @perms = perms
310
+ @email = email
311
+ retrieve unless id
312
+ end
313
+
314
+ # Retrieves security information for grantee identified by email.
315
+ # Returns +nil+ if such user does not has any permissions for this queue or
316
+ # +true+ if perms updated successfully.
317
+ def retrieve # :nodoc:
318
+ @id = nil
319
+ @name = nil
320
+ @perms = []
321
+
322
+ hash = @queue.sqs.interface.list_grants(@queue.url, @email)
323
+ return nil if hash.empty?
324
+
325
+ grantee = hash.shift
326
+ @id = grantee[0]
327
+ @name = grantee[1][:name]
328
+ @perms = grantee[1][:perms]
329
+ true
330
+ end
331
+
332
+ # Adds permissions for grantee.
333
+ # Permission: 'FULLCONTROL' | 'RECEIVEMESSAGE' | 'SENDMESSAGE'.
334
+ # User must have @email set.
335
+ def grant(permission=nil)
336
+ raise "You can't grant permission without defining a grantee email address!" unless @email
337
+ @queue.sqs.interface.add_grant(@queue.url, @email, permission)
338
+ retrieve
339
+ end
340
+
341
+ # Revokes permissions for grantee.
342
+ # Permission: 'FULLCONTROL' | 'RECEIVEMESSAGE' | 'SENDMESSAGE'.
343
+ # Default value is 'FULLCONTROL'.
344
+ # User must have +@email+ or +@id+ set.
345
+ # Returns +true+.
346
+ def revoke(permission='FULLCONTROL')
347
+ @queue.sqs.interface.remove_grant(@queue.url, @email || @id, permission)
348
+ unless @email # if email is unknown - just remove permission from local perms list...
349
+ @perms.delete(permission)
350
+ else # ... else retrieve updated information from Amazon
351
+ retrieve
352
+ end
353
+ true
354
+ end
355
+
356
+ # Revokes all permissions for this grantee.
357
+ # Returns +true+
358
+ def drop
359
+ @perms.each do |permission|
360
+ @queue.sqs.interface.remove_grant(@queue.url, @email || @id, permission)
361
+ end
362
+ retrieve
363
+ true
364
+ end
365
+
366
+ end
367
+
368
+ end
369
+ end
@@ -0,0 +1,655 @@
1
+ #
2
+ # Copyright (c) 2007 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ module RightAws
25
+
26
+ class SqsInterface
27
+
28
+ SIGNATURE_VERSION = "1"
29
+ API_VERSION = "2007-05-01"
30
+ DEFAULT_HOST = "queue.amazonaws.com"
31
+ DEFAULT_PORT = 443
32
+ DEFAULT_PROTOCOL = 'https'
33
+ REQUEST_TTL = 30
34
+ DEFAULT_VISIBILITY_TIMEOUT = 30
35
+ # A list if Amazons problems we can handle by AWSErrorHandler.
36
+ @@amazon_problems = RightAws::AMAZON_PROBLEMS
37
+
38
+ # Current aws_access_key_id
39
+ attr_reader :aws_access_key_id
40
+ # Last HTTP request object
41
+ attr_reader :last_request
42
+ # Last HTTP response object
43
+ attr_reader :last_response
44
+ # Last AWS errors list (used by AWSErrorHandler)
45
+ attr_accessor :last_errors
46
+ # Last AWS request id (used by AWSErrorHandler)
47
+ attr_accessor :last_request_id
48
+ # Logger object
49
+ attr_accessor :logger
50
+ # Initial params hash
51
+ attr_accessor :params
52
+
53
+ @@bench_sqs = Benchmark::Tms.new()
54
+ @@bench_xml = Benchmark::Tms.new()
55
+
56
+ # Benchmark::Tms instance for SQS access benchmark.
57
+ def self.bench_sqs; @@bench_sqs; end
58
+ # Benchmark::Tms instance for XML parsing benchmark.
59
+ def self.bench_xml; @@bench_xml; end # For benchmark puposes.
60
+
61
+ # Returns a list of Amazon service responses which are known as problems on Amazon side.
62
+ # We have to re-request again if we've got any of them - probably the problem will disappear. By default returns the same value as AMAZON_PROBLEMS const.
63
+ def self.amazon_problems
64
+ @@amazon_problems
65
+ end
66
+
67
+ # Sets a list of Amazon side problems.
68
+ def self.amazon_problems=(problems_list)
69
+ @@amazon_problems = problems_list
70
+ end
71
+
72
+ # Creates new RightSqs instance.
73
+ #
74
+ # sqs = RightSqs.new('1E3GDYEOGFJPIT75KDT40','hgTHt68JY07JKUY08ftHYtERkjgtfERn57DFE379', {:multi_thread => true, :logger => Logger.new('/tmp/x.log')}) #=> <RightSqs:0xb7af6264>
75
+ #
76
+ # Params is a hash:
77
+ #
78
+ # {:server => 'queue.amazonaws.com' # Amazon service host: 'queue.amazonaws.com'(default)
79
+ # :port => 443 # Amazon service port: 80 or 443(default)
80
+ # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default)
81
+ # :logger => Logger Object} # Logger instance: logs to STDOUT if omitted }
82
+ #
83
+ def initialize(aws_access_key_id, aws_secret_access_key, params={})
84
+ @params = params
85
+ raise AwsError.new("AWS access keys are required to operate on SQS") \
86
+ if aws_access_key_id.blank? || aws_secret_access_key.blank?
87
+ @aws_access_key_id = aws_access_key_id
88
+ @aws_secret_access_key = aws_secret_access_key
89
+ # params
90
+ @params[:server] ||= DEFAULT_HOST
91
+ @params[:port] ||= DEFAULT_PORT
92
+ @params[:protocol] ||= DEFAULT_PROTOCOL
93
+ @params[:multi_thread] ||= defined?(AWS_DAEMON)
94
+ # set logger
95
+ @logger = @params[:logger]
96
+ @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
97
+ @logger = Logger.new(STDOUT) if !@logger
98
+ @logger.info "New #{self.class.name} using #{@params[:multi_thread] ? 'multi' : 'single'}-threaded mode"
99
+ end
100
+
101
+ def on_exception(options={:raise=>true, :log=>true}) # :nodoc:
102
+ AwsError::on_aws_exception(self, options)
103
+ end
104
+
105
+ # Return the +true+ if this RightS3 instance works in multi_thread state and +false+ otherwise.
106
+ def multi_thread
107
+ @params[:multi_thread]
108
+ end
109
+
110
+ #-----------------------------------------------------------------
111
+ # Requests
112
+ #-----------------------------------------------------------------
113
+
114
+ # Generates request hash for QUERY API
115
+ def generate_request(action, param={}) # :nodoc:
116
+ # Sometimes we need to use queue uri (delete queue etc)
117
+ # In that case we will use Symbol key: 'param[:queue_url]'
118
+ queue_uri = param[:queue_url] ? URI(param[:queue_url]).path : '/'
119
+ # remove unset(=optional) and symbolyc keys
120
+ param.each{ |key, value| param.delete(key) if (value.nil? || key.is_a?(Symbol)) }
121
+ # prepare output hash
122
+ request_hash = { "Action" => action,
123
+ "Expires" => Time.now.utc.since(REQUEST_TTL).strftime("%Y-%m-%dT%H:%M:%SZ"),
124
+ "AWSAccessKeyId" => @aws_access_key_id,
125
+ "Version" => API_VERSION,
126
+ "SignatureVersion" => SIGNATURE_VERSION }
127
+ request_hash.update(param)
128
+ request_data = request_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
129
+ request_hash['Signature'] = Base64.encode64( OpenSSL::HMAC.digest( OpenSSL::Digest::Digest.new( "sha1" ), @aws_secret_access_key, request_data)).strip
130
+ request_params = request_hash.to_a.collect{|key,val| key.to_s + "=" + CGI::escape(val.to_s) }.join("&")
131
+ request = Net::HTTP::Get.new("#{queue_uri}?#{request_params}")
132
+ # prepare output hash
133
+ { :request => request,
134
+ :server => @params[:server],
135
+ :port => @params[:port],
136
+ :protocol => @params[:protocol] }
137
+ end
138
+
139
+ # Generates request hash for REST API
140
+ def generate_rest_request(method, param) # :nodoc:
141
+ queue_uri = param[:queue_url] ? URI(param[:queue_url]).path : '/'
142
+ message = param[:message] # extract message body if nesessary
143
+ # remove unset(=optional) and symbolyc keys
144
+ param.each{ |key, value| param.delete(key) if (value.nil? || key.is_a?(Symbol)) }
145
+ # created request
146
+ param_to_str = param.to_a.collect{|key,val| key.to_s + "=" + CGI::escape(val.to_s) }.join("&")
147
+ param_to_str = "?#{param_to_str}" unless param_to_str.blank?
148
+ request = "Net::HTTP::#{method.titleize}".constantize.new("#{queue_uri}#{param_to_str}")
149
+ request.body = message if message
150
+ # set main headers
151
+ request['content-md5'] = ''
152
+ request['Content-Type'] = 'text/plain'
153
+ request['Date'] = Time.now.httpdate
154
+ # generate authorization string
155
+ auth_string = "#{method.upcase}\n#{request['content-md5']}\n#{request['Content-Type']}\n#{request['Date']}\n#{CGI::unescape(queue_uri)}"
156
+ signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new("sha1"), @aws_secret_access_key, auth_string)).strip
157
+ # set other headers
158
+ request['Authorization'] = "AWS #{@aws_access_key_id}:#{signature}"
159
+ request['AWS-Version'] = API_VERSION
160
+ # prepare output hash
161
+ { :request => request,
162
+ :server => @params[:server],
163
+ :port => @params[:port],
164
+ :protocol => @params[:protocol] }
165
+ end
166
+
167
+
168
+ # Sends request to Amazon and parses the response
169
+ # Raises AwsError if any banana happened
170
+ def request_info(request, parser) # :nodoc:
171
+ thread = @params[:multi_thread] ? Thread.current : Thread.main
172
+ thread[:sqs_connection] ||= Rightscale::HttpConnection.new(:exception => AwsError)
173
+ @last_request = request[:request]
174
+ @last_response = nil
175
+ response=nil
176
+
177
+ @@bench_sqs.add!{ response = thread[:sqs_connection].request(request) }
178
+ # check response for errors...
179
+ @last_response = response
180
+ if response.is_a?(Net::HTTPSuccess)
181
+ @error_handler = nil
182
+ @@bench_xml.add! do
183
+ if parser.kind_of?(RightAWSParser)
184
+ REXML::Document.parse_stream(response.body, parser)
185
+ else
186
+ parser.parse(response)
187
+ end
188
+ end
189
+ return parser.result
190
+ else
191
+ @error_handler = AWSErrorHandler.new(self, parser, @@amazon_problems) unless @error_handler
192
+ check_result = @error_handler.check(request)
193
+ if check_result
194
+ @error_handler = nil
195
+ return check_result
196
+ end
197
+ raise AwsError.new(@last_errors, @last_response.code, @last_request_id)
198
+ end
199
+ rescue
200
+ @error_handler = nil
201
+ raise
202
+ end
203
+
204
+
205
+ # Creates new queue. Returns new queue link.
206
+ #
207
+ # sqs.create_queue('my_awesome_queue') #=> 'http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue'
208
+ #
209
+ def create_queue(queue_name, default_visibility_timeout=nil)
210
+ req_hash = generate_request('CreateQueue',
211
+ 'QueueName' => queue_name,
212
+ 'DefaultVisibilityTimeout' => default_visibility_timeout || DEFAULT_VISIBILITY_TIMEOUT )
213
+ request_info(req_hash, SqsCreateQueueParser.new)
214
+ end
215
+
216
+ # Creates new queue. If +queue_name_prefix+ is omitted then retrieves a list of all queues.
217
+ #
218
+ # sqs.create_queue('my_awesome_queue')
219
+ # sqs.create_queue('my_awesome_queue_2')
220
+ # sqs.list_queues('my_qwesome') #=> ['http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue','http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue_2']
221
+ #
222
+ def list_queues(queue_name_prefix=nil)
223
+ req_hash = generate_request('ListQueues', 'QueueNamePrefix' => queue_name_prefix)
224
+ request_info(req_hash, SqsListQueuesParser.new)
225
+ rescue
226
+ on_exception
227
+ end
228
+
229
+ # Deletes queue (queue must be empty or +force_deletion+ must be set to true). Queue is identified by url. Returns +true+ or an exception.
230
+ #
231
+ # sqs.delete_queue('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue_2') #=> true
232
+ #
233
+ def delete_queue(queue_url, force_deletion = false)
234
+ req_hash = generate_request('DeleteQueue',
235
+ 'ForceDeletion' => force_deletion.to_s,
236
+ :queue_url => queue_url)
237
+ request_info(req_hash, SqsStatusParser.new)
238
+ rescue
239
+ on_exception
240
+ end
241
+
242
+ # Retrieves the queue attribute(s). Returns a hash of attribute(s) or an exception.
243
+ #
244
+ # sqs.get_queue_attributes('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> {"ApproximateNumberOfMessages"=>"0", "VisibilityTimeout"=>"30"}
245
+ #
246
+ def get_queue_attributes(queue_url, attribute='All')
247
+ req_hash = generate_request('GetQueueAttributes',
248
+ 'Attribute' => attribute,
249
+ :queue_url => queue_url)
250
+ request_info(req_hash, SqsGetQueueAttributesParser.new)
251
+ rescue
252
+ on_exception
253
+ end
254
+
255
+ # Sets queue attribute. Returns +true+ or an exception.
256
+ #
257
+ # sqs.set_queue_attributes('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', "VisibilityTimeout", 10) #=> true
258
+ #
259
+ # P.S. Hmm... Amazon returns success even if such attribute does not exist. And they need some seconds to change attribute value.
260
+ def set_queue_attributes(queue_url, attribute, value)
261
+ req_hash = generate_request('SetQueueAttributes',
262
+ 'Attribute' => attribute,
263
+ 'Value' => value,
264
+ :queue_url => queue_url)
265
+ request_info(req_hash, SqsStatusParser.new)
266
+ rescue
267
+ on_exception
268
+ end
269
+
270
+ # Sets visibility timeout. Returns +true+ or an exception.
271
+ #
272
+ # sqs.set_visibility_timeout('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', 15) #=> true
273
+ #
274
+ # See also: +set_queue_attributes+
275
+ #
276
+ def set_visibility_timeout(queue_url, visibility_timeout=nil)
277
+ req_hash = generate_request('SetVisibilityTimeout',
278
+ 'VisibilityTimeout' => visibility_timeout || DEFAULT_VISIBILITY_TIMEOUT,
279
+ :queue_url => queue_url )
280
+ request_info(req_hash, SqsStatusParser.new)
281
+ rescue
282
+ on_exception
283
+ end
284
+
285
+ # Retrieves visibility timeout.
286
+ #
287
+ # sqs.get_visibility_timeout('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> 15
288
+ #
289
+ # See also: +get_queue_attributes+
290
+ #
291
+ def get_visibility_timeout(queue_url)
292
+ req_hash = generate_request('GetVisibilityTimeout', :queue_url => queue_url )
293
+ request_info(req_hash, SqsGetVisibilityTimeoutParser.new)
294
+ rescue
295
+ on_exception
296
+ end
297
+
298
+ # Adds grants for user (identified by email he registered at Amazon). Returns +true+ or an exception. Permission = 'FULLCONTROL' | 'RECEIVEMESSAGE' | 'SENDMESSAGE'.
299
+ #
300
+ # sqs.add_grant('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', 'my_awesome_friend@gmail.com', 'FULLCONTROL') #=> true
301
+ #
302
+ def add_grant(queue_url, grantee_email_address, permission = nil)
303
+ req_hash = generate_request('AddGrant',
304
+ 'Grantee.EmailAddress' => grantee_email_address,
305
+ 'Permission' => permission,
306
+ :queue_url => queue_url)
307
+ request_info(req_hash, SqsStatusParser.new)
308
+ rescue
309
+ on_exception
310
+ end
311
+
312
+ # Retrieves hash of +grantee_id+ => +perms+ for this queue:
313
+ #
314
+ # sqs.list_grants('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=>
315
+ # {"000000000000000000000001111111111117476c7fea6efb2c3347ac3ab2792a"=>{:name=>"root", :perms=>["FULLCONTROL"]},
316
+ # "00000000000000000000000111111111111e5828344600fc9e4a784a09e97041"=>{:name=>"myawesomefriend", :perms=>["FULLCONTROL"]}
317
+ #
318
+ def list_grants(queue_url, grantee_email_address=nil, permission = nil)
319
+ req_hash = generate_request('ListGrants',
320
+ 'Grantee.EmailAddress' => grantee_email_address,
321
+ 'Permission' => permission,
322
+ :queue_url => queue_url)
323
+ response = request_info(req_hash, SqsListGrantsParser.new)
324
+ # One user may have up to 3 permission records for every queue.
325
+ # We will join these records to one.
326
+ result = {}
327
+ response.each do |perm|
328
+ id = perm[:id]
329
+ # create hash for new user if unexisit
330
+ result[id] = {:perms=>[]} unless result[id]
331
+ # fill current grantee params
332
+ result[id][:perms] << perm[:permission]
333
+ result[id][:name] = perm[:name]
334
+ end
335
+ result
336
+ end
337
+
338
+ # Revokes permission from user. Returns +true+ or an exception.
339
+ #
340
+ # sqs.remove_grant('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', 'my_awesome_friend@gmail.com', 'FULLCONTROL') #=> true
341
+ #
342
+ def remove_grant(queue_url, grantee_email_address_or_id, permission = nil)
343
+ grantee_key = grantee_email_address_or_id.include?('@') ? 'Grantee.EmailAddress' : 'Grantee.ID'
344
+ req_hash = generate_request('RemoveGrant',
345
+ grantee_key => grantee_email_address_or_id,
346
+ 'Permission' => permission,
347
+ :queue_url => queue_url)
348
+ request_info(req_hash, SqsStatusParser.new)
349
+ rescue
350
+ on_exception
351
+ end
352
+
353
+ # Retrieves a list of messages from queue. Returns an array of hashes in format: <tt>{:id=>'message_id', body=>'message_body'}</tt>
354
+ #
355
+ # sqs.receive_messages('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue',10, 5) #=>
356
+ # [{:id=>"12345678904GEZX9746N|0N9ED344VK5Z3SV1DTM0|1RVYH4X3TJ0987654321", :body=>"message_1"}, ..., {}]
357
+ #
358
+ # P.S. Usually returns less messages than have been requested even if they are available.
359
+ #
360
+ def receive_messages(queue_url, number_of_messages=1, visibility_timeout=nil)
361
+ return [] if number_of_messages == 0
362
+ req_hash = generate_rest_request('GET',
363
+ 'NumberOfMessages' => number_of_messages,
364
+ 'VisibilityTimeout' => visibility_timeout,
365
+ :queue_url => "#{queue_url}/front" )
366
+ request_info(req_hash, SqsReceiveMessagesParser.new)
367
+ rescue
368
+ on_exception
369
+ end
370
+
371
+ # Peeks message from queue by message id. Returns message in format of <tt>{:id=>'message_id', :body=>'message_body'}</tt> or +nil+.
372
+ #
373
+ # sqs.peek_message('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', '1234567890...0987654321') #=>
374
+ # {:id=>"12345678904GEZX9746N|0N9ED344VK5Z3SV1DTM0|1RVYH4X3TJ0987654321", :body=>"message_1"}
375
+ #
376
+ def peek_message(queue_url, message_id)
377
+ req_hash = generate_rest_request('GET', :queue_url => "#{queue_url}/#{CGI::escape message_id}" )
378
+ messages = request_info(req_hash, SqsReceiveMessagesParser.new)
379
+ messages.blank? ? nil : messages[0]
380
+ rescue
381
+ on_exception
382
+ end
383
+
384
+ # Sends new message to queue.Returns 'message_id' or raises an exception.
385
+ #
386
+ # sqs.send_message('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', 'message_1') #=> "1234567890...0987654321"
387
+ #
388
+ def send_message(queue_url, message)
389
+ req_hash = generate_rest_request('PUT',
390
+ :message => message,
391
+ :queue_url => "#{queue_url}/back")
392
+ request_info(req_hash, SqsSendMessagesParser.new)
393
+ rescue
394
+ on_exception
395
+ end
396
+
397
+ # Deletes message from queue. Returns +true+ or an exception.
398
+ #
399
+ # sqs.delete_message('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', '12345678904...0987654321') #=> true
400
+ #
401
+ def delete_message(queue_url, message_id)
402
+ req_hash = generate_request('DeleteMessage',
403
+ 'MessageId' => message_id,
404
+ :queue_url => queue_url)
405
+ request_info(req_hash, SqsStatusParser.new)
406
+ rescue
407
+ on_exception
408
+ end
409
+
410
+ # Changes message visibility timeout. Returns +true+ or an exception.
411
+ #
412
+ # sqs.change_message_visibility('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', '1234567890...0987654321', 10) #=> true
413
+ #
414
+ def change_message_visibility(queue_url, message_id, visibility_timeout=0)
415
+ req_hash = generate_request('ChangeMessageVisibility',
416
+ 'MessageId' => message_id,
417
+ 'VisibilityTimeout' => visibility_timeout.to_s,
418
+ :queue_url => queue_url)
419
+ request_info(req_hash, SqsStatusParser.new)
420
+ rescue
421
+ on_exception
422
+ end
423
+
424
+ # Returns queue url by queue short name or +nil+ if queue is not found
425
+ #
426
+ # sqs.queue_url_by_name('my_awesome_queue') #=> 'http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue'
427
+ #
428
+ def queue_url_by_name(queue_name)
429
+ return queue_name if queue_name.include?('/')
430
+ queue_urls = list_queues(queue_name)
431
+ queue_urls.each do |queue_url|
432
+ return queue_url if queue_name_by_url(queue_url) == queue_name
433
+ end
434
+ nil
435
+ rescue
436
+ on_exception
437
+ end
438
+
439
+ # Returns short queue name by url.
440
+ #
441
+ # RightSqs.queue_name_by_url('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> 'my_awesome_queue'
442
+ #
443
+ def self.queue_name_by_url(queue_url)
444
+ queue_url[/[^\/]*$/]
445
+ rescue
446
+ on_exception
447
+ end
448
+
449
+ # Returns short queue name by url.
450
+ #
451
+ # sqs.queue_name_by_url('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> 'my_awesome_queue'
452
+ #
453
+ def queue_name_by_url(queue_url)
454
+ self.class.queue_name_by_url(queue_url)
455
+ rescue
456
+ on_exception
457
+ end
458
+
459
+ # Returns approximate amount of messages in queue.
460
+ #
461
+ # sqs.get_queue_length('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> 3
462
+ #
463
+ def get_queue_length(queue_url)
464
+ get_queue_attributes(queue_url)['ApproximateNumberOfMessages'].to_i
465
+ rescue
466
+ on_exception
467
+ end
468
+
469
+ # Removes all visible messages from queue. Return +true+ or an exception.
470
+ #
471
+ # sqs.clear_queue('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> true
472
+ #
473
+ def clear_queue(queue_url)
474
+ while (m = pop_message(queue_url)) ; end # delete all messages in queue
475
+ true
476
+ rescue
477
+ on_exception
478
+ end
479
+
480
+ # Deletes queue then re-creates it (restores attributes also). The fastest method to clear big queue or queue with invisible messages. Return +true+ or an exception.
481
+ #
482
+ # sqs.force_clear_queue('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> true
483
+ #
484
+ def force_clear_queue(queue_url)
485
+ queue_name = queue_name_by_url(queue_url)
486
+ queue_attributes = get_queue_attributes(queue_url)
487
+ force_delete_queue(queue_url)
488
+ create_queue(queue_name)
489
+ # hmmm... The next like is a trick. Amazon do not want change attributes immediately after queue creation
490
+ # So we do 'empty' get_queue_attributes. Probably they need some time to allow attributes change.
491
+ get_queue_attributes(queue_url)
492
+ queue_attributes.each{ |attribute, value| set_queue_attributes(queue_url, attribute, value) }
493
+ true
494
+ rescue
495
+ on_exception
496
+ end
497
+
498
+ # Deletes queue even if it has messages. Return +true+ or an exception.
499
+ #
500
+ # force_delete_queue('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=> true
501
+ #
502
+ # P.S. same as <tt>delete_queue('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', true)</tt>
503
+ def force_delete_queue(queue_url)
504
+ delete_queue(queue_url, true)
505
+ rescue
506
+ on_exception
507
+ end
508
+
509
+ # Reads first accessible message from queue. Returns message as a hash: <tt>{:id=>'message_id', :body=>'message_body'}</tt> or +nil+.
510
+ #
511
+ # sqs.receive_message('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', 10) #=>
512
+ # {:id=>"12345678904GEZX9746N|0N9ED344VK5Z3SV1DTM0|1RVYH4X3TJ0987654321", :body=>"message_1"}
513
+ #
514
+ def receive_message(queue_url, visibility_timeout=nil)
515
+ result = receive_messages(queue_url, 1, visibility_timeout)
516
+ result.blank? ? nil : result[0]
517
+ rescue
518
+ on_exception
519
+ end
520
+
521
+ # Same as send_message
522
+ alias_method :push_message, :send_message
523
+
524
+ # Pops (retrieves and deletes) up to 'number_of_messages' from queue. Returns an array of retrieved messages in format: <tt>[{:id=>'message_id', :body=>'message_body'}]</tt>.
525
+ #
526
+ # sqs.pop_messages('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue', 3) #=>
527
+ # [{:id=>"12345678904GEZX9746N|0N9ED344VK5Z3SV1DTM0|1RVYH4X3TJ0987654321", :body=>"message_1"}, ..., {}]
528
+ #
529
+ def pop_messages(queue_url, number_of_messages=1)
530
+ messages = receive_messages(queue_url, number_of_messages)
531
+ messages.each do |message|
532
+ delete_message(queue_url, message[:id])
533
+ end
534
+ messages
535
+ rescue
536
+ on_exception
537
+ end
538
+
539
+ # Pops (retrieves and deletes) first accessible message from queue. Returns the message in format <tt>{:id=>'message_id', :body=>'message_body'}</tt> or +nil+.
540
+ #
541
+ # sqs.pop_message('http://queue.amazonaws.com/ZZ7XXXYYYBINS/my_awesome_queue') #=>
542
+ # {:id=>"12345678904GEZX9746N|0N9ED344VK5Z3SV1DTM0|1RVYH4X3TJ0987654321", :body=>"message_1"}
543
+ #
544
+ def pop_message(queue_url)
545
+ messages = pop_messages(queue_url)
546
+ messages.blank? ? nil : messages[0]
547
+ rescue
548
+ on_exception
549
+ end
550
+
551
+ #-----------------------------------------------------------------
552
+ # PARSERS: Status Response Parser
553
+ #-----------------------------------------------------------------
554
+
555
+ class SqsStatusParser < RightAWSParser # :nodoc:
556
+ def tagend(name)
557
+ if name == 'StatusCode'
558
+ @result = @text=='Success' ? true : false
559
+ end
560
+ end
561
+ end
562
+
563
+ #-----------------------------------------------------------------
564
+ # PARSERS: Queue
565
+ #-----------------------------------------------------------------
566
+
567
+ class SqsCreateQueueParser < RightAWSParser # :nodoc:
568
+ def tagend(name)
569
+ @result = @text if name == 'QueueUrl'
570
+ end
571
+ end
572
+
573
+ class SqsListQueuesParser < RightAWSParser # :nodoc:
574
+ def reset
575
+ @result = []
576
+ end
577
+ def tagend(name)
578
+ @result << @text if name == 'QueueUrl'
579
+ end
580
+ end
581
+
582
+ class SqsGetQueueAttributesParser < RightAWSParser # :nodoc:
583
+ def reset
584
+ @result = {}
585
+ end
586
+ def tagend(name)
587
+ case name
588
+ when 'Attribute' ; @current_attribute = @text
589
+ when 'Value' ; @result[@current_attribute] = @text
590
+ # when 'StatusCode'; @result['status_code'] = @text
591
+ # when 'RequestId' ; @result['request_id'] = @text
592
+ end
593
+ end
594
+ end
595
+
596
+ #-----------------------------------------------------------------
597
+ # PARSERS: Timeouts
598
+ #-----------------------------------------------------------------
599
+
600
+ class SqsGetVisibilityTimeoutParser < RightAWSParser # :nodoc:
601
+ def tagend(name)
602
+ @result = @text.to_i if name == 'VisibilityTimeout'
603
+ end
604
+ end
605
+
606
+ #-----------------------------------------------------------------
607
+ # PARSERS: Permissions
608
+ #-----------------------------------------------------------------
609
+
610
+ class SqsListGrantsParser < RightAWSParser # :nodoc:
611
+ def reset
612
+ @result = []
613
+ end
614
+ def tagstart(name, attributes)
615
+ @current_perms = {} if name == 'GrantList'
616
+ end
617
+ def tagend(name)
618
+ case name
619
+ when 'ID' ; @current_perms[:id] = @text
620
+ when 'DisplayName'; @current_perms[:name] = @text
621
+ when 'Permission' ; @current_perms[:permission] = @text
622
+ when 'GrantList' ; @result << @current_perms
623
+ end
624
+ end
625
+ end
626
+
627
+ #-----------------------------------------------------------------
628
+ # PARSERS: Messages
629
+ #-----------------------------------------------------------------
630
+
631
+ class SqsReceiveMessagesParser < RightAWSParser # :nodoc:
632
+ def reset
633
+ @result = []
634
+ end
635
+ def tagstart(name, attributes)
636
+ @current_message = {} if name == 'Message'
637
+ end
638
+ def tagend(name)
639
+ case name
640
+ when 'MessageId' ; @current_message[:id] = @text
641
+ when 'MessageBody'; @current_message[:body] = @text
642
+ when 'Message' ; @result << @current_message
643
+ end
644
+ end
645
+ end
646
+
647
+ class SqsSendMessagesParser < RightAWSParser # :nodoc:
648
+ def tagend(name)
649
+ @result = @text if name == 'MessageId'
650
+ end
651
+ end
652
+
653
+ end
654
+
655
+ end