waz-queues 0.1.0

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