gz_activemessaging 0.13.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.travis.yml +40 -0
- data/Appraisals +19 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +87 -0
- data/README.md +27 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/activemessaging.gemspec +137 -0
- data/gemfiles/activesupport23.gemfile +10 -0
- data/gemfiles/activesupport23.gemfile.lock +51 -0
- data/gemfiles/activesupport30.gemfile +11 -0
- data/gemfiles/activesupport30.gemfile.lock +53 -0
- data/gemfiles/activesupport31.gemfile +11 -0
- data/gemfiles/activesupport31.gemfile.lock +55 -0
- data/gemfiles/activesupport32.gemfile +10 -0
- data/gemfiles/activesupport32.gemfile.lock +55 -0
- data/generators/a13g_test_harness/a13g_test_harness_generator.rb +19 -0
- data/generators/a13g_test_harness/templates/active_messaging_test.rhtml +13 -0
- data/generators/a13g_test_harness/templates/active_messaging_test_controller.rb +29 -0
- data/generators/a13g_test_harness/templates/index.rhtml +17 -0
- data/generators/filter/USAGE +0 -0
- data/generators/filter/filter_generator.rb +19 -0
- data/generators/filter/templates/filter.rb +12 -0
- data/generators/filter/templates/filter_test.rb +28 -0
- data/generators/processor/USAGE +8 -0
- data/generators/processor/processor_generator.rb +31 -0
- data/generators/processor/templates/application_processor.rb +18 -0
- data/generators/processor/templates/broker.yml +140 -0
- data/generators/processor/templates/jruby_poller +117 -0
- data/generators/processor/templates/messaging.rb +12 -0
- data/generators/processor/templates/poller +25 -0
- data/generators/processor/templates/poller.rb +26 -0
- data/generators/processor/templates/processor.rb +8 -0
- data/generators/processor/templates/processor_test.rb +20 -0
- data/generators/tracer/USAGE +8 -0
- data/generators/tracer/templates/controller.rb +14 -0
- data/generators/tracer/templates/helper.rb +2 -0
- data/generators/tracer/templates/index.rhtml +4 -0
- data/generators/tracer/templates/layout.rhtml +16 -0
- data/generators/tracer/templates/trace_processor.rb +100 -0
- data/generators/tracer/tracer_generator.rb +25 -0
- data/init.rb +1 -0
- data/lib/activemessaging.rb +133 -0
- data/lib/activemessaging/adapter.rb +20 -0
- data/lib/activemessaging/adapters/amqp.rb +215 -0
- data/lib/activemessaging/adapters/asqs.rb +487 -0
- data/lib/activemessaging/adapters/base.rb +71 -0
- data/lib/activemessaging/adapters/beanstalk.rb +88 -0
- data/lib/activemessaging/adapters/jms.rb +243 -0
- data/lib/activemessaging/adapters/reliable_msg.rb +186 -0
- data/lib/activemessaging/adapters/stomp.rb +212 -0
- data/lib/activemessaging/adapters/synch.rb +95 -0
- data/lib/activemessaging/adapters/test.rb +137 -0
- data/lib/activemessaging/adapters/wmq.rb +193 -0
- data/lib/activemessaging/base_message.rb +28 -0
- data/lib/activemessaging/filter.rb +29 -0
- data/lib/activemessaging/gateway.rb +429 -0
- data/lib/activemessaging/message_sender.rb +30 -0
- data/lib/activemessaging/named_base.rb +54 -0
- data/lib/activemessaging/processor.rb +44 -0
- data/lib/activemessaging/railtie.rb +26 -0
- data/lib/activemessaging/test_helper.rb +189 -0
- data/lib/activemessaging/threaded_poller.rb +234 -0
- data/lib/activemessaging/trace_filter.rb +34 -0
- data/lib/generators/active_messaging/install/USAGE +21 -0
- data/lib/generators/active_messaging/install/install_generator.rb +39 -0
- data/lib/generators/active_messaging/install/templates/application_processor.rb +18 -0
- data/lib/generators/active_messaging/install/templates/broker.yml +139 -0
- data/lib/generators/active_messaging/install/templates/poller +24 -0
- data/lib/generators/active_messaging/install/templates/poller.rb +22 -0
- data/lib/generators/active_messaging/install/templates/threaded_poller +46 -0
- data/lib/generators/active_messaging/processor/USAGE +2 -0
- data/lib/generators/active_messaging/processor/processor_generator.rb +39 -0
- data/lib/generators/active_messaging/processor/templates/messaging.rb +12 -0
- data/lib/generators/active_messaging/processor/templates/processor.rb +8 -0
- data/lib/generators/active_messaging/processor/templates/processor_spec.rb +24 -0
- data/lib/generators/active_messaging/processor/templates/processor_test.rb +20 -0
- data/lib/tasks/start_consumers.rake +8 -0
- data/poller.rb +14 -0
- data/test/all_tests.rb +10 -0
- data/test/app/config/broker.yml +4 -0
- data/test/asqs_test.rb +125 -0
- data/test/config_test.rb +42 -0
- data/test/filter_test.rb +131 -0
- data/test/gateway_test.rb +220 -0
- data/test/jms_test.rb +64 -0
- data/test/reliable_msg_test.rb +83 -0
- data/test/stomp_test.rb +168 -0
- data/test/test_helper.rb +36 -0
- data/test/tracer_test.rb +57 -0
- metadata +202 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActiveMessaging
|
2
|
+
|
3
|
+
# include this module to make a new adapter - will register the adapter w/gateway so an be used in connection config
|
4
|
+
module Adapter
|
5
|
+
|
6
|
+
def self.included(included_by)
|
7
|
+
class << included_by
|
8
|
+
def register adapter_name
|
9
|
+
Gateway.register_adapter adapter_name, self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def logger()
|
15
|
+
@@logger ||= ActiveMessaging.logger
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require 'carrot'
|
2
|
+
require 'digest/md5'
|
3
|
+
require 'bert'
|
4
|
+
|
5
|
+
# make sure ActiveMessaging::Processor is already loaded so we actually override it!
|
6
|
+
require 'activemessaging/processor'
|
7
|
+
require 'activemessaging/adapters/base'
|
8
|
+
|
9
|
+
require 'emissary/message'
|
10
|
+
|
11
|
+
module ActiveMessaging
|
12
|
+
class Processor
|
13
|
+
def self.subscribes_to destination_name, headers={}
|
14
|
+
# let's default to using the same exchange_type/exchange_name as defined in the messages.rb
|
15
|
+
# for the given destination. XXX: THIS IS A BIG TIME MONKEY PATCH! Might consider pushing a
|
16
|
+
# proper patch upstream instead of jury-rigging this.
|
17
|
+
d = ActiveMessaging::Gateway.find_destination(destination_name)
|
18
|
+
type, name = [ d.publish_headers[:exchange_type], d.publish_headers[:exchange_name] ]
|
19
|
+
ActiveMessaging::Gateway.subscribe_to destination_name, self, { :exchange_type => type, :exchange_name => name }.merge(headers)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Adapters
|
24
|
+
module Amqp
|
25
|
+
class Connection
|
26
|
+
include ActiveMessaging::Adapter
|
27
|
+
register :amqp
|
28
|
+
|
29
|
+
SERVER_RETRY_MAX_ATTEMPTS = 10
|
30
|
+
|
31
|
+
DEFAULT_QUEUE_CONFIG = {
|
32
|
+
:durable => true,
|
33
|
+
:auto_delete => false,
|
34
|
+
:exclusive => true
|
35
|
+
}
|
36
|
+
|
37
|
+
class InvalidExchangeType < ArgumentError; end
|
38
|
+
|
39
|
+
def initialize config = {}
|
40
|
+
@connect_options = {
|
41
|
+
:user => config[:user] || 'guest',
|
42
|
+
:pass => config[:pass] || 'guest',
|
43
|
+
:host => config[:host] || 'localhost',
|
44
|
+
:port => config[:port] || (config[:ssl] ? 5671 : 5672),
|
45
|
+
:vhost => config[:vhost] || nil,
|
46
|
+
:ssl => config[:ssl] || false,
|
47
|
+
:ssl_verify => config[:ssl_verify] || OpenSSL::SSL::VERIFY_PEER,
|
48
|
+
}
|
49
|
+
|
50
|
+
@debug = config[:debug].to_i rescue 0
|
51
|
+
|
52
|
+
Carrot.logging = true unless @debug < 5
|
53
|
+
|
54
|
+
@auto_generated_queue = false
|
55
|
+
unless config[:queue_name]
|
56
|
+
@queue_name = Digest::MD5.hexdigest Time.now.to_s
|
57
|
+
@auto_generated_queue = true
|
58
|
+
else
|
59
|
+
@queue_name = config[:queue_name]
|
60
|
+
end
|
61
|
+
|
62
|
+
@queue_config = DEFAULT_QUEUE_CONFIG
|
63
|
+
unless @auto_generated_queue
|
64
|
+
@queue_config.merge!({
|
65
|
+
:durable => !!config[:queue_durable],
|
66
|
+
:auto_delete => !!config[:queue_auto_delete],
|
67
|
+
:exclusive => !!config[:queue_exclusive]
|
68
|
+
})
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def received message, headers = {}
|
73
|
+
puts "Received Message - ACK'ing with delivery_tag '#{message.headers[:delivery_tag]}'" if @debug > 0
|
74
|
+
client.server.send_frame(::Carrot::AMQP::Protocol::Basic::Ack.new(:delivery_tag => message.headers[:delivery_tag]))
|
75
|
+
end
|
76
|
+
|
77
|
+
def unreceive message, headers = {}
|
78
|
+
puts "Un-Receiving Message - REJECTing with delivery_tag '#{message.headers[:delivery_tag]}'" if @debug > 0
|
79
|
+
client.server.send_frame(::Carrot::AMQP::Protocol::Basic::Reject.new(:delivery_tag => message.headers[:delivery_tag]))
|
80
|
+
end
|
81
|
+
|
82
|
+
def receive(options={})
|
83
|
+
while true
|
84
|
+
message = queue.pop(:ack => true)
|
85
|
+
unless message.nil?
|
86
|
+
message = AmqpMessage.decode(message).stamp_received! unless message.nil?
|
87
|
+
message.delivery_tag = queue.delivery_tag
|
88
|
+
puts "RECEIVE: #{message.inspect}" if @debug
|
89
|
+
return message
|
90
|
+
end
|
91
|
+
sleep 0.2
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def send queue_name, data, headers = {}
|
96
|
+
headers[:routing_key] ||= queue_name
|
97
|
+
message = AmqpMessage.new({:headers => headers, :data => data}, queue_name)
|
98
|
+
|
99
|
+
if @debug > 0
|
100
|
+
puts "Sending the following message: "; pp message
|
101
|
+
end
|
102
|
+
|
103
|
+
begin
|
104
|
+
exchange(*exchange_info(headers)).publish(message.stamp_sent!.encode, :key => headers[:routing_key])
|
105
|
+
rescue ::Carrot::AMQP::Server::ServerDown
|
106
|
+
retry_attempts = retry_attempts.nil? ? 1 : retry_attempts + 1
|
107
|
+
sleep(retry_attempts * 0.25)
|
108
|
+
retry unless retry_attempts >= SERVER_RETRY_MAX_ATTEMPTS
|
109
|
+
raise e
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def subscribe queue_name, headers = {}, subId = nil
|
114
|
+
if @debug > 1
|
115
|
+
puts "Begin Subscribe Request:"
|
116
|
+
puts " Queue Name: #{queue_name.inspect}"
|
117
|
+
puts " Headers: #{headers.inspect}"
|
118
|
+
puts " subId: #{subId.inspect}"
|
119
|
+
puts " EXCH INFO: #{exchange_info(headers).inspect}"
|
120
|
+
puts "End Subscribe Request."
|
121
|
+
end
|
122
|
+
|
123
|
+
routing_key = headers[:routing_key] || queue_name
|
124
|
+
queue.bind(exchange(*exchange_info(headers)), :key => routing_key)
|
125
|
+
end
|
126
|
+
|
127
|
+
def unsubscribe(queue_name, headers={}, subId=nil)
|
128
|
+
if @debug > 1
|
129
|
+
puts "Begin UNsubscribe Request:"
|
130
|
+
puts " Queue Name: #{queue_name.inspect}"
|
131
|
+
puts " Headers: #{headers.inspect}"
|
132
|
+
puts " subId: #{subId.inspect}"
|
133
|
+
puts "End UNsubscribe Request."
|
134
|
+
end
|
135
|
+
|
136
|
+
routing_key = headers[:routing_key] || queue_name
|
137
|
+
queue.unbind(exchange(*exchange_info(headers)), :key => routing_key)
|
138
|
+
end
|
139
|
+
|
140
|
+
def disconnect(headers={})
|
141
|
+
@client.stop
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def exchange_info headers
|
147
|
+
[ (headers[:exchange_type].to_sym rescue nil) || :direct, headers[:exchange_name] || nil]
|
148
|
+
end
|
149
|
+
|
150
|
+
def exchange type, name, *args
|
151
|
+
type = type.to_sym rescue nil
|
152
|
+
unless [:topic, :fanout, :direct].include? type
|
153
|
+
raise InvalidExchangeType, "The carrot library does not support an exchange type of '#{type.inspect}'"
|
154
|
+
end
|
155
|
+
|
156
|
+
name ||= "amq.#{type}"
|
157
|
+
puts "Exchange [#{type}::#{name}]: #{args.inspect}" if @debug > 3
|
158
|
+
(@exchanges||={})[name] ||= ::Carrot::AMQP::Exchange.new client, type, name, *args
|
159
|
+
end
|
160
|
+
|
161
|
+
def queue
|
162
|
+
return @queue unless @queue.nil?
|
163
|
+
puts "Queue [#{@queue_name}]: #{@queue_config.inspect}" if @debug > 0
|
164
|
+
@queue ||= client.queue(@queue_name, @queue_config)
|
165
|
+
end
|
166
|
+
|
167
|
+
def client
|
168
|
+
return @client unless @client.nil?
|
169
|
+
puts "Client [amqp]: #{@connect_options.inspect}" if @debug > 0
|
170
|
+
@client ||= Carrot.new(@connect_options)
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
class AmqpMessage < Emissary::Message
|
176
|
+
attr_reader :command
|
177
|
+
attr_accessor :destination, :delivery_tag
|
178
|
+
|
179
|
+
def initialize(data, queue_name = nil)
|
180
|
+
data[:data] = data.delete(:body) unless (data[:data] && !data[:body])
|
181
|
+
|
182
|
+
super(data)
|
183
|
+
|
184
|
+
@delivery_tag ||= (data[:headers][:delivery_tag] rescue nil)
|
185
|
+
@destination ||= (data[:headers][:destination] rescue nil) || queue_name || routing_key
|
186
|
+
|
187
|
+
@command = "MESSAGE"
|
188
|
+
end
|
189
|
+
|
190
|
+
alias :body :data
|
191
|
+
|
192
|
+
def headers
|
193
|
+
super.merge({
|
194
|
+
:destination => routing_key,
|
195
|
+
:delivery_tag => @delivery_tag
|
196
|
+
})
|
197
|
+
end
|
198
|
+
|
199
|
+
def matches_subscription?(subscription)
|
200
|
+
# use routing key first, otherwise, use the defined destination value
|
201
|
+
destination = subscription.subscribe_headers[:routing_key] || subscription.destination.value.to_s
|
202
|
+
|
203
|
+
if destination.match(/(\#|\*)/)
|
204
|
+
dest_regex = ::Regexp.new(destination.gsub('.*', '[.][^.]+').gsub(/\.\#.*/, '[.].*'))
|
205
|
+
!!(headers[:destination].to_s =~ dest_regex)
|
206
|
+
else
|
207
|
+
!!(headers[:destination].to_s == destination)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
@@ -0,0 +1,487 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'openssl'
|
5
|
+
require 'base64'
|
6
|
+
require 'cgi'
|
7
|
+
require 'time'
|
8
|
+
require 'uri'
|
9
|
+
require 'rexml/document'
|
10
|
+
|
11
|
+
require 'activemessaging/adapters/base'
|
12
|
+
|
13
|
+
module ActiveMessaging
|
14
|
+
module Adapters
|
15
|
+
module AmazonSqs
|
16
|
+
|
17
|
+
class Connection < ActiveMessaging::Adapters::BaseConnection
|
18
|
+
register :asqs
|
19
|
+
|
20
|
+
QUEUE_NAME_LENGTH = 1..80
|
21
|
+
VISIBILITY_TIMEOUT = 0..(24 * 60 * 60)
|
22
|
+
NUMBER_OF_MESSAGES = 1..255
|
23
|
+
GET_QUEUE_ATTRIBUTES = ['All', 'ApproximateNumberOfMessages', 'VisibilityTimeout']
|
24
|
+
SET_QUEUE_ATTRIBUTES = ['VisibilityTimeout']
|
25
|
+
|
26
|
+
#configurable params
|
27
|
+
attr_accessor :reconnect_delay, :access_key_id, :secret_access_key, :aws_version, :content_type, :host, :port, :poll_interval, :cache_queue_list, :max_message_size
|
28
|
+
|
29
|
+
#generic init method needed by a13g
|
30
|
+
def initialize cfg
|
31
|
+
raise "Must specify a access_key_id" if (cfg[:access_key_id].nil? || cfg[:access_key_id].empty?)
|
32
|
+
raise "Must specify a secret_access_key" if (cfg[:secret_access_key].nil? || cfg[:secret_access_key].empty?)
|
33
|
+
|
34
|
+
@access_key_id=cfg[:access_key_id]
|
35
|
+
@secret_access_key=cfg[:secret_access_key]
|
36
|
+
@request_expires = cfg[:requestExpires] || 10
|
37
|
+
@request_retry_count = cfg[:requestRetryCount] || 5
|
38
|
+
@aws_version = cfg[:aws_version] || '2008-01-01'
|
39
|
+
@content_type = cfg[:content_type] || 'text/plain'
|
40
|
+
@host = cfg[:host] || 'queue.amazonaws.com'
|
41
|
+
@port = cfg[:port] || 80
|
42
|
+
@protocol = cfg[:protocol] || 'http'
|
43
|
+
@poll_interval = cfg[:poll_interval] || 1
|
44
|
+
@reconnect_delay = cfg[:reconnectDelay] || 5
|
45
|
+
|
46
|
+
@max_message_size = cfg[:max_message_size].to_i > 0 ? cfg[:max_message_size].to_i : 8
|
47
|
+
|
48
|
+
@aws_url="#{@protocol}://#{@host}"
|
49
|
+
|
50
|
+
@cache_queue_list = cfg[:cache_queue_list].nil? ? true : cfg[:cache_queue_list]
|
51
|
+
@reliable = cfg[:reliable].nil? ? true : cfg[:reliable]
|
52
|
+
|
53
|
+
#initialize the subscriptions and queues
|
54
|
+
@subscriptions = {}
|
55
|
+
@queues_by_priority = {}
|
56
|
+
@current_subscription = 0
|
57
|
+
queues
|
58
|
+
end
|
59
|
+
|
60
|
+
def disconnect
|
61
|
+
#it's an http request - there is no disconnect - ha!
|
62
|
+
return true
|
63
|
+
end
|
64
|
+
|
65
|
+
# queue_name string, headers hash
|
66
|
+
# for sqs, make sure queue exists, if not create, then add to list of polled queues
|
67
|
+
def subscribe queue_name, message_headers={}
|
68
|
+
# look at the existing queues, create any that are missing
|
69
|
+
queue = get_or_create_queue queue_name
|
70
|
+
if @subscriptions.has_key? queue.name
|
71
|
+
@subscriptions[queue.name].add
|
72
|
+
else
|
73
|
+
@subscriptions[queue.name] = Subscription.new(queue.name, message_headers)
|
74
|
+
end
|
75
|
+
priority = @subscriptions[queue.name].priority
|
76
|
+
|
77
|
+
@queues_by_priority[priority] = [] unless @queues_by_priority.has_key?(priority)
|
78
|
+
@queues_by_priority[priority] << queue.name unless @queues_by_priority[priority].include?(queue.name)
|
79
|
+
end
|
80
|
+
|
81
|
+
# queue_name string, headers hash
|
82
|
+
# for sqs, attempt delete the queues, won't work if not empty, that's ok
|
83
|
+
def unsubscribe queue_name, message_headers={}
|
84
|
+
if @subscriptions[queue_name]
|
85
|
+
@subscriptions[queue_name].remove
|
86
|
+
if @subscriptions[queue_name].count <= 0
|
87
|
+
sub = @subscriptions.delete(queue_name)
|
88
|
+
@queues_by_priority[sub.priority].delete(queue_name)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# queue_name string, body string, headers hash
|
94
|
+
# send a single message to a queue
|
95
|
+
def send queue_name, message_body, message_headers={}
|
96
|
+
queue = get_or_create_queue queue_name
|
97
|
+
send_messsage queue, message_body
|
98
|
+
end
|
99
|
+
|
100
|
+
# def receive(options={})
|
101
|
+
# while true
|
102
|
+
# # this will try all the queues once, by priority
|
103
|
+
# # returns nil if nothing there
|
104
|
+
# message = receive_message(options)
|
105
|
+
|
106
|
+
# if message
|
107
|
+
# return message
|
108
|
+
# else
|
109
|
+
# sleep(poll_interval)
|
110
|
+
# end
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
|
114
|
+
# new receive respects priorities
|
115
|
+
def receive(options={})
|
116
|
+
message = nil
|
117
|
+
|
118
|
+
only_priorities = options[:priorities]
|
119
|
+
|
120
|
+
# loop through the priorities
|
121
|
+
@queues_by_priority.keys.sort.each do |priority|
|
122
|
+
|
123
|
+
# skip this priority if there is a list, and it is not in the list
|
124
|
+
next if only_priorities && !only_priorities.include?(priority.to_i)
|
125
|
+
|
126
|
+
# puts " - priority: #{priority}"
|
127
|
+
# loop through queues for the priority in random order each time
|
128
|
+
@queues_by_priority[priority].shuffle.each do |queue_name|
|
129
|
+
# puts " - queue_name: #{queue_name}"
|
130
|
+
queue = queues[queue_name]
|
131
|
+
subscription = @subscriptions[queue_name]
|
132
|
+
|
133
|
+
next if queue.nil? || subscription.nil?
|
134
|
+
messages = retrieve_messsages(queue, 1, subscription.headers[:visibility_timeout])
|
135
|
+
|
136
|
+
if (messages && !messages.empty?)
|
137
|
+
message = messages[0]
|
138
|
+
end
|
139
|
+
|
140
|
+
break if message
|
141
|
+
end
|
142
|
+
|
143
|
+
break if message
|
144
|
+
end
|
145
|
+
|
146
|
+
# puts " - message: #{message}"
|
147
|
+
message
|
148
|
+
end
|
149
|
+
|
150
|
+
# # receive a single message from any of the subscribed queues
|
151
|
+
# # check each queue once, then sleep for poll_interval
|
152
|
+
# def receive
|
153
|
+
# raise "No subscriptions to receive messages from." if (@subscriptions.nil? || @subscriptions.empty?)
|
154
|
+
# start = @current_subscription
|
155
|
+
# while true
|
156
|
+
# # puts "calling receive..."
|
157
|
+
# @current_subscription = ((@current_subscription < @subscriptions.length-1) ? @current_subscription + 1 : 0)
|
158
|
+
# sleep poll_interval if (@current_subscription == start)
|
159
|
+
# queue_name = @subscriptions.keys.sort[@current_subscription]
|
160
|
+
# queue = queues[queue_name]
|
161
|
+
# subscription = @subscriptions[queue_name]
|
162
|
+
# unless queue.nil?
|
163
|
+
# messages = retrieve_messsages queue, 1, subscription.headers[:visibility_timeout]
|
164
|
+
# return messages[0] unless (messages.nil? or messages.empty? or messages[0].nil?)
|
165
|
+
# end
|
166
|
+
# end
|
167
|
+
# end
|
168
|
+
|
169
|
+
def received message, headers={}
|
170
|
+
begin
|
171
|
+
delete_message message
|
172
|
+
rescue Object=>exception
|
173
|
+
logger.error "Exception in ActiveMessaging::Adapters::AmazonSWS::Connection.received() logged and ignored: "
|
174
|
+
logger.error exception
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# do nothing; by not deleting the message will eventually become visible again
|
179
|
+
def unreceive message, headers={}
|
180
|
+
return true
|
181
|
+
end
|
182
|
+
|
183
|
+
protected
|
184
|
+
|
185
|
+
def create_queue(name)
|
186
|
+
validate_new_queue name
|
187
|
+
response = make_request('CreateQueue', nil, {'QueueName'=>name})
|
188
|
+
add_queue(response.get_text("//QueueUrl")) unless response.nil?
|
189
|
+
end
|
190
|
+
|
191
|
+
def delete_queue queue
|
192
|
+
validate_queue queue
|
193
|
+
response = make_request('DeleteQueue', "#{queue.queue_url}")
|
194
|
+
end
|
195
|
+
|
196
|
+
def list_queues(queue_name_prefix=nil)
|
197
|
+
validate_queue_name queue_name_prefix unless queue_name_prefix.nil?
|
198
|
+
params = queue_name_prefix.nil? ? {} : {"QueueNamePrefix"=>queue_name_prefix}
|
199
|
+
response = make_request('ListQueues', nil, params)
|
200
|
+
response.nil? ? [] : response.nodes("//QueueUrl").collect{ |n| add_queue(n.text) }
|
201
|
+
end
|
202
|
+
|
203
|
+
def get_queue_attributes(queue, attribute='All')
|
204
|
+
validate_get_queue_attribute(attribute)
|
205
|
+
params = {'AttributeName'=>attribute}
|
206
|
+
response = make_request('GetQueueAttributes', "#{queue.queue_url}")
|
207
|
+
attributes = {}
|
208
|
+
response.each_node('/GetQueueAttributesResponse/GetQueueAttributesResult/Attribute') { |n|
|
209
|
+
n = n.elements['Name'].text
|
210
|
+
v = n.elements['Value'].text
|
211
|
+
attributes[n] = v
|
212
|
+
}
|
213
|
+
if attribute != 'All'
|
214
|
+
attributes[attribute]
|
215
|
+
else
|
216
|
+
attributes
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def set_queue_attribute(queue, attribute, value)
|
221
|
+
validate_set_queue_attribute(attribute)
|
222
|
+
params = {'Attribute.Name'=>attribute, 'Attribute.Value'=>value.to_s}
|
223
|
+
response = make_request('SetQueueAttributes', "#{queue.queue_url}", params)
|
224
|
+
end
|
225
|
+
|
226
|
+
def delete_queue queue
|
227
|
+
validate_queue queue
|
228
|
+
response = make_request('DeleteQueue', "#{queue.queue_url}")
|
229
|
+
end
|
230
|
+
|
231
|
+
# in progress
|
232
|
+
def send_messsage queue, message
|
233
|
+
validate_queue queue
|
234
|
+
validate_message message
|
235
|
+
response = make_request('SendMessage', queue.queue_url, {'MessageBody'=>message})
|
236
|
+
response.get_text("//MessageId") unless response.nil?
|
237
|
+
end
|
238
|
+
|
239
|
+
def retrieve_messsages queue, num_messages=1, timeout=nil
|
240
|
+
validate_queue queue
|
241
|
+
validate_number_of_messages num_messages
|
242
|
+
validate_timeout timeout if timeout
|
243
|
+
|
244
|
+
params = {'MaxNumberOfMessages'=>num_messages.to_s}
|
245
|
+
params['VisibilityTimeout'] = timeout.to_s if timeout
|
246
|
+
|
247
|
+
response = make_request('ReceiveMessage', "#{queue.queue_url}", params)
|
248
|
+
response.nodes("//Message").collect{ |n| Message.from_element n, response, queue } unless response.nil?
|
249
|
+
end
|
250
|
+
|
251
|
+
def delete_message message
|
252
|
+
response = make_request('DeleteMessage', "#{message.queue.queue_url}", {'ReceiptHandle'=>message.receipt_handle})
|
253
|
+
end
|
254
|
+
|
255
|
+
def make_request(action, url=nil, params = {})
|
256
|
+
# puts "make_request a=#{action} u=#{url} p=#{params}"
|
257
|
+
url ||= @aws_url
|
258
|
+
|
259
|
+
# Add Actions
|
260
|
+
params['Action'] = action
|
261
|
+
params['Version'] = @aws_version
|
262
|
+
params['AWSAccessKeyId'] = @access_key_id
|
263
|
+
params['Expires']= (Time.now + @request_expires).gmtime.iso8601
|
264
|
+
params['SignatureVersion'] = '1'
|
265
|
+
|
266
|
+
# Sign the string
|
267
|
+
sorted_params = params.sort_by { |key,value| key.downcase }
|
268
|
+
string_to_sign = sorted_params.collect { |key, value| key.to_s + value.to_s }.join()
|
269
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
270
|
+
hmac = OpenSSL::HMAC.digest(digest, @secret_access_key, string_to_sign)
|
271
|
+
params['Signature'] = Base64.encode64(hmac).chomp
|
272
|
+
|
273
|
+
# Construct request
|
274
|
+
query_params = params.collect { |key, value| key + "=" + CGI.escape(value.to_s) }.join("&")
|
275
|
+
|
276
|
+
# Put these together to get the request query string
|
277
|
+
request_url = "#{url}?#{query_params}"
|
278
|
+
# puts "request_url = #{request_url}"
|
279
|
+
request = Net::HTTP::Get.new(request_url)
|
280
|
+
|
281
|
+
retry_count = 0
|
282
|
+
while retry_count < @request_retry_count.to_i
|
283
|
+
retry_count = retry_count + 1
|
284
|
+
# puts "make_request try retry_count=#{retry_count}"
|
285
|
+
begin
|
286
|
+
response = SQSResponse.new(http_request(host,port,request))
|
287
|
+
check_errors(response)
|
288
|
+
return response
|
289
|
+
rescue Object=>ex
|
290
|
+
# puts "make_request caught #{ex}"
|
291
|
+
raise ex unless reliable
|
292
|
+
sleep(@reconnect_delay)
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# I wrap this so I can move to a different client, or easily mock for testing
|
298
|
+
def http_request h, p, r
|
299
|
+
return Net::HTTP.start(h, p){ |http| http.request(r) }
|
300
|
+
end
|
301
|
+
|
302
|
+
def http_request h, p, r
|
303
|
+
http = Net::HTTP.new(h, p)
|
304
|
+
http.use_ssl = true if "https" == @protocol
|
305
|
+
# Don't carp about SSL cert verification
|
306
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
307
|
+
return http.request(r)
|
308
|
+
end
|
309
|
+
|
310
|
+
def check_errors(response)
|
311
|
+
raise "http response was nil" if (response.nil?)
|
312
|
+
raise response.errors if (response && response.errors?)
|
313
|
+
response
|
314
|
+
end
|
315
|
+
|
316
|
+
private
|
317
|
+
|
318
|
+
# internal data structure methods
|
319
|
+
def add_queue(url)
|
320
|
+
q = Queue.from_url url
|
321
|
+
queues[q.name] = q if self.cache_queue_list
|
322
|
+
return q
|
323
|
+
end
|
324
|
+
|
325
|
+
def get_or_create_queue queue_name
|
326
|
+
qs = queues
|
327
|
+
q = qs.has_key?(queue_name) ? qs[queue_name] : create_queue(queue_name)
|
328
|
+
raise "could not get or create queue: #{queue_name}" unless q
|
329
|
+
q
|
330
|
+
end
|
331
|
+
|
332
|
+
def queues
|
333
|
+
return @queues if (@queues && cache_queue_list)
|
334
|
+
@queues = {}
|
335
|
+
list_queues.each{|q| @queues[q.name]=q }
|
336
|
+
return @queues
|
337
|
+
end
|
338
|
+
|
339
|
+
# validation methods
|
340
|
+
def validate_queue_name qn
|
341
|
+
raise "Queue name, '#{qn}', must be between #{QUEUE_NAME_LENGTH.min} and #{QUEUE_NAME_LENGTH.max} characters." unless QUEUE_NAME_LENGTH.include?(qn.length)
|
342
|
+
raise "Queue name, '#{qn}', must be alphanumeric only." if (qn =~ /[^\w\-\_]/ )
|
343
|
+
end
|
344
|
+
|
345
|
+
def validate_new_queue qn
|
346
|
+
validate_queue_name qn
|
347
|
+
raise "Queue already exists: #{qn}" if queues.has_key? qn
|
348
|
+
end
|
349
|
+
|
350
|
+
def validate_queue q
|
351
|
+
raise "Never heard of queue, can't use it: #{q.name}" unless queues.has_key? q.name
|
352
|
+
end
|
353
|
+
|
354
|
+
def validate_message m
|
355
|
+
raise "Message cannot be nil." if m.nil?
|
356
|
+
raise "Message length, #{m.length}, must be between #{message_size_range.min} and #{message_size_range.max}." unless message_size_range.include?(m.length)
|
357
|
+
end
|
358
|
+
|
359
|
+
def message_size_range
|
360
|
+
@_message_size_range ||= 1..(max_message_size * 1024)
|
361
|
+
end
|
362
|
+
|
363
|
+
def validate_timeout to
|
364
|
+
raise "Timeout, #{to}, must be between #{VISIBILITY_TIMEOUT.min} and #{VISIBILITY_TIMEOUT.max}." unless VISIBILITY_TIMEOUT.include?(to)
|
365
|
+
end
|
366
|
+
|
367
|
+
def validate_get_queue_attribute qa
|
368
|
+
raise "Queue Attribute name, #{qa}, not in list of valid attributes to get: #{GET_QUEUE_ATTRIBUTES.to_sentence}." unless GET_QUEUE_ATTRIBUTES.include?(qa)
|
369
|
+
end
|
370
|
+
|
371
|
+
def validate_set_queue_attribute qa
|
372
|
+
raise "Queue Attribute name, #{qa}, not in list of valid attributes to set: #{SET_QUEUE_ATTRIBUTES.to_sentence}." unless SET_QUEUE_ATTRIBUTES.include?(qa)
|
373
|
+
end
|
374
|
+
|
375
|
+
def validate_number_of_messages nom
|
376
|
+
raise "Number of messages, #{nom}, must be between #{NUMBER_OF_MESSAGES.min} and #{NUMBER_OF_MESSAGES.max}." unless NUMBER_OF_MESSAGES.include?(nom)
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
class SQSResponse
|
381
|
+
attr_accessor :headers, :doc, :http_response
|
382
|
+
|
383
|
+
def initialize response
|
384
|
+
# puts "response.body = #{response.body}"
|
385
|
+
@http_response = response
|
386
|
+
@headers = response.to_hash()
|
387
|
+
@doc = REXML::Document.new(response.body)
|
388
|
+
end
|
389
|
+
|
390
|
+
def message_type
|
391
|
+
return doc ? doc.root.name : ''
|
392
|
+
end
|
393
|
+
|
394
|
+
def errors?
|
395
|
+
(not http_response.kind_of?(Net::HTTPSuccess)) or (message_type == "ErrorResponse")
|
396
|
+
end
|
397
|
+
|
398
|
+
def errors
|
399
|
+
return "HTTP Error: #{http_response.code} : #{http_response.message}" unless http_response.kind_of?(Net::HTTPSuccess)
|
400
|
+
|
401
|
+
msg = nil
|
402
|
+
each_node('//Error') { |n|
|
403
|
+
msg ||= ""
|
404
|
+
c = n.elements['Code'].text
|
405
|
+
m = n.elements['Message'].text
|
406
|
+
msg << ", " if msg != ""
|
407
|
+
msg << "#{c} : #{m}"
|
408
|
+
}
|
409
|
+
|
410
|
+
return msg
|
411
|
+
end
|
412
|
+
|
413
|
+
def get_text(xpath,default='')
|
414
|
+
e = REXML::XPath.first( doc, xpath)
|
415
|
+
e.nil? ? default : e.text
|
416
|
+
end
|
417
|
+
|
418
|
+
def each_node(xp)
|
419
|
+
REXML::XPath.each(doc.root, xp) {|n| yield n}
|
420
|
+
end
|
421
|
+
|
422
|
+
def nodes(xp)
|
423
|
+
doc.elements.to_a(xp)
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
class Subscription
|
428
|
+
attr_accessor :destination, :headers, :count, :priority
|
429
|
+
|
430
|
+
def initialize(destination, headers={}, count=1)
|
431
|
+
@priority = headers.delete(:priority) || 1001
|
432
|
+
@destination, @headers, @count = destination, headers, count
|
433
|
+
end
|
434
|
+
|
435
|
+
def add
|
436
|
+
@count += 1
|
437
|
+
end
|
438
|
+
|
439
|
+
def remove
|
440
|
+
@count -= 1
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
class Queue
|
445
|
+
attr_accessor :name, :pathinfo, :domain, :visibility_timeout
|
446
|
+
|
447
|
+
def self.from_url url
|
448
|
+
u = URI.parse(url)
|
449
|
+
name = u.path.gsub(/\//, "")
|
450
|
+
domain = u.host
|
451
|
+
return Queue.new(name,domain)
|
452
|
+
end
|
453
|
+
|
454
|
+
def queue_url
|
455
|
+
"#{pathinfo}/#{name}"
|
456
|
+
end
|
457
|
+
|
458
|
+
def initialize name, domain, vt=nil
|
459
|
+
@name, @pathinfo, @domain, @visibility_timeout = name, pathinfo, domain, vt
|
460
|
+
end
|
461
|
+
|
462
|
+
def to_s
|
463
|
+
"<AmazonSQS::Queue name='#{name}' url='#{queue_url}' domain='#{domain}'>"
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
# based on stomp message, has pointer to the SQSResponseObject
|
468
|
+
class Message < ActiveMessaging::BaseMessage
|
469
|
+
attr_accessor :response, :queue, :md5_of_body, :receipt_handle
|
470
|
+
|
471
|
+
def self.from_element e, response, queue
|
472
|
+
Message.new(e.elements['Body'].text, response.headers, e.elements['MessageId'].text, e.elements['MD5OfBody'].text, e.elements['ReceiptHandle'].text, response, queue)
|
473
|
+
end
|
474
|
+
|
475
|
+
def initialize body, headers, id, md5_of_body, receipt_handle, response, queue
|
476
|
+
super(body, id, headers, queue.name)
|
477
|
+
@md5_of_body, @receipt_handle, @response, @queue = md5_of_body, receipt_handle, response, queue
|
478
|
+
end
|
479
|
+
|
480
|
+
def to_s
|
481
|
+
"<AmazonSQS::Message id='#{id}' body='#{body}' headers='#{headers.inspect}' response='#{response}'>"
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|