bunny 0.6.3.rc2 → 0.7

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 (68) hide show
  1. data/.gitignore +8 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +15 -0
  4. data/.yardopts +9 -0
  5. data/CHANGELOG +3 -0
  6. data/Gemfile +39 -0
  7. data/Gemfile.lock +34 -0
  8. data/LICENSE +5 -4
  9. data/README.textile +54 -0
  10. data/Rakefile +15 -13
  11. data/bunny.gemspec +42 -61
  12. data/examples/simple_08.rb +4 -2
  13. data/examples/simple_09.rb +4 -2
  14. data/examples/simple_ack_08.rb +3 -1
  15. data/examples/simple_ack_09.rb +3 -1
  16. data/examples/simple_consumer_08.rb +4 -2
  17. data/examples/simple_consumer_09.rb +4 -2
  18. data/examples/simple_fanout_08.rb +3 -1
  19. data/examples/simple_fanout_09.rb +3 -1
  20. data/examples/simple_headers_08.rb +5 -3
  21. data/examples/simple_headers_09.rb +5 -3
  22. data/examples/simple_publisher_08.rb +3 -1
  23. data/examples/simple_publisher_09.rb +3 -1
  24. data/examples/simple_topic_08.rb +5 -3
  25. data/examples/simple_topic_09.rb +5 -3
  26. data/ext/amqp-0.8.json +616 -0
  27. data/ext/amqp-0.9.1.json +388 -0
  28. data/ext/config.yml +4 -0
  29. data/ext/qparser.rb +463 -0
  30. data/lib/bunny.rb +88 -66
  31. data/lib/bunny/channel08.rb +38 -38
  32. data/lib/bunny/channel09.rb +37 -37
  33. data/lib/bunny/client08.rb +184 -206
  34. data/lib/bunny/client09.rb +277 -363
  35. data/lib/bunny/consumer.rb +35 -0
  36. data/lib/bunny/exchange08.rb +37 -41
  37. data/lib/bunny/exchange09.rb +106 -124
  38. data/lib/bunny/queue08.rb +216 -202
  39. data/lib/bunny/queue09.rb +256 -326
  40. data/lib/bunny/subscription08.rb +30 -29
  41. data/lib/bunny/subscription09.rb +84 -83
  42. data/lib/bunny/version.rb +5 -0
  43. data/lib/qrack/amq-client-url.rb +165 -0
  44. data/lib/qrack/channel.rb +19 -17
  45. data/lib/qrack/client.rb +152 -151
  46. data/lib/qrack/errors.rb +5 -0
  47. data/lib/qrack/protocol/protocol08.rb +132 -130
  48. data/lib/qrack/protocol/protocol09.rb +133 -131
  49. data/lib/qrack/protocol/spec08.rb +2 -0
  50. data/lib/qrack/protocol/spec09.rb +2 -0
  51. data/lib/qrack/qrack08.rb +7 -10
  52. data/lib/qrack/qrack09.rb +7 -10
  53. data/lib/qrack/queue.rb +27 -40
  54. data/lib/qrack/subscription.rb +102 -101
  55. data/lib/qrack/transport/buffer08.rb +266 -264
  56. data/lib/qrack/transport/buffer09.rb +268 -264
  57. data/lib/qrack/transport/frame08.rb +13 -11
  58. data/lib/qrack/transport/frame09.rb +9 -7
  59. data/spec/spec_08/bunny_spec.rb +48 -45
  60. data/spec/spec_08/connection_spec.rb +10 -7
  61. data/spec/spec_08/exchange_spec.rb +145 -143
  62. data/spec/spec_08/queue_spec.rb +161 -161
  63. data/spec/spec_09/bunny_spec.rb +46 -44
  64. data/spec/spec_09/connection_spec.rb +15 -8
  65. data/spec/spec_09/exchange_spec.rb +147 -145
  66. data/spec/spec_09/queue_spec.rb +182 -184
  67. metadata +60 -41
  68. data/README.rdoc +0 -66
@@ -1,166 +1,161 @@
1
+ # encoding: utf-8
2
+
3
+ require "qrack/amq-client-url"
4
+
1
5
  module Qrack
