bunny 0.8.0 → 0.9.0.pre1

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 (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