bunny 0.8.0 → 0.9.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/.gitignore +7 -1
  2. data/.travis.yml +14 -4
  3. data/ChangeLog.md +72 -0
  4. data/Gemfile +17 -11
  5. data/README.md +82 -0
  6. data/bunny.gemspec +6 -13
  7. data/examples/connection/heartbeat.rb +17 -0
  8. data/lib/bunny.rb +40 -56
  9. data/lib/bunny/channel.rb +615 -19
  10. data/lib/bunny/channel_id_allocator.rb +59 -0
  11. data/lib/bunny/compatibility.rb +24 -0
  12. data/lib/bunny/concurrent/condition.rb +63 -0
  13. data/lib/bunny/consumer.rb +42 -26
  14. data/lib/bunny/consumer_tag_generator.rb +22 -0
  15. data/lib/bunny/consumer_work_pool.rb +67 -0
  16. data/lib/bunny/exceptions.rb +128 -0
  17. data/lib/bunny/exchange.rb +131 -136
  18. data/lib/bunny/framing.rb +53 -0
  19. data/lib/bunny/heartbeat_sender.rb +59 -0
  20. data/lib/bunny/main_loop.rb +70 -0
  21. data/lib/bunny/message_metadata.rb +126 -0
  22. data/lib/bunny/queue.rb +102 -275
  23. data/lib/bunny/session.rb +478 -0
  24. data/lib/bunny/socket.rb +44 -0
  25. data/lib/bunny/system_timer.rb +9 -9
  26. data/lib/bunny/transport.rb +179 -0
  27. data/lib/bunny/version.rb +1 -1
  28. data/spec/compatibility/queue_declare_spec.rb +40 -0
  29. data/spec/higher_level_api/integration/basic_ack_spec.rb +54 -0
  30. data/spec/higher_level_api/integration/basic_consume_spec.rb +51 -0
  31. data/spec/higher_level_api/integration/basic_get_spec.rb +47 -0
  32. data/spec/higher_level_api/integration/basic_nack_spec.rb +39 -0
  33. data/spec/higher_level_api/integration/basic_publish_spec.rb +105 -0
  34. data/spec/higher_level_api/integration/basic_qos_spec.rb +32 -0
  35. data/spec/higher_level_api/integration/basic_recover_spec.rb +18 -0
  36. data/spec/higher_level_api/integration/basic_reject_spec.rb +53 -0
  37. data/spec/higher_level_api/integration/basic_return_spec.rb +33 -0
  38. data/spec/higher_level_api/integration/channel_close_spec.rb +29 -0
  39. data/spec/higher_level_api/integration/channel_flow_spec.rb +24 -0
  40. data/spec/higher_level_api/integration/channel_open_spec.rb +57 -0
  41. data/spec/higher_level_api/integration/channel_open_stress_spec.rb +22 -0
  42. data/spec/higher_level_api/integration/confirm_select_spec.rb +19 -0
  43. data/spec/higher_level_api/integration/connection_spec.rb +340 -0
  44. data/spec/higher_level_api/integration/exchange_bind_spec.rb +31 -0
  45. data/spec/higher_level_api/integration/exchange_declare_spec.rb +183 -0
  46. data/spec/higher_level_api/integration/exchange_delete_spec.rb +37 -0
  47. data/spec/higher_level_api/integration/exchange_unbind_spec.rb +40 -0
  48. data/spec/higher_level_api/integration/queue_bind_spec.rb +109 -0
  49. data/spec/higher_level_api/integration/queue_declare_spec.rb +129 -0
  50. data/spec/higher_level_api/integration/queue_delete_spec.rb +38 -0
  51. data/spec/higher_level_api/integration/queue_purge_spec.rb +30 -0
  52. data/spec/higher_level_api/integration/queue_unbind_spec.rb +33 -0
  53. data/spec/higher_level_api/integration/tx_commit_spec.rb +21 -0
  54. data/spec/higher_level_api/integration/tx_rollback_spec.rb +21 -0
  55. data/spec/lower_level_api/integration/basic_cancel_spec.rb +57 -0
  56. data/spec/lower_level_api/integration/basic_consume_spec.rb +100 -0
  57. data/spec/spec_helper.rb +64 -0
  58. data/spec/unit/bunny_spec.rb +15 -0
  59. data/spec/unit/concurrent/condition_spec.rb +66 -0
  60. metadata +135 -93
  61. data/CHANGELOG +0 -21
  62. data/README.textile +0 -76
  63. data/Rakefile +0 -14
  64. data/examples/simple.rb +0 -32
  65. data/examples/simple_ack.rb +0 -35
  66. data/examples/simple_consumer.rb +0 -55
  67. data/examples/simple_fanout.rb +0 -41
  68. data/examples/simple_headers.rb +0 -42
  69. data/examples/simple_publisher.rb +0 -29
  70. data/examples/simple_topic.rb +0 -61
  71. data/ext/amqp-0.9.1.json +0 -389
  72. data/ext/config.yml +0 -4
  73. data/ext/qparser.rb +0 -426
  74. data/lib/bunny/client.rb +0 -370
  75. data/lib/bunny/subscription.rb +0 -92
  76. data/lib/qrack/amq-client-url.rb +0 -165
  77. data/lib/qrack/channel.rb +0 -20
  78. data/lib/qrack/client.rb +0 -247
  79. data/lib/qrack/errors.rb +0 -5
  80. data/lib/qrack/protocol/protocol.rb +0 -135
  81. data/lib/qrack/protocol/spec.rb +0 -525
  82. data/lib/qrack/qrack.rb +0 -20
  83. data/lib/qrack/queue.rb +0 -40
  84. data/lib/qrack/subscription.rb +0 -152
  85. data/lib/qrack/transport/buffer.rb +0 -305
  86. data/lib/qrack/transport/frame.rb +0 -102
  87. data/spec/spec_09/amqp_url_spec.rb +0 -19
  88. data/spec/spec_09/bunny_spec.rb +0 -76
  89. data/spec/spec_09/connection_spec.rb +0 -34
  90. data/spec/spec_09/exchange_spec.rb +0 -173
  91. data/spec/spec_09/queue_spec.rb +0 -240