2
-
3
- class ClientTimeout < Timeout::Error; end
6
+
7
+ class ClientTimeout < Timeout::Error; end
4
8
  class ConnectionTimeout < Timeout::Error; end
5
-
6
- # Client ancestor class
7
- class Client
8
-
9
- CONNECT_TIMEOUT = 5.0
9
+
10
+ # Client ancestor class
11
+ class Client
12
+
13
+ CONNECT_TIMEOUT = 5.0
10
14
  RETRY_DELAY = 10.0
11
15
 
12
16
  attr_reader :status, :host, :vhost, :port, :logging, :spec, :heartbeat
13
- attr_accessor :channel, :logfile, :exchanges, :queues, :channels, :message_in, :message_out,
14
- :connecting
17
+ attr_accessor :channel, :logfile, :exchanges, :queues, :channels, :message_in, :message_out, :connecting
18
+
15
19
 
16
- def initialize(opts = {})
17
- @host = opts[:host] || 'localhost'
20
+ def initialize(connection_string_or_opts = Hash.new, opts = Hash.new)
21
+ opts = case connection_string_or_opts
22
+ when String then
23
+ AMQ::Client::Settings.parse_amqp_url(connection_string_or_opts)
24
+ when Hash then
25
+ connection_string_or_opts
26
+ else
27
+ Hash.new
28
+ end.merge(opts)
29
+
30
+ @host = opts[:host] || 'localhost'
18
31
  @user = opts[:user] || 'guest'
19
32
  @pass = opts[:pass] || 'guest'
20
33
  @vhost = opts[:vhost] || '/'
21
- @logfile = opts[:logfile] || nil
22
- @logging = opts[:logging] || false
23
- @ssl = opts[:ssl] || false
34
+ @logfile = opts[:logfile] || nil
35
+ @logging = opts[:logging] || false
36
+ @ssl = opts[:ssl] || false
24
37
  @verify_ssl = opts[:verify_ssl].nil? || opts[:verify_ssl]
25
38
  @status = :not_connected
26
- @frame_max = opts[:frame_max] || 131072
27
- @channel_max = opts[:channel_max] || 0
28
- @heartbeat = opts[:heartbeat] || 0
39
+ @frame_max = opts[:frame_max] || 131072
40
+ @channel_max = opts[:channel_max] || 0
41
+ @heartbeat = opts[:heartbeat] || 0
29
42
  @connect_timeout = opts[:connect_timeout] || CONNECT_TIMEOUT
30
- @logger = nil
31
- create_logger if @logging
32
- @message_in = false
33
- @message_out = false
34
- @connecting = false
35
- @channels ||= []
36
- # Create channel 0
43
+ @read_write_timeout = opts[:socket_timeout]
44
+ @read_write_timeout = nil if @read_write_timeout == 0
45
+ @disconnect_timeout = @read_write_timeout || @connect_timeout
46
+ @logger = nil
47
+ create_logger if @logging
48
+ @message_in = false
49
+ @message_out = false
50
+ @connecting = false
51
+ @channels ||= []
52
+ # Create channel 0
37
53
  @channel = create_channel()
