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.
@@ -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,11 @@
1
+ module WAZ
2
+ module Queues
3
+ module VERSION #:nodoc:
4
+ MAJOR = '0'
5
+ MINOR = '1'
6
+ TINY = '0'
7
+ end
8
+
9
+ Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY].compact * '.'
10
+ end
11
+ 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
@@ -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
+