SQS 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006 Zachary Holt
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,3 @@
1
+ Please see the MIT-LICENSE file for copyright and distribution information.
2
+
3
+ SQS is a Ruby interface to Amazon's Simple Queue Service.
data/lib/sqs.rb ADDED
@@ -0,0 +1,294 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ dir = File::dirname( __FILE__ )
4
+
5
+ #require "#{dir}/aws"
6
+ require 'base64'
7
+ require 'erb'
8
+ require 'net/http'
9
+ require 'openssl'
10
+ require 'rexml/document'
11
+ #include Digest
12
+ include ERB::Util
13
+ include OpenSSL
14
+ include REXML
15
+
16
+
17
+ class SQS
18
+
19
+ dir = File.dirname( __FILE__ )
20
+ require "#{dir}/sqs/queue"
21
+ require "#{dir}/sqs/message"
22
+ require "#{dir}/sqs/grant"
23
+
24
+
25
+ def self.new
26
+ # rather than a singleton class, never allow instantiation
27
+ return SQS
28
+ end
29
+
30
+ def self.prepare_parameters_for_signing( hsh={} )
31
+ # Param-name1Param-value1Param-name2Param-value2...Param-nameNParam-valueN
32
+ # see http://docs.amazonwebservices.com/AWSSimpleQueueService/2006-04-01/Query/QueryAuth.html
33
+ hsh.keys.sort{ |a,b| a.to_s.casecmp( b.to_s ) }.map{ |a| "#{a.to_s}#{hsh[a].to_s}"}.join( '' )
34
+ end
35
+
36
+ def self.prepare_parameters_for_query( hsh={} )
37
+ # Param-name1Param-value1Param-name2Param-value2...Param-nameNParam-valueN
38
+ # see http://docs.amazonwebservices.com/AWSSimpleQueueService/2006-04-01/Query/QueryAuth.html
39
+ hsh.keys.map{ |a| "#{url_encode( a.to_s )}=#{url_encode( hsh[a].to_s )}"}.join( '&' )
40
+ end
41
+
42
+ def self.access_key_id=( i )
43
+ @@access_key_id = i
44
+ end
45
+ def self.access_key_id
46
+ @@access_key_id
47
+ end
48
+ def self.secret_access_key=( k )
49
+ @@secret_access_key = k
50
+ end
51
+ def self.secret_access_key
52
+ @@secret_access_key
53
+ end
54
+ def self.url_for_auery=( u )
55
+ @@url_for_query = u
56
+ end
57
+ def self.url_for_query
58
+ @@url_for_query
59
+ end
60
+
61
+ def self.create_signature( params )
62
+ prepared_parameters = self.prepare_parameters_for_signing( params )
63
+ digest = OpenSSL::HMAC::digest( OpenSSL::Digest::Digest.new("SHA1"), self.secret_access_key, prepared_parameters )
64
+ b64_hmac = Base64.encode64( digest ).strip
65
+ b64_hmac
66
+ end
67
+
68
+ def self.permissions( perm=nil )
69
+ perms = { :full => 'FULLCONTROL', :send => 'SENDMESSAGE', :receive => 'RECEIVEMESSAGE' }
70
+ if perm.nil?
71
+ perms
72
+ else
73
+ perms.has_value?( perm.to_s.upcase ) ? perm.to_s.upcase : perms[perm.to_sym]
74
+ end
75
+ end
76
+
77
+ def self.create_queue( params={} )
78
+ params = params.is_a?( Hash ) ? params : { :name => params }
79
+ params[:Action] = 'CreateQueue'
80
+ return false if params[:name].to_s.empty?
81
+ params[:QueueName] = params[:name]
82
+ params.delete( :name )
83
+
84
+ doc = self.call_web_service( params )
85
+ queue_url = doc.queue_url
86
+ SQS::Queue.new( :url => queue_url )
87
+
88
+ rescue Exception => e
89
+ raise e
90
+ end
91
+
92
+
93
+
94
+ def self.get_queue( params={} )
95
+ params = params.is_a?( Hash ) ? params : { :name => params }
96
+ self.list_queues( :prefix => params[:name] ).each do |queue|
97
+ return queue if queue.name == params[:name]
98
+ end
99
+
100
+ raise "Could not find the queue named '#{params[:name]}'"
101
+ rescue Exception => e
102
+ raise e
103
+ end
104
+
105
+
106
+ def self.list_queues( params={} )
107
+ # use :prefix => 'Boo' to get queues whose names begin with 'Boo'
108
+ params = params.is_a?( Hash ) ? params : { }
109
+ params[:Action] = 'ListQueues'
110
+ if ( params.has_key?( :prefix ) )
111
+ params[:QueueNamePrefix] = params[:prefix]
112
+ params.delete( :prefix )
113
+ end
114
+
115
+ doc = self.call_web_service( params )
116
+ queues = Array.new()
117
+
118
+ begin
119
+ XPath.each( doc, '/ListQueuesResponse/QueueUrl' ) do |element|
120
+ queues << SQS::Queue.new( :url => element.text )
121
+ end
122
+ rescue RuntimeError => e
123
+ queues = Array.new()
124
+ end
125
+ queues
126
+ rescue Exception => e
127
+ raise e
128
+ end
129
+
130
+ def self.each_queue( params={}, &block )
131
+ self.list_queues( params ).each do |q|
132
+ block.call( q )
133
+ end
134
+ rescue Exception => e
135
+ raise e
136
+ end
137
+
138
+
139
+
140
+ def self.api_version
141
+ '2006-04-01'
142
+ end
143
+
144
+ def self.signature_version
145
+ 1
146
+ end
147
+
148
+ def self.request_ttl
149
+ 30
150
+ end
151
+
152
+ def self.retry_attempts
153
+ 10
154
+ end
155
+
156
+ def self.params_for_query( t=nil )
157
+ expires = t.is_a?( Time ) ? t : Time.now.request_expires
158
+
159
+ {
160
+ :DefaultVisibilityTimeout => SQS::Queue.default_visibility_timeout,
161
+ :Version => self.api_version,
162
+ :AWSAccessKeyId => self.access_key_id,
163
+ :SignatureVersion => self.signature_version,
164
+ :Expires => expires.to_iso8601
165
+ }
166
+ end
167
+
168
+ def self.counter
169
+ @@counter
170
+ end
171
+ def self.reset_counter
172
+ @@counter = 0
173
+ end
174
+ def self.increment_counter
175
+ @@counter += 1
176
+ end
177
+ protected
178
+ @@counter = 0;
179
+ @@secret_access_key = 'Replace this with your secret access key'
180
+ @@access_key_id = 'Replace this with your access key id'
181
+ @@url_for_query = 'http://queue.amazonaws.com/'
182
+
183
+
184
+ def self.reasons_not_to_retry
185
+ [ 'Success', 'InvalidParameterValue' ]
186
+ end
187
+
188
+ def self.call_web_service( params )
189
+ params = self.params_for_query.merge( params )
190
+
191
+ begin
192
+ retries = params[:retries] + 0
193
+ params.delete( :retries )
194
+ retries = 1 if retries < 1
195
+ rescue
196
+ retries = self.retry_attempts
197
+ end
198
+
199
+ if !params[:queue].nil? && params[:queue].respond_to?( :url )
200
+ url = params[:queue].url
201
+ params.delete( :queue )
202
+ else
203
+ url = "#{self.url_for_query}"
204
+ end
205
+ url += "?#{self.prepare_parameters_for_query( params )}&Signature="
206
+
207
+ url_encoded_signature = url_encode( self.create_signature( params ) )
208
+ response = Net::HTTP.get( URI.parse( url + url_encoded_signature ) )
209
+ self.increment_counter
210
+ doc = Document.new( response )
211
+ status = doc.status
212
+
213
+ unless status == 'Success'
214
+ error_code = doc.error_code
215
+ error_message = doc.error_message
216
+ case error_code
217
+ when 'AuthFailure', 'InvalidParameterValue', 'AWS.SimpleQueueService.NonEmptyQueue'
218
+ raise( "#{error_code}: #{error_message} (#{url}#{url_encoded_signature})" )
219
+ else
220
+ raise( "Unknown Error: #{doc} (#{url}#{url_encoded_signature})" )
221
+ end
222
+ end
223
+
224
+ doc
225
+ rescue Exception => e
226
+ raise e.is_a?( SocketError ) ? "Do you have internet access? (#{e.message})" : e
227
+ end
228
+ end
229
+
230
+ class Time
231
+ def to_iso8601
232
+ self.utc.strftime( "%Y-%m-%dT%H:%M:%SZ" )
233
+ end
234
+
235
+ def request_expires
236
+ self + SQS.request_ttl
237
+ end
238
+ end
239
+
240
+ module REXML
241
+ class Document
242
+
243
+ def error_code ; self.node_text( '/Response/Errors/Error/Code' ) ; end
244
+ def error_message ; self.node_text( '/Response/Errors/Error/Message' ) ; end
245
+ def request_id ; self.node_text( '*/ResponseStatus/RequestId' ) ; end
246
+
247
+ def queue_url ; self.node_text( '/CreateQueueResponse/QueueUrl' ) ; end
248
+ def status ; self.node_text( '*/ResponseStatus/StatusCode' ) ; end
249
+
250
+ def message_id( type=:send ) ; self.node_text( "/#{type.to_s.capitalize}MessageResponse/#{ type == :send ? '' : 'Message/'}MessageId" ) ; end
251
+ def message_body( type=:peek ) ; self.node_text( "/#{type.to_s.capitalize}MessageResponse/Message/MessageBody" ) ; end
252
+
253
+ def messages( type=:receive ) ; self.nodes( "/#{type.to_s.capitalize}MessageResponse#{ type == :send ? '' : '/Message'}" ) ; end
254
+
255
+ def visibility_timeout( type=:get ) ; self.node_text( "/#{type.to_s.capitalize}VisibilityTimeoutResponse/VisibilityTimeout" ).to_i ; end
256
+
257
+ def grant_lists( type=:list ) ; self.nodes("/#{type.to_s.capitalize}GrantsResponse#{ type == :send ? '' : '/GrantList'}" ) ; end
258
+
259
+ protected
260
+ def node_text( xpath )
261
+ code = XPath.first( self, xpath )
262
+ code.to_s.empty? ? '' : code.text
263
+ end
264
+
265
+ def nodes( xpath )
266
+ XPath.match( self, xpath )
267
+ end
268
+ end
269
+
270
+
271
+ class Element
272
+ def message_id ; self.node_text( "MessageId" ) ; end
273
+ def message_body ; self.node_text( "MessageBody" ) ; end
274
+
275
+ def id( type=:message ) ; self.node_text( type == :grantee ? "ID" : "Id" ) ; end
276
+ def display_name ; self.node_text( "DisplayName" ) ; end
277
+ def permission ; self.node_text( "Permission" ) ; end
278
+
279
+ def grantees( type=:list ) ; self.nodes("Grantee" ) ; end
280
+
281
+ protected
282
+ def node_text( xpath )
283
+ code = XPath.first( self, xpath )
284
+ code.to_s.empty? ? '' : code.text
285
+ end
286
+
287
+ def nodes( xpath )
288
+ XPath.match( self, xpath )
289
+ end
290
+ end
291
+
292
+ end
293
+
294
+ Dir[File.join( File.join( File.dirname( __FILE__ ), 'sqs' ), "*.rb" )].each { |f| require f }
data/lib/sqs/grant.rb ADDED
@@ -0,0 +1,34 @@
1
+
2
+ class SQS::Grant
3
+ def self.atts
4
+ [:email, :display_name, :id, :_permission]
5
+ end
6
+
7
+ self.atts.each do |att|
8
+ attr_accessor att
9
+ end
10
+
11
+ def initialize( options={} )
12
+ if options.is_a?( Hash )
13
+ SQS::Grant.atts.each do |thing|
14
+ self.send "#{thing}=", options[thing.to_s =~ /^_(.*)$/ ? $1.to_sym : thing]
15
+ end
16
+ else
17
+ self.email = options
18
+ end
19
+ end
20
+
21
+ def permission
22
+ self._permission
23
+ end
24
+
25
+ def permission=( p )
26
+ if SQS.permissions.has_key?( p.to_s.to_sym )
27
+ self._permission = SQS.permissions[p.to_s.to_sym]
28
+ else
29
+ self._permission = p.to_s.upcase
30
+ end
31
+ end
32
+ end
33
+
34
+
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/local/ruby
2
+
3
+ class SQS::Message
4
+ attr_accessor :queue, :_body, :id
5
+
6
+ def initialize( options={} )
7
+ self.body = options.is_a?( Hash ) ? options[:body] : nil
8
+ self.queue = options.is_a?( Hash ) ? options[:queue] : nil
9
+ self.id = options.is_a?( Hash ) ? options[:id] : options.to_s
10
+ end
11
+
12
+
13
+ def delete
14
+ raise "This #{self.class.to_s} cannot be deleted, because there is no associated SQS::Queue" unless self.queue.is_a?( SQS::Queue )
15
+ raise "Cannot delete a message with no id." if self.id.to_s.empty?
16
+
17
+ doc = SQS.call_web_service( :Action => 'DeleteMessage', :queue => self.queue, :MessageId => self.id )
18
+ true
19
+ rescue Exception => e
20
+ raise e
21
+ end
22
+
23
+
24
+ def peek
25
+ raise "This #{self.class.to_s} cannot be peeked, because there is no associated SQS::Queue" unless self.queue.is_a?( SQS::Queue )
26
+ raise "Cannot peek a message with no id." if self.id.to_s.empty?
27
+
28
+ doc = SQS.call_web_service( :Action => 'PeekMessage', :queue => self.queue, :MessageId => self.id )
29
+ self.body = doc.message_body
30
+ true
31
+ rescue Exception => e
32
+ raise e
33
+ end
34
+
35
+ def body
36
+ self.peek if self._body.to_s.empty?
37
+ self._body
38
+ end
39
+ def body=( b )
40
+ self._body = b
41
+ end
42
+
43
+ end
data/lib/sqs/queue.rb ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/local/ruby
2
+
3
+ require 'uri'
4
+ require 'rexml/document'
5
+ include REXML
6
+
7
+ class SQS::Queue
8
+ private
9
+ @attributes = {}
10
+ @@acceptable_schemes = ['https', 'http']
11
+
12
+ public
13
+ attr_accessor :url
14
+ def initialize( options={} )
15
+ self.url = options.is_a?( Hash ) ? options[:url] : options
16
+ bad_url = false
17
+
18
+ begin
19
+ u = URI.parse( self.url )
20
+ bad_url = !@@acceptable_schemes.include?( u.scheme ) || u.host.to_s.empty? || u.path.to_s.empty?
21
+ rescue URI::InvalidURIError, NoMethodError
22
+ bad_url = true
23
+ end
24
+ raise "'#{self.url}' does not seem to be a parseable URL. (Please provide all parts of the URL.)" if bad_url
25
+ end
26
+
27
+ def ==( other_queue )
28
+ self.url == other_queue.url
29
+ rescue
30
+ false
31
+ end
32
+
33
+ def to_s
34
+ "#{self.class.to_s} '#{self.name}' (#{self.url})"
35
+ end
36
+
37
+ def delete
38
+ doc = SQS.call_web_service( :Action => 'DeleteQueue', :queue => self )
39
+ true
40
+ rescue Exception => e
41
+ raise e
42
+ end
43
+
44
+ def force_delete
45
+ begin
46
+ self.delete
47
+ rescue
48
+ while m = self.receive_message
49
+ m.delete if m.respond_to?( :delete )
50
+ end
51
+ populated = 2
52
+ while populated > 0
53
+ begin
54
+ self.delete
55
+ populated = 0
56
+ rescue
57
+ populated -= 1
58
+ end
59
+ end
60
+ end
61
+ true
62
+ rescue Exception => e
63
+ raise e
64
+ end
65
+
66
+
67
+ def send_message( m )
68
+ doc = SQS.call_web_service( :Action => 'SendMessage', :queue => self, :MessageBody => m.to_s )
69
+ m = SQS::Message.new( :id => doc.message_id, :queue => self )
70
+ rescue Exception => e
71
+ raise e
72
+ end
73
+
74
+ def receive_message
75
+ # returns the first element of what receive_messages returns
76
+ m = self.receive_messages( :count => 1 ).first
77
+ yield( m ) if block_given?
78
+ m
79
+ end
80
+ def receive_messages( options={} )
81
+ # returns an array of SQS::Message instances (could be empty)
82
+ count = options.has_key?( :count ) ? options[:count].to_i : 1
83
+ params = { :Action => 'ReceiveMessage', :queue => self, :NumberOfMessages => count }
84
+ params[:VisibilityTimeout] = options[:message_ttl] if options.has_key?( :message_ttl )
85
+ doc = SQS.call_web_service( params )
86
+
87
+ messages = Array.new
88
+ doc.messages.each do |m|
89
+ messages << SQS::Message.new( :id => m.message_id, :queue => self, :body => m.message_body)
90
+ end
91
+ messages
92
+ rescue Exception => e
93
+ raise e
94
+ end
95
+
96
+ def visibility_timeout=( timeout )
97
+ doc = SQS.call_web_service( :Action => 'SetVisibilityTimeout', :queue => self, :VisibilityTimeout => timeout.to_i )
98
+ true
99
+ rescue Exception => e
100
+ raise e
101
+ end
102
+ def visibility_timeout
103
+ doc = SQS.call_web_service( :Action => 'GetVisibilityTimeout', :queue => self )
104
+ doc.visibility_timeout.to_i
105
+ rescue Exception => e
106
+ raise e
107
+ end
108
+
109
+
110
+
111
+ def add_grant( options={} )
112
+ doc = SQS.call_web_service(
113
+ :Action => 'AddGrant',
114
+ :queue => self,
115
+ :Permission => SQS.permissions[options[:permission]],
116
+ 'Grantee.EmailAddress'.to_sym => options[:email].respond_to?( :email ) ? options[:email].email : options[:email]
117
+ )
118
+ true
119
+ rescue Exception => e
120
+ raise e
121
+ end
122
+
123
+ def remove_grant( options={} )
124
+ params = {
125
+ :Action => 'RemoveGrant',
126
+ :queue => self,
127
+ :Permission => SQS.permissions( options[:permission] ),
128
+ }
129
+
130
+ if !options[:id].nil?
131
+ params['Grantee.ID'.to_sym] = options[:id].respond_to?( :id ) && !options[:id].is_a?( String ) ? options[:id].id : options[:id]
132
+ elsif !options[:email].nil?
133
+ params['Grantee.EmailAddress'.to_sym] = options[:email].respond_to?( :email ) ? options[:email].email : options[:email]
134
+ else
135
+ raise "You must specify either a grant id (:id) or a grantee email address (:email) when calling remove_grant."
136
+ end
137
+
138
+ doc = SQS.call_web_service( params )
139
+ true
140
+ rescue Exception => e
141
+ raise e
142
+ end
143
+
144
+ def list_grants
145
+ doc = SQS.call_web_service( :Action => 'ListGrants', :queue => self )
146
+ grants = Array.new
147
+ doc.grant_lists.each do |list|
148
+ list.grantees.each do |g|
149
+ grants << SQS::Grant.new( :id => g.id( :grantee ), :display_name => g.display_name, :permission => list.permission )
150
+ end
151
+ end
152
+ grants
153
+ rescue Exception => e
154
+ raise e
155
+ end
156
+
157
+ def each_grant( &block )
158
+ self.list_grants.each do |g|
159
+ block.call( g )
160
+ end
161
+ end
162
+
163
+ def name
164
+ # return everything after the last slash
165
+ self.url =~ /([^\/]*)$/ ? $1 : ''
166
+ end
167
+
168
+ def self.default_visibility_timeout
169
+ 30
170
+ end
171
+
172
+ def exist?
173
+ SQS.get_queue( self.name )
174
+ true
175
+ rescue
176
+ false
177
+ end
178
+ alias_method :exists?, :exist?
179
+ end