bunny 0.9.0.pre13 → 0.9.0.rc1
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.
- checksums.yaml +4 -4
- data/ChangeLog.md +23 -0
- data/README.md +2 -2
- data/lib/bunny.rb +15 -2
- data/lib/bunny/authentication/credentials_encoder.rb +18 -0
- data/lib/bunny/authentication/external_mechanism_encoder.rb +1 -0
- data/lib/bunny/authentication/plain_mechanism_encoder.rb +2 -0
- data/lib/bunny/channel.rb +9 -3
- data/lib/bunny/channel_id_allocator.rb +8 -0
- data/lib/bunny/concurrent/condition.rb +2 -0
- data/lib/bunny/concurrent/continuation_queue.rb +3 -0
- data/lib/bunny/concurrent/linked_continuation_queue.rb +3 -0
- data/lib/bunny/consumer.rb +2 -0
- data/lib/bunny/consumer_tag_generator.rb +1 -0
- data/lib/bunny/consumer_work_pool.rb +2 -0
- data/lib/bunny/delivery_info.rb +18 -2
- data/lib/bunny/exceptions.rb +49 -10
- data/lib/bunny/framing.rb +3 -0
- data/lib/bunny/heartbeat_sender.rb +4 -1
- data/lib/bunny/message_properties.rb +22 -0
- data/lib/bunny/queue.rb +49 -0
- data/lib/bunny/reader_loop.rb +1 -0
- data/lib/bunny/return_info.rb +11 -0
- data/lib/bunny/session.rb +53 -19
- data/lib/bunny/socket.rb +1 -0
- data/lib/bunny/ssl_socket.rb +29 -5
- data/lib/bunny/system_timer.rb +2 -0
- data/lib/bunny/test_kit.rb +4 -3
- data/lib/bunny/transport.rb +41 -25
- data/lib/bunny/version.rb +2 -1
- data/spec/compatibility/queue_declare_spec.rb +4 -0
- data/spec/compatibility/queue_declare_with_default_channel_spec.rb +33 -0
- data/spec/higher_level_api/integration/basic_get_spec.rb +35 -0
- data/spec/higher_level_api/integration/basic_nack_spec.rb +19 -1
- data/spec/higher_level_api/integration/connection_spec.rb +5 -0
- data/spec/higher_level_api/integration/queue_unbind_spec.rb +23 -2
- data/spec/higher_level_api/integration/tls_connection_spec.rb +47 -0
- data/spec/lower_level_api/integration/basic_cancel_spec.rb +8 -1
- data/spec/tls/cacert.pem +18 -0
- data/spec/tls/client_cert.pem +18 -0
- data/spec/tls/client_key.pem +27 -0
- data/spec/tls/server_cert.pem +18 -0
- data/spec/tls/server_key.pem +27 -0
- metadata +16 -2
data/lib/bunny/framing.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
module Bunny
|
2
|
+
# @private
|
2
3
|
module Framing
|
3
4
|
ENCODINGS_SUPPORTED = defined? Encoding
|
4
5
|
HEADER_SLICE = (0..6).freeze
|
5
6
|
DATA_SLICE = (7..-1).freeze
|
6
7
|
PAYLOAD_SLICE = (0..-2).freeze
|
7
8
|
|
9
|
+
# @private
|
8
10
|
module String
|
9
11
|
class Frame < AMQ::Protocol::Frame
|
10
12
|
def self.decode(string)
|
@@ -30,6 +32,7 @@ module Bunny
|
|
30
32
|
end # String
|
31
33
|
|
32
34
|
|
35
|
+
# @private
|
33
36
|
module IO
|
34
37
|
class Frame < AMQ::Protocol::Frame
|
35
38
|
def self.decode(io)
|
@@ -3,6 +3,9 @@ require "amq/protocol/client"
|
|
3
3
|
require "amq/protocol/frame"
|
4
4
|
|
5
5
|
module Bunny
|
6
|
+
# Periodically sends heartbeats, keeping track of the last publishing activity.
|
7
|
+
#
|
8
|
+
# @private
|
6
9
|
class HeartbeatSender
|
7
10
|
|
8
11
|
#
|
@@ -50,7 +53,7 @@ module Bunny
|
|
50
53
|
@logger.error "I/O error in the hearbeat sender: #{ioe.message}"
|
51
54
|
stop
|
52
55
|
rescue Exception => e
|
53
|
-
@logger.error "
|
56
|
+
@logger.error "Error in the hearbeat sender: #{e.message}"
|
54
57
|
stop
|
55
58
|
end
|
56
59
|
end
|
@@ -14,82 +14,104 @@ module Bunny
|
|
14
14
|
# API
|
15
15
|
#
|
16
16
|
|
17
|
+
# @private
|
17
18
|
def initialize(properties)
|
18
19
|
@properties = properties
|
19
20
|
end
|
20
21
|
|
22
|
+
# Iterates over the message properties
|
23
|
+
# @see Enumerable#each
|
21
24
|
def each(*args, &block)
|
22
25
|
@properties.each(*args, &block)
|
23
26
|
end
|
24
27
|
|
28
|
+
# Accesses message properties by key
|
29
|
+
# @see Hash#[]
|
25
30
|
def [](k)
|
26
31
|
@properties[k]
|
27
32
|
end
|
28
33
|
|
34
|
+
# @return [Hash] Hash representation of this delivery info
|
29
35
|
def to_hash
|
30
36
|
@properties
|
31
37
|
end
|
32
38
|
|
39
|
+
# @private
|
33
40
|
def to_s
|
34
41
|
to_hash.to_s
|
35
42
|
end
|
36
43
|
|
44
|
+
# @private
|
37
45
|
def inspect
|
38
46
|
to_hash.inspect
|
39
47
|
end
|
40
48
|
|
49
|
+
# @return [String] (Optional) content type of the message, as set by the publisher
|
41
50
|
def content_type
|
42
51
|
@properties[:content_type]
|
43
52
|
end
|
44
53
|
|
54
|
+
# @return [String] (Optional) content encoding of the message, as set by the publisher
|
45
55
|
def content_encoding
|
46
56
|
@properties[:content_encoding]
|
47
57
|
end
|
48
58
|
|
59
|
+
# @return [String] Message headers
|
49
60
|
def headers
|
50
61
|
@properties[:headers]
|
51
62
|
end
|
52
63
|
|
64
|
+
# @return [Integer] Delivery mode (persistent or transient)
|
53
65
|
def delivery_mode
|
54
66
|
@properties[:delivery_mode]
|
55
67
|
end
|
56
68
|
|
69
|
+
# @return [Integer] Message priority, as set by the publisher
|
57
70
|
def priority
|
58
71
|
@properties[:priority]
|
59
72
|
end
|
60
73
|
|
74
|
+
# @return [String] What message this message is a reply to (or corresponds to), as set by the publisher
|
61
75
|
def correlation_id
|
62
76
|
@properties[:correlation_id]
|
63
77
|
end
|
64
78
|
|
79
|
+
# @return [String] (Optional) How to reply to the publisher (usually a reply queue name)
|
65
80
|
def reply_to
|
66
81
|
@properties[:reply_to]
|
67
82
|
end
|
68
83
|
|
84
|
+
# @return [String] Message expiration, as set by the publisher
|
69
85
|
def expiration
|
70
86
|
@properties[:expiration]
|
71
87
|
end
|
72
88
|
|
89
|
+
# @return [String] Message ID, as set by the publisher
|
73
90
|
def message_id
|
74
91
|
@properties[:message_id]
|
75
92
|
end
|
76
93
|
|
94
|
+
# @return [Time] Message timestamp, as set by the publisher
|
77
95
|
def timestamp
|
78
96
|
@properties[:timestamp]
|
79
97
|
end
|
80
98
|
|
99
|
+
# @return [String] Message type, as set by the publisher
|
81
100
|
def type
|
82
101
|
@properties[:type]
|
83
102
|
end
|
84
103
|
|
104
|
+
# @return [String] Publishing user, as set by the publisher
|
85
105
|
def user_id
|
86
106
|
@properties[:user_id]
|
87
107
|
end
|
88
108
|
|
109
|
+
# @return [String] Publishing application, as set by the publisher
|
89
110
|
def app_id
|
90
111
|
@properties[:app_id]
|
91
112
|
end
|
92
113
|
|
114
|
+
# @return [String] Cluster ID, as set by the publisher
|
93
115
|
def cluster_id
|
94
116
|
@properties[:cluster_id]
|
95
117
|
end
|
data/lib/bunny/queue.rb
CHANGED
@@ -256,6 +256,55 @@ module Bunny
|
|
256
256
|
end
|
257
257
|
end
|
258
258
|
|
259
|
+
# @param [Hash] opts Options
|
260
|
+
#
|
261
|
+
# @option opts [Boolean] :ack (false) Will the message be acknowledged manually?
|
262
|
+
# @option opts [Float] :timeout (1.0) Max amount of time, in seconds, to wait before raising an exception
|
263
|
+
#
|
264
|
+
# @return [Array] Triple of delivery info, message properties and message content.
|
265
|
+
# If the queue is empty, all three will be nils.
|
266
|
+
#
|
267
|
+
# @raises [Bunny::ClientException] When no message is available in the amount of time provided via the :timeout option
|
268
|
+
#
|
269
|
+
# @see http://rubybunny.info/articles/queues.html Queues and Consumers guide
|
270
|
+
# @see Bunny::Queue#subscribe
|
271
|
+
# @api public
|
272
|
+
#
|
273
|
+
# @example
|
274
|
+
# conn = Bunny.new
|
275
|
+
# conn.start
|
276
|
+
#
|
277
|
+
# ch = conn.create_channel
|
278
|
+
# q = ch.queue("test1")
|
279
|
+
# x = ch.default_exchange
|
280
|
+
# x.publish("Hello, everybody!", :routing_key => 'test1')
|
281
|
+
#
|
282
|
+
# delivery_info, properties, payload = q.pop_waiting(:timeout => 0.5)
|
283
|
+
#
|
284
|
+
# puts "This is the message: " + payload + "\n\n"
|
285
|
+
# conn.close
|
286
|
+
def pop_waiting(opts = {:ack => false, :timeout => 1.0}, &block)
|
287
|
+
delivery_info, properties, content = nil, nil, nil
|
288
|
+
|
289
|
+
Bunny::Timer.timeout(opts[:timeout], ClientTimeout) do
|
290
|
+
loop do
|
291
|
+
delivery_info, properties, content = @channel.basic_get(@name, opts)
|
292
|
+
|
293
|
+
if !content.nil?
|
294
|
+
break
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
if block
|
300
|
+
block.call(delivery_info, properties, content)
|
301
|
+
else
|
302
|
+
[delivery_info, properties, content]
|
303
|
+
end
|
304
|
+
end
|
305
|
+
alias get_waiting pop_waiting
|
306
|
+
|
307
|
+
|
259
308
|
# Publishes a message to the queue via default exchange. Takes the same arguments
|
260
309
|
# as {Bunny::Exchange#publish}
|
261
310
|
#
|
data/lib/bunny/reader_loop.rb
CHANGED
data/lib/bunny/return_info.rb
CHANGED
@@ -24,38 +24,49 @@ module Bunny
|
|
24
24
|
}
|
25
25
|
end
|
26
26
|
|
27
|
+
# Iterates over the returned delivery properties
|
28
|
+
# @see Enumerable#each
|
27
29
|
def each(*args, &block)
|
28
30
|
@hash.each(*args, &block)
|
29
31
|
end
|
30
32
|
|
33
|
+
# Accesses returned delivery properties by key
|
34
|
+
# @see Hash#[]
|
31
35
|
def [](k)
|
32
36
|
@hash[k]
|
33
37
|
end
|
34
38
|
|
39
|
+
# @return [Hash] Hash representation of this returned delivery info
|
35
40
|
def to_hash
|
36
41
|
@hash
|
37
42
|
end
|
38
43
|
|
44
|
+
# @private
|
39
45
|
def to_s
|
40
46
|
to_hash.to_s
|
41
47
|
end
|
42
48
|
|
49
|
+
# @private
|
43
50
|
def inspect
|
44
51
|
to_hash.inspect
|
45
52
|
end
|
46
53
|
|
54
|
+
# @return [Integer] Reply (status) code of the cause
|
47
55
|
def reply_code
|
48
56
|
@basic_return.reply_code
|
49
57
|
end
|
50
58
|
|
59
|
+
# @return [Integer] Reply (status) text of the cause, explaining why the message was returned
|
51
60
|
def reply_text
|
52
61
|
@basic_return.reply_text
|
53
62
|
end
|
54
63
|
|
64
|
+
# @return [String] Exchange the message was published to
|
55
65
|
def exchange
|
56
66
|
@basic_return.exchange
|
57
67
|
end
|
58
68
|
|
69
|
+
# @return [String] Routing key the message has
|
59
70
|
def routing_key
|
60
71
|
@basic_return.routing_key
|
61
72
|
end
|
data/lib/bunny/session.rb
CHANGED
@@ -54,8 +54,10 @@ module Bunny
|
|
54
54
|
:information => "http://rubybunny.info",
|
55
55
|
}
|
56
56
|
|
57
|
+
# @private
|
57
58
|
DEFAULT_LOCALE = "en_GB"
|
58
59
|
|
60
|
+
# Default reconnection interval for TCP connection failures
|
59
61
|
DEFAULT_NETWORK_RECOVERY_INTERVAL = 5.0
|
60
62
|
|
61
63
|
|
@@ -63,6 +65,8 @@ module Bunny
|
|
63
65
|
# API
|
64
66
|
#
|
65
67
|
|
68
|
+
# @return [Bunny::Transport]
|
69
|
+
attr_reader :transport
|
66
70
|
attr_reader :status, :host, :port, :heartbeat, :user, :pass, :vhost, :frame_max, :threaded
|
67
71
|
attr_reader :server_capabilities, :server_properties, :server_authentication_mechanisms, :server_locales
|
68
72
|
attr_reader :default_channel
|
@@ -138,6 +142,8 @@ module Bunny
|
|
138
142
|
@channels = Hash.new
|
139
143
|
|
140
144
|
self.reset_continuations
|
145
|
+
|
146
|
+
self.initialize_transport
|
141
147
|
end
|
142
148
|
|
143
149
|
# @return [String] RabbitMQ hostname (or IP address) used
|
@@ -169,7 +175,20 @@ module Bunny
|
|
169
175
|
@threaded
|
170
176
|
end
|
171
177
|
|
172
|
-
|
178
|
+
def configure_socket(&block)
|
179
|
+
raise ArgumentError, "No block provided!" if block.nil?
|
180
|
+
|
181
|
+
if @transport
|
182
|
+
@transport.configure_socket(&block)
|
183
|
+
else
|
184
|
+
@socket_configurator = block
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Starts the connection process.
|
189
|
+
#
|
190
|
+
# @see http://rubybunny.info/articles/getting_started.html
|
191
|
+
# @see http://rubybunny.info/articles/connecting.html
|
173
192
|
# @api public
|
174
193
|
def start
|
175
194
|
return self if connected?
|
@@ -183,6 +202,13 @@ module Bunny
|
|
183
202
|
self.maybe_close_transport
|
184
203
|
self.initialize_transport
|
185
204
|
|
205
|
+
@transport.initialize_socket
|
206
|
+
@transport.connect
|
207
|
+
|
208
|
+
if @socket_configurator
|
209
|
+
@transport.configure_socket(&@socket_configurator)
|
210
|
+
end
|
211
|
+
|
186
212
|
self.init_connection
|
187
213
|
self.open_connection
|
188
214
|
|
@@ -198,6 +224,9 @@ module Bunny
|
|
198
224
|
self
|
199
225
|
end
|
200
226
|
|
227
|
+
# Socket operation timeout used by this connection
|
228
|
+
# @return [Integer]
|
229
|
+
# @private
|
201
230
|
def read_write_timeout
|
202
231
|
@transport.read_write_timeout
|
203
232
|
end
|
@@ -397,6 +426,7 @@ module Bunny
|
|
397
426
|
raise @last_connection_error if @last_connection_error
|
398
427
|
end
|
399
428
|
|
429
|
+
# @private
|
400
430
|
def handle_frameset(ch_number, frames)
|
401
431
|
method = frames.first
|
402
432
|
|
@@ -486,11 +516,15 @@ module Bunny
|
|
486
516
|
when 501 then
|
487
517
|
FrameError
|
488
518
|
when 503 then
|
489
|
-
|
519
|
+
CommandInvalid
|
490
520
|
when 504 then
|
491
521
|
ChannelError
|
492
522
|
when 505 then
|
493
523
|
UnexpectedFrame
|
524
|
+
when 506 then
|
525
|
+
ResourceError
|
526
|
+
when 541 then
|
527
|
+
InternalError
|
494
528
|
else
|
495
529
|
raise "Unknown reply code: #{frame.reply_code}, text: #{frame.reply_text}"
|
496
530
|
end
|
@@ -615,7 +649,7 @@ module Bunny
|
|
615
649
|
# Sends multiple frames, one by one. For thread safety this method takes a channel
|
616
650
|
# object and synchronizes on it.
|
617
651
|
#
|
618
|
-
# @
|
652
|
+
# @private
|
619
653
|
def send_frameset(frames, channel)
|
620
654
|
# some developers end up sharing channels between threads and when multiple
|
621
655
|
# threads publish on the same channel aggressively, at some point frames will be
|
@@ -632,7 +666,7 @@ module Bunny
|
|
632
666
|
# object and synchronizes on it. Uses transport implementation that does not perform
|
633
667
|
# timeout control.
|
634
668
|
#
|
635
|
-
# @
|
669
|
+
# @private
|
636
670
|
def send_frameset_without_timeout(frames, channel)
|
637
671
|
# some developers end up sharing channels between threads and when multiple
|
638
672
|
# threads publish on the same channel aggressively, at some point frames will be
|
@@ -653,7 +687,7 @@ module Bunny
|
|
653
687
|
|
654
688
|
protected
|
655
689
|
|
656
|
-
# @
|
690
|
+
# @private
|
657
691
|
def init_connection
|
658
692
|
self.send_preamble
|
659
693
|
|
@@ -668,7 +702,7 @@ module Bunny
|
|
668
702
|
@status = :connected
|
669
703
|
end
|
670
704
|
|
671
|
-
# @
|
705
|
+
# @private
|
672
706
|
def open_connection
|
673
707
|
@transport.send_frame(AMQ::Protocol::Connection::StartOk.encode(@client_properties, @mechanism, self.encode_credentials(username, password), @locale))
|
674
708
|
@logger.debug "Sent connection.start-ok"
|
@@ -732,7 +766,7 @@ module Bunny
|
|
732
766
|
0 == val || val.nil?
|
733
767
|
end
|
734
768
|
|
735
|
-
# @
|
769
|
+
# @private
|
736
770
|
def negotiate_value(client_value, server_value)
|
737
771
|
return server_value if client_value == :server
|
738
772
|
|
@@ -743,59 +777,59 @@ module Bunny
|
|
743
777
|
end
|
744
778
|
end
|
745
779
|
|
746
|
-
# @
|
780
|
+
# @private
|
747
781
|
def initialize_heartbeat_sender
|
748
782
|
@logger.debug "Initializing heartbeat sender..."
|
749
783
|
@heartbeat_sender = HeartbeatSender.new(@transport, @logger)
|
750
784
|
@heartbeat_sender.start(@heartbeat)
|
751
785
|
end
|
752
786
|
|
753
|
-
# @
|
787
|
+
# @private
|
754
788
|
def maybe_shutdown_heartbeat_sender
|
755
789
|
@heartbeat_sender.stop if @heartbeat_sender
|
756
790
|
end
|
757
791
|
|
758
|
-
# @
|
792
|
+
# @private
|
759
793
|
def initialize_transport
|
760
794
|
@transport = Transport.new(self, @host, @port, @opts.merge(:session_thread => Thread.current))
|
761
795
|
end
|
762
796
|
|
763
|
-
# @
|
797
|
+
# @private
|
764
798
|
def maybe_close_transport
|
765
799
|
@transport.close if @transport
|
766
800
|
end
|
767
801
|
|
768
802
|
# Sends AMQ protocol header (also known as preamble).
|
769
|
-
# @
|
803
|
+
# @private
|
770
804
|
def send_preamble
|
771
805
|
@transport.write(AMQ::Protocol::PREAMBLE)
|
772
806
|
@logger.debug "Sent protocol preamble"
|
773
807
|
end
|
774
808
|
|
775
809
|
|
776
|
-
# @
|
810
|
+
# @private
|
777
811
|
def encode_credentials(username, password)
|
778
812
|
@credentials_encoder.encode_credentials(username, password)
|
779
813
|
end # encode_credentials(username, password)
|
780
814
|
|
781
|
-
# @
|
815
|
+
# @private
|
782
816
|
def credentials_encoder_for(mechanism)
|
783
817
|
Authentication::CredentialsEncoder.for_session(self)
|
784
818
|
end
|
785
819
|
|
786
820
|
if defined?(JRUBY_VERSION)
|
787
|
-
# @
|
821
|
+
# @private
|
788
822
|
def reset_continuations
|
789
823
|
@continuations = Concurrent::LinkedContinuationQueue.new
|
790
824
|
end
|
791
825
|
else
|
792
|
-
# @
|
826
|
+
# @private
|
793
827
|
def reset_continuations
|
794
828
|
@continuations = Concurrent::ContinuationQueue.new
|
795
829
|
end
|
796
830
|
end
|
797
831
|
|
798
|
-
# @
|
832
|
+
# @private
|
799
833
|
def wait_on_continuations
|
800
834
|
unless @threaded
|
801
835
|
reader_loop.run_once until @continuations.length > 0
|
@@ -804,7 +838,7 @@ module Bunny
|
|
804
838
|
@continuations.poll(@continuation_timeout)
|
805
839
|
end
|
806
840
|
|
807
|
-
# @
|
841
|
+
# @private
|
808
842
|
def init_logger(level)
|
809
843
|
@logger = ::Logger.new(@logfile)
|
810
844
|
@logger.level = normalize_log_level(level)
|
@@ -813,7 +847,7 @@ module Bunny
|
|
813
847
|
@logger
|
814
848
|
end
|
815
849
|
|
816
|
-
# @
|
850
|
+
# @private
|
817
851
|
def normalize_log_level(level)
|
818
852
|
case level
|
819
853
|
when :debug, Logger::DEBUG, "debug" then Logger::DEBUG
|