amq-client 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. data/.gitignore +8 -0
  2. data/.gitmodules +9 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +7 -0
  5. data/.yardopts +1 -0
  6. data/CONTRIBUTORS +3 -0
  7. data/Gemfile +27 -0
  8. data/LICENSE +20 -0
  9. data/README.textile +61 -0
  10. data/amq-client.gemspec +34 -0
  11. data/bin/jenkins.sh +23 -0
  12. data/bin/set_test_suite_realms_up.sh +24 -0
  13. data/examples/coolio_adapter/basic_consume.rb +49 -0
  14. data/examples/coolio_adapter/basic_consume_with_acknowledgements.rb +43 -0
  15. data/examples/coolio_adapter/basic_consume_with_rejections.rb +43 -0
  16. data/examples/coolio_adapter/basic_publish.rb +35 -0
  17. data/examples/coolio_adapter/channel_close.rb +24 -0
  18. data/examples/coolio_adapter/example_helper.rb +39 -0
  19. data/examples/coolio_adapter/exchange_declare.rb +28 -0
  20. data/examples/coolio_adapter/kitchen_sink1.rb +48 -0
  21. data/examples/coolio_adapter/queue_bind.rb +32 -0
  22. data/examples/coolio_adapter/queue_purge.rb +32 -0
  23. data/examples/coolio_adapter/queue_unbind.rb +37 -0
  24. data/examples/eventmachine_adapter/authentication/plain_password_with_custom_role_credentials.rb +36 -0
  25. data/examples/eventmachine_adapter/authentication/plain_password_with_default_role_credentials.rb +27 -0
  26. data/examples/eventmachine_adapter/authentication/plain_password_with_incorrect_credentials.rb +18 -0
  27. data/examples/eventmachine_adapter/basic_cancel.rb +49 -0
  28. data/examples/eventmachine_adapter/basic_consume.rb +51 -0
  29. data/examples/eventmachine_adapter/basic_consume_with_acknowledgements.rb +45 -0
  30. data/examples/eventmachine_adapter/basic_consume_with_rejections.rb +45 -0
  31. data/examples/eventmachine_adapter/basic_get.rb +57 -0
  32. data/examples/eventmachine_adapter/basic_get_with_empty_queue.rb +53 -0
  33. data/examples/eventmachine_adapter/basic_publish.rb +38 -0
  34. data/examples/eventmachine_adapter/basic_qos.rb +29 -0
  35. data/examples/eventmachine_adapter/basic_recover.rb +29 -0
  36. data/examples/eventmachine_adapter/basic_return.rb +34 -0
  37. data/examples/eventmachine_adapter/channel_close.rb +24 -0
  38. data/examples/eventmachine_adapter/channel_flow.rb +36 -0
  39. data/examples/eventmachine_adapter/channel_level_exception_handling.rb +44 -0
  40. data/examples/eventmachine_adapter/example_helper.rb +39 -0
  41. data/examples/eventmachine_adapter/exchange_declare.rb +54 -0
  42. data/examples/eventmachine_adapter/extensions/rabbitmq/handling_confirm_select_ok.rb +31 -0
  43. data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +56 -0
  44. data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_unroutable_message.rb +46 -0
  45. data/examples/eventmachine_adapter/kitchen_sink1.rb +50 -0
  46. data/examples/eventmachine_adapter/queue_bind.rb +32 -0
  47. data/examples/eventmachine_adapter/queue_declare.rb +34 -0
  48. data/examples/eventmachine_adapter/queue_purge.rb +32 -0
  49. data/examples/eventmachine_adapter/queue_unbind.rb +37 -0
  50. data/examples/eventmachine_adapter/tx_commit.rb +29 -0
  51. data/examples/eventmachine_adapter/tx_rollback.rb +29 -0
  52. data/examples/eventmachine_adapter/tx_select.rb +27 -0
  53. data/examples/socket_adapter/basics.rb +19 -0
  54. data/examples/socket_adapter/connection.rb +53 -0
  55. data/examples/socket_adapter/multiple_connections.rb +17 -0
  56. data/irb.rb +66 -0
  57. data/lib/amq/client.rb +15 -0
  58. data/lib/amq/client/adapter.rb +356 -0
  59. data/lib/amq/client/adapters/coolio.rb +221 -0
  60. data/lib/amq/client/adapters/event_machine.rb +228 -0
  61. data/lib/amq/client/adapters/socket.rb +89 -0
  62. data/lib/amq/client/channel.rb +338 -0
  63. data/lib/amq/client/connection.rb +246 -0
  64. data/lib/amq/client/entity.rb +117 -0
  65. data/lib/amq/client/exceptions.rb +86 -0
  66. data/lib/amq/client/exchange.rb +163 -0
  67. data/lib/amq/client/extensions/rabbitmq.rb +5 -0
  68. data/lib/amq/client/extensions/rabbitmq/basic.rb +36 -0
  69. data/lib/amq/client/extensions/rabbitmq/confirm.rb +254 -0
  70. data/lib/amq/client/framing/io/frame.rb +32 -0
  71. data/lib/amq/client/framing/string/frame.rb +62 -0
  72. data/lib/amq/client/logging.rb +56 -0
  73. data/lib/amq/client/mixins/anonymous_entity.rb +21 -0
  74. data/lib/amq/client/mixins/status.rb +62 -0
  75. data/lib/amq/client/protocol/get_response.rb +55 -0
  76. data/lib/amq/client/queue.rb +450 -0
  77. data/lib/amq/client/settings.rb +83 -0
  78. data/lib/amq/client/version.rb +5 -0
  79. data/spec/benchmarks/adapters.rb +77 -0
  80. data/spec/client/framing/io_frame_spec.rb +57 -0
  81. data/spec/client/framing/string_frame_spec.rb +57 -0
  82. data/spec/client/protocol/get_response_spec.rb +79 -0
  83. data/spec/integration/coolio/basic_ack_spec.rb +41 -0
  84. data/spec/integration/coolio/basic_get_spec.rb +73 -0
  85. data/spec/integration/coolio/basic_return_spec.rb +33 -0
  86. data/spec/integration/coolio/channel_close_spec.rb +26 -0
  87. data/spec/integration/coolio/channel_flow_spec.rb +46 -0
  88. data/spec/integration/coolio/spec_helper.rb +31 -0
  89. data/spec/integration/coolio/tx_commit_spec.rb +40 -0
  90. data/spec/integration/coolio/tx_rollback_spec.rb +44 -0
  91. data/spec/integration/eventmachine/basic_ack_spec.rb +40 -0
  92. data/spec/integration/eventmachine/basic_get_spec.rb +73 -0
  93. data/spec/integration/eventmachine/basic_return_spec.rb +35 -0
  94. data/spec/integration/eventmachine/channel_close_spec.rb +26 -0
  95. data/spec/integration/eventmachine/channel_flow_spec.rb +32 -0
  96. data/spec/integration/eventmachine/spec_helper.rb +22 -0
  97. data/spec/integration/eventmachine/tx_commit_spec.rb +47 -0
  98. data/spec/integration/eventmachine/tx_rollback_spec.rb +35 -0
  99. data/spec/regression/bad_frame_slicing_in_adapters_spec.rb +59 -0
  100. data/spec/spec_helper.rb +24 -0
  101. data/spec/unit/client/adapter_spec.rb +49 -0
  102. data/spec/unit/client/entity_spec.rb +49 -0
  103. data/spec/unit/client/logging_spec.rb +60 -0
  104. data/spec/unit/client/mixins/status_spec.rb +72 -0
  105. data/spec/unit/client/settings_spec.rb +27 -0
  106. data/spec/unit/client_spec.rb +11 -0
  107. data/tasks.rb +11 -0
  108. metadata +202 -0
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ __dir = File.dirname(File.expand_path(__FILE__))
5
+ require File.join(__dir, "example_helper")
6
+
7
+ amq_client_example "Choose to use acknowledgement transactions on a channel using tx.select" do |client|
8
+ channel = AMQ::Client::Channel.new(client, 1)
9
+ channel.open do
10
+ channel.tx_select do
11
+ puts "Channel #{channel.id} is now using ack transactions"
12
+ end
13
+
14
+ show_stopper = Proc.new {
15
+ client.disconnect do
16
+ puts
17
+ puts "AMQP connection is now properly closed"
18
+ EM.stop
19
+ end
20
+ }
21
+
22
+ Signal.trap "INT", show_stopper
23
+ Signal.trap "TERM", show_stopper
24
+
25
+ EM.add_timer(1, show_stopper)
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+
6
+ Bundler.setup
7
+ Bundler.require(:default)
8
+
9
+ $LOAD_PATH.unshift(File.expand_path("../../../lib", __FILE__))
10
+
11
+ require "amq/client/adapters/socket"
12
+
13
+ AMQ::Client::SocketClient.connect(:host => "localhost") do |client|
14
+ # Socket API is synchronous, so we don't need any callback here:
15
+ tasks = client.queue("tasks", 1)
16
+ tasks.consume do |headers, message| # TODO: this is async, we need to use a loop
17
+ puts ""
18
+ end
19
+ end
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "bundler"
5
+
6
+ Bundler.setup
7
+ Bundler.require(:default)
8
+
9
+ $LOAD_PATH.unshift(File.expand_path("../../../lib", __FILE__))
10
+
11
+ require "amq/client/adapters/socket"
12
+ require "amq/client/amqp/queue"
13
+ require "amq/client/amqp/exchange"
14
+
15
+ AMQ::Client::SocketClient.connect(:port => 5672) do |client|
16
+ begin
17
+ client.handshake
18
+
19
+ # Ruby developers are used to use blocks usually synchronously
20
+ # (so they are called +/- immediately), but this is NOT the case!
21
+ # We always have to wait for the response from the broker, so think
22
+ # about the following blocks are true callbacks as you know them
23
+ # from JavaScript (i. e. window.onload = function () {}).
24
+
25
+ # The only exception is when you use {nowait: true}, then the
26
+ # callback is called immediately.
27
+ channel = AMQ::Client::Channel.new(client, 1)
28
+ channel.open { puts "Channel #{channel.id} opened!" }
29
+
30
+ queue = AMQ::Client::Queue.new(client, "", channel)
31
+ queue.declare { puts "Queue #{queue.name.inspect} declared!" }
32
+
33
+ exchange = AMQ::Client::Exchange.new(client, "tasks", :fanout, channel)
34
+ exchange.declare { puts "Exchange #{exchange.name.inspect} declared!" }
35
+
36
+ until client.connection.closed?
37
+ client.receive_async
38
+ sleep 1
39
+ end
40
+ rescue Interrupt
41
+ warn "Manually interrupted, terminating ..."
42
+ rescue Exception => exception
43
+ STDERR.puts "\n\e[1;31m[#{exception.class}] #{exception.message}\e[0m"
44
+ exception.backtrace.each do |line|
45
+ line = "\e[0;36m#{line}\e[0m" if line.match(Regexp::quote(File.basename(__FILE__)))
46
+ STDERR.puts " - " + line
47
+ end
48
+ end
49
+ end
50
+
51
+ # TODO:
52
+ # AMQ::Client.connect(:adapter => :socket)
53
+ # Support for frame_max, heartbeat from Connection.Tune
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ # Each connection respond to a TCP connection,
5
+ # hence we need to use more client.connect calls.
6
+
7
+ Thread.new do
8
+ AMQ::Client::SocketClient.connect(:port => 5672) do |client|
9
+ # ...
10
+ end
11
+ end
12
+
13
+ Thread.new do
14
+ AMQ::Client::SocketClient.connect(:port => 5672) do |client|
15
+ # ...
16
+ end
17
+ end
data/irb.rb ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ # This file is supposed to make inspecting AMQ client easier.
5
+
6
+ # How does it work:
7
+ # 1) This file is executed.
8
+ # 2) We load irb, redefine where IRB looks for .irbrc and start IRB.
9
+ # 3) IRB loads .irbrc, which we redefined, so it loads this file again.
10
+ # However now the second branch of "if __FILE__ == $0" gets executed,
11
+ # so it runs our custom code which loads the original .irbrc and then
12
+ # it redefines some IRB settings. In this case it add IRB hook which
13
+ # is executed after IRB is started.
14
+
15
+ # Although it looks unnecessarily complicated, I can't see any easier
16
+ # solution to this problem in case that you need to patch original settings.
17
+ # Obviously in case you don't have the need, you'll be happy with simple:
18
+
19
+ # require "irb"
20
+ #
21
+ # require_relative "lib/amq/protocol/client.rb"
22
+ # include AMQ::Protocol
23
+ #
24
+ # IRB.start(__FILE__)
25
+
26
+ require "irb"
27
+ require "bundler"
28
+
29
+ Bundler.setup
30
+ Bundler.require(:default)
31
+
32
+ $LOAD_PATH.unshift(File.expand_path("../lib", __FILE__))
33
+
34
+ if __FILE__ == $0
35
+ puts "~ Using #{__FILE__} as an executable ..."
36
+
37
+ def IRB.rc_file_generators
38
+ yield Proc.new { |_| __FILE__ }
39
+ end
40
+
41
+ IRB.start(__FILE__)
42
+ else
43
+ begin
44
+ irbrc = File.join(ENV["HOME"], ".irbrc")
45
+ puts "~ Using #{__FILE__} as a custom .irbrc .."
46
+
47
+ require "amq/client.rb"
48
+ include AMQ::Client
49
+
50
+ require "amq/protocol/client"
51
+ include AMQ
52
+
53
+ require "stringio"
54
+
55
+ def fd(data)
56
+ Frame.decode(StringIO.new(data))
57
+ end
58
+
59
+ puts "~ Loading original #{irbrc} ..."
60
+ load irbrc
61
+
62
+ puts "Loading finished."
63
+ rescue Exception => exception # it just discards all the exceptions!
64
+ abort exception.message + "\n - " + exception.backtrace.join("\n - ")
65
+ end
66
+ end
data/lib/amq/client.rb ADDED
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client/version"
4
+ require "amq/client/exceptions"
5
+ require "amq/client/adapter"
6
+
7
+ begin
8
+ require "amq/protocol/client"
9
+ rescue LoadError => exception
10
+ if exception.message.match("amq/protocol")
11
+ raise LoadError.new("You have to install amq-protocol library first!")
12
+ else
13
+ raise exception
14
+ end
15
+ end
@@ -0,0 +1,356 @@
1
+ # encoding: utf-8
2
+
3
+ require "amq/client/logging"
4
+ require "amq/client/settings"
5
+ require "amq/client/entity"
6
+ require "amq/client/connection"
7
+
8
+ module AMQ
9
+ # For overview of AMQP client adapters API, see {AMQ::Client::Adapter}
10
+ module Client
11
+ # Base adapter class. Specific implementations (for example, EventMachine-based, Cool.io-based or
12
+ # sockets-based) subclass it and must implement Adapter API methods:
13
+ #
14
+ # * #send_raw(data)
15
+ # * #estabilish_connection(settings)
16
+ # * #close_connection
17
+ #
18
+ # Adapters also must indicate whether they operate in asynchronous or synchronous mode
19
+ # using AMQ::Client::Adapter.sync accessor:
20
+ #
21
+ # @example EventMachine adapter indicates that it is asynchronous
22
+ # module AMQ
23
+ # module Client
24
+ # class EventMachineClient
25
+ #
26
+ # #
27
+ # # Behaviors
28
+ # #
29
+ #
30
+ # include AMQ::Client::Adapter
31
+ # include EventMachine::Deferrable
32
+ #
33
+ # self.sync = false
34
+ #
35
+ # # the rest of implementation code ...
36
+ #
37
+ # end
38
+ # end
39
+ # end
40
+ #
41
+ # @abstract
42
+ module Adapter
43
+
44
+ def self.included(host)
45
+ host.extend(ClassMethods)
46
+
47
+ host.class_eval do
48
+ attr_accessor :logger, :settings, :connection
49
+
50
+ # Authentication mechanism
51
+ attr_accessor :mechanism
52
+
53
+ # Security response data
54
+ attr_accessor :response
55
+
56
+ # The locale defines the language in which the server will send reply texts.
57
+ #
58
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.2)
59
+ attr_accessor :locale
60
+
61
+ # @api plugin
62
+ # @see #disconnect
63
+ # @note Adapters must implement this method but it is NOT supposed to be used directly.
64
+ # AMQ protocol defines two-step process of closing connection (send Connection.Close
65
+ # to the peer and wait for Connection.Close-Ok), implemented by {Adapter#disconnect}
66
+ def close_connection
67
+ raise MissingInterfaceMethodError.new("AMQ::Client.close_connection")
68
+ end unless defined?(:close_connection) # since it is a module, this method may already be defined
69
+ end
70
+ end # self.included(host)
71
+
72
+
73
+
74
+ module ClassMethods
75
+ # Settings
76
+ def settings
77
+ @settings ||= AMQ::Client::Settings.default
78
+ end
79
+
80
+ def logger
81
+ @logger ||= begin
82
+ require "logger"
83
+ Logger.new(STDERR)
84
+ end
85
+ end
86
+
87
+ def logger=(logger)
88
+ methods = AMQ::Client::Logging::REQUIRED_METHODS
89
+ unless methods.all? { |method| logger.respond_to?(method) }
90
+ raise AMQ::Client::Logging::IncompatibleLoggerError.new(methods)
91
+ end
92
+
93
+ @logger = logger
94
+ end
95
+
96
+ # @return [Boolean] Current value of logging flag.
97
+ def logging
98
+ settings[:logging]
99
+ end
100
+
101
+ # Turns loggin on or off.
102
+ def logging=(boolean)
103
+ settings[:logging] = boolean
104
+ end
105
+
106
+
107
+ # @example Registering Channel implementation
108
+ # Adapter.register_entity(:channel, Channel)
109
+ # # ... so then I can do:
110
+ # channel = client.channel(1)
111
+ # # instead of:
112
+ # channel = Channel.new(client, 1)
113
+ def register_entity(name, klass)
114
+ define_method(name) do |*args, &block|
115
+ klass.new(self, *args, &block)
116
+ end
117
+ end
118
+
119
+ # Establishes connection to AMQ broker and returns it. New connection object is yielded to
120
+ # the block if it is given.
121
+ #
122
+ # @param [Hash] Connection parameters, including :adapter to use.
123
+ # @api public
124
+ def connect(settings = nil, &block)
125
+ if settings && settings[:adapter]
126
+ adapter = load_adapter(settings[:adapter])
127
+ else
128
+ adapter = self
129
+ end
130
+
131
+ @settings = AMQ::Client::Settings.configure(settings)
132
+ instance = adapter.new
133
+ instance.establish_connection(@settings)
134
+ # We don't need anything more, once the server receives the preable, he sends Connection.Start, we just have to reply.
135
+
136
+ if block
137
+ block.call(instance)
138
+
139
+ instance.disconnect
140
+ else
141
+ instance
142
+ end
143
+ end
144
+
145
+
146
+ # Loads adapter from amq/client/adapters.
147
+ #
148
+ # @raise [InvalidAdapterNameError] When loading attempt failed (LoadError was raised).
149
+ def load_adapter(adapter)
150
+ require "amq/client/adapters/#{adapter}"
151
+
152
+ const_name = adapter.to_s.gsub(/(^|_)(.)/) { $2.upcase! }
153
+ const_get(const_name)
154
+ rescue LoadError
155
+ raise InvalidAdapterNameError.new(adapter)
156
+ end
157
+
158
+ # @see AMQ::Client::Adapter
159
+ def sync=(boolean)
160
+ @sync = boolean
161
+ end
162
+
163
+ # Use this method to detect whether adapter is synchronous or asynchronous.
164
+ #
165
+ # @return [Boolean] true if this adapter is synchronous
166
+ # @api plugin
167
+ # @see AMQ::Client::Adapter
168
+ def sync?
169
+ @sync == true
170
+ end
171
+
172
+ # @see #sync?
173
+ # @api plugin
174
+ # @see AMQ::Client::Adapter
175
+ def async?
176
+ !sync?
177
+ end
178
+ end # ClassMethods
179
+
180
+
181
+ #
182
+ # Behaviors
183
+ #
184
+
185
+ include AMQ::Client::StatusMixin
186
+
187
+
188
+ #
189
+ # API
190
+ #
191
+
192
+ def self.load_adapter(adapter)
193
+ ClassMethods.load_adapter(adapter)
194
+ end
195
+
196
+
197
+
198
+ def initialize(*args)
199
+ super(*args)
200
+
201
+ self.logger = self.class.logger
202
+ self.settings = self.class.settings
203
+
204
+ @frames = Array.new
205
+ end
206
+
207
+
208
+
209
+ # Establish socket connection to the server.
210
+ #
211
+ # @api plugin
212
+ def establish_connection(settings)
213
+ raise MissingInterfaceMethodError.new("AMQ::Client#establish_connection(settings)")
214
+ end
215
+
216
+ def handshake(mechanism = "PLAIN", response = "\0guest\0guest", locale = "en_GB")
217
+ self.send_preamble
218
+ self.connection = AMQ::Client::Connection.new(self, mechanism, response, locale)
219
+ if self.sync?
220
+ self.receive # Start/Start-Ok
221
+ self.receive # Tune/Tune-Ok
222
+ end
223
+ end
224
+
225
+ # Properly close connection with AMQ broker, as described in
226
+ # section 2.2.4 of the {http://bit.ly/hw2ELX AMQP 0.9.1 specification}.
227
+ #
228
+ # @api plugin
229
+ # @todo This method should await broker's response with Close-Ok. {http://github.com/michaelklishin MK}.
230
+ # @see #close_connection
231
+ def disconnect(reply_code = 200, reply_text = "Goodbye", &block)
232
+ self.on_disconnection(&block)
233
+ closing!
234
+ self.connection.close(reply_code, reply_text)
235
+ end
236
+ alias close disconnect
237
+
238
+ # Sends AMQ protocol header (also known as preamble).
239
+ #
240
+ # @note This must be implemented by all AMQP clients.
241
+ # @api plugin
242
+ # @see http://bit.ly/hw2ELX AMQP 0.9.1 specification (Section 2.2)
243
+ def send_preamble
244
+ self.send_raw(AMQ::Protocol::PREAMBLE)
245
+ end
246
+
247
+ def send(frame)
248
+ if self.connection.closed?
249
+ raise ConnectionClosedError.new(frame)
250
+ else
251
+ self.send_raw(frame.encode)
252
+ end
253
+ end
254
+
255
+ def send_frameset(frames)
256
+ frames.each { |frame| self.send(frame) }
257
+ end # send_frameset(frames)
258
+
259
+
260
+ # Sends opaque data to AMQ broker over active connection.
261
+ #
262
+ # @note This must be implemented by all AMQP clients.
263
+ # @api plugin
264
+ def send_raw(data)
265
+ raise MissingInterfaceMethodError.new("AMQ::Client#send_raw(data)")
266
+ end
267
+
268
+ def receive_frame(frame)
269
+ @frames << frame
270
+ if frameset_complete?(@frames)
271
+ receive_frameset(@frames)
272
+ @frames.clear
273
+ else
274
+ # puts "#{frame.inspect} is NOT final"
275
+ end
276
+ end
277
+
278
+ # When the adapter receives all the frames related to
279
+ # given method frame, it's supposed to call this method.
280
+ # It calls handler for method class of the first (method)
281
+ # frame with all the other frames as arguments. Handlers
282
+ # are defined in amq/client/* by the handle(klass, &block)
283
+ # method.
284
+ def receive_frameset(frames)
285
+ frame = frames.first
286
+
287
+ if Protocol::HeartbeatFrame === frame
288
+ @last_server_heartbeat = Time.now
289
+ else
290
+ callable = AMQ::Client::Entity.handlers[frame.method_class]
291
+ if callable
292
+ callable.call(self, frames.first, frames[1..-1])
293
+ else
294
+ raise MissingHandlerError.new(frames.first)
295
+ end
296
+ end
297
+ end
298
+
299
+ def send_heartbeat
300
+ if tcp_connection_established?
301
+ if @last_server_heartbeat < (Time.now - (self.heartbeat_interval * 2))
302
+ logger.error "Reconnecting due to missing server heartbeats"
303
+ # TODO: reconnect
304
+ end
305
+ send(Protocol::HeartbeatFrame)
306
+ end
307
+ end # send_heartbeat
308
+
309
+
310
+ # @see .sync?
311
+ # @api plugin
312
+ # @see AMQ::Client::Adapter
313
+ def sync?
314
+ self.class.sync?
315
+ end
316
+
317
+ # @see .async?
318
+ # @api plugin
319
+ # @see AMQ::Client::Adapter
320
+ def async?
321
+ self.class.async?
322
+ end
323
+
324
+ # Returns heartbeat interval this client uses, in seconds.
325
+ # This value may or may not be used depending on broker capabilities.
326
+ #
327
+ # @return [Fixnum] Heartbeat interval this client uses, in seconds.
328
+ # @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.4.2.6)
329
+ def heartbeat_interval
330
+ @settings[:heartbeat] || @settings[:heartbeat_interval] || 0
331
+ end # heartbeat_interval
332
+
333
+ protected
334
+
335
+ # Utility methods
336
+
337
+ # Determines, whether the received frameset is ready to be further processed
338
+ def frameset_complete?(frames)
339
+ return false if frames.empty?
340
+ first_frame = frames[0]
341
+ first_frame.final? || (first_frame.method_class.has_content? && content_complete?(frames[1..-1]))
342
+ end
343
+
344
+ # Determines, whether given frame array contains full content body
345
+ def content_complete?(frames)
346
+ return false if frames.empty?
347
+ header = frames[0]
348
+ raise "Not a content header frame first: #{header.inspect}" unless header.kind_of?(AMQ::Protocol::HeaderFrame)
349
+ header.body_size == frames[1..-1].inject(0) {|sum, frame| sum + frame.payload.size }
350
+ end
351
+
352
+ end # Adapter
353
+ end # Client
354
+ end # AMQ
355
+
356
+ require "amq/client/channel"