waz-queues 0.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.
- 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
|
+
|