gz_activemessaging 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +40 -0
  3. data/Appraisals +19 -0
  4. data/Gemfile +15 -0
  5. data/Gemfile.lock +87 -0
  6. data/README.md +27 -0
  7. data/Rakefile +49 -0
  8. data/VERSION +1 -0
  9. data/activemessaging.gemspec +137 -0
  10. data/gemfiles/activesupport23.gemfile +10 -0
  11. data/gemfiles/activesupport23.gemfile.lock +51 -0
  12. data/gemfiles/activesupport30.gemfile +11 -0
  13. data/gemfiles/activesupport30.gemfile.lock +53 -0
  14. data/gemfiles/activesupport31.gemfile +11 -0
  15. data/gemfiles/activesupport31.gemfile.lock +55 -0
  16. data/gemfiles/activesupport32.gemfile +10 -0
  17. data/gemfiles/activesupport32.gemfile.lock +55 -0
  18. data/generators/a13g_test_harness/a13g_test_harness_generator.rb +19 -0
  19. data/generators/a13g_test_harness/templates/active_messaging_test.rhtml +13 -0
  20. data/generators/a13g_test_harness/templates/active_messaging_test_controller.rb +29 -0
  21. data/generators/a13g_test_harness/templates/index.rhtml +17 -0
  22. data/generators/filter/USAGE +0 -0
  23. data/generators/filter/filter_generator.rb +19 -0
  24. data/generators/filter/templates/filter.rb +12 -0
  25. data/generators/filter/templates/filter_test.rb +28 -0
  26. data/generators/processor/USAGE +8 -0
  27. data/generators/processor/processor_generator.rb +31 -0
  28. data/generators/processor/templates/application_processor.rb +18 -0
  29. data/generators/processor/templates/broker.yml +140 -0
  30. data/generators/processor/templates/jruby_poller +117 -0
  31. data/generators/processor/templates/messaging.rb +12 -0
  32. data/generators/processor/templates/poller +25 -0
  33. data/generators/processor/templates/poller.rb +26 -0
  34. data/generators/processor/templates/processor.rb +8 -0
  35. data/generators/processor/templates/processor_test.rb +20 -0
  36. data/generators/tracer/USAGE +8 -0
  37. data/generators/tracer/templates/controller.rb +14 -0
  38. data/generators/tracer/templates/helper.rb +2 -0
  39. data/generators/tracer/templates/index.rhtml +4 -0
  40. data/generators/tracer/templates/layout.rhtml +16 -0
  41. data/generators/tracer/templates/trace_processor.rb +100 -0
  42. data/generators/tracer/tracer_generator.rb +25 -0
  43. data/init.rb +1 -0
  44. data/lib/activemessaging.rb +133 -0
  45. data/lib/activemessaging/adapter.rb +20 -0
  46. data/lib/activemessaging/adapters/amqp.rb +215 -0
  47. data/lib/activemessaging/adapters/asqs.rb +487 -0
  48. data/lib/activemessaging/adapters/base.rb +71 -0
  49. data/lib/activemessaging/adapters/beanstalk.rb +88 -0
  50. data/lib/activemessaging/adapters/jms.rb +243 -0
  51. data/lib/activemessaging/adapters/reliable_msg.rb +186 -0
  52. data/lib/activemessaging/adapters/stomp.rb +212 -0
  53. data/lib/activemessaging/adapters/synch.rb +95 -0
  54. data/lib/activemessaging/adapters/test.rb +137 -0
  55. data/lib/activemessaging/adapters/wmq.rb +193 -0
  56. data/lib/activemessaging/base_message.rb +28 -0
  57. data/lib/activemessaging/filter.rb +29 -0
  58. data/lib/activemessaging/gateway.rb +429 -0
  59. data/lib/activemessaging/message_sender.rb +30 -0
  60. data/lib/activemessaging/named_base.rb +54 -0
  61. data/lib/activemessaging/processor.rb +44 -0
  62. data/lib/activemessaging/railtie.rb +26 -0
  63. data/lib/activemessaging/test_helper.rb +189 -0
  64. data/lib/activemessaging/threaded_poller.rb +234 -0
  65. data/lib/activemessaging/trace_filter.rb +34 -0
  66. data/lib/generators/active_messaging/install/USAGE +21 -0
  67. data/lib/generators/active_messaging/install/install_generator.rb +39 -0
  68. data/lib/generators/active_messaging/install/templates/application_processor.rb +18 -0
  69. data/lib/generators/active_messaging/install/templates/broker.yml +139 -0
  70. data/lib/generators/active_messaging/install/templates/poller +24 -0
  71. data/lib/generators/active_messaging/install/templates/poller.rb +22 -0
  72. data/lib/generators/active_messaging/install/templates/threaded_poller +46 -0
  73. data/lib/generators/active_messaging/processor/USAGE +2 -0
  74. data/lib/generators/active_messaging/processor/processor_generator.rb +39 -0
  75. data/lib/generators/active_messaging/processor/templates/messaging.rb +12 -0
  76. data/lib/generators/active_messaging/processor/templates/processor.rb +8 -0
  77. data/lib/generators/active_messaging/processor/templates/processor_spec.rb +24 -0
  78. data/lib/generators/active_messaging/processor/templates/processor_test.rb +20 -0
  79. data/lib/tasks/start_consumers.rake +8 -0
  80. data/poller.rb +14 -0
  81. data/test/all_tests.rb +10 -0
  82. data/test/app/config/broker.yml +4 -0
  83. data/test/asqs_test.rb +125 -0
  84. data/test/config_test.rb +42 -0
  85. data/test/filter_test.rb +131 -0
  86. data/test/gateway_test.rb +220 -0
  87. data/test/jms_test.rb +64 -0
  88. data/test/reliable_msg_test.rb +83 -0
  89. data/test/stomp_test.rb +168 -0
  90. data/test/test_helper.rb +36 -0
  91. data/test/tracer_test.rb +57 -0
  92. 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