openwferu-sqs 0.9.8

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.
Files changed (2) hide show
  1. data/lib/openwfe/util/sqs.rb +581 -0
  2. metadata +46 -0
@@ -0,0 +1,581 @@
1
+ #
2
+ #--
3
+ # Copyright (c) 2007, John Mettraux, OpenWFE.org
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ #
9
+ # . Redistributions of source code must retain the above copyright notice, this
10
+ # list of conditions and the following disclaimer.
11
+ #
12
+ # . Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ #
16
+ # . Neither the name of the "OpenWFE" nor the names of its contributors may be
17
+ # used to endorse or promote products derived from this software without
18
+ # specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+ #++
32
+ #
33
+ # $Id$
34
+ #
35
+
36
+ #
37
+ # Made in Japan
38
+ #
39
+ # John dot Mettraux at OpenWFE dot org
40
+ #
41
+
42
+ require 'base64'
43
+ require 'cgi'
44
+ require 'net/https'
45
+ require 'rexml/document'
46
+ require 'time'
47
+ require 'pp'
48
+
49
+
50
+ module SQS
51
+
52
+ #
53
+ # An SQS message (after its creation).
54
+ #
55
+ class Message
56
+
57
+ attr_reader :queue, :message_id, :message_body
58
+
59
+ def initialize (queue, xml_element)
60
+
61
+ @queue = queue
62
+ @message_id = SQS::get_element_text(xml_element, "MessageId")
63
+ @message_body = SQS::get_element_text(xml_element, "MessageBody")
64
+ end
65
+
66
+ #
67
+ # Connects to the queue service and deletes this message in its queue.
68
+ #
69
+ def delete
70
+ @queue.queue_service.delete_message(@queue, @message_id)
71
+ end
72
+ end
73
+
74
+ #
75
+ # An SQS queue (gathering all the necessary info about it
76
+ # in a single class).
77
+ #
78
+ class Queue
79
+
80
+ attr_reader :queue_service, :host, :path, :name
81
+
82
+ def initialize (queue_service, xml_element)
83
+
84
+ @queue_service = queue_service
85
+
86
+ s = xml_element.text.to_s
87
+ m = Regexp.compile('^http://(.*)(/.*)(/.*$)').match(s)
88
+ @host = m[1]
89
+ @name = m[3][1..-1]
90
+ @path = m[2] + m[3]
91
+ end
92
+ end
93
+
94
+ class QueueService
95
+
96
+ AWS_VERSION = "2006-04-01"
97
+ DEFAULT_QUEUE_HOST = "queue.amazonaws.com"
98
+
99
+ def initialize (queue_host=nil)
100
+
101
+ @queue_host = queue_host
102
+ @queue_host = DEFAULT_QUEUE_HOST unless @queue_host
103
+ end
104
+
105
+ #
106
+ # Lists the queues for the active AWS account.
107
+ # If 'prefix' is given, only queues whose name begin with that
108
+ # prefix will be returned.
109
+ #
110
+ def list_queues (prefix=nil)
111
+
112
+ queues = []
113
+
114
+ path = "/"
115
+ path = "#{path}?QueueNamePrefix=#{prefix}" if prefix
116
+
117
+ doc = do_action :get, @queue_host, path
118
+
119
+ doc.elements.each("//QueueUrl") do |e|
120
+ queues << Queue.new(self, e)
121
+ end
122
+
123
+ return queues
124
+ end
125
+
126
+ #
127
+ # Creates a queue.
128
+ #
129
+ # If the queue name doesn't comply with SQS requirements for it,
130
+ # an error will be raised.
131
+ #
132
+ def create_queue (queue_name)
133
+
134
+ doc = do_action :post, @queue_host, "/?QueueName=#{queue_name}"
135
+
136
+ doc.elements.each("//QueueUrl") do |e|
137
+ return e.text.to_s
138
+ end
139
+ end
140
+
141
+ #
142
+ # Given some content ('text/plain' content), send it as a message to
143
+ # a queue.
144
+ # Returns the SQS message id (a String).
145
+ #
146
+ # The queue might be a queue name (String) or a Queue instance.
147
+ #
148
+ def put_message (queue, content)
149
+
150
+ queue = resolve_queue(queue)
151
+
152
+ doc = do_action :put, queue.host, "#{queue.path}/back", content
153
+
154
+ #puts doc.to_s
155
+
156
+ #status_code = SQS::get_element_text(doc, '//StatusCode')
157
+ #message_id = SQS::get_element_text(doc, '//MessageId')
158
+ #request_id = SQS::get_element_text(doc, '//RequestId')
159
+ #{ :status_code => status_code,
160
+ # :message_id => message_id,
161
+ # :request_id => request_id }
162
+
163
+ SQS::get_element_text(doc, '//MessageId')
164
+ end
165
+
166
+ alias :send_message :put_message
167
+
168
+ #
169
+ # Retrieves a bunch of messages from a queue. Returns a list of
170
+ # Message instances.
171
+ #
172
+ # There are actually two optional params that this method understands :
173
+ #
174
+ # - :timeout the duration in seconds of the message visibility in the
175
+ # queue
176
+ # - :count the max number of message to be returned by this call
177
+ #
178
+ # The queue might be a queue name (String) or a Queue instance.
179
+ #
180
+ def get_messages (queue, params={})
181
+
182
+ queue = resolve_queue(queue)
183
+
184
+ path = "#{queue.path}/front"
185
+
186
+ path += "?" if params.size > 0
187
+
188
+ timeout = params[:timeout]
189
+ count = params[:count]
190
+
191
+ path += "VisibilityTimeout=#{timeout}" if timeout
192
+ path += "&" if timeout and count
193
+ path += "NumberOfMessages=#{count}" if count
194
+
195
+ doc = do_action :get, queue.host, path
196
+
197
+ messages = []
198
+
199
+ doc.elements.each("//Message") do |me|
200
+ messages << Message.new(queue, me)
201
+ end
202
+
203
+ messages
204
+ end
205
+
206
+ #
207
+ # Retrieves a single message from a queue. Returns an instance of
208
+ # Message.
209
+ #
210
+ # The queue might be a queue name (String) or a Queue instance.
211
+ #
212
+ def get_message (queue, message_id)
213
+
214
+ queue = resolve_queue(queue)
215
+
216
+ path = "#{queue.path}/#{message_id}"
217
+
218
+ begin
219
+ doc = do_action :get, queue.host, path
220
+ Message.new(queue, doc.root.elements[1])
221
+ rescue Exception => e
222
+ #puts e.message
223
+ return nil if e.message.match "^404 .*$"
224
+ raise e
225
+ end
226
+ end
227
+
228
+ #
229
+ # Deletes a given message.
230
+ #
231
+ # The queue might be a queue name (String) or a Queue instance.
232
+ #
233
+ def delete_message (queue, message_id)
234
+
235
+ queue = resolve_queue(queue)
236
+
237
+ path = "#{queue.path}/#{message_id}"
238
+ #path = "#{queue.path}/#{CGI::escape(message_id)}"
239
+
240
+ doc = do_action :delete, queue.host, path
241
+
242
+ SQS::get_element_text(doc, "//StatusCode") == "Success"
243
+ end
244
+
245
+ #
246
+ # Use with care !
247
+ #
248
+ # Attempts at deleting all the messages in a queue.
249
+ # Returns the total count of messages deleted.
250
+ #
251
+ # A call on this method might take a certain time, as it has
252
+ # to delete each message individually. AWS will perhaps
253
+ # add a proper 'flush_queue' method later.
254
+ #
255
+ # The queue might be a queue name (String) or a Queue instance.
256
+ #
257
+ def flush_queue (queue)
258
+
259
+ count = 0
260
+
261
+ while true
262
+
263
+ l = get_messages(queue, :timeout => 0, :count => 255)
264
+ break if l.length < 1
265
+
266
+ l.each do |m|
267
+ m.delete
268
+ count += 1
269
+ end
270
+ end
271
+
272
+ return count
273
+ end
274
+
275
+ #
276
+ # Deletes the queue. Returns true if the delete was successful.
277
+ # You can empty a queue by called the method #flush_queue
278
+ #
279
+ # If 'force' is set to true, a flush will be performed on the
280
+ # queue before the actual delete operation. It should ensure
281
+ # a successful removal of the queue.
282
+ #
283
+ def delete_queue (queue, force=false)
284
+
285
+ queue = resolve_queue(queue)
286
+
287
+ flush_queue(queue) if force
288
+
289
+ begin
290
+
291
+ doc = do_action :delete, @queue_host, queue.path
292
+
293
+ rescue Exception => e
294
+
295
+ return false if e.message.match "^400 .*$"
296
+ end
297
+
298
+ SQS::get_element_text(doc, "//StatusCode") == "Success"
299
+ end
300
+
301
+ #
302
+ # Given a queue name, a Queue instance is returned.
303
+ #
304
+ def get_queue (queue_name)
305
+
306
+ l = list_queues(queue_name)
307
+
308
+ l.each do |q|
309
+ return q if q.name == queue_name
310
+ end
311
+
312
+ #return nil
313
+ raise "found no queue named '#{queue_name}'"
314
+ end
315
+
316
+ protected
317
+
318
+ #
319
+ # 'queue' might be a Queue instance or a queue name.
320
+ # If it's a Queue instance, it is immediately returned,
321
+ # else the Queue instance is looked up and returned.
322
+ #
323
+ def resolve_queue (queue)
324
+ return queue if queue.kind_of? Queue
325
+ return get_queue(queue.to_s)
326
+ end
327
+
328
+ #
329
+ # The actual http request/response job is done here.
330
+ #
331
+ def do_action (action, host, path, content=nil)
332
+
333
+ #puts "___path : #{path}"
334
+
335
+ doc = nil
336
+
337
+ http = Net::HTTP.new(host)
338
+ http.start do
339
+
340
+ date = Time.now.httpdate
341
+
342
+ req = if action == :get
343
+ Net::HTTP::Get.new(path)
344
+ elsif action == :post
345
+ Net::HTTP::Post.new(path)
346
+ elsif action == :put
347
+ Net::HTTP::Put.new(path)
348
+ else #action == :delete
349
+ Net::HTTP::Delete.new(path)
350
+ end
351
+
352
+ req['AWS-Version'] = AWS_VERSION
353
+ req['Date'] = date
354
+ req['Content-type'] = 'text/plain'
355
+
356
+ if action == :put or action == :post
357
+ req.body = content
358
+ req['Content-length'] = content.length.to_s if content
359
+ end
360
+
361
+ req['Authorization'] = generate_auth_header(
362
+ action, path, date, "text/plain")
363
+
364
+ #req.each_header do |k, v|
365
+ # puts " - '#{k}' => '#{v}'"
366
+ #end
367
+
368
+ res = http.request(req)
369
+
370
+ case res
371
+ when Net::HTTPSuccess, Net::HTTPRedirection
372
+ doc = REXML::Document.new(res.read_body)
373
+ else
374
+ res.error!
375
+ end
376
+ end
377
+ raise_errors(doc)
378
+ return doc
379
+ end
380
+
381
+ #
382
+ # Scans the SQS XML reply for potential errors and raises an
383
+ # error if he encounters one.
384
+ #
385
+ def raise_errors (doc)
386
+
387
+ doc.elements.each("//Error") do |e|
388
+
389
+ code = get_element_text(e, "Code")
390
+ return unless code
391
+
392
+ message = get_element_text(e, "Message")
393
+ raise "SQS::#{code} : #{m.text.to_s}"
394
+ end
395
+ end
396
+
397
+ #
398
+ # Generates the 'AWS x:y" authorization header value.
399
+ #
400
+ def generate_auth_header (action, path, date, content_type)
401
+
402
+ s = ""
403
+ s << action.to_s.upcase
404
+ s << "\n"
405
+
406
+ #s << Base64.encode64(Digest::MD5.digest(content)).strip \
407
+ # if content
408
+ #
409
+ # documented but not necessary (not working)
410
+ s << "\n"
411
+
412
+ s << content_type
413
+ s << "\n"
414
+
415
+ s << date
416
+ s << "\n"
417
+
418
+ i = path.index '?'
419
+ path = path[0..i-1] if i
420
+ s << path
421
+
422
+ #puts ">>>#{s}<<<"
423
+
424
+ digest = OpenSSL::Digest::Digest.new 'sha1'
425
+
426
+ key = ENV['AMAZON_SECRET_ACCESS_KEY']
427
+
428
+ raise "No $AMAZON_SECRET_ACCESS_KEY env variable found" \
429
+ unless key
430
+
431
+ sig = OpenSSL::HMAC.digest(digest, key, s)
432
+ sig = Base64.encode64(sig).strip
433
+
434
+ "AWS #{ENV['AMAZON_ACCESS_KEY_ID']}:#{sig}"
435
+ end
436
+
437
+ end
438
+
439
+ #
440
+ # A convenience method for returning the text of a sub element,
441
+ # maybe there is something better in REXML, but I haven't found out
442
+ # yet.
443
+ #
444
+ def SQS.get_element_text (parent_elt, elt_name)
445
+ e = parent_elt.elements[elt_name]
446
+ return nil unless e
447
+ return e.text.to_s
448
+ end
449
+ end
450
+
451
+
452
+ #
453
+ # running directly...
454
+
455
+ if $0 == __FILE__
456
+
457
+ if ENV['AMAZON_ACCESS_KEY_ID'] == nil or ENV['AMAZON_SECRET_ACCESS_KEY'] == nil
458
+
459
+ puts
460
+ puts "env variables $AMAZON_ACCESS_KEY_ID and $AMAZON_SECRET_ACCESS_KEY are not set"
461
+ puts
462
+ exit 1
463
+ end
464
+
465
+ ACTIONS = {
466
+ :list_queues => :list_queues,
467
+ :lq => :list_queues,
468
+ :create_queue => :create_queue,
469
+ :cq => :create_queue,
470
+ :delete_queue => :delete_queue,
471
+ :dq => :delete_queue,
472
+ :flush_queue => :flush_queue,
473
+ :fq => :flush_queue,
474
+ :get_message => :get_message,
475
+ :gm => :get_message,
476
+ :delete_message => :delete_message,
477
+ :dm => :delete_message,
478
+ :puts_message => :put_message,
479
+ :pm => :put_message
480
+ }
481
+
482
+ b64 = false
483
+ queue_host = nil
484
+
485
+ require 'optparse'
486
+
487
+ opts = OptionParser.new
488
+
489
+ opts.banner = "Usage: sqs.rb [options] {action} [queue_name] [message_id]"
490
+ opts.separator("")
491
+ opts.separator(" known actions are :")
492
+ opts.separator("")
493
+
494
+ keys = ACTIONS.keys.collect { |k| k.to_s }.sort
495
+ keys.each { |k| opts.separator(" - '#{k}' (#{ACTIONS[k.intern]})") }
496
+
497
+ opts.separator("")
498
+ opts.separator(" options are :")
499
+ opts.separator("")
500
+
501
+ opts.on("-H", "--host", "AWS queue host") do |host|
502
+ queue_host = host
503
+ end
504
+
505
+ opts.on("-h", "--help", "displays this help / usage") do
506
+ STDERR.puts "\n#{opts.to_s}\n"
507
+ exit 0
508
+ end
509
+
510
+ opts.on("-b", "--base64", "encode/decode messages with base64") do
511
+ b64 = true
512
+ end
513
+
514
+ argv = opts.parse(ARGV)
515
+
516
+ if argv.length < 1
517
+ STDERR.puts "\n#{opts.to_s}\n"
518
+ exit 0
519
+ end
520
+
521
+ a = argv[0]
522
+ queue_name = argv[1]
523
+ message_id = argv[2]
524
+
525
+ action = ACTIONS[a.intern]
526
+
527
+ unless action
528
+ STDERR.puts "unknown action '#{a}'"
529
+ exit 1
530
+ end
531
+
532
+ qs = SQS::QueueService.new
533
+
534
+ STDERR.puts "#{action.to_s}..."
535
+
536
+ #
537
+ # just do it
538
+
539
+ case action
540
+ when :list_queues, :create_queue, :delete_queue, :flush_queue
541
+
542
+ pp qs.send(action, queue_name)
543
+
544
+ when :get_message
545
+
546
+ if message_id
547
+ m = qs.get_message(queue_name, message_id)
548
+ body = m.message_body
549
+ body = Base64.decode64(body) if b64
550
+ puts body
551
+ else
552
+ pp qs.get_messages(queue_name, :timeout => 0, :count => 255)
553
+ end
554
+
555
+ when :delete_message
556
+
557
+ raise "argument 'message_id' is missing" unless message_id
558
+ pp qs.delete_message(queue_name, message_id)
559
+
560
+ when :put_message
561
+
562
+ message = argv[2]
563
+
564
+ unless message
565
+ message = ""
566
+ while true
567
+ s = STDIN.gets()
568
+ break if s == nil
569
+ message += s[0..-2]
570
+ end
571
+ end
572
+
573
+ message = Base64.encode64(message).strip if b64
574
+
575
+ pp qs.put_message(queue_name, message)
576
+ else
577
+
578
+ STDERR.puts "not yet implemented..."
579
+ end
580
+
581
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: openwferu-sqs
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.9.8
7
+ date: 2007-04-05 00:00:00 +09:00
8
+ summary: Accessing Amazon SQS over its REST API
9
+ require_paths:
10
+ - lib
11
+ email: john at openwfe dot org
12
+ homepage: http://openwferu.rubyforge.org/scheduler.html
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: openwferu-sqs
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - John Mettraux
31
+ files:
32
+ - lib/openwfe/util/sqs.rb
33
+ test_files: []
34
+
35
+ rdoc_options: []
36
+
37
+ extra_rdoc_files: []
38
+
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ requirements: []
44
+
45
+ dependencies: []
46
+