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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +23 -0
  3. data/README.md +2 -2
  4. data/lib/bunny.rb +15 -2
  5. data/lib/bunny/authentication/credentials_encoder.rb +18 -0
  6. data/lib/bunny/authentication/external_mechanism_encoder.rb +1 -0
  7. data/lib/bunny/authentication/plain_mechanism_encoder.rb +2 -0
  8. data/lib/bunny/channel.rb +9 -3
  9. data/lib/bunny/channel_id_allocator.rb +8 -0
  10. data/lib/bunny/concurrent/condition.rb +2 -0
  11. data/lib/bunny/concurrent/continuation_queue.rb +3 -0
  12. data/lib/bunny/concurrent/linked_continuation_queue.rb +3 -0
  13. data/lib/bunny/consumer.rb +2 -0
  14. data/lib/bunny/consumer_tag_generator.rb +1 -0
  15. data/lib/bunny/consumer_work_pool.rb +2 -0
  16. data/lib/bunny/delivery_info.rb +18 -2
  17. data/lib/bunny/exceptions.rb +49 -10
  18. data/lib/bunny/framing.rb +3 -0
  19. data/lib/bunny/heartbeat_sender.rb +4 -1
  20. data/lib/bunny/message_properties.rb +22 -0
  21. data/lib/bunny/queue.rb +49 -0
  22. data/lib/bunny/reader_loop.rb +1 -0
  23. data/lib/bunny/return_info.rb +11 -0
  24. data/lib/bunny/session.rb +53 -19
  25. data/lib/bunny/socket.rb +1 -0
  26. data/lib/bunny/ssl_socket.rb +29 -5
  27. data/lib/bunny/system_timer.rb +2 -0
  28. data/lib/bunny/test_kit.rb +4 -3
  29. data/lib/bunny/transport.rb +41 -25
  30. data/lib/bunny/version.rb +2 -1
  31. data/spec/compatibility/queue_declare_spec.rb +4 -0
  32. data/spec/compatibility/queue_declare_with_default_channel_spec.rb +33 -0
  33. data/spec/higher_level_api/integration/basic_get_spec.rb +35 -0
  34. data/spec/higher_level_api/integration/basic_nack_spec.rb +19 -1
  35. data/spec/higher_level_api/integration/connection_spec.rb +5 -0
  36. data/spec/higher_level_api/integration/queue_unbind_spec.rb +23 -2
  37. data/spec/higher_level_api/integration/tls_connection_spec.rb +47 -0
  38. data/spec/lower_level_api/integration/basic_cancel_spec.rb +8 -1
  39. data/spec/tls/cacert.pem +18 -0
  40. data/spec/tls/client_cert.pem +18 -0
  41. data/spec/tls/client_key.pem +27 -0
  42. data/spec/tls/server_cert.pem +18 -0
  43. data/spec/tls/server_key.pem +27 -0
  44. metadata +16 -2
@@ -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 "I/O error in the hearbeat sender: #{ioe.message}"
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
@@ -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
  #
@@ -6,6 +6,7 @@ module Bunny
6
6
  # This loop uses a separate thread internally.
7
7
  #
8
8
  # This mimics the way RabbitMQ Java is designed quite closely.
9
+ # @private
9
10
  class ReaderLoop
10
11
 
11
12
  def initialize(transport, session, session_thread)
@@ -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
@@ -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
- # Starts connection process
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
- InvalidCommand
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
- # @api private
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
- # @api private
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
- # @api private
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
- # @api private
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
- # @api private
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
- # @api private
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
- # @api private
787
+ # @private
754
788
  def maybe_shutdown_heartbeat_sender
755
789
  @heartbeat_sender.stop if @heartbeat_sender
756
790
  end
757
791
 
758
- # @api private
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
- # @api private
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
- # @api private
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
- # @api private
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
- # @api private
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
- # @api private
821
+ # @private
788
822
  def reset_continuations
789
823
  @continuations = Concurrent::LinkedContinuationQueue.new
790
824
  end
791
825
  else
792
- # @api private
826
+ # @private
793
827
  def reset_continuations
794
828
  @continuations = Concurrent::ContinuationQueue.new
795
829
  end
796
830
  end
797
831
 
798
- # @api private
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
- # @api private
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
- # @api private
850
+ # @private
817
851
  def normalize_log_level(level)
818
852
  case level
819
853
  when :debug, Logger::DEBUG, "debug" then Logger::DEBUG