@@ -1,4 +0,0 @@
1
- ---
2
- :spec_out: '../lib/qrack/protocol/spec.rb'
3
- :frame_out: '../lib/qrack/transport/frame.rb'
4
- :spec_in: 'amqp-0.9.1.json'
@@ -1,426 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'json'
4
- require 'erb'
5
- require 'pathname'
6
- require 'yaml'
7
-
8
- def spec_details(spec)
9
- meta = {}
10
-
11
- meta['major'] = spec['major-version']
12
- meta['minor'] = spec['minor-version']
13
- meta['revision'] = spec['revision']
14
- meta['port'] = spec['port']
15
- meta['ssl_port'] = spec['ssl_port']
16
- meta['comment'] = "AMQ Protocol version #{meta['major']}.#{meta['minor']}.#{meta['revision']}"
17
-
18
- meta
19
- end
20
-
21
- def process_constants(spec)
22
- # AMQP constants
23
-
24
- frame_constants = {}
25
- other_constants = {}
26
-
27
- spec['constants'].each do |constant|
28
- if constant['name'].match(/^frame/i)
29
- frame_constants[constant['value'].to_i] =
30
- constant['name'].sub(/^frame./i,'').split(/\s|-/).map{|w| w.downcase.capitalize}.join
31
- else
32
- other_constants[constant['value']] = constant['name']
33
- end
34
- end
35
-
36
- [frame_constants.sort, other_constants.sort]
37
- end
38
-
39
- def domain_types(spec, major, minor, revision)
40
- # AMQP domain types
41
- dt_arr = []
42
- spec["domains"].each do |domain|
43
- # JSON spec gives domain types as two element arrays like ["channel-id", "longstr"]
44
- dt_arr << domain.last
45
- end
46
-
47
- # Return sorted array
48
- dt_arr.uniq.sort
49
- end
50
-
51
- def classes(spec, major, minor, revision)
52
- # AMQP classes
53
- spec['classes'].map do |amqp_class|
54
- cls_hash = {}
55
- cls_hash[:name] = amqp_class['name']
56
- cls_hash[:index] = amqp_class['id']
57
- # Get fields for class
58
- cls_hash[:fields] = fields(amqp_class) # are these amqp_class["properties"] ?
59
- # Get methods for class
60
- meth_arr = class_methods(amqp_class)
61
- # Add array to class hash
62
- cls_hash[:methods] = meth_arr
63
- cls_hash
64
- end
65
- end
66
-
67
- # Get methods for class
68
- def class_methods(amqp_class)
69
- amqp_class['methods'].map do |method|
70
- meth_hash = {}
71
- meth_hash[:name] = method['name']
72
- meth_hash[:index] = method['id']
73
- # Get fields for method
74
- meth_hash[:fields] = fields(method)
75
- meth_hash
76
- end
77
- end
78
-
79
- # Get the fields for a class or method
80
- def fields(element)
81
- # The JSON spec puts these in "properties" for a class and "arguments" for a
82
- # method
83
- (element['arguments'] || element['properties'] || []).map do |field|
84
- field_hash = {}
85
- field_hash[:name] = field['name'].tr(' ', '-')
86
- field_hash[:domain] = field['type'] || field['domain']
87
-
88
- # Convert domain type if necessary
89
- conv_arr = convert_type(field_hash[:domain])
90
- field_hash[:domain] = conv_arr.last unless conv_arr.empty?
91
-
92
- field_hash
93
- end
94
- end
95
-
96
- def convert_type(name)
97
- type_arr = @type_conversion.select {|k,v| k == name}.flatten
98
- end
99
-
100
- # Start of Main program
101
-
102
- # Read in config options
103
- CONFIG = YAML::load(File.read('config.yml'))
104
-
105
- # Get path to the spec file and the spec file name on its own
106
- specpath = CONFIG[:spec_in]
107
- path = Pathname.new(specpath)
108
- specfile = path.basename.to_s
109
-
110
- # Read in the spec file
111
- spec = JSON.parse(IO.read(specpath))
112
-
113
- # Declare type conversion hash
114
- @type_conversion = {'path' => 'shortstr',
115
- 'known hosts' => 'shortstr',
116
- 'known-hosts' => 'shortstr',
117
- 'reply code' => 'short',
118
- 'reply-code' => 'short',
119
- 'reply text' => 'shortstr',
120
- 'reply-text' => 'shortstr',
121
- 'class id' => 'short',
122
- 'class-id' => 'short',
123
- 'method id' => 'short',
124
- 'method-id' => 'short',
125
- 'channel-id' => 'longstr',
126
- 'access ticket' => 'short',
127
- 'access-ticket' => 'short',
128
- 'exchange name' => 'shortstr',
129
- 'exchange-name' => 'shortstr',
130
- 'queue name' => 'shortstr',
131
- 'queue-name' => 'shortstr',
132
- 'consumer tag' => 'shortstr',
133
- 'consumer-tag' => 'shortstr',
134
- 'delivery tag' => 'longlong',
135
- 'delivery-tag' => 'longlong',
136
- 'redelivered' => 'bit',
137
- 'no ack' => 'bit',
138
- 'no-ack' => 'bit',
139
- 'no local' => 'bit',
140
- 'no-local' => 'bit',
141
- 'peer properties' => 'table',
142
- 'peer-properties' => 'table',
143
- 'destination' => 'shortstr',
144
- 'duration' => 'longlong',
145
- 'security-token' => 'longstr',
146
- 'reject-code' => 'short',
147
- 'reject-text' => 'shortstr',
148
- 'offset' => 'longlong',
149
- 'no-wait' => 'bit',
150
- 'message-count' => 'long'
151
- }
152
-
153
- # Spec details
154
- spec_info = spec_details(spec)
155
-
156
- # Constants
157
- constants = process_constants(spec)
158
-
159
- # Frame constants
160
- frame_constants = constants[0].select {|k,v| k <= 8}
161
- frame_footer = constants[0].select {|k,v| v == 'End'}[0][0]
162
-
163
- # Other constants
164
- other_constants = constants[1]
165
-
166
- # Domain types
167
- data_types = domain_types(spec, spec_info['major'], spec_info['minor'], spec_info['revision'])
168
-
169
- # Classes
170
- class_defs = classes(spec, spec_info['major'], spec_info['minor'], spec_info['revision'])
171
-
172
- # Generate spec.rb
173
- spec_rb = File.open(CONFIG[:spec_out], 'w')
174
- spec_rb.puts(
175
- ERB.new(%q[
176
- # encoding: utf-8
177
-
178
-
179
- #:stopdoc:
180
- # this file was autogenerated on <%= Time.now.to_s %>
181
- # using <%= specfile.ljust(16) %> (mtime: <%= File.mtime(specpath) %>)
182
- #
183
- # DO NOT EDIT! (edit ext/qparser.rb and config.yml instead, and run 'ruby qparser.rb')
184
-
185
- module Qrack
186
- module Protocol
187
- HEADER = "AMQP".freeze
188
- VERSION_MAJOR = <%= spec_info['major'] %>
189
- VERSION_MINOR = <%= spec_info['minor'] %>
190
- REVISION = <%= spec_info['revision'] %>
191
- PORT = <%= spec_info['port'] %>
192
- SSL_PORT = <%= spec_info['ssl_port'] %>
193
-
194
- RESPONSES = {
195
- <%- other_constants.each do |value, name| -%>
196
- <%= value %> => :<%= name.gsub(/\s|-/, '_').upcase -%>,
197
- <%- end -%>
198
- }
199
-
200
- FIELDS = [
201
- <%- data_types.each do |d| -%>
202
- :<%= d -%>,
203
- <%- end -%>
204
- ]
205
-
206
- class Class
207
- class << self
208
- FIELDS.each do |f|
209
- class_eval %[
210
- def #{f} name
211
- properties << [ :#{f}, name ] unless properties.include?([:#{f}, name])
212
- attr_accessor name
213
- end
214
- ]
215
- end
216
-
217
- def properties() @properties ||= [] end
218
-
219
- def id() self::ID end
220
- def name() self::NAME.to_s end
221
- end
222
-
223
- class Method
224
- class << self
225
- FIELDS.each do |f|
226
- class_eval %[
227
- def #{f} name
228
- arguments << [ :#{f}, name ] unless arguments.include?([:#{f}, name])
229
- attr_accessor name
230
- end
231
- ]
232
- end
233
-
234
- def arguments() @arguments ||= [] end
235
-
236
- def parent() Protocol.const_get(self.to_s[/Protocol::(.+?)::/,1]) end
237
- def id() self::ID end
238
- def name() self::NAME.to_s end
239
- end
240
-
241
- def == b
242
- self.class.arguments.inject(true) do |eql, (type, name)|
243
- eql and __send__("#{name}") == b.__send__("#{name}")
244
- end
245
- end
246
- end
247
-
248
- def self.methods() @methods ||= {} end
249
-
250
- def self.Method(id, name)
251
- @_base_methods ||= {}
252
- @_base_methods[id] ||= ::Class.new(Method) do
253
- class_eval %[
254
- def self.inherited klass
255
- klass.const_set(:ID, #{id})
256
- klass.const_set(:NAME, :#{name.to_s})
257
- klass.parent.methods[#{id}] = klass
258
- klass.parent.methods[klass::NAME] = klass
259
- end
260
- ]
261
- end
262
- end
263
- end
264
-
265
- def self.classes() @classes ||= {} end
266
-
267
- def self.Class(id, name)
268
- @_base_classes ||= {}
269
- @_base_classes[id] ||= ::Class.new(Class) do
270
- class_eval %[
271
- def self.inherited klass
272
- klass.const_set(:ID, #{id})
273
- klass.const_set(:NAME, :#{name.to_s})
274
- Protocol.classes[#{id}] = klass
275
- Protocol.classes[klass::NAME] = klass
276
- end
277
- ]
278
- end
279
- end
280
- end
281
- end
282
-
283
- module Qrack
284
- module Protocol
285
- <%- class_defs.each do |h| -%>
286
- class <%= h[:name].capitalize.ljust(12) %> < Class( <%= h[:index].to_s.rjust(3) %>, :<%= h[:name].ljust(12) %> ); end
287
- <%- end -%>
288
-
289
- <%- class_defs.each do |c| -%>
290
- class <%= c[:name].capitalize %>
291
- <%- c[:fields].each do |p| -%>
292
- <%= p[:domain].ljust(10) %> :<%= p[:name].tr('-','_') %>
293
- <%- end if c[:fields] -%>
294
-
295
- <%- c[:methods].each do |m| -%>
296
- class <%= m[:name].capitalize.gsub(/-(.)/){ "#{$1.upcase}"}.ljust(12) %> < Method( <%= m[:index].to_s.rjust(3) %>, :<%= m[:name].tr('- ','_').ljust(14) %> ); end
297
- <%- end -%>
298
-
299
- <%- c[:methods].each do |m| -%>
300
-
301
- class <%= m[:name].capitalize.gsub(/-(.)/){ "#{$1.upcase}"} %>
302
- <%- m[:fields].each do |a| -%>
303
- <%- if a[:domain] -%>
304
- <%= a[:domain].ljust(16) %> :<%= a[:name].tr('- ','_') %>
305
- <%- end -%>
306
- <%- end -%>
307
- end
308
- <%- end -%>
309
-
310
- end
311
-
312
- <%- end -%>
313
- end
314
-
315
- end
316
- ].gsub!(/^ /,''), nil, '>-%').result(binding)
317
- )
318
-
319
- # Close spec.rb file
320
- spec_rb.close
321
-
322
- # Generate frame.rb file
323
-
324
- frame_rb = File.open(CONFIG[:frame_out], 'w')
325
- frame_rb.puts(
326
- ERB.new(%q[
327
- # encoding: utf-8
328
-
329
-
330
- #:stopdoc:
331
- # this file was autogenerated on <%= Time.now.to_s %>
332
- #
333
- # DO NOT EDIT! (edit ext/qparser.rb and config.yml instead, and run 'ruby qparser.rb')
334
-
335
- module Qrack
336
- module Transport
337
- class Frame
338
-
339
- FOOTER = <%= frame_footer %>
340
- ID = 0
341
-
342
- @types = {
343
- <%- frame_constants.each do |value, name| -%>
344
- <%= value %> => '<%= name %>',
345
- <%- end -%>
346
- }
347
-
348
- attr_accessor :channel, :payload
349
-
350
- def initialize payload = nil, channel = 0
351
- @channel, @payload = channel, payload
352
- end
353
-
354
- def id
355
- self.class::ID
356
- end
357
-
358
- def to_binary
359
- buf = Transport::Buffer.new
360
- buf.write :octet, id
361
- buf.write :short, channel
362
- buf.write :longstr, payload
363
- buf.write :octet, FOOTER
364
- buf.rewind
365
- buf
366
- end
367
-
368
- def to_s
369
- to_binary.to_s
370
- end
371
-
372
- def == frame
373
- [ :id, :channel, :payload ].inject(true) do |eql, field|
374
- eql and __send__(field) == frame.__send__(field)
375
- end
376
- end
377
-
378
- def self.parse(buf, cancellator = nil)
379
- buf = Transport::Buffer.new(buf) unless buf.is_a? Transport::Buffer
380
- buf.extract do
381
- id, channel, payload, footer = buf.read(cancellator, :octet, :short, :longstr, :octet)
382
- Qrack::Transport.const_get(@types[id]).new(payload, channel) if footer == FOOTER
383
- end
384
- end
385
-
386
- end
387
-
388
- class Method < Frame
389
-
390
- ID = 1
391
-
392
- def initialize payload = nil, channel = 0
393
- super
394
- unless @payload.is_a? Protocol::Class::Method or @payload.nil?
395
- @payload = Protocol.parse(@payload)
396
- end
397
- end
398
- end
399
-
400
- class Header < Frame
401
-
402
- ID = 2
403
-
404
- def initialize payload = nil, channel = 0
405
- super
406
- unless @payload.is_a? Protocol::Header or @payload.nil?
407
- @payload = Protocol::Header.new(@payload)
408
- end
409
- end
410
- end
411
-
412
- <%- frame_constants.each do |value, name| -%>
413
- <%- if value > 2 -%>
414
- class <%= name %> < Frame
415
- ID = <%= value %>
416
- end
417
-
418
- <%- end -%>
419
- <%- end -%>
420
- end
421
- end
422
- ].gsub!(/^ /,''), nil, '>-%').result(binding)
423
- )
424
-
425
- # Close frame.rb file
426
- frame_rb.close
@@ -1,370 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module Bunny
4
-
5
- # The Client class provides the major Bunny API methods.
6
- class Client < Qrack::Client
7
-
8
- # Sets up a Bunny::Client object ready for connection to a broker.
9
- # {Client.status} is set to @:not_connected@.
10
- #
11
- # @option opts [String] :host ("localhost")
12
- # @option opts [Integer] :port (5672 or 5671 if :ssl set to true)
13
- # @option opts [String] :vhost ("/")
14
- # @option opts [String] :user ("guest")
15
- # @option opts [String] :pass ("guest")
16
- # @option opts [Boolean] :ssl (false)
17
- # If set to @true@, ssl encryption will be used and port will default to 5671.
18
- # @option opts [Boolean] :verify_ssl (true)
19
- # If ssl is enabled, this will cause OpenSSL to validate
20
- # the server certificate unless this parameter is set to @false@.
21
- # @option opts [String] :logfile (nil)
22
- # @option opts [Boolean] :logging (false)
23
- # If set to @true@, session information is sent to STDOUT if @:logfile@
24
- # has not been specified. Otherwise, session information is written to @:logfile@.
25
- # @option opts [Integer] :frame_max (131072)
26
- # Maximum frame size in bytes.
27
- # @option opts [Integer] :channel_max (0)
28
- # Maximum number of channels. Defaults to 0 which means no maximum.
29
- # @option opts [Integer] :heartbeat (0)
30
- # Number of seconds. Defaults to 0 which means no heartbeat.
31
- # @option opts [Integer] :connect_timeout (5)
32
- # Number of seconds before {Qrack::ConnectionTimeout} is raised.@
33
- def initialize(connection_string_or_opts = Hash.new, opts = Hash.new)
34
- super
35
- end
36
-
37
- # Checks response from AMQP methods and takes appropriate action
38
- def check_response(received_method, expected_method, err_msg, err_class = Bunny::ProtocolError)
39
- @last_method = received_method
40
-
41
- case
42
- when received_method.is_a?(Qrack::Protocol::Connection::Close)
43
- # Clean up the socket
44
- close_socket
45
-
46
- raise Bunny::ForcedConnectionCloseError, "Error Reply Code: #{received_method.reply_code}\nError Reply Text: #{received_method.reply_text}"
47
-
48
- when received_method.is_a?(Qrack::Protocol::Channel::Close)
49
- # Clean up the channel
50
- channel.active = false
51
-
52
- raise Bunny::ForcedChannelCloseError, "Error Reply Code: #{received_method.reply_code}\nError Reply Text: #{received_method.reply_text}"
53
-
54
- when !received_method.is_a?(expected_method)
55
- raise err_class, err_msg
56
-
57
- else
58
- :response_ok
59
- end
60
- end
61
-
62
- def close_connection
63
- # Set client channel to zero
64
- switch_channel(0)
65
-
66
- send_frame(Qrack::Protocol::Connection::Close.new(:reply_code => 200, :reply_text => 'Goodbye', :class_id => 0, :method_id => 0))
67
-
68
- method = next_method
69
-
70
- check_response(method, Qrack::Protocol::Connection::CloseOk, "Error closing connection")
71
-
72
- end
73
-
74
- def create_channel
75
- channels.each do |c|
76
- return c if (!c.open? and c.number != 0)
77
- end
78
- # If no channel to re-use instantiate new one
79
- Bunny::Channel.new(self)
80
- end
81
-
82
- # Declares an exchange to the broker/server. If the exchange does not exist, a new one is created
83
- # using the arguments passed in. If the exchange already exists, a reference to it is created, provided
84
- # that the arguments passed in do not conflict with the existing attributes of the exchange. If an error
85
- # occurs a _Bunny_::_ProtocolError_ is raised.
86
- #
87
- # @option opts [Symbol] :type (:direct)
88
- # One of :direct@, @:fanout@, @:topic@, or @:headers@.
89
- #
90
- # @option opts [Boolean] :passive
91
- # If set to @true@, the server will not create the exchange.
92
- # The client can use this to check whether an exchange exists without modifying the server state.
93
- #
94
- # @option opts [Boolean] :durable (false)
95
- # If set to @true@ when creating a new exchange, the exchange
96
- # will be marked as durable. Durable exchanges remain active
97
- # when a server restarts. Non-durable exchanges (transient exchanges)
98
- # are purged if/when a server restarts.
99
- #
100
- # @option opts [Boolean] :auto_delete (false)
101
- # If set to @true@, the exchange is deleted when all queues have finished using it.
102
- #
103
- # @option opts [Boolean] :nowait (false)
104
- # Ignored by Bunny, always @false@.
105
- #
106
- # @return [Bunny::Exchange]
107
- def exchange(name, opts = {})
108
- exchanges[name] || Bunny::Exchange.new(self, name, opts)
109
- end
110
-
111
- def init_connection
112
- write(Qrack::Protocol::HEADER)
113
- write([0, Qrack::Protocol::VERSION_MAJOR, Qrack::Protocol::VERSION_MINOR, Qrack::Protocol::REVISION].pack('C4'))
114
-
115
- frame = next_frame
116
- if frame.nil? or !frame.payload.is_a?(Qrack::Protocol::Connection::Start)
117
- raise Bunny::ProtocolError, 'Connection initiation failed'
118
- end
119
- end
120
-
121
- def next_frame(opts = {})
122
- frame = nil
123
-
124
- case
125
- when channel.frame_buffer.size > 0
126
- frame = channel.frame_buffer.shift
127
- else
128
- frame = Qrack::Transport::Frame.parse(buffer, opts)
129
- end
130
-
131
- @logger.info("received") { frame } if @logging
132
-
133
- raise Bunny::ConnectionError, 'No connection to server' if (frame.nil? and !connecting?)
134
-
135
- # Monitor server activity and discard heartbeats
136
- @message_in = true
137
-
138
- case
139
- when frame.is_a?(Qrack::Transport::Heartbeat)
140
- next_frame(opts)
141
- when frame.nil?
142
- frame
143
- when ((frame.channel != channel.number) and (frame.channel != 0))
144
- channel.frame_buffer << frame
145
- next_frame(opts)
146
- else
147
- frame
148
- end
149
-
150
- end
151
-
152
- def open_connection
153
- client_props = { :platform => 'Ruby', :product => 'Bunny', :information => 'http://github.com/ruby-amqp/bunny', :version => VERSION }
154
- start_opts = {
155
- :client_properties => client_props,
156
- :mechanism => 'PLAIN',
157
- :response => "\0" + @user + "\0" + @pass,
158
- :locale => 'en_US'
159
- }
160
- send_frame(Qrack::Protocol::Connection::StartOk.new(start_opts))
161
-
162
- frame = next_frame
163
- raise Bunny::ProtocolError, "Connection failed - user: #{@user}" if frame.nil?
164
-
165
- method = frame.payload
166
-
167
- if method.is_a?(Qrack::Protocol::Connection::Tune)
168
- send_frame(Qrack::Protocol::Connection::TuneOk.new(:channel_max => @channel_max, :frame_max => @frame_max, :heartbeat => @heartbeat))
169
- end
170
-
171
- send_frame(Qrack::Protocol::Connection::Open.new(:virtual_host => @vhost, :reserved_1 => 0, :reserved_2 => false))
172
-
173
- raise Bunny::ProtocolError, 'Cannot open connection' unless next_method.is_a?(Qrack::Protocol::Connection::OpenOk)
174
- end
175
-
176
- # Requests a specific quality of service. The QoS can be specified for the current channel
177
- # or for all channels on the connection. The particular properties and semantics of a QoS
178
- # method always depend on the content class semantics. Though the QoS method could in principle
179
- # apply to both peers, it is currently meaningful only for the server.
180
- #
181
- # @option opts [Integer] :prefetch_size (0)
182
- # Size in number of octets. The client can request that messages be sent in advance
183
- # so that when the client finishes processing a message, the following message is
184
- # already held locally, rather than needing to be sent down the channel. refetching
185
- # gives a performance improvement. This field specifies the prefetch window size
186
- # in octets. The server will send a message in advance if it is equal to or smaller
187
- # in size than the available prefetch size (and also falls into other prefetch limits).
188
- # May be set to zero, meaning "no specific limit", although other prefetch limits may
189
- # still apply. The prefetch-size is ignored if the no-ack option is set.
190
- #
191
- # @option opts [Integer] :prefetch_count (1)
192
- # Number of messages to prefetch. Specifies a prefetch window in terms of whole messages.
193
- # This field may be used in combination with the prefetch-size field; a message will only
194
- # be sent in advance if both prefetch windows (and those at the channel and connection level)
195
- # allow it. The prefetch-count is ignored if the no-ack option is set.
196
- #
197
- # @option opts [Boolean] :global (false)
198
- # By default the QoS settings apply to the current channel only. If set to true,
199
- # they are applied to the entire connection.
200
- #
201
- # @return [Symbol] @:qos_ok@ if successful.
202
- def qos(opts = {})
203
- send_frame(Qrack::Protocol::Basic::Qos.new({ :prefetch_size => 0, :prefetch_count => 1, :global => false }.merge(opts)))
204
-
205
- method = next_method
206
-
207
- check_response(method, Qrack::Protocol::Basic::QosOk, "Error specifying Quality of Service")
208
-
209
- # return confirmation
210
- :qos_ok
211
- end
212
-
213
- # Declares a queue to the broker/server. If the queue does not exist, a new one is created
214
- # using the arguments passed in. If the queue already exists, a reference to it is created, provided
215
- # that the arguments passed in do not conflict with the existing attributes of the queue. If an error
216
- # occurs a {Bunny::ProtocolError} is raised.
217
- #
218
- # @option opts [Boolean] :passive (false)
219
- # If set to @true@, the server will not create the queue. The client can use this to check
220
- # whether a queue exists without modifying the server state.
221
- #
222
- # @option opts [Boolean] :durable (false)
223
- # If set to @true@ when creating a new queue, the queue will be marked as durable.
224
- # Durable queues remain active when a server restarts. Non-durable queues (transient ones)
225
- # are purged if/when a server restarts. Note that durable queues do not necessarily hold
226
- # persistent messages, although it does not make sense to send persistent messages
227
- # to a transient queue.
228
- #
229
- # @option opts [Boolean] :exclusive (false)
230
- # If set to @true@, requests an exclusive queue. Exclusive queues may only be consumed
231
- # from by the current connection. Setting the 'exclusive' flag always implies 'auto-delete'.
232
- #
233
- # @option opts [Boolean] :auto_delete (false)
234
- # If set to @true@, the queue is deleted when all consumers have finished using it.
235
- # Last consumer can be cancelled either explicitly or because its channel is closed.
236
- # If there has never been a consumer on the queue, it is not deleted.
237
- #
238
- # @option opts [Boolean] :nowait (false)
239
- # Ignored by Bunny, always @false@.
240
- #
241
- # @return [Bunny::Queue]
242
- def queue(name = nil, opts = {})
243
- if name.is_a?(Hash)
244
- opts = name
245
- name = nil
246
- end
247
-
248
- # Queue is responsible for placing itself in the list of queues
249
- queues[name] || Bunny::Queue.new(self, name, opts)
250
- end
251
-
252
- # Asks the broker to redeliver all unacknowledged messages on a specified channel. Zero or
253
- # more messages may be redelivered.
254
- #
255
- # @option opts [Boolean] :requeue (false)
256
- # If set to @false@, the message will be redelivered to the original recipient.
257
- # If set to @true@, the server will attempt to requeue the message, potentially
258
- # then delivering it to an alternative subscriber.
259
- def recover(opts = {})
260
- send_frame(Qrack::Protocol::Basic::Recover.new({ :requeue => false }.merge(opts)))
261
- end
262
-
263
- def send_frame(*args)
264
- args.each do |data|
265
- data = data.to_frame(channel.number) unless data.is_a?(Qrack::Transport::Frame)
266
- data.channel = channel.number
267
-
268
- @logger.info("send") { data } if @logging
269
- write(data.to_s)
270
-
271
- # Monitor client activity for heartbeat purposes
272
- @message_out = true
273
- end
274
-
275
- nil
276
- end
277
-
278
- def send_heartbeat
279
- # Create a new heartbeat frame
280
- hb = Qrack::Transport::Heartbeat.new('')
281
- # Channel 0 must be used
282
- switch_channel(0) if @channel.number > 0
283
- # Send the heartbeat to server
284
- send_frame(hb)
285
- end
286
-
287
- # Opens a communication channel and starts a connection. If an error occurs, a
288
- # {Bunny::ProtocolError} is raised. If successful, {Client.status} is set to @:connected@.
289
- #
290
- # @return [Symbol] @:connected@ if successful.
291
- def start_session
292
- @connecting = true
293
-
294
- # Create/get socket
295
- socket
296
-
297
- # Initiate connection
298
- init_connection
299
-
300
- # Open connection
301
- open_connection
302
-
303
- # Open another channel because channel zero is used for specific purposes
304
- c = create_channel()
305
- c.open
306
-
307
- @connecting = false
308
-
309
- # return status
310
- @status = :connected
311
- end
312
-
313
- alias start start_session
314
-
315
- # This method commits all messages published and acknowledged in
316
- # the current transaction. A new transaction starts immediately
317
- # after a commit.
318
- #
319
- # @return [Symbol] @:commit_ok@ if successful.
320
- def tx_commit
321
- send_frame(Qrack::Protocol::Tx::Commit.new())
322
-
323
- method = next_method
324
-
325
- check_response(method, Qrack::Protocol::Tx::CommitOk, "Error commiting transaction")
326
-
327
- # return confirmation
328
- :commit_ok
329
- end
330
-
331
- # This method abandons all messages published and acknowledged in
332
- # the current transaction. A new transaction starts immediately
333
- # after a rollback.
334
- #
335
- # @return [Symbol] @:rollback_ok@ if successful.
336
- def tx_rollback
337
- send_frame(Qrack::Protocol::Tx::Rollback.new())
338
-
339
- method = next_method
340
-
341
- check_response(method, Qrack::Protocol::Tx::RollbackOk, "Error rolling back transaction")
342
-
343
- # return confirmation
344
- :rollback_ok
345
- end
346
-
347
- # This method sets the channel to use standard transactions. The
348
- # client must use this method at least once on a channel before
349
- # using the Commit or Rollback methods.
350
- #
351
- # @return [Symbol] @:select_ok@ if successful.
352
- def tx_select
353
- send_frame(Qrack::Protocol::Tx::Select.new())
354
-
355
- method = next_method
356
-
357
- check_response(method, Qrack::Protocol::Tx::SelectOk, "Error initiating transactions for current channel")
358
-
359
- # return confirmation
360
- :select_ok
361
- end
362
-
363
- private
364
-
365
- def buffer
366
- @buffer ||= Qrack::Transport::Buffer.new(self)
367
- end
368
-
369
- end
370
- end