openwferu-sqs 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
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
+