gz_activemessaging 0.13.1
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.
- 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
|