38
- @exchanges ||= {}
39
- @queues ||= {}
40
- end
41
-
42
- =begin rdoc
43
-
44
- === DESCRIPTION:
45
-
46
- Closes all active communication channels and connection. If an error occurs a
47
- _Bunny_::_ProtocolError_ is raised. If successful, _Client_._status_ is set to <tt>:not_connected</tt>.
48
-
49
- ==== RETURNS:
50
-
51
- <tt>:not_connected</tt> if successful.
52
-
53
- =end
54
-
55
- def close
56
- # Close all active channels
57
- channels.each do |c|
58
- c.close if c.open?
59
- end
60
-
61
- # Close connection to AMQP server
62
- close_connection
63
-
64
- # Clear the channels
65
- @channels = []
66
-
67
- # Create channel 0
68
- @channel = create_channel()
69
-
70
- # Close TCP Socket
71
- close_socket
72
- end
73
-
74
- alias stop close
75
-
76
- def connected?
77
- status == :connected
78
- end
79
-
80
- def connecting?
81
- connecting
82
- end
83
-
84
- def logging=(bool)
85
- @logging = bool
86
- create_logger if @logging
87
- end
88
-
54
+ @exchanges ||= {}
55
+ @queues ||= {}
56
+ end
57
+
58
+
59
+ # Closes all active communication channels and connection. If an error occurs a @Bunny::ProtocolError@ is raised. If successful, @Client.status@ is set to @:not_connected@.
60
+
61
+ # @return [Symbol] @:not_connected@ if successful.
62
+ def close
63
+ return if @socket.nil? || @socket.closed?
64
+
65
+ # Close all active channels
66
+ channels.each do |c|
67
+ Bunny::Timer::timeout(@disconnect_timeout) { c.close } if c.open?
68
+ end
69
+
70
+ # Close connection to AMQP server
71
+ Bunny::Timer::timeout(@disconnect_timeout) { close_connection }
72
+
73
+ rescue Exception
74
+ # http://cheezburger.com/Asset/View/4033311488
75
+ ensure
76
+ # Clear the channels
77
+ @channels = []
78
+
79
+ # Create channel 0
80
+ @channel = create_channel()
81
+
82
+ # Close TCP Socket
83
+ close_socket
84
+ end
85
+
86
+ alias stop close
87
+
88
+ def connected?
89
+ status == :connected
90
+ end
91
+
92
+ def connecting?
93
+ connecting
94
+ end
95
+
96
+ def logging=(bool)
97
+ @logging = bool
98
+ create_logger if @logging
99
+ end
100
+
89
101
  def next_payload(options = {})
90
- next_frame(options).payload
102
+ res = next_frame(options)
103
+ res.payload if res
91
104
  end
92
105
 
93
- alias next_method next_payload
106
+ alias next_method next_payload
94
107
 
95
108
  def read(*args)
96
- begin
97
- send_command(:read, *args)
109
+ send_command(:read, *args)
98
110
  # Got a SIGINT while waiting; give any traps a chance to run
99
- rescue Errno::EINTR
100
- retry
111
+ rescue Errno::EINTR
112
+ retry
113
+ end
114
+
115
+ # Checks to see whether or not an undeliverable message has been returned as a result of a publish
116
+ # with the <tt>:immediate</tt> or <tt>:mandatory</tt> options.
117
+
118
+ # @param [Hash] opts Options.
119
+ # @option opts [Numeric] :timeout (0.1) The method will wait for a return message until this timeout interval is reached.
120
+ # @return [Hash] @{:header => nil, :payload => :no_return, :return_details => nil}@ if message is not returned before timeout. @{:header, :return_details, :payload}@ if message is returned. @:return_details@ is a hash @{:reply_code, :reply_text, :exchange, :routing_key}@.
121
+ def returned_message(opts = {})
122
+
123
+ begin
124
+ frame = next_frame(:timeout => opts[:timeout] || 0.1)
125
+ rescue Qrack::ClientTimeout
126
+ return {:header => nil, :payload => :no_return, :return_details => nil}
127
+ end
128
+
129
+ method = frame.payload
130
+ header = next_payload
131
+
132
+ # If maximum frame size is smaller than message payload body then message
133
+ # will have a message header and several message bodies
134
+ msg = ''
135
+ while msg.length < header.size
136
+ msg << next_payload
137
+ end
138
+
139
+ # Return the message and related info
140
+ {:header => header, :payload => msg, :return_details => method.arguments}
141
+ end
142
+
143
+ def switch_channel(chann)
144
+ if (0...channels.size).include? chann
145
+ @channel = channels[chann]
146
+ chann
147
+ else
148
+ raise RuntimeError, "Invalid channel number - #{chann}"
101
149
  end
102
-
103
150
  end
104
151
 
