right_aws 1.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.
@@ -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