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.
- data/.gitignore +8 -0
- data/.gitmodules +9 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/CONTRIBUTORS +3 -0
- data/Gemfile +27 -0
- data/LICENSE +20 -0
- data/README.textile +61 -0
- data/amq-client.gemspec +34 -0
- data/bin/jenkins.sh +23 -0
- data/bin/set_test_suite_realms_up.sh +24 -0
- data/examples/coolio_adapter/basic_consume.rb +49 -0
- data/examples/coolio_adapter/basic_consume_with_acknowledgements.rb +43 -0
- data/examples/coolio_adapter/basic_consume_with_rejections.rb +43 -0
- data/examples/coolio_adapter/basic_publish.rb +35 -0
- data/examples/coolio_adapter/channel_close.rb +24 -0
- data/examples/coolio_adapter/example_helper.rb +39 -0
- data/examples/coolio_adapter/exchange_declare.rb +28 -0
- data/examples/coolio_adapter/kitchen_sink1.rb +48 -0
- data/examples/coolio_adapter/queue_bind.rb +32 -0
- data/examples/coolio_adapter/queue_purge.rb +32 -0
- data/examples/coolio_adapter/queue_unbind.rb +37 -0
- data/examples/eventmachine_adapter/authentication/plain_password_with_custom_role_credentials.rb +36 -0
- data/examples/eventmachine_adapter/authentication/plain_password_with_default_role_credentials.rb +27 -0
- data/examples/eventmachine_adapter/authentication/plain_password_with_incorrect_credentials.rb +18 -0
- data/examples/eventmachine_adapter/basic_cancel.rb +49 -0
- data/examples/eventmachine_adapter/basic_consume.rb +51 -0
- data/examples/eventmachine_adapter/basic_consume_with_acknowledgements.rb +45 -0
- data/examples/eventmachine_adapter/basic_consume_with_rejections.rb +45 -0
- data/examples/eventmachine_adapter/basic_get.rb +57 -0
- data/examples/eventmachine_adapter/basic_get_with_empty_queue.rb +53 -0
- data/examples/eventmachine_adapter/basic_publish.rb +38 -0
- data/examples/eventmachine_adapter/basic_qos.rb +29 -0
- data/examples/eventmachine_adapter/basic_recover.rb +29 -0
- data/examples/eventmachine_adapter/basic_return.rb +34 -0
- data/examples/eventmachine_adapter/channel_close.rb +24 -0
- data/examples/eventmachine_adapter/channel_flow.rb +36 -0
- data/examples/eventmachine_adapter/channel_level_exception_handling.rb +44 -0
- data/examples/eventmachine_adapter/example_helper.rb +39 -0
- data/examples/eventmachine_adapter/exchange_declare.rb +54 -0
- data/examples/eventmachine_adapter/extensions/rabbitmq/handling_confirm_select_ok.rb +31 -0
- data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_transient_messages.rb +56 -0
- data/examples/eventmachine_adapter/extensions/rabbitmq/publisher_confirmations_with_unroutable_message.rb +46 -0
- data/examples/eventmachine_adapter/kitchen_sink1.rb +50 -0
- data/examples/eventmachine_adapter/queue_bind.rb +32 -0
- data/examples/eventmachine_adapter/queue_declare.rb +34 -0
- data/examples/eventmachine_adapter/queue_purge.rb +32 -0
- data/examples/eventmachine_adapter/queue_unbind.rb +37 -0
- data/examples/eventmachine_adapter/tx_commit.rb +29 -0
- data/examples/eventmachine_adapter/tx_rollback.rb +29 -0
- data/examples/eventmachine_adapter/tx_select.rb +27 -0
- data/examples/socket_adapter/basics.rb +19 -0
- data/examples/socket_adapter/connection.rb +53 -0
- data/examples/socket_adapter/multiple_connections.rb +17 -0
- data/irb.rb +66 -0
- data/lib/amq/client.rb +15 -0
- data/lib/amq/client/adapter.rb +356 -0
- data/lib/amq/client/adapters/coolio.rb +221 -0
- data/lib/amq/client/adapters/event_machine.rb +228 -0
- data/lib/amq/client/adapters/socket.rb +89 -0
- data/lib/amq/client/channel.rb +338 -0
- data/lib/amq/client/connection.rb +246 -0
- data/lib/amq/client/entity.rb +117 -0
- data/lib/amq/client/exceptions.rb +86 -0
- data/lib/amq/client/exchange.rb +163 -0
- data/lib/amq/client/extensions/rabbitmq.rb +5 -0
- data/lib/amq/client/extensions/rabbitmq/basic.rb +36 -0
- data/lib/amq/client/extensions/rabbitmq/confirm.rb +254 -0
- data/lib/amq/client/framing/io/frame.rb +32 -0
- data/lib/amq/client/framing/string/frame.rb +62 -0
- data/lib/amq/client/logging.rb +56 -0
- data/lib/amq/client/mixins/anonymous_entity.rb +21 -0
- data/lib/amq/client/mixins/status.rb +62 -0
- data/lib/amq/client/protocol/get_response.rb +55 -0
- data/lib/amq/client/queue.rb +450 -0
- data/lib/amq/client/settings.rb +83 -0
- data/lib/amq/client/version.rb +5 -0
- data/spec/benchmarks/adapters.rb +77 -0
- data/spec/client/framing/io_frame_spec.rb +57 -0
- data/spec/client/framing/string_frame_spec.rb +57 -0
- data/spec/client/protocol/get_response_spec.rb +79 -0
- data/spec/integration/coolio/basic_ack_spec.rb +41 -0
- data/spec/integration/coolio/basic_get_spec.rb +73 -0
- data/spec/integration/coolio/basic_return_spec.rb +33 -0
- data/spec/integration/coolio/channel_close_spec.rb +26 -0
- data/spec/integration/coolio/channel_flow_spec.rb +46 -0
- data/spec/integration/coolio/spec_helper.rb +31 -0
- data/spec/integration/coolio/tx_commit_spec.rb +40 -0
- data/spec/integration/coolio/tx_rollback_spec.rb +44 -0
- data/spec/integration/eventmachine/basic_ack_spec.rb +40 -0
- data/spec/integration/eventmachine/basic_get_spec.rb +73 -0
- data/spec/integration/eventmachine/basic_return_spec.rb +35 -0
- data/spec/integration/eventmachine/channel_close_spec.rb +26 -0
- data/spec/integration/eventmachine/channel_flow_spec.rb +32 -0
- data/spec/integration/eventmachine/spec_helper.rb +22 -0
- data/spec/integration/eventmachine/tx_commit_spec.rb +47 -0
- data/spec/integration/eventmachine/tx_rollback_spec.rb +35 -0
- data/spec/regression/bad_frame_slicing_in_adapters_spec.rb +59 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/unit/client/adapter_spec.rb +49 -0
- data/spec/unit/client/entity_spec.rb +49 -0
- data/spec/unit/client/logging_spec.rb +60 -0
- data/spec/unit/client/mixins/status_spec.rb +72 -0
- data/spec/unit/client/settings_spec.rb +27 -0
- data/spec/unit/client_spec.rb +11 -0
- data/tasks.rb +11 -0
- metadata +202 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require "amq/client/exceptions"
|
|
4
|
+
|
|
5
|
+
module AMQ
|
|
6
|
+
module Client
|
|
7
|
+
module Framing
|
|
8
|
+
module IO
|
|
9
|
+
|
|
10
|
+
class Frame < AMQ::Protocol::Frame
|
|
11
|
+
def self.decode(io)
|
|
12
|
+
header = io.read(7)
|
|
13
|
+
type, channel, size = self.decode_header(header)
|
|
14
|
+
data = io.read(size + 1)
|
|
15
|
+
payload, frame_end = data[0..-2], data[-1, 1]
|
|
16
|
+
# TODO: this will hang if the size is bigger than expected or it'll leave there some chars -> make it more error-proof:
|
|
17
|
+
# BTW: socket#eof?
|
|
18
|
+
raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
|
|
19
|
+
self.new(type, payload, channel)
|
|
20
|
+
end # self.from
|
|
21
|
+
|
|
22
|
+
end # Frame
|
|
23
|
+
end # IO
|
|
24
|
+
end # Framing
|
|
25
|
+
end # Client
|
|
26
|
+
end # AMQ
|
|
27
|
+
|
|
28
|
+
class AMQ::Protocol::Frame
|
|
29
|
+
def final?
|
|
30
|
+
true ####### HACK for testing, implement & move to amq-protocol!
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
# This will be probably used by all the async libraries like EventMachine.
|
|
4
|
+
# It expects the whole frame as one string, so if library of your choice
|
|
5
|
+
# gives you input chunk-by-chunk, you'll need to have something like this:
|
|
6
|
+
#
|
|
7
|
+
# class Client
|
|
8
|
+
# include EventMachine::Deferrable
|
|
9
|
+
#
|
|
10
|
+
# def receive_data(chunk)
|
|
11
|
+
# if @payload.nil?
|
|
12
|
+
# self.decode_from_string(chunk[0..6])
|
|
13
|
+
# @payload = ""
|
|
14
|
+
# elsif @payload && chunk[-1] != FINAL_OCTET
|
|
15
|
+
# @payload += chunk
|
|
16
|
+
# @size += chunk.bytesize
|
|
17
|
+
# else
|
|
18
|
+
# check_size(@size, @payload.bytesize)
|
|
19
|
+
# Frame.decode(@payload) # we need the whole payload
|
|
20
|
+
# @size, @payload = nil
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# NOTE: the client should also implement waiting for another frames, in case that some header/body frames are expected.
|
|
25
|
+
# end
|
|
26
|
+
|
|
27
|
+
require "amq/client/exceptions"
|
|
28
|
+
|
|
29
|
+
module AMQ
|
|
30
|
+
module Client
|
|
31
|
+
module Framing
|
|
32
|
+
module String
|
|
33
|
+
class Frame < AMQ::Protocol::Frame
|
|
34
|
+
ENCODINGS_SUPPORTED = defined? Encoding
|
|
35
|
+
HEADER_SLICE = (0..6).freeze
|
|
36
|
+
DATA_SLICE = (7..-1).freeze
|
|
37
|
+
PAYLOAD_SLICE = (0..-2).freeze
|
|
38
|
+
|
|
39
|
+
def self.decode(string)
|
|
40
|
+
header = string[HEADER_SLICE]
|
|
41
|
+
type, channel, size = self.decode_header(header)
|
|
42
|
+
data = string[DATA_SLICE]
|
|
43
|
+
payload = data[PAYLOAD_SLICE]
|
|
44
|
+
frame_end = data[-1, 1]
|
|
45
|
+
|
|
46
|
+
frame_end.force_encoding(AMQ::Protocol::Frame::FINAL_OCTET.encoding) if ENCODINGS_SUPPORTED
|
|
47
|
+
|
|
48
|
+
# 1) the size is miscalculated
|
|
49
|
+
if payload.bytesize != size
|
|
50
|
+
raise BadLengthError.new(size, payload.bytesize)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# 2) the size is OK, but the string doesn't end with FINAL_OCTET
|
|
54
|
+
raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET
|
|
55
|
+
|
|
56
|
+
self.new(type, payload, channel)
|
|
57
|
+
end # self.from
|
|
58
|
+
end # Frame
|
|
59
|
+
end # String
|
|
60
|
+
end # Framing
|
|
61
|
+
end # Client
|
|
62
|
+
end # AMQ
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
# You can use arbitrary logger which responds to #debug, #info, #error and #fatal methods, so for example the logger from standard library will work fine:
|
|
4
|
+
#
|
|
5
|
+
# AMQ::Client.logging = true
|
|
6
|
+
# AMQ::Client.logger = MyLogger.new(STDERR)
|
|
7
|
+
#
|
|
8
|
+
# AMQ::Client.logger defaults to a new instance of Ruby stdlib logger.
|
|
9
|
+
#
|
|
10
|
+
# If you want to be able to log messages just from specified classes or even instances, just make the instance respond to #logging and set it to desired value. So for example <tt>Queue.class_eval { def logging; true; end }</tt> will turn on logging for the whole Queue class whereas <tt>queue = Queue.new; def queue.logging; false; end</tt> will disable logging for given Queue instance.
|
|
11
|
+
|
|
12
|
+
module AMQ
|
|
13
|
+
module Client
|
|
14
|
+
module Logging
|
|
15
|
+
def self.included(klass)
|
|
16
|
+
unless klass.method_defined?(:client)
|
|
17
|
+
raise NotImplementedError.new("Class #{klass} has to provide #client method!")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.logging
|
|
22
|
+
@logging ||= false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.logging=(boolean)
|
|
26
|
+
@logging = boolean
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
REQUIRED_METHODS = [:debug, :info, :error, :fatal].freeze
|
|
30
|
+
|
|
31
|
+
def debug(message)
|
|
32
|
+
log(:debug, message)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def info(message)
|
|
36
|
+
log(:info, message)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def error(message)
|
|
40
|
+
log(:error, message)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def fatal(message)
|
|
44
|
+
log(:fatal, message)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
protected
|
|
48
|
+
def log(method, message)
|
|
49
|
+
if self.respond_to?(:logging) ? self.logging : AMQ::Client::Logging.logging
|
|
50
|
+
self.client.logger.__send__(method, message)
|
|
51
|
+
message
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module AMQ
|
|
4
|
+
module Client
|
|
5
|
+
# Common behavior of AMQ entities that can be either client or server-named, for example, exchanges and queues.
|
|
6
|
+
module AnonymousEntityMixin
|
|
7
|
+
|
|
8
|
+
# @return [Boolean] true if this entity is anonymous (server-named)
|
|
9
|
+
def anonymous?
|
|
10
|
+
@name.nil? or @name.empty?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def dup
|
|
14
|
+
if anonymous?
|
|
15
|
+
raise RuntimeError.new("You can't clone anonymous queue until it receives back the name in Queue.Declare-Ok response. Move the code with #dup to the callback for the #declare method.") # TODO: that's not true in all cases, imagine the user didn't call #declare yet.
|
|
16
|
+
end
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
end # AnonymousEntityMixin
|
|
20
|
+
end # Client
|
|
21
|
+
end # AMQ
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module AMQ
|
|
4
|
+
module Client
|
|
5
|
+
module StatusMixin
|
|
6
|
+
VALUES = [:opened, :closed, :opening, :closing].freeze
|
|
7
|
+
|
|
8
|
+
class ImproperStatusError < ArgumentError
|
|
9
|
+
def initialize(value)
|
|
10
|
+
super("Value #{value.inspect} isn't permitted. Choose one of: #{AMQ::Client::StatusMixin::VALUES.inspect}")
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
attr_reader :status
|
|
15
|
+
def status=(value)
|
|
16
|
+
if VALUES.include?(value)
|
|
17
|
+
@status = value
|
|
18
|
+
else
|
|
19
|
+
raise ImproperStatusError.new(value)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def opened?
|
|
24
|
+
@status == :opened
|
|
25
|
+
end
|
|
26
|
+
alias open? opened?
|
|
27
|
+
|
|
28
|
+
def closed?
|
|
29
|
+
@status == :closed
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def opening?
|
|
35
|
+
@status == :opening
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def closing?
|
|
39
|
+
@status == :closing
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def opened!
|
|
44
|
+
@status = :opened
|
|
45
|
+
end # opened!
|
|
46
|
+
|
|
47
|
+
def closed!
|
|
48
|
+
@status = :closed
|
|
49
|
+
end # closed!
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def opening!
|
|
54
|
+
@status = :opening
|
|
55
|
+
end # opening!
|
|
56
|
+
|
|
57
|
+
def closing!
|
|
58
|
+
@status = :closing
|
|
59
|
+
end # closing!
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
# Purpose of this class is to simplify work with GetOk and GetEmpty.
|
|
4
|
+
module AMQ
|
|
5
|
+
module Protocol
|
|
6
|
+
class GetResponse
|
|
7
|
+
attr_reader :method
|
|
8
|
+
def initialize(method)
|
|
9
|
+
@method = method
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def empty?
|
|
13
|
+
@method.is_a?(::AMQ::Protocol::Basic::GetEmpty)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# GetOk attributes
|
|
17
|
+
def delivery_tag
|
|
18
|
+
if @method.respond_to?(:delivery_tag)
|
|
19
|
+
@method.delivery_tag
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def redelivered
|
|
24
|
+
if @method.respond_to?(:redelivered)
|
|
25
|
+
@method.redelivered
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def exchange
|
|
30
|
+
if @method.respond_to?(:exchange)
|
|
31
|
+
@method.exchange
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def routing_key
|
|
36
|
+
if @method.respond_to?(:routing_key)
|
|
37
|
+
@method.routing_key
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def message_count
|
|
42
|
+
if @method.respond_to?(:message_count)
|
|
43
|
+
@method.message_count
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# GetEmpty attributes
|
|
48
|
+
def cluster_id
|
|
49
|
+
if @method.respond_to?(:cluster_id)
|
|
50
|
+
@method.cluster_id
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require "amq/client/entity"
|
|
4
|
+
require "amq/client/adapter"
|
|
5
|
+
require "amq/client/mixins/anonymous_entity"
|
|
6
|
+
require "amq/client/protocol/get_response"
|
|
7
|
+
|
|
8
|
+
module AMQ
|
|
9
|
+
module Client
|
|
10
|
+
class Queue < Entity
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Behaviors
|
|
14
|
+
#
|
|
15
|
+
|
|
16
|
+
include AnonymousEntityMixin
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
#
|
|
20
|
+
# API
|
|
21
|
+
#
|
|
22
|
+
|
|
23
|
+
# Qeueue name. May be server-generated or assigned directly.
|
|
24
|
+
attr_reader :name
|
|
25
|
+
|
|
26
|
+
# Channel this queue belongs to.
|
|
27
|
+
attr_reader :channel
|
|
28
|
+
|
|
29
|
+
# @param [AMQ::Client::Adapter] AMQ networking adapter to use.
|
|
30
|
+
# @param [AMQ::Client::Channel] AMQ channel this queue object uses.
|
|
31
|
+
# @param [String] Queue name. Please note that AMQP spec does not require brokers to support Unicode for queue names.
|
|
32
|
+
# @api public
|
|
33
|
+
def initialize(client, channel, name = AMQ::Protocol::EMPTY_STRING)
|
|
34
|
+
super(client)
|
|
35
|
+
|
|
36
|
+
@name = name
|
|
37
|
+
@channel = channel
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def dup
|
|
41
|
+
if @name.empty?
|
|
42
|
+
raise RuntimeError.new("You can't clone anonymous queue until it receives server-generated name. Move the code with #dup to the callback for the #declare method.")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
o = super
|
|
46
|
+
o.reset_consumer_tag!
|
|
47
|
+
o
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# @return [Boolean] true if this queue was declared as durable (will survive broker restart).
|
|
52
|
+
# @api public
|
|
53
|
+
def durable?
|
|
54
|
+
@durable
|
|
55
|
+
end # durable?
|
|
56
|
+
|
|
57
|
+
# @return [Boolean] true if this queue was declared as exclusive (limited to just one consumer)
|
|
58
|
+
# @api public
|
|
59
|
+
def exclusive?
|
|
60
|
+
@exclusive
|
|
61
|
+
end # exclusive?
|
|
62
|
+
|
|
63
|
+
# @return [Boolean] true if this queue was declared as automatically deleted (deleted as soon as last consumer unbinds).
|
|
64
|
+
# @api public
|
|
65
|
+
def auto_delete?
|
|
66
|
+
@auto_delete
|
|
67
|
+
end # auto_delete?
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Declares this queue.
|
|
71
|
+
#
|
|
72
|
+
#
|
|
73
|
+
# @return [Queue] self
|
|
74
|
+
#
|
|
75
|
+
# @api public
|
|
76
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.7.2.1.)
|
|
77
|
+
def declare(passive = false, durable = false, exclusive = false, auto_delete = false, nowait = false, arguments = nil, &block)
|
|
78
|
+
raise ArgumentError, "declaration with nowait does not make sense for server-named queues! Either specify name other than empty string or use #declare without nowait" if nowait && self.anonymous?
|
|
79
|
+
|
|
80
|
+
@durable = durable
|
|
81
|
+
@exclusive = exclusive
|
|
82
|
+
@auto_delete = auto_delete
|
|
83
|
+
|
|
84
|
+
nowait = true if !block && !@name.empty?
|
|
85
|
+
@client.send(Protocol::Queue::Declare.encode(@channel.id, @name, passive, durable, exclusive, auto_delete, nowait, arguments))
|
|
86
|
+
|
|
87
|
+
if !nowait
|
|
88
|
+
self.append_callback(:declare, &block)
|
|
89
|
+
@channel.queues_awaiting_declare_ok.push(self)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if @client.sync?
|
|
93
|
+
@client.read_until_receives(Protocol::Queue::DeclareOk) unless nowait
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
self
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Deletes this queue.
|
|
100
|
+
#
|
|
101
|
+
# @param [Boolean] if_unused delete only if queue has no consumers (subscribers).
|
|
102
|
+
# @param [Boolean] if_empty delete only if queue has no messages in it.
|
|
103
|
+
# @param [Boolean] nowait Don't wait for reply from broker.
|
|
104
|
+
# @return [Queue] self
|
|
105
|
+
#
|
|
106
|
+
# @api public
|
|
107
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.7.2.9.)
|
|
108
|
+
def delete(if_unused = false, if_empty = false, nowait = false, &block)
|
|
109
|
+
nowait = true unless block
|
|
110
|
+
@client.send(Protocol::Queue::Delete.encode(@channel.id, @name, if_unused, if_empty, nowait))
|
|
111
|
+
|
|
112
|
+
if !nowait
|
|
113
|
+
self.append_callback(:delete, &block)
|
|
114
|
+
|
|
115
|
+
# TODO: delete itself from queues cache
|
|
116
|
+
@channel.queues_awaiting_delete_ok.push(self)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
self
|
|
120
|
+
end # delete(channel, queue, if_unused, if_empty, nowait, &block)
|
|
121
|
+
|
|
122
|
+
#
|
|
123
|
+
# @return [Queue] self
|
|
124
|
+
#
|
|
125
|
+
# @api public
|
|
126
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.7.2.3.)
|
|
127
|
+
def bind(exchange, routing_key = AMQ::Protocol::EMPTY_STRING, nowait = false, arguments = nil, &block)
|
|
128
|
+
nowait = true unless block
|
|
129
|
+
exchange_name = if exchange.respond_to?(:name)
|
|
130
|
+
exchange.name
|
|
131
|
+
else
|
|
132
|
+
|
|
133
|
+
exchange
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
@client.send(Protocol::Queue::Bind.encode(@channel.id, @name, exchange_name, routing_key, nowait, arguments))
|
|
137
|
+
|
|
138
|
+
if !nowait
|
|
139
|
+
self.append_callback(:bind, &block)
|
|
140
|
+
|
|
141
|
+
# TODO: handle channel & connection-level exceptions
|
|
142
|
+
@channel.queues_awaiting_bind_ok.push(self)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
self
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
#
|
|
149
|
+
# @return [Queue] self
|
|
150
|
+
#
|
|
151
|
+
# @api public
|
|
152
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.7.2.5.)
|
|
153
|
+
def unbind(exchange, routing_key = AMQ::Protocol::EMPTY_STRING, arguments = nil, &block)
|
|
154
|
+
exchange_name = if exchange.respond_to?(:name)
|
|
155
|
+
exchange.name
|
|
156
|
+
else
|
|
157
|
+
|
|
158
|
+
exchange
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
@client.send(Protocol::Queue::Unbind.encode(@channel.id, @name, exchange_name, routing_key, arguments))
|
|
162
|
+
|
|
163
|
+
self.append_callback(:unbind, &block)
|
|
164
|
+
# TODO: handle channel & connection-level exceptions
|
|
165
|
+
@channel.queues_awaiting_unbind_ok.push(self)
|
|
166
|
+
|
|
167
|
+
self
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
#
|
|
172
|
+
# @return [Queue] self
|
|
173
|
+
#
|
|
174
|
+
# @api public
|
|
175
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.3.)
|
|
176
|
+
def consume(no_ack = false, exclusive = false, nowait = false, no_local = false, arguments = nil, &block)
|
|
177
|
+
raise RuntimeError.new("This instance is already being consumed! Create another one using #dup.") if @consumer_tag
|
|
178
|
+
|
|
179
|
+
nowait = true unless block
|
|
180
|
+
@consumer_tag = generate_consumer_tag(name)
|
|
181
|
+
@client.send(Protocol::Basic::Consume.encode(@channel.id, @name, @consumer_tag, no_local, no_ack, exclusive, nowait, arguments))
|
|
182
|
+
|
|
183
|
+
@channel.consumers[@consumer_tag] = self
|
|
184
|
+
|
|
185
|
+
if !nowait
|
|
186
|
+
# unlike #get, here it is reasonable to expect more than one callback
|
|
187
|
+
# so we use #append_callback
|
|
188
|
+
self.append_callback(:consume, &block)
|
|
189
|
+
|
|
190
|
+
@channel.queues_awaiting_consume_ok.push(self)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
self
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Unique string supposed to be used as a consumer tag.
|
|
197
|
+
#
|
|
198
|
+
# @return [String] Unique string.
|
|
199
|
+
# @api plugin
|
|
200
|
+
def generate_consumer_tag(name)
|
|
201
|
+
"#{name}-#{Time.now.to_i * 1000}-#{Kernel.rand(999_999_999_999)}"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Resets consumer tag by setting it to nil.
|
|
205
|
+
# @return [String] Consumer tag this queue previously used.
|
|
206
|
+
#
|
|
207
|
+
# @api plugin
|
|
208
|
+
def reset_consumer_tag!
|
|
209
|
+
ct = @consumer_tag.dup
|
|
210
|
+
@consumer_tag = nil
|
|
211
|
+
|
|
212
|
+
ct
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
#
|
|
217
|
+
# @return [Queue] self
|
|
218
|
+
#
|
|
219
|
+
# @api public
|
|
220
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.10.)
|
|
221
|
+
def get(no_ack = false, &block)
|
|
222
|
+
@client.send(Protocol::Basic::Get.encode(@channel.id, @name, no_ack))
|
|
223
|
+
|
|
224
|
+
# most people only want one callback per #get call. Consider the following example:
|
|
225
|
+
#
|
|
226
|
+
# 100.times { queue.get { ... } }
|
|
227
|
+
#
|
|
228
|
+
# most likely you won't expect 100 callback runs per messages here. MK.
|
|
229
|
+
self.redefine_callback(:get, &block)
|
|
230
|
+
@channel.queues_awaiting_get_response.push(self)
|
|
231
|
+
|
|
232
|
+
self
|
|
233
|
+
end # get(no_ack = false, &block)
|
|
234
|
+
|
|
235
|
+
#
|
|
236
|
+
# @return [Queue] self
|
|
237
|
+
#
|
|
238
|
+
# @api public
|
|
239
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.5.)
|
|
240
|
+
def cancel(nowait = false, &block)
|
|
241
|
+
raise "This instance isn't being consumed!" if @consumer_tag.nil?
|
|
242
|
+
|
|
243
|
+
@client.send(Protocol::Basic::Cancel.encode(@channel.id, @consumer_tag, nowait))
|
|
244
|
+
@consumer_tag = nil
|
|
245
|
+
self.clear_callbacks(:delivery)
|
|
246
|
+
self.clear_callbacks(:consume)
|
|
247
|
+
|
|
248
|
+
if !nowait
|
|
249
|
+
self.redefine_callback(:cancel, &block)
|
|
250
|
+
@channel.queues_awaiting_cancel_ok.push(self)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
self
|
|
254
|
+
end # cancel(&block)
|
|
255
|
+
|
|
256
|
+
#
|
|
257
|
+
# @return [Queue] self
|
|
258
|
+
#
|
|
259
|
+
# @api public
|
|
260
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.7.2.7.)
|
|
261
|
+
def purge(nowait = false, &block)
|
|
262
|
+
nowait = true unless block
|
|
263
|
+
@client.send(Protocol::Queue::Purge.encode(@channel.id, @name, nowait))
|
|
264
|
+
|
|
265
|
+
if !nowait
|
|
266
|
+
self.redefine_callback(:purge, &block)
|
|
267
|
+
# TODO: handle channel & connection-level exceptions
|
|
268
|
+
@channel.queues_awaiting_purge_ok.push(self)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
self
|
|
272
|
+
end # purge(nowait = false, &block)
|
|
273
|
+
|
|
274
|
+
#
|
|
275
|
+
# @return [Queue] self
|
|
276
|
+
#
|
|
277
|
+
# @api public
|
|
278
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.13.)
|
|
279
|
+
def acknowledge(delivery_tag)
|
|
280
|
+
@channel.acknowledge(delivery_tag)
|
|
281
|
+
|
|
282
|
+
self
|
|
283
|
+
end # acknowledge(delivery_tag)
|
|
284
|
+
|
|
285
|
+
#
|
|
286
|
+
# @return [Queue] self
|
|
287
|
+
#
|
|
288
|
+
# @api public
|
|
289
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Section 1.8.3.14.)
|
|
290
|
+
def reject(delivery_tag, requeue = true)
|
|
291
|
+
@channel.reject(delivery_tag, requeue)
|
|
292
|
+
|
|
293
|
+
self
|
|
294
|
+
end # reject(delivery_tag, requeue = true)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# @api public
|
|
299
|
+
# @see http://bit.ly/htCzCX AMQP 0.9.1 protocol documentation (Sections 1.8.3.9)
|
|
300
|
+
def on_delivery(&block)
|
|
301
|
+
self.append_callback(:delivery, &block)
|
|
302
|
+
end # on_delivery(&block)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
#
|
|
307
|
+
# Implementation
|
|
308
|
+
#
|
|
309
|
+
|
|
310
|
+
def handle_declare_ok(method)
|
|
311
|
+
@name = method.queue if self.anonymous?
|
|
312
|
+
@channel.register_queue(self)
|
|
313
|
+
|
|
314
|
+
self.exec_callback_once_yielding_self(:declare, method)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def handle_delete_ok(method)
|
|
318
|
+
self.exec_callback(:delete, method)
|
|
319
|
+
end # handle_delete_ok(method)
|
|
320
|
+
|
|
321
|
+
def handle_consume_ok(method)
|
|
322
|
+
self.exec_callback(:consume, method)
|
|
323
|
+
end # handle_consume_ok(method)
|
|
324
|
+
|
|
325
|
+
def handle_purge_ok(method)
|
|
326
|
+
self.exec_callback(:purge, method)
|
|
327
|
+
end # handle_purge_ok(method)
|
|
328
|
+
|
|
329
|
+
def handle_bind_ok(method)
|
|
330
|
+
self.exec_callback(:bind)
|
|
331
|
+
end # handle_bind_ok(method)
|
|
332
|
+
|
|
333
|
+
def handle_unbind_ok(method)
|
|
334
|
+
self.exec_callback(:unbind)
|
|
335
|
+
end # handle_unbind_ok(method)
|
|
336
|
+
|
|
337
|
+
def handle_delivery(method, header, payload)
|
|
338
|
+
self.exec_callback(:delivery, method, header, payload)
|
|
339
|
+
end # handle_delivery
|
|
340
|
+
|
|
341
|
+
def handle_cancel_ok(method)
|
|
342
|
+
@consumer_tag = nil
|
|
343
|
+
self.exec_callback(:cancel, method)
|
|
344
|
+
end # handle_cancel_ok(method)
|
|
345
|
+
|
|
346
|
+
def handle_get_ok(method, header, payload)
|
|
347
|
+
method = Protocol::GetResponse.new(method)
|
|
348
|
+
self.exec_callback(:get, method, header, payload)
|
|
349
|
+
end # handle_get_ok(method, header, payload)
|
|
350
|
+
|
|
351
|
+
def handle_get_empty(method)
|
|
352
|
+
method = Protocol::GetResponse.new(method)
|
|
353
|
+
self.exec_callback(:get, method)
|
|
354
|
+
end # handle_get_empty(method)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
# Get the first queue which didn't receive Queue.Declare-Ok yet and run its declare callback.
|
|
359
|
+
# The cache includes only queues with {nowait: false}.
|
|
360
|
+
self.handle(Protocol::Queue::DeclareOk) do |client, frame|
|
|
361
|
+
method = frame.decode_payload
|
|
362
|
+
|
|
363
|
+
channel = client.connection.channels[frame.channel]
|
|
364
|
+
queue = channel.queues_awaiting_declare_ok.shift
|
|
365
|
+
|
|
366
|
+
queue.handle_declare_ok(method)
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
self.handle(Protocol::Queue::DeleteOk) do |client, frame|
|
|
371
|
+
channel = client.connection.channels[frame.channel]
|
|
372
|
+
queue = channel.queues_awaiting_delete_ok.shift
|
|
373
|
+
queue.handle_delete_ok(frame.decode_payload)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
self.handle(Protocol::Queue::BindOk) do |client, frame|
|
|
378
|
+
channel = client.connection.channels[frame.channel]
|
|
379
|
+
queue = channel.queues_awaiting_bind_ok.shift
|
|
380
|
+
|
|
381
|
+
queue.handle_bind_ok(frame.decode_payload)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
self.handle(Protocol::Queue::UnbindOk) do |client, frame|
|
|
386
|
+
channel = client.connection.channels[frame.channel]
|
|
387
|
+
queue = channel.queues_awaiting_unbind_ok.shift
|
|
388
|
+
|
|
389
|
+
queue.handle_unbind_ok(frame.decode_payload)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
self.handle(Protocol::Basic::ConsumeOk) do |client, frame|
|
|
394
|
+
channel = client.connection.channels[frame.channel]
|
|
395
|
+
queue = channel.queues_awaiting_consume_ok.shift
|
|
396
|
+
|
|
397
|
+
queue.handle_consume_ok(frame.decode_payload)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
self.handle(Protocol::Basic::CancelOk) do |client, frame|
|
|
402
|
+
channel = client.connection.channels[frame.channel]
|
|
403
|
+
queue = channel.queues_awaiting_cancel_ok.shift
|
|
404
|
+
|
|
405
|
+
queue.handle_consume_ok(frame.decode_payload)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
# Basic.Deliver
|
|
410
|
+
self.handle(Protocol::Basic::Deliver) do |client, method_frame, content_frames|
|
|
411
|
+
channel = client.connection.channels[method_frame.channel]
|
|
412
|
+
method = method_frame.decode_payload
|
|
413
|
+
queue = channel.consumers[method.consumer_tag]
|
|
414
|
+
|
|
415
|
+
header = content_frames.shift
|
|
416
|
+
body = content_frames.map { |frame| frame.payload }.join
|
|
417
|
+
queue.handle_delivery(method, header, body)
|
|
418
|
+
# TODO: ack if necessary
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
self.handle(Protocol::Queue::PurgeOk) do |client, frame|
|
|
423
|
+
channel = client.connection.channels[frame.channel]
|
|
424
|
+
queue = channel.queues_awaiting_purge_ok.shift
|
|
425
|
+
|
|
426
|
+
queue.handle_purge_ok(frame.decode_payload)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
self.handle(Protocol::Basic::GetOk) do |client, frame, content_frames|
|
|
431
|
+
channel = client.connection.channels[frame.channel]
|
|
432
|
+
queue = channel.queues_awaiting_get_response.shift
|
|
433
|
+
method = frame.decode_payload
|
|
434
|
+
|
|
435
|
+
header = content_frames.shift
|
|
436
|
+
body = content_frames.map {|frame| frame.payload }.join
|
|
437
|
+
|
|
438
|
+
queue.handle_get_ok(method, header, body) if queue
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
self.handle(Protocol::Basic::GetEmpty) do |client, frame|
|
|
443
|
+
channel = client.connection.channels[frame.channel]
|
|
444
|
+
queue = channel.queues_awaiting_get_response.shift
|
|
445
|
+
|
|
446
|
+
queue.handle_get_empty(frame.decode_payload)
|
|
447
|
+
end
|
|
448
|
+
end # Queue
|
|
449
|
+
end # Client
|
|
450
|
+
end # AMQ
|