105
- =begin rdoc
106
-
107
- === DESCRIPTION:
108
-
109
- Checks to see whether or not an undeliverable message has been returned as a result of a publish
110
- with the <tt>:immediate</tt> or <tt>:mandatory</tt> options.
111
-
112
- ==== OPTIONS:
113
-
114
- * <tt>:timeout => number of seconds (default = 0.1) - The method will wait for a return
115
- message until this timeout interval is reached.
116
-
117
- ==== RETURNS:
118
-
119
- <tt>{:header => nil, :payload => :no_return, :return_details => nil}</tt> if message is
120
- not returned before timeout.
121
- <tt>{:header, :return_details, :payload}</tt> if message is returned. <tt>:return_details</tt> is
122
- a hash <tt>{:reply_code, :reply_text, :exchange, :routing_key}</tt>.
123
-
124
- =end
125
-
126
- def returned_message(opts = {})
127
-
128
- begin
129
- frame = next_frame(:timeout => opts[:timeout] || 0.1)
130
- rescue Qrack::ClientTimeout
131
- return {:header => nil, :payload => :no_return, :return_details => nil}
132
- end
133
-
134
- method = frame.payload
135
- header = next_payload
136
-
137
- # If maximum frame size is smaller than message payload body then message
138
- # will have a message header and several message bodies
139
- msg = ''
140
- while msg.length < header.size
141
- msg += next_payload
142
- end
143
-
144
- # Return the message and related info
145
- {:header => header, :payload => msg, :return_details => method.arguments}
146
- end
147
-
148
- def switch_channel(chann)
149
- if (0...channels.size).include? chann
150
- @channel = channels[chann]
151
- chann
152
- else
153
- raise RuntimeError, "Invalid channel number - #{chann}"
154
- end
155
- end
156
-
157
- def write(*args)
152
+ def write(*args)
158
153
  send_command(:write, *args)
159
154
  end
160
-
161
- private
162
-
163
- def close_socket(reason=nil)
155
+
156
+ private
157
+
158
+ def close_socket(reason=nil)
164
159
  # Close the socket. The server is not considered dead.
165
160
  @socket.close if @socket and not @socket.closed?
166
161
  @socket = nil
@@ -168,16 +163,22 @@ a hash <tt>{:reply_code, :reply_text, :exchange, :routing_key}</tt>.
168
163
  end
169
164
 
170
165
  def create_logger
171
- @logfile ? @logger = Logger.new("#{logfile}") : @logger = Logger.new(STDOUT)
172
- @logger.level = Logger::INFO
173
- @logger.datetime_format = "%Y-%m-%d %H:%M:%S"
166
+ @logfile ? @logger = Logger.new("#{logfile}") : @logger = Logger.new(STDOUT)
167
+ @logger.level = Logger::INFO
168
+ @logger.datetime_format = "%Y-%m-%d %H:%M:%S"
174
169
  end
175
-
176
- def send_command(cmd, *args)
170
+
171
+ def send_command(cmd, *args)
177
172
  begin
178
- raise Bunny::ConnectionError, 'No connection - socket has not been created' if !@socket
179
- @socket.__send__(cmd, *args)
180
- rescue Errno::EPIPE, IOError => e
173
+ raise Bunny::ConnectionError, 'No connection - socket has not been created' if !@socket
174
+ if @read_write_timeout
175
+ Bunny::Timer::timeout(@read_write_timeout, Qrack::ClientTimeout) do
176
+ @socket.__send__(cmd, *args)
177
+ end
178
+ else
179
+ @socket.__send__(cmd, *args)
180
+ end
181
+ rescue Errno::EPIPE, Errno::EAGAIN, Qrack::ClientTimeout, IOError => e
181
182
  # Ensure we close the socket when we are down to prevent further
182
183
  # attempts to write to a closed socket
183
184
  close_socket
@@ -190,7 +191,7 @@ a hash <tt>{:reply_code, :reply_text, :exchange, :routing_key}</tt>.
190
191
 
191
192
  begin
192
193
  # Attempt to connect.
193
- @socket = timeout(@connect_timeout, ConnectionTimeout) do
194
+ @socket = Bunny::Timer::timeout(@connect_timeout, ConnectionTimeout) do
194
195
  TCPSocket.new(host, port)
195
196
  end
196
197
 
@@ -213,7 +214,7 @@ a hash <tt>{:reply_code, :reply_text, :exchange, :routing_key}</tt>.
213
214
 
214
215
  @socket
215
216
  end
216
-
217
- end
218
-
217
+
218
+ end
219
+
219
220
  end
