bunny 0.9.0.pre13 → 0.9.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.md +23 -0
- data/README.md +2 -2
- data/lib/bunny.rb +15 -2
- data/lib/bunny/authentication/credentials_encoder.rb +18 -0
- data/lib/bunny/authentication/external_mechanism_encoder.rb +1 -0
- data/lib/bunny/authentication/plain_mechanism_encoder.rb +2 -0
- data/lib/bunny/channel.rb +9 -3
- data/lib/bunny/channel_id_allocator.rb +8 -0
- data/lib/bunny/concurrent/condition.rb +2 -0
- data/lib/bunny/concurrent/continuation_queue.rb +3 -0
- data/lib/bunny/concurrent/linked_continuation_queue.rb +3 -0
- data/lib/bunny/consumer.rb +2 -0
- data/lib/bunny/consumer_tag_generator.rb +1 -0
- data/lib/bunny/consumer_work_pool.rb +2 -0
- data/lib/bunny/delivery_info.rb +18 -2
- data/lib/bunny/exceptions.rb +49 -10
- data/lib/bunny/framing.rb +3 -0
- data/lib/bunny/heartbeat_sender.rb +4 -1
- data/lib/bunny/message_properties.rb +22 -0
- data/lib/bunny/queue.rb +49 -0
- data/lib/bunny/reader_loop.rb +1 -0
- data/lib/bunny/return_info.rb +11 -0
- data/lib/bunny/session.rb +53 -19
- data/lib/bunny/socket.rb +1 -0
- data/lib/bunny/ssl_socket.rb +29 -5
- data/lib/bunny/system_timer.rb +2 -0
- data/lib/bunny/test_kit.rb +4 -3
- data/lib/bunny/transport.rb +41 -25
- data/lib/bunny/version.rb +2 -1
- data/spec/compatibility/queue_declare_spec.rb +4 -0
- data/spec/compatibility/queue_declare_with_default_channel_spec.rb +33 -0
- data/spec/higher_level_api/integration/basic_get_spec.rb +35 -0
- data/spec/higher_level_api/integration/basic_nack_spec.rb +19 -1
- data/spec/higher_level_api/integration/connection_spec.rb +5 -0
- data/spec/higher_level_api/integration/queue_unbind_spec.rb +23 -2
- data/spec/higher_level_api/integration/tls_connection_spec.rb +47 -0
- data/spec/lower_level_api/integration/basic_cancel_spec.rb +8 -1
- data/spec/tls/cacert.pem +18 -0
- data/spec/tls/client_cert.pem +18 -0
- data/spec/tls/client_key.pem +27 -0
- data/spec/tls/server_cert.pem +18 -0
- data/spec/tls/server_key.pem +27 -0
- metadata +16 -2
data/lib/bunny/socket.rb
CHANGED
data/lib/bunny/ssl_socket.rb
CHANGED
@@ -4,9 +4,24 @@ module Bunny
|
|
4
4
|
begin
|
5
5
|
require "openssl"
|
6
6
|
|
7
|
+
# TLS-enabled TCP socket that implements convenience
|
8
|
+
# methods found in Bunny::Socket.
|
7
9
|
class SSLSocket < OpenSSL::SSL::SSLSocket
|
10
|
+
|
11
|
+
# IO::WaitReadable is 1.9+ only
|
12
|
+
READ_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
|
13
|
+
READ_RETRY_EXCEPTION_CLASSES << IO::WaitReadable if IO.const_defined?(:WaitReadable)
|
14
|
+
|
15
|
+
|
16
|
+
# Reads given number of bytes with an optional timeout
|
17
|
+
#
|
18
|
+
# @param [Integer] count How many bytes to read
|
19
|
+
# @param [Integer] timeout Timeout
|
20
|
+
#
|
21
|
+
# @return [String] Data read from the socket
|
22
|
+
# @api public
|
8
23
|
def read_fully(count, timeout = nil)
|
9
|
-
return nil if @
|
24
|
+
return nil if @__bunny_socket_eof_flag__
|
10
25
|
|
11
26
|
value = ''
|
12
27
|
begin
|
@@ -14,10 +29,19 @@ module Bunny
|
|
14
29
|
value << read_nonblock(count - value.bytesize)
|
15
30
|
break if value.bytesize >= count
|
16
31
|
end
|
17
|
-
rescue EOFError
|
18
|
-
@
|
19
|
-
rescue
|
20
|
-
|
32
|
+
rescue EOFError => e
|
33
|
+
@__bunny_socket_eof_flag__ = true
|
34
|
+
rescue OpenSSL::SSL::SSLError => e
|
35
|
+
if e.message == "read would block"
|
36
|
+
if IO.select([self], nil, nil, timeout)
|
37
|
+
retry
|
38
|
+
else
|
39
|
+
raise Timeout::Error, "IO timeout when reading #{count} bytes"
|
40
|
+
end
|
41
|
+
else
|
42
|
+
raise e
|
43
|
+
end
|
44
|
+
rescue *READ_RETRY_EXCEPTION_CLASSES => e
|
21
45
|
if IO.select([self], nil, nil, timeout)
|
22
46
|
retry
|
23
47
|
else
|
data/lib/bunny/system_timer.rb
CHANGED
@@ -5,6 +5,8 @@ require "system_timer"
|
|
5
5
|
module Bunny
|
6
6
|
# Used for Ruby before 1.9
|
7
7
|
class SystemTimer
|
8
|
+
# Executes a block of code, raising if the execution does not finish
|
9
|
+
# in the alloted period of time, in seconds.
|
8
10
|
def self.timeout(seconds, exception)
|
9
11
|
if seconds
|
10
12
|
::SystemTimer.timeout_after(seconds, exception) do
|
data/lib/bunny/test_kit.rb
CHANGED
@@ -10,10 +10,11 @@ module Bunny
|
|
10
10
|
Range.new(a, b).to_a.sample
|
11
11
|
end
|
12
12
|
|
13
|
-
# @param [Integer] Lower bound of message size, in KB
|
14
|
-
# @param [Integer] Upper bound of message size, in KB
|
15
|
-
# @param [Integer] Random number to use in message generation
|
13
|
+
# @param [Integer] a Lower bound of message size, in KB
|
14
|
+
# @param [Integer] b Upper bound of message size, in KB
|
15
|
+
# @param [Integer] i Random number to use in message generation
|
16
16
|
# @return [String] Message payload of length in the given range, with non-ASCII characters
|
17
|
+
# @api public
|
17
18
|
def message_in_kb(a, b, i)
|
18
19
|
s = "Ю#{i}"
|
19
20
|
n = random_in_range(a, b) / s.bytesize
|
data/lib/bunny/transport.rb
CHANGED
@@ -11,6 +11,7 @@ require "bunny/exceptions"
|
|
11
11
|
require "bunny/socket"
|
12
12
|
|
13
13
|
module Bunny
|
14
|
+
# @private
|
14
15
|
class Transport
|
15
16
|
|
16
17
|
#
|
@@ -23,6 +24,7 @@ module Bunny
|
|
23
24
|
|
24
25
|
|
25
26
|
attr_reader :session, :host, :port, :socket, :connect_timeout, :read_write_timeout, :disconnect_timeout
|
27
|
+
attr_reader :tls_context
|
26
28
|
|
27
29
|
def initialize(session, host, port, opts)
|
28
30
|
@session = session
|
@@ -37,6 +39,7 @@ module Bunny
|
|
37
39
|
@tls_certificate = opts[:tls_certificate] || opts[:ssl_cert_string]
|
38
40
|
@tls_key = opts[:tls_key] || opts[:ssl_key_string]
|
39
41
|
@tls_certificate_store = opts[:tls_certificate_store]
|
42
|
+
@tls_ca_certificates = opts.fetch(:tls_ca_certificates, [])
|
40
43
|
@verify_peer = opts[:verify_ssl] || opts[:verify_peer]
|
41
44
|
|
42
45
|
@read_write_timeout = opts[:socket_timeout] || 3
|
@@ -46,9 +49,6 @@ module Bunny
|
|
46
49
|
@disconnect_timeout = @read_write_timeout || @connect_timeout
|
47
50
|
|
48
51
|
@writes_mutex = Mutex.new
|
49
|
-
|
50
|
-
initialize_socket
|
51
|
-
connect
|
52
52
|
end
|
53
53
|
|
54
54
|
|
@@ -76,6 +76,10 @@ module Bunny
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
+
def configure_socket(&block)
|
80
|
+
block.call(@socket)
|
81
|
+
end
|
82
|
+
|
79
83
|
|
80
84
|
# Writes data to the socket. If read/write timeout was specified, Bunny::ClientTimeout will be raised
|
81
85
|
# if the operation times out.
|
@@ -149,7 +153,7 @@ module Bunny
|
|
149
153
|
|
150
154
|
|
151
155
|
def close(reason = nil)
|
152
|
-
@socket.close
|
156
|
+
@socket.close if @socket && !@socket.closed?
|
153
157
|
end
|
154
158
|
|
155
159
|
def open?
|
@@ -218,21 +222,6 @@ module Bunny
|
|
218
222
|
raise ConnectionTimeout.new("#{host}:#{port} is unreachable") if !reacheable?(host, port, timeout)
|
219
223
|
end
|
220
224
|
|
221
|
-
|
222
|
-
protected
|
223
|
-
|
224
|
-
def tls_enabled?(opts)
|
225
|
-
opts[:tls] || opts[:ssl] || (opts[:port] == AMQ::Protocol::TLS_PORT) || false
|
226
|
-
end
|
227
|
-
|
228
|
-
def tls_certificate_path_from(opts)
|
229
|
-
opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
|
230
|
-
end
|
231
|
-
|
232
|
-
def tls_key_path_from(opts)
|
233
|
-
opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
|
234
|
-
end
|
235
|
-
|
236
225
|
def initialize_socket
|
237
226
|
begin
|
238
227
|
s = Bunny::Timer.timeout(@connect_timeout, ConnectionTimeout) do
|
@@ -254,12 +243,26 @@ module Bunny
|
|
254
243
|
@socket
|
255
244
|
end
|
256
245
|
|
246
|
+
protected
|
247
|
+
|
248
|
+
def tls_enabled?(opts)
|
249
|
+
opts[:tls] || opts[:ssl] || (opts[:port] == AMQ::Protocol::TLS_PORT) || false
|
250
|
+
end
|
251
|
+
|
252
|
+
def tls_certificate_path_from(opts)
|
253
|
+
opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path]
|
254
|
+
end
|
255
|
+
|
256
|
+
def tls_key_path_from(opts)
|
257
|
+
opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path]
|
258
|
+
end
|
259
|
+
|
257
260
|
def wrap_in_tls_socket(socket)
|
258
261
|
read_tls_keys!
|
259
262
|
|
260
|
-
|
263
|
+
@tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new)
|
261
264
|
|
262
|
-
s = Bunny::SSLSocket.new(socket,
|
265
|
+
s = Bunny::SSLSocket.new(socket, @tls_context)
|
263
266
|
s.sync_close = true
|
264
267
|
s
|
265
268
|
end
|
@@ -280,15 +283,28 @@ module Bunny
|
|
280
283
|
end
|
281
284
|
|
282
285
|
def initialize_tls_context(ctx)
|
283
|
-
ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate)
|
284
|
-
ctx.key = OpenSSL::PKey::RSA.new(@tls_key)
|
285
|
-
ctx.cert_store =
|
286
|
-
|
286
|
+
ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate)
|
287
|
+
ctx.key = OpenSSL::PKey::RSA.new(@tls_key)
|
288
|
+
ctx.cert_store = if @tls_certificate_store
|
289
|
+
@tls_certificate_store
|
290
|
+
else
|
291
|
+
initialize_tls_certificate_store(@tls_ca_certificates)
|
292
|
+
end
|
293
|
+
|
294
|
+
# setting TLS/SSL version only works correctly when done
|
295
|
+
# vis set_params. MK.
|
296
|
+
ctx.set_params(:ssl_version => @opts.fetch(:tls_protocol, DEFAULT_TLS_PROTOCOL))
|
287
297
|
ctx.set_params(:verify_mode => OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT) if @verify_peer
|
288
298
|
|
289
299
|
ctx
|
290
300
|
end
|
291
301
|
|
302
|
+
def initialize_tls_certificate_store(certs)
|
303
|
+
OpenSSL::X509::Store.new.tap do |store|
|
304
|
+
certs.each { |path| store.add_file(path) }
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
292
308
|
def timeout_from(options)
|
293
309
|
options[:connect_timeout] || options[:connection_timeout] || options[:timeout] || DEFAULT_CONNECTION_TIMEOUT
|
294
310
|
end
|
data/lib/bunny/version.rb
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Bunny::Session do
|
4
|
+
let(:connection) do
|
5
|
+
c = Bunny.new
|
6
|
+
c.start
|
7
|
+
c
|
8
|
+
end
|
9
|
+
|
10
|
+
after :all do
|
11
|
+
connection.close if connection.open?
|
12
|
+
end
|
13
|
+
|
14
|
+
it "proxies #queue to the pre-opened channel for backwards compatibility" do
|
15
|
+
q = connection.queue("", :exclusive => true)
|
16
|
+
q.name.should =~ /^amq.gen/
|
17
|
+
end
|
18
|
+
|
19
|
+
it "proxies #fanout to the pre-opened channel for backwards compatibility" do
|
20
|
+
x = connection.fanout("amq.fanout")
|
21
|
+
x.name.should == "amq.fanout"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "proxies #topic to the pre-opened channel for backwards compatibility" do
|
25
|
+
x = connection.topic("amq.topic")
|
26
|
+
x.name.should == "amq.topic"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "proxies #direct to the pre-opened channel for backwards compatibility" do
|
30
|
+
x = connection.topic("amq.direct")
|
31
|
+
x.name.should == "amq.direct"
|
32
|
+
end
|
33
|
+
end
|
@@ -30,6 +30,25 @@ describe Bunny::Queue, "#pop" do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
|
33
|
+
context "with all defaults and a timeout that is never hit" do
|
34
|
+
it "fetches a messages which is automatically acknowledged" do
|
35
|
+
ch = connection.create_channel
|
36
|
+
|
37
|
+
q = ch.queue("", :exclusive => true)
|
38
|
+
x = ch.default_exchange
|
39
|
+
|
40
|
+
x.publish("xyzzy", :routing_key => q.name)
|
41
|
+
|
42
|
+
sleep(0.5)
|
43
|
+
delivery_info, properties, content = q.pop_waiting(:timeout => 1.0)
|
44
|
+
content.should == "xyzzy"
|
45
|
+
q.message_count.should == 0
|
46
|
+
|
47
|
+
ch.close
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
33
52
|
context "with an empty queue" do
|
34
53
|
it "returns an empty response" do
|
35
54
|
ch = connection.create_channel
|
@@ -44,4 +63,20 @@ describe Bunny::Queue, "#pop" do
|
|
44
63
|
ch.close
|
45
64
|
end
|
46
65
|
end
|
66
|
+
|
67
|
+
|
68
|
+
context "with an empty queue and a timeout" do
|
69
|
+
it "raises an exception" do
|
70
|
+
ch = connection.create_channel
|
71
|
+
|
72
|
+
q = ch.queue("", :exclusive => true)
|
73
|
+
q.purge
|
74
|
+
|
75
|
+
lambda {
|
76
|
+
_, _, content = q.pop_waiting(:timeout => 0.5)
|
77
|
+
}.should raise_error(Timeout::Error)
|
78
|
+
|
79
|
+
ch.close
|
80
|
+
end
|
81
|
+
end
|
47
82
|
end
|
@@ -34,7 +34,25 @@ describe Bunny::Channel, "#nack" do
|
|
34
34
|
end
|
35
35
|
|
36
36
|
context "with multiple = true" do
|
37
|
-
it "rejects multiple messages"
|
37
|
+
it "rejects multiple messages" do
|
38
|
+
q = subject.queue("bunny.basic.nack.with-requeue-true-multi-true", :exclusive => true)
|
39
|
+
x = subject.default_exchange
|
40
|
+
|
41
|
+
3.times do
|
42
|
+
x.publish("bunneth", :routing_key => q.name)
|
43
|
+
end
|
44
|
+
sleep(0.5)
|
45
|
+
q.message_count.should == 3
|
46
|
+
_, _, _ = q.pop(:ack => true)
|
47
|
+
_, _, _ = q.pop(:ack => true)
|
48
|
+
delivery_info, _, content = q.pop(:ack => true)
|
49
|
+
|
50
|
+
subject.nack(delivery_info.delivery_tag, true, true)
|
51
|
+
sleep(0.5)
|
52
|
+
q.message_count.should == 3
|
53
|
+
|
54
|
+
subject.close
|
55
|
+
end
|
38
56
|
end
|
39
57
|
|
40
58
|
|
@@ -62,6 +62,11 @@ describe Bunny::Session do
|
|
62
62
|
Bunny.new
|
63
63
|
end
|
64
64
|
|
65
|
+
it "provides a way to fine tune socket options" do
|
66
|
+
subject.start
|
67
|
+
subject.transport.socket.should respond_to(:setsockopt)
|
68
|
+
end
|
69
|
+
|
65
70
|
it "successfully negotiates the connection" do
|
66
71
|
subject.start
|
67
72
|
subject.should be_connected
|
@@ -12,7 +12,20 @@ describe Bunny::Queue, "bound to an exchange" do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
|
15
|
-
it "can be unbound from an exchange it was bound to"
|
15
|
+
it "can be unbound from an exchange it was bound to" do
|
16
|
+
ch = connection.create_channel
|
17
|
+
x = ch.fanout("amq.fanout")
|
18
|
+
q = ch.queue("", :exclusive => true).bind(x)
|
19
|
+
|
20
|
+
x.publish("")
|
21
|
+
sleep 0.3
|
22
|
+
q.message_count.should == 1
|
23
|
+
|
24
|
+
q.unbind(x)
|
25
|
+
|
26
|
+
x.publish("")
|
27
|
+
q.message_count.should == 1
|
28
|
+
end
|
16
29
|
end
|
17
30
|
|
18
31
|
|
@@ -29,5 +42,13 @@ describe Bunny::Queue, "NOT bound to an exchange" do
|
|
29
42
|
end
|
30
43
|
|
31
44
|
|
32
|
-
it "cannot be unbound (raises a channel error)"
|
45
|
+
it "cannot be unbound (raises a channel error)" do
|
46
|
+
ch = connection.create_channel
|
47
|
+
x = ch.fanout("amq.fanout")
|
48
|
+
q = ch.queue("", :exclusive => true)
|
49
|
+
|
50
|
+
lambda {
|
51
|
+
q.unbind(x)
|
52
|
+
}.should raise_error(Bunny::NotFound, /no binding/)
|
53
|
+
end
|
33
54
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
unless ENV["CI"]
|
5
|
+
describe "TLS connection to RabbitMQ" do
|
6
|
+
let(:connection) do
|
7
|
+
c = Bunny.new(:user => "bunny_gem",
|
8
|
+
:password => "bunny_password",
|
9
|
+
:vhost => "bunny_testbed",
|
10
|
+
:tls => true,
|
11
|
+
:tls_cert => "spec/tls/client_cert.pem",
|
12
|
+
:tls_key => "spec/tls/client_key.pem",
|
13
|
+
:tls_ca_certificates => ["./spec/tls/cacert.pem"])
|
14
|
+
c.start
|
15
|
+
c
|
16
|
+
end
|
17
|
+
|
18
|
+
after :all do
|
19
|
+
connection.close
|
20
|
+
end
|
21
|
+
|
22
|
+
it "provides the same API as a regular connection" do
|
23
|
+
ch = connection.create_channel
|
24
|
+
|
25
|
+
q = ch.queue("", :exclusive => true)
|
26
|
+
x = ch.default_exchange
|
27
|
+
|
28
|
+
x.publish("xyzzy", :routing_key => q.name).
|
29
|
+
publish("xyzzy", :routing_key => q.name).
|
30
|
+
publish("xyzzy", :routing_key => q.name).
|
31
|
+
publish("xyzzy", :routing_key => q.name)
|
32
|
+
|
33
|
+
sleep 0.5
|
34
|
+
q.message_count.should == 4
|
35
|
+
|
36
|
+
i = 0
|
37
|
+
q.subscribe do |delivery_info, _, payload|
|
38
|
+
i += 1
|
39
|
+
end
|
40
|
+
sleep 1.0
|
41
|
+
i.should == 4
|
42
|
+
q.message_count.should == 0
|
43
|
+
|
44
|
+
ch.close
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -57,6 +57,13 @@ describe Bunny::Channel, "#basic_cancel" do
|
|
57
57
|
end
|
58
58
|
|
59
59
|
context "when the given consumer tag is invalid (was never registered)" do
|
60
|
-
it "
|
60
|
+
it "DOES NOT cause a channel error" do
|
61
|
+
ch = connection.create_channel
|
62
|
+
|
63
|
+
# RabbitMQ 3.1 does not raise an exception w/ unknown consumer tag. MK.
|
64
|
+
ch.basic_cancel("878798s7df89#{rand}#{Time.now.to_i}")
|
65
|
+
|
66
|
+
ch.close
|
67
|
+
end
|
61
68
|
end
|
62
69
|
end
|