waz-queues 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/waz-queues.rb +19 -0
- data/lib/waz/queues/base.rb +22 -0
- data/lib/waz/queues/exceptions.rb +30 -0
- data/lib/waz/queues/message.rb +31 -0
- data/lib/waz/queues/queue.rb +96 -0
- data/lib/waz/queues/service.rb +95 -0
- data/lib/waz/queues/version.rb +11 -0
- data/lib/waz/storage/core_service.rb +56 -0
- data/rakefile +40 -0
- metadata +82 -0
data/lib/waz-queues.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'cgi'
|
3
|
+
require 'base64'
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'rexml/xpath'
|
6
|
+
|
7
|
+
require 'restclient'
|
8
|
+
require 'hmac-sha2'
|
9
|
+
|
10
|
+
$:.unshift(File.dirname(__FILE__))
|
11
|
+
require 'waz/storage/core_service'
|
12
|
+
require 'waz/queues/base'
|
13
|
+
require 'waz/queues/exceptions'
|
14
|
+
require 'waz/queues/message'
|
15
|
+
require 'waz/queues/queue'
|
16
|
+
require 'waz/queues/service'
|
17
|
+
require 'waz/queues/version'
|
18
|
+
|
19
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Queues
|
3
|
+
class Base
|
4
|
+
class << self
|
5
|
+
def establish_connection!(options = {})
|
6
|
+
raise InvalidOption.new(:account_name) unless options.keys.include? :account_name
|
7
|
+
raise InvalidOption.new(:access_key) unless options.keys.include? :access_key
|
8
|
+
options[:use_ssl] = false unless options.keys.include? :use_ssl
|
9
|
+
@connection = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def default_connection
|
13
|
+
return @connection
|
14
|
+
end
|
15
|
+
|
16
|
+
def connected?
|
17
|
+
return !@connection.nil?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Queues
|
3
|
+
class WAZStorageException < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class InvalidOption < WAZStorageException
|
7
|
+
def initialize(missing_option)
|
8
|
+
super("You did not provide one of the required parameters. Please provide the #{missing_option}.")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class QueueAlreadyExists < WAZStorageException
|
13
|
+
def initialize(name)
|
14
|
+
super("The queue #{name} already exists on your account.")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class OptionOutOfRange < WAZStorageException
|
19
|
+
def initialize(args = {})
|
20
|
+
super("The #{args[:name]} parameter is out of range allowed values go from #{args[:min]} to #{args[:max]}.")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class InvalidOperation < WAZStorageException
|
25
|
+
def initialize()
|
26
|
+
super("A peeked message cannot be delete, you need to lock it first (pop_receipt required).")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Queues
|
3
|
+
class Message
|
4
|
+
attr_accessor :message_id, :message_text, :pop_receipt, :expiration_time, :insertion_time, :time_next_visible
|
5
|
+
def initialize(params = {})
|
6
|
+
self.message_id = params[:message_id]
|
7
|
+
self.message_text = params[:message_text]
|
8
|
+
self.pop_receipt = params[:pop_receipt]
|
9
|
+
self.expiration_time = params[:expiration_time]
|
10
|
+
self.insertion_time = params[:insertion_time]
|
11
|
+
self.time_next_visible = params[:time_next_visible]
|
12
|
+
@queue_name = params[:queue_name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def queue_name
|
16
|
+
return @queue_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy!
|
20
|
+
raise WAZ::Queues::InvalidOperation if pop_receipt.nil?
|
21
|
+
service_instance.delete_message(queue_name, message_id, pop_receipt)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def service_instance
|
26
|
+
options = Base.default_connection
|
27
|
+
@service_instance ||= Service.new(options[:account_name], options[:access_key])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Queues
|
3
|
+
class Queue
|
4
|
+
class << self
|
5
|
+
def list
|
6
|
+
service_instance.list_queues.map do |queue|
|
7
|
+
WAZ::Queues::Queue.new(queue)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def create(queue_name, metadata = {})
|
12
|
+
service_instance.create_queue(queue_name, metadata)
|
13
|
+
WAZ::Queues::Queue.new({:name => queue_name, :url => service_instance.generate_request_uri(nil, queue_name)})
|
14
|
+
end
|
15
|
+
|
16
|
+
def find(queue_name)
|
17
|
+
begin
|
18
|
+
service_instance.get_queue_metadata(queue_name)
|
19
|
+
WAZ::Queues::Queue.new({:name => queue_name, :url => service_instance.generate_request_uri(nil, queue_name)})
|
20
|
+
rescue RestClient::ResourceNotFound
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def service_instance
|
27
|
+
options = Base.default_connection
|
28
|
+
@service_instance ||= Service.new(options[:account_name], options[:access_key])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_accessor :name, :url
|
33
|
+
|
34
|
+
def initialize(options = {})
|
35
|
+
raise WAZ::Queues::InvalidOption, :name unless options.keys.include?(:name)
|
36
|
+
raise WAZ::Queues::InvalidOption, :url unless options.keys.include?(:url)
|
37
|
+
self.name = options[:name]
|
38
|
+
self.url = options[:url]
|
39
|
+
end
|
40
|
+
|
41
|
+
def destroy!
|
42
|
+
service_instance.delete_queue(self.name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def metadata
|
46
|
+
service_instance.get_queue_metadata(self.name)
|
47
|
+
end
|
48
|
+
|
49
|
+
# when overwrite passed different than true it overrides
|
50
|
+
# the metadata for the queue
|
51
|
+
def put_properties!(new_metadata = {}, overwrite = false)
|
52
|
+
new_metadata.merge!(metadata.reject { |k, v| !k.to_s.start_with? "x_ms_meta"} ) unless overwrite
|
53
|
+
service_instance.set_queue_metadata(new_metadata)
|
54
|
+
end
|
55
|
+
|
56
|
+
def enqueue!(message, ttl = 604800)
|
57
|
+
service_instance.enqueue(self.name, message, ttl)
|
58
|
+
end
|
59
|
+
|
60
|
+
def size
|
61
|
+
metadata[:x_ms_approximate_messages_count].to_i
|
62
|
+
end
|
63
|
+
|
64
|
+
def lock(num_of_messages = 1, visibility_timeout = nil)
|
65
|
+
options = {}
|
66
|
+
options[:num_of_messages] = num_of_messages
|
67
|
+
options[:visiblity_timeout] = visibility_timeout unless visibility_timeout.nil?
|
68
|
+
messages = service_instance.get_messages(self.name, options).map do |raw_message|
|
69
|
+
WAZ::Queues::Message.new(raw_message.merge(:queue_name => self.name))
|
70
|
+
end
|
71
|
+
return messages.first() if num_of_messages == 1
|
72
|
+
return messages
|
73
|
+
end
|
74
|
+
|
75
|
+
def peek(num_of_messages = 1)
|
76
|
+
options = {}
|
77
|
+
options[:num_of_messages] = num_of_messages
|
78
|
+
messages = service_instance.peek(self.name, options).map do |raw_message|
|
79
|
+
WAZ::Queues::Message.new(raw_message.merge(:queue_name => self.name))
|
80
|
+
end
|
81
|
+
return messages.first() if num_of_messages == 1
|
82
|
+
return messages
|
83
|
+
end
|
84
|
+
|
85
|
+
def clear
|
86
|
+
service_instance.clear_queue(self.name)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
def service_instance
|
91
|
+
options = Base.default_connection
|
92
|
+
@service_instance ||= Service.new(options[:account_name], options[:access_key])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Queues
|
3
|
+
class Service
|
4
|
+
include WAZ::Storage::SharedKeyCoreService
|
5
|
+
|
6
|
+
def list_queues(options ={})
|
7
|
+
url = generate_request_uri("list", nil)
|
8
|
+
request = generate_request("GET", url)
|
9
|
+
doc = REXML::Document.new(request.execute())
|
10
|
+
queues = []
|
11
|
+
REXML::XPath.each(doc, '//Queue/') do |item|
|
12
|
+
queues << { :name => REXML::XPath.first(item, "QueueName").text,
|
13
|
+
:url => REXML::XPath.first(item, "Url").text }
|
14
|
+
end
|
15
|
+
return queues
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_queue(queue_name, metadata = {})
|
19
|
+
begin
|
20
|
+
url = generate_request_uri(nil, queue_name)
|
21
|
+
request = generate_request("PUT", url, metadata)
|
22
|
+
request.execute()
|
23
|
+
rescue RestClient::RequestFailed
|
24
|
+
raise WAZ::Queues::QueueAlreadyExists, queue_name if $!.http_code == 409
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete_queue(queue_name)
|
29
|
+
url = generate_request_uri(nil, queue_name)
|
30
|
+
request = generate_request("DELETE", url)
|
31
|
+
request.execute()
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_queue_metadata(queue_name)
|
35
|
+
url = generate_request_uri("metadata", queue_name)
|
36
|
+
request = generate_request("HEAD", url)
|
37
|
+
request.execute().headers
|
38
|
+
end
|
39
|
+
|
40
|
+
def set_queue_metadata(queue_name, metadata = {})
|
41
|
+
url = generate_request_uri("metadata", queue_name)
|
42
|
+
request = generate_request("PUT", url, metadata)
|
43
|
+
request.execute()
|
44
|
+
end
|
45
|
+
|
46
|
+
# ttl Specifies the time-to-live interval for the message, in seconds.
|
47
|
+
# The maximum time-to-live allowed is 7 days. If this parameter is omitted, the default time-to-live is 7 days.
|
48
|
+
def enqueue(queue_name, message_payload, ttl = 604800)
|
49
|
+
url = generate_request_uri(nil, "#{queue_name}/messages", "messagettl" => ttl)
|
50
|
+
payload = "<?xml version=\"1.0\" encoding=\"utf-8\"?><QueueMessage><MessageText>#{message_payload}</MessageText></QueueMessage>"
|
51
|
+
request = generate_request("POST", url, { "Content-Type" => "application/xml" }, payload)
|
52
|
+
request.execute()
|
53
|
+
end
|
54
|
+
|
55
|
+
# :num_of_messages option specifies the max number of messages to get (maximum 32)
|
56
|
+
# :visibility_timeout option specifies the timeout of the message locking in seconds (max two hours)
|
57
|
+
def get_messages(queue_name, options = {})
|
58
|
+
raise WAZ::Queues::OptionOutOfRange, {:name => :num_of_messages, :min => 1, :max => 32} if (options.keys.include?(:num_of_messages) && (options[:num_of_messages].to_i < 1 || options[:num_of_messages].to_i > 32))
|
59
|
+
raise WAZ::Queues::OptionOutOfRange, {:name => :visibility_timeout, :min => 1, :max => 7200} if (options.keys.include?(:visibility_timeout) && (options[:visibility_timeout].to_i < 1 || options[:visibility_timeout].to_i > 7200))
|
60
|
+
url = generate_request_uri(nil, "#{queue_name}/messages", options)
|
61
|
+
request = generate_request("GET", url)
|
62
|
+
doc = REXML::Document.new(request.execute())
|
63
|
+
messages = []
|
64
|
+
REXML::XPath.each(doc, '//QueueMessage/') do |item|
|
65
|
+
message = { :message_id => REXML::XPath.first(item, "MessageId").text,
|
66
|
+
:message_text => REXML::XPath.first(item, "MessageText").text,
|
67
|
+
:expiration_time => Time.httpdate(REXML::XPath.first(item, "ExpirationTime").text),
|
68
|
+
:insertion_time => Time.httpdate(REXML::XPath.first(item, "InsertionTime").text) }
|
69
|
+
|
70
|
+
# This are only valid when peek-locking messages
|
71
|
+
message[:pop_receipt] = REXML::XPath.first(item, "PopReceipt").text unless REXML::XPath.first(item, "PopReceipt").nil?
|
72
|
+
message[:time_next_visible] = Time.httpdate(REXML::XPath.first(item, "TimeNextVisible").text) unless REXML::XPath.first(item, "TimeNextVisible").nil?
|
73
|
+
messages << message
|
74
|
+
end
|
75
|
+
return messages
|
76
|
+
end
|
77
|
+
|
78
|
+
def peek(queue_name, options = {})
|
79
|
+
return get_messages(queue_name, {:peek_only => true}.merge(options))
|
80
|
+
end
|
81
|
+
|
82
|
+
def delete_message(queue_name, message_id, pop_receipt)
|
83
|
+
url = generate_request_uri(nil, "#{queue_name}/messages/#{message_id}", :pop_receipt => pop_receipt)
|
84
|
+
request = generate_request("DELETE", url)
|
85
|
+
request.execute()
|
86
|
+
end
|
87
|
+
|
88
|
+
def clear_queue(queue_name)
|
89
|
+
url = generate_request_uri(nil, "#{queue_name}/messages")
|
90
|
+
request = generate_request("DELETE", url)
|
91
|
+
request.execute()
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module WAZ
|
2
|
+
module Storage
|
3
|
+
module SharedKeyCoreService
|
4
|
+
attr_accessor :account_name, :account_key, :use_ssl, :base_url
|
5
|
+
|
6
|
+
def initialize(account_name, account_key, use_ssl = false, base_url = "queue.core.windows.net" )
|
7
|
+
self.account_name = account_name
|
8
|
+
self.account_key = account_key
|
9
|
+
self.use_ssl = use_ssl
|
10
|
+
self.base_url = base_url
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_request(verb, url, headers = {}, payload = nil)
|
14
|
+
http_headers = {}
|
15
|
+
headers.each{ |k, v| http_headers[k.to_s.gsub(/_/, '-')] = v} unless headers.nil?
|
16
|
+
request = RestClient::Request.new(:method => verb.downcase.to_sym, :url => url, :headers => http_headers, :payload => payload)
|
17
|
+
request.headers["x-ms-Date"] = Time.new.httpdate
|
18
|
+
request.headers["Content-Length"] = (request.payload or "").length
|
19
|
+
request.headers["Authorization"] = "SharedKey #{account_name}:#{generate_signature(request)}"
|
20
|
+
return request
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate_request_uri(operation = nil, path = nil, options = {})
|
24
|
+
protocol = use_ssl ? "https" : "http"
|
25
|
+
query_params = options.keys.sort{ |a, b| a.to_s <=> b.to_s}.map{ |k| "#{k.to_s.gsub(/_/, '')}=#{options[k]}"}.join("&") unless options.empty?
|
26
|
+
uri = "#{protocol}://#{account_name}.#{base_url}#{(path or "").start_with?("/") ? "" : "/"}#{(path or "")}#{operation ? "?comp=" + operation : ""}"
|
27
|
+
uri << "#{operation ? "&" : "?"}#{query_params}" if query_params
|
28
|
+
return uri
|
29
|
+
end
|
30
|
+
|
31
|
+
def canonicalize_headers(headers)
|
32
|
+
cannonicalized_headers = headers.keys.select {|h| h.to_s.start_with? 'x-ms'}.map{ |h| "#{h.downcase.strip}:#{headers[h].strip}" }.sort{ |a, b| a <=> b }.join("\x0A")
|
33
|
+
return cannonicalized_headers
|
34
|
+
end
|
35
|
+
|
36
|
+
def canonicalize_message(url)
|
37
|
+
uri_component = url.gsub(/https?:\/\/[^\/]+\//i, '').gsub(/\?.*/i, '')
|
38
|
+
comp_component = url.scan(/(comp=[^&]+)/i).first()
|
39
|
+
uri_component << "?#{comp_component}" if comp_component
|
40
|
+
canonicalized_message = "/#{self.account_name}/#{uri_component}"
|
41
|
+
return canonicalized_message
|
42
|
+
end
|
43
|
+
|
44
|
+
def generate_signature(request)
|
45
|
+
signature = request.method.to_s.upcase + "\x0A" +
|
46
|
+
(request.headers["Content-MD5"] or "") + "\x0A" +
|
47
|
+
(request.headers["Content-Type"] or "") + "\x0A" +
|
48
|
+
(request.headers["Date"] or "")+ "\x0A" +
|
49
|
+
canonicalize_headers(request.headers) + "\x0A" +
|
50
|
+
canonicalize_message(request.url)
|
51
|
+
|
52
|
+
return Base64.encode64(HMAC::SHA256.new(Base64.decode64(self.account_key)).update(signature.toutf8).digest)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'lib/waz-queues'
|
6
|
+
|
7
|
+
|
8
|
+
namespace :test do
|
9
|
+
Spec::Rake::SpecTask.new('run_with_rcov') do |t|
|
10
|
+
puts "running tests and code coverage"
|
11
|
+
t.spec_files = FileList['tests/waz/queues/*.rb']
|
12
|
+
t.rcov = true
|
13
|
+
t.rcov_opts = ['--text-report', '--exclude', "exclude.*/.gem,test,Library,#{ENV['GEM_HOME']}", '--sort', 'coverage' ]
|
14
|
+
t.spec_opts = ['-cfn']
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
namespace :dist do
|
19
|
+
spec = Gem::Specification.new do |s|
|
20
|
+
s.name = 'waz-queues'
|
21
|
+
s.version = Gem::Version.new(WAZ::Queues::Version)
|
22
|
+
s.summary = "Client library for Windows Azure's Queue Storage Service's REST API"
|
23
|
+
s.description = s.summary
|
24
|
+
s.email = 'johnny.halife@me.com'
|
25
|
+
s.author = 'Johnny G. Halife'
|
26
|
+
s.homepage = 'http://github.com/johnnyhalife/waz-queues'
|
27
|
+
s.require_paths = ["lib"]
|
28
|
+
s.files = FileList['rakefile', 'lib/**/*.rb']
|
29
|
+
s.test_files = Dir['test/**/*']
|
30
|
+
s.has_rdoc = false
|
31
|
+
|
32
|
+
# Dependencies
|
33
|
+
s.add_runtime_dependency 'rest-client', '>= 1.0.3'
|
34
|
+
s.add_dependency 'ruby-hmac'
|
35
|
+
end
|
36
|
+
|
37
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
38
|
+
pkg.need_tar = true
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: waz-queues
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Johnny G. Halife
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-09 00:00:00 -03:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rest-client
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ruby-hmac
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
description: Client library for Windows Azure's Queue Storage Service's REST API
|
36
|
+
email: johnny.halife@me.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- rakefile
|
45
|
+
- lib/waz/queues/base.rb
|
46
|
+
- lib/waz/queues/exceptions.rb
|
47
|
+
- lib/waz/queues/message.rb
|
48
|
+
- lib/waz/queues/queue.rb
|
49
|
+
- lib/waz/queues/service.rb
|
50
|
+
- lib/waz/queues/version.rb
|
51
|
+
- lib/waz/storage/core_service.rb
|
52
|
+
- lib/waz-queues.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/johnnyhalife/waz-queues
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: "0"
|
67
|
+
version:
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: "0"
|
73
|
+
version:
|
74
|
+
requirements: []
|
75
|
+
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.3.5
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Client library for Windows Azure's Queue Storage Service's REST API
|
81
|
+
test_files: []
|
82
|
+
|