@@ -0,0 +1,5 @@
1
+ module Qrack
2
+ # Errors
3
+ class BufferOverflowError < StandardError; end
4
+ class InvalidTypeError < StandardError; end
5
+ end
@@ -1,132 +1,134 @@
1
+ # encoding: utf-8
2
+
1
3
  module Qrack
2
- module Protocol
3
- #:stopdoc:
4
- class Class::Method
5
- def initialize *args
6
- opts = args.pop if args.last.is_a? Hash
7
- opts ||= {}
8
-
9
- if args.size == 1 and args.first.is_a? Transport::Buffer
10
- buf = args.shift
11
- else
12
- buf = nil
13
- end
14
-
15
- self.class.arguments.each do |type, name|
16
- val = buf ? buf.read(type) :
17
- args.shift || opts[name] || opts[name.to_s]
18
- instance_variable_set("@#{name}", val)
19
- end
20
- end
21
-
22
- def arguments
23
- self.class.arguments.inject({}) do |hash, (type, name)|
24
- hash.update name => instance_variable_get("@#{name}")
25
- end
26
- end
27
-
28
- def to_binary
29
- buf = Transport::Buffer.new
30
- buf.write :short, self.class.parent.id
31
- buf.write :short, self.class.id
32
-
33
- bits = []
34
-
35
- self.class.arguments.each do |type, name|
36
- val = instance_variable_get("@#{name}")
37
- if type == :bit
38
- bits << (val || false)
39
- else
40
- unless bits.empty?
41
- buf.write :bit, bits
42
- bits = []
43
- end
44
- buf.write type, val
45
- end
46
- end
47
-
48
- buf.write :bit, bits unless bits.empty?
49
- buf.rewind
50
-
51
- buf
52
- end
53
-
54
- def to_s
55
- to_binary.to_s
56
- end
57
-
58
- def to_frame channel = 0
59
- Transport::Method.new(self, channel)
60
- end
61
- end
62
-
63
- class Header
64
- def initialize *args
65
- opts = args.pop if args.last.is_a? Hash
66
- opts ||= {}
67
-
68
- first = args.shift
69
-
70
- if first.is_a? ::Class and first.ancestors.include? Protocol::Class
71
- @klass = first
72
- @size = args.shift || 0
73
- @weight = args.shift || 0
74
- @properties = opts
75
-
76
- elsif first.is_a? Transport::Buffer or first.is_a? String
77
- buf = first
78
- buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
79
-
80
- @klass = Protocol.classes[buf.read(:short)]
81
- @weight = buf.read(:short)
82
- @size = buf.read(:longlong)
83
-
84
- props = buf.read(:properties, *klass.properties.map{|type,_| type })
85
- @properties = Hash[*klass.properties.map{|_,name| name }.zip(props).reject{|k,v| v.nil? }.flatten]
86
-
87
- else
88
- raise ArgumentError, 'Invalid argument'
89
- end
90
-
91
- end
92
- attr_accessor :klass, :size, :weight, :properties
93
-
94
- def to_binary
95
- buf = Transport::Buffer.new
96
- buf.write :short, klass.id
97
- buf.write :short, weight # XXX rabbitmq only supports weight == 0
98
- buf.write :longlong, size
99
- buf.write :properties, (klass.properties.map do |type, name|
100
- [ type, properties[name] || properties[name.to_s] ]
101
- end)
102
- buf.rewind
103
- buf
104
- end
105
-
106
- def to_s
107
- to_binary.to_s
108
- end
109
-
110
- def to_frame channel = 0
111
- Transport::Header.new(self, channel)
112
- end
113
-
114
- def == header
115
- [ :klass, :size, :weight, :properties ].inject(true) do |eql, field|
116
- eql and __send__(field) == header.__send__(field)
117
- end
118
- end
119
-
120
- def method_missing meth, *args, &blk
121
- @properties.has_key?(meth) || @klass.properties.find{|_,name| name == meth } ? @properties[meth] : super
122
- end
123
- end
124
-
125
- def self.parse buf
126
- buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
127
- class_id, method_id = buf.read(:short, :short)
128
- classes[class_id].methods[method_id].new(buf)
129
- end
130
-
131
- end
4
+ module Protocol
5
+ #:stopdoc:
6
+ class Class::Method
7
+ def initialize *args
8
+ opts = args.pop if args.last.is_a? Hash
9
+ opts ||= {}
10
+
11
+ if args.size == 1 and args.first.is_a? Transport::Buffer
12
+ buf = args.shift
13
+ else
14
+ buf = nil
15
+ end
16
+
17
+ self.class.arguments.each do |type, name|
18
+ val = buf ? buf.read(type) :
19
+ args.shift || opts[name] || opts[name.to_s]
20
+ instance_variable_set("@#{name}", val)
21
+ end
22
+ end
23
+
24
+ def arguments
25
+ self.class.arguments.inject({}) do |hash, (type, name)|
26
+ hash.update name => instance_variable_get("@#{name}")
27
+ end
28
+ end
29
+
30
+ def to_binary
31
+ buf = Transport::Buffer.new
32
+ buf.write :short, self.class.parent.id
33
+ buf.write :short, self.class.id
34
+
35
+ bits = []
36
+
37
+ self.class.arguments.each do |type, name|
38
+ val = instance_variable_get("@#{name}")
39
+ if type == :bit
40
+ bits << (val || false)
41
+ else
42
+ unless bits.empty?
43
+ buf.write :bit, bits
44
+ bits = []
45
+ end
46
+ buf.write type, val
47
+ end
48
+ end
49
+
50
+ buf.write :bit, bits unless bits.empty?
51
+ buf.rewind
52
+
53
+ buf
54
+ end
55
+
56
+ def to_s
57
+ to_binary.to_s
58
+ end
59
+
60
+ def to_frame channel = 0
61
+ Transport::Method.new(self, channel)
62
+ end
63
+ end
64
+
65
+ class Header
66
+ def initialize *args
67
+ opts = args.pop if args.last.is_a? Hash
68
+ opts ||= {}
69
+
70
+ first = args.shift
71
+
72
+ if first.is_a? ::Class and first.ancestors.include? Protocol::Class
73
+ @klass = first
74
+ @size = args.shift || 0
75
+ @weight = args.shift || 0
76
+ @properties = opts
77
+
78
+ elsif first.is_a? Transport::Buffer or first.is_a? String
79
+ buf = first
80
+ buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
81
+
82
+ @klass = Protocol.classes[buf.read(:short)]
83
+ @weight = buf.read(:short)
84
+ @size = buf.read(:longlong)
85
+
86
+ props = buf.read(:properties, *klass.properties.map{|type,_| type })
87
+ @properties = Hash[*klass.properties.map{|_,name| name }.zip(props).reject{|k,v| v.nil? }.flatten]
88
+
89
+ else
90
+ raise ArgumentError, 'Invalid argument'
91
+ end
92
+
93
+ end
94
+ attr_accessor :klass, :size, :weight, :properties
95
+
96
+ def to_binary
97
+ buf = Transport::Buffer.new
98
+ buf.write :short, klass.id
99
+ buf.write :short, weight # XXX rabbitmq only supports weight == 0
100
+ buf.write :longlong, size
101
+ buf.write :properties, (klass.properties.map do |type, name|
102
+ [ type, properties[name] || properties[name.to_s] ]
103
+ end)
104
+ buf.rewind
105
+ buf
106
+ end
107
+
108
+ def to_s
109
+ to_binary.to_s
110
+ end
111
+
112
+ def to_frame channel = 0
113
+ Transport::Header.new(self, channel)
114
+ end
115
+
116
+ def == header
117
+ [ :klass, :size, :weight, :properties ].inject(true) do |eql, field|
118
+ eql and __send__(field) == header.__send__(field)
119
+ end
120
+ end
121
+
122
+ def method_missing meth, *args, &blk
123
+ @properties.has_key?(meth) || @klass.properties.find{|_,name| name == meth } ? @properties[meth] : super
124
+ end
125
+ end
126
+
127
+ def self.parse buf
128
+ buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
129
+ class_id, method_id = buf.read(:short, :short)
130
+ classes[class_id].methods[method_id].new(buf)
131
+ end
132
+
133
+ end
132
134
  end