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,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