bunny 0.6.3.rc2 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
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