amq-client 0.5.0

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