bunny 1.5.1 → 1.6.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.
- checksums.yaml +4 -4
- data/ChangeLog.md +22 -0
- data/bunny.gemspec +1 -1
- data/lib/bunny/channel.rb +20 -20
- data/lib/bunny/cruby/socket.rb +24 -10
- data/lib/bunny/cruby/ssl_socket.rb +48 -0
- data/lib/bunny/session.rb +22 -6
- data/lib/bunny/transport.rb +65 -35
- data/lib/bunny/version.rb +1 -1
- data/spec/higher_level_api/integration/tls_connection_spec.rb +0 -47
- data/spec/issues/issue100_spec.rb +2 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de1627dd44089041db13ab6a5ea76ba2ead5086d
|
4
|
+
data.tar.gz: 41ae99e1499f57a77636392e2a5d870c6c698ef7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e24bb89154db20fe6badb7427bab93de1d9a2e4c880b972340b5d76f4d465aa6410c75c55f5a51a091526deec93d30ced4cd815080d7dc0a7dbce06eb89d95b
|
7
|
+
data.tar.gz: 4d210c02531f031fdfc5ab021d02b32abdc2d4019440bf76650743e950f310cf2ec27e09193feee987cfc97f65c6d026a1cd0451a4367f35f84c8ce003fe7318
|
data/ChangeLog.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
## Changes between Bunny 1.5.0 and 1.6.0
|
2
|
+
|
3
|
+
### Socket Read and Write Timeout Improvements
|
4
|
+
|
5
|
+
Bunny now sets a read timeout on the sockets it opens, and uses
|
6
|
+
`IO.select` timeouts as the most reliable option available
|
7
|
+
on Ruby 1.9 and later.
|
8
|
+
|
9
|
+
GH issue: [#254](https://github.com/ruby-amqp/bunny/pull/254).
|
10
|
+
|
11
|
+
Contributed by Andre Foeken (Nedap).
|
12
|
+
|
13
|
+
### Inline TLS Certificates Support
|
14
|
+
|
15
|
+
TLS certificate options now accept inline certificates as well as
|
16
|
+
file paths.
|
17
|
+
|
18
|
+
GH issues: [#255](https://github.com/ruby-amqp/bunny/pull/255), [#256](https://github.com/ruby-amqp/bunny/pull/256).
|
19
|
+
|
20
|
+
Contributed by Will Barrett (Sqwiggle).
|
21
|
+
|
22
|
+
|
1
23
|
## Changes between Bunny 1.4.0 and 1.5.0
|
2
24
|
|
3
25
|
### Improved Uncaught Exception Handler
|
data/bunny.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.version = Bunny::VERSION.dup
|
10
10
|
s.homepage = "http://rubybunny.info"
|
11
11
|
s.summary = "Popular easy to use Ruby client for RabbitMQ"
|
12
|
-
s.description = "Easy to use, feature complete Ruby client for RabbitMQ
|
12
|
+
s.description = "Easy to use, feature complete Ruby client for RabbitMQ 3.3 and later versions."
|
13
13
|
s.license = "MIT"
|
14
14
|
|
15
15
|
# Sorted alphabetically.
|
data/lib/bunny/channel.rb
CHANGED
@@ -203,8 +203,8 @@ module Bunny
|
|
203
203
|
attr_reader :recoveries_counter
|
204
204
|
|
205
205
|
# @private
|
206
|
-
def
|
207
|
-
@connection.
|
206
|
+
def wait_on_continuations_timeout
|
207
|
+
@connection.transport_write_timeout
|
208
208
|
end
|
209
209
|
|
210
210
|
# Opens the channel and resets its internal state
|
@@ -629,7 +629,7 @@ module Bunny
|
|
629
629
|
|
630
630
|
@connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id, 0, count, global))
|
631
631
|
|
632
|
-
Bunny::Timeout.timeout(
|
632
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
633
633
|
@last_basic_qos_ok = wait_on_continuations
|
634
634
|
end
|
635
635
|
raise_if_continuation_resulted_in_a_channel_error!
|
@@ -648,7 +648,7 @@ module Bunny
|
|
648
648
|
raise_if_no_longer_open!
|
649
649
|
|
650
650
|
@connection.send_frame(AMQ::Protocol::Basic::Recover.encode(@id, requeue))
|
651
|
-
Bunny::Timeout.timeout(
|
651
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
652
652
|
@last_basic_recover_ok = wait_on_continuations
|
653
653
|
end
|
654
654
|
raise_if_continuation_resulted_in_a_channel_error!
|
@@ -850,7 +850,7 @@ module Bunny
|
|
850
850
|
arguments))
|
851
851
|
|
852
852
|
begin
|
853
|
-
Bunny::Timeout.timeout(
|
853
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
854
854
|
@last_basic_consume_ok = wait_on_continuations
|
855
855
|
end
|
856
856
|
rescue Exception => e
|
@@ -900,7 +900,7 @@ module Bunny
|
|
900
900
|
consumer.arguments))
|
901
901
|
|
902
902
|
begin
|
903
|
-
Bunny::Timeout.timeout(
|
903
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
904
904
|
@last_basic_consume_ok = wait_on_continuations
|
905
905
|
end
|
906
906
|
rescue Exception => e
|
@@ -935,7 +935,7 @@ module Bunny
|
|
935
935
|
def basic_cancel(consumer_tag)
|
936
936
|
@connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, false))
|
937
937
|
|
938
|
-
Bunny::Timeout.timeout(
|
938
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
939
939
|
@last_basic_cancel_ok = wait_on_continuations
|
940
940
|
end
|
941
941
|
|
@@ -1009,7 +1009,7 @@ module Bunny
|
|
1009
1009
|
opts[:if_unused],
|
1010
1010
|
opts[:if_empty],
|
1011
1011
|
false))
|
1012
|
-
Bunny::Timeout.timeout(
|
1012
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1013
1013
|
@last_queue_delete_ok = wait_on_continuations
|
1014
1014
|
end
|
1015
1015
|
raise_if_continuation_resulted_in_a_channel_error!
|
@@ -1029,7 +1029,7 @@ module Bunny
|
|
1029
1029
|
|
1030
1030
|
@connection.send_frame(AMQ::Protocol::Queue::Purge.encode(@id, name, false))
|
1031
1031
|
|
1032
|
-
Bunny::Timeout.timeout(
|
1032
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1033
1033
|
@last_queue_purge_ok = wait_on_continuations
|
1034
1034
|
end
|
1035
1035
|
raise_if_continuation_resulted_in_a_channel_error!
|
@@ -1065,7 +1065,7 @@ module Bunny
|
|
1065
1065
|
(opts[:routing_key] || opts[:key]),
|
1066
1066
|
false,
|
1067
1067
|
opts[:arguments]))
|
1068
|
-
Bunny::Timeout.timeout(
|
1068
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1069
1069
|
@last_queue_bind_ok = wait_on_continuations
|
1070
1070
|
end
|
1071
1071
|
|
@@ -1100,7 +1100,7 @@ module Bunny
|
|
1100
1100
|
exchange_name,
|
1101
1101
|
opts[:routing_key],
|
1102
1102
|
opts[:arguments]))
|
1103
|
-
Bunny::Timeout.timeout(
|
1103
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1104
1104
|
@last_queue_unbind_ok = wait_on_continuations
|
1105
1105
|
end
|
1106
1106
|
|
@@ -1139,7 +1139,7 @@ module Bunny
|
|
1139
1139
|
opts.fetch(:internal, false),
|
1140
1140
|
false, # nowait
|
1141
1141
|
opts[:arguments]))
|
1142
|
-
Bunny::Timeout.timeout(
|
1142
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1143
1143
|
@last_exchange_declare_ok = wait_on_continuations
|
1144
1144
|
end
|
1145
1145
|
|
@@ -1164,7 +1164,7 @@ module Bunny
|
|
1164
1164
|
name,
|
1165
1165
|
opts[:if_unused],
|
1166
1166
|
false))
|
1167
|
-
Bunny::Timeout.timeout(
|
1167
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1168
1168
|
@last_exchange_delete_ok = wait_on_continuations
|
1169
1169
|
end
|
1170
1170
|
|
@@ -1208,7 +1208,7 @@ module Bunny
|
|
1208
1208
|
opts[:routing_key],
|
1209
1209
|
false,
|
1210
1210
|
opts[:arguments]))
|
1211
|
-
Bunny::Timeout.timeout(
|
1211
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1212
1212
|
@last_exchange_bind_ok = wait_on_continuations
|
1213
1213
|
end
|
1214
1214
|
|
@@ -1252,7 +1252,7 @@ module Bunny
|
|
1252
1252
|
opts[:routing_key],
|
1253
1253
|
false,
|
1254
1254
|
opts[:arguments]))
|
1255
|
-
Bunny::Timeout.timeout(
|
1255
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1256
1256
|
@last_exchange_unbind_ok = wait_on_continuations
|
1257
1257
|
end
|
1258
1258
|
|
@@ -1280,7 +1280,7 @@ module Bunny
|
|
1280
1280
|
raise_if_no_longer_open!
|
1281
1281
|
|
1282
1282
|
@connection.send_frame(AMQ::Protocol::Channel::Flow.encode(@id, active))
|
1283
|
-
Bunny::Timeout.timeout(
|
1283
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1284
1284
|
@last_channel_flow_ok = wait_on_continuations
|
1285
1285
|
end
|
1286
1286
|
raise_if_continuation_resulted_in_a_channel_error!
|
@@ -1301,7 +1301,7 @@ module Bunny
|
|
1301
1301
|
raise_if_no_longer_open!
|
1302
1302
|
|
1303
1303
|
@connection.send_frame(AMQ::Protocol::Tx::Select.encode(@id))
|
1304
|
-
Bunny::Timeout.timeout(
|
1304
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1305
1305
|
@last_tx_select_ok = wait_on_continuations
|
1306
1306
|
end
|
1307
1307
|
raise_if_continuation_resulted_in_a_channel_error!
|
@@ -1317,7 +1317,7 @@ module Bunny
|
|
1317
1317
|
raise_if_no_longer_open!
|
1318
1318
|
|
1319
1319
|
@connection.send_frame(AMQ::Protocol::Tx::Commit.encode(@id))
|
1320
|
-
Bunny::Timeout.timeout(
|
1320
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1321
1321
|
@last_tx_commit_ok = wait_on_continuations
|
1322
1322
|
end
|
1323
1323
|
raise_if_continuation_resulted_in_a_channel_error!
|
@@ -1332,7 +1332,7 @@ module Bunny
|
|
1332
1332
|
raise_if_no_longer_open!
|
1333
1333
|
|
1334
1334
|
@connection.send_frame(AMQ::Protocol::Tx::Rollback.encode(@id))
|
1335
|
-
Bunny::Timeout.timeout(
|
1335
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1336
1336
|
@last_tx_rollback_ok = wait_on_continuations
|
1337
1337
|
end
|
1338
1338
|
raise_if_continuation_resulted_in_a_channel_error!
|
@@ -1379,7 +1379,7 @@ module Bunny
|
|
1379
1379
|
@confirms_callback = callback
|
1380
1380
|
|
1381
1381
|
@connection.send_frame(AMQ::Protocol::Confirm::Select.encode(@id, false))
|
1382
|
-
Bunny::Timeout.timeout(
|
1382
|
+
Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout) do
|
1383
1383
|
@last_confirm_select_ok = wait_on_continuations
|
1384
1384
|
end
|
1385
1385
|
@confirm_mode = true
|
data/lib/bunny/cruby/socket.rb
CHANGED
@@ -13,8 +13,12 @@ module Bunny
|
|
13
13
|
READ_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
|
14
14
|
READ_RETRY_EXCEPTION_CLASSES << IO::WaitReadable if IO.const_defined?(:WaitReadable)
|
15
15
|
|
16
|
+
# IO::WaitWritable is 1.9+ only
|
17
|
+
WRITE_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
|
18
|
+
WRITE_RETRY_EXCEPTION_CLASSES << IO::WaitWritable if IO.const_defined?(:WaitWritable)
|
19
|
+
|
16
20
|
def self.open(host, port, options = {})
|
17
|
-
Timeout.timeout(options[:
|
21
|
+
Timeout.timeout(options[:connect_timeout], ClientTimeout) do
|
18
22
|
sock = new(host, port)
|
19
23
|
if ::Socket.constants.include?('TCP_NODELAY') || ::Socket.constants.include?(:TCP_NODELAY)
|
20
24
|
sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
|
@@ -62,22 +66,32 @@ module Bunny
|
|
62
66
|
# if this is not appropriate in your case.
|
63
67
|
#
|
64
68
|
# @param [String] data Data to write
|
69
|
+
# @param [Integer] timeout Timeout
|
65
70
|
#
|
66
71
|
# @api public
|
67
|
-
def write_nonblock_fully(data)
|
72
|
+
def write_nonblock_fully(data, timeout = nil)
|
68
73
|
return nil if @__bunny_socket_eof_flag__
|
69
74
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
75
|
+
length = data.bytesize
|
76
|
+
total_count = 0
|
77
|
+
count = 0
|
78
|
+
loop do
|
79
|
+
begin
|
80
|
+
count = self.write_nonblock(data)
|
81
|
+
rescue *WRITE_RETRY_EXCEPTION_CLASSES
|
82
|
+
if IO.select([], [self], nil, timeout)
|
83
|
+
retry
|
84
|
+
else
|
85
|
+
raise Timeout::Error, "IO timeout when writing to socket"
|
86
|
+
end
|
74
87
|
end
|
75
|
-
|
76
|
-
|
77
|
-
|
88
|
+
|
89
|
+
total_count += count
|
90
|
+
return total_count if total_count >= length
|
91
|
+
data = data.byteslice(count..-1)
|
78
92
|
end
|
79
93
|
|
80
|
-
data.bytesize
|
81
94
|
end
|
95
|
+
|
82
96
|
end
|
83
97
|
end
|
@@ -12,6 +12,9 @@ module Bunny
|
|
12
12
|
READ_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
|
13
13
|
READ_RETRY_EXCEPTION_CLASSES << IO::WaitReadable if IO.const_defined?(:WaitReadable)
|
14
14
|
|
15
|
+
# IO::WaitWritable is 1.9+ only
|
16
|
+
WRITE_RETRY_EXCEPTION_CLASSES = [Errno::EAGAIN, Errno::EWOULDBLOCK]
|
17
|
+
WRITE_RETRY_EXCEPTION_CLASSES << IO::WaitWritable if IO.const_defined?(:WaitWritable)
|
15
18
|
|
16
19
|
# Reads given number of bytes with an optional timeout
|
17
20
|
#
|
@@ -50,6 +53,51 @@ module Bunny
|
|
50
53
|
end
|
51
54
|
value
|
52
55
|
end
|
56
|
+
|
57
|
+
# Writes provided data using IO#write_nonblock, taking care of handling
|
58
|
+
# of exceptions it raises when writing would fail (e.g. due to socket buffer
|
59
|
+
# being full).
|
60
|
+
#
|
61
|
+
# IMPORTANT: this method will mutate (slice) the argument. Pass in duplicates
|
62
|
+
# if this is not appropriate in your case.
|
63
|
+
#
|
64
|
+
# @param [String] data Data to write
|
65
|
+
# @param [Integer] timeout Timeout
|
66
|
+
#
|
67
|
+
# @api public
|
68
|
+
def write_nonblock_fully(data, timeout = nil)
|
69
|
+
return nil if @__bunny_socket_eof_flag__
|
70
|
+
|
71
|
+
length = data.bytesize
|
72
|
+
total_count = 0
|
73
|
+
count = 0
|
74
|
+
loop do
|
75
|
+
begin
|
76
|
+
count = self.write_nonblock(data)
|
77
|
+
rescue OpenSSL::SSL::SSLError => e
|
78
|
+
if e.message == "write would block"
|
79
|
+
if IO.select([], [self], nil, timeout)
|
80
|
+
retry
|
81
|
+
else
|
82
|
+
raise Timeout::Error, "IO timeout when writing to socket"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
raise e
|
86
|
+
rescue *WRITE_RETRY_EXCEPTION_CLASSES
|
87
|
+
if IO.select([], [self], nil, timeout)
|
88
|
+
retry
|
89
|
+
else
|
90
|
+
raise Timeout::Error, "IO timeout when writing to socket"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
total_count += count
|
95
|
+
return total_count if total_count >= length
|
96
|
+
data = data.byteslice(count..-1)
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
53
101
|
end
|
54
102
|
rescue LoadError => le
|
55
103
|
puts "Could not load OpenSSL"
|
data/lib/bunny/session.rb
CHANGED
@@ -154,7 +154,7 @@ module Bunny
|
|
154
154
|
@network_recovery_interval = opts.fetch(:network_recovery_interval, DEFAULT_NETWORK_RECOVERY_INTERVAL)
|
155
155
|
@recover_from_connection_close = opts.fetch(:recover_from_connection_close, false)
|
156
156
|
# in ms
|
157
|
-
@continuation_timeout
|
157
|
+
@continuation_timeout = opts.fetch(:continuation_timeout, DEFAULT_CONTINUATION_TIMEOUT)
|
158
158
|
|
159
159
|
@status = :not_connected
|
160
160
|
@blocked = false
|
@@ -279,9 +279,11 @@ module Bunny
|
|
279
279
|
self.start_reader_loop if threaded?
|
280
280
|
|
281
281
|
rescue TCPConnectionFailed => e
|
282
|
-
self.initialize_transport
|
283
282
|
|
284
283
|
@logger.warn e.message
|
284
|
+
|
285
|
+
self.initialize_transport
|
286
|
+
|
285
287
|
@logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
|
286
288
|
|
287
289
|
return self.start
|
@@ -299,11 +301,11 @@ module Bunny
|
|
299
301
|
self
|
300
302
|
end
|
301
303
|
|
302
|
-
# Socket operation timeout used by this connection
|
304
|
+
# Socket operation write timeout used by this connection
|
303
305
|
# @return [Integer]
|
304
306
|
# @private
|
305
|
-
def
|
306
|
-
@transport.
|
307
|
+
def transport_write_timeout
|
308
|
+
@transport.write_timeout
|
307
309
|
end
|
308
310
|
|
309
311
|
# Opens a new channel and returns it. This method will block the calling
|
@@ -645,8 +647,9 @@ module Bunny
|
|
645
647
|
sleep @network_recovery_interval
|
646
648
|
@logger.debug "About to start connection recovery..."
|
647
649
|
|
648
|
-
self.reset_host_index # since we are starting a fresh try.
|
649
650
|
self.initialize_transport
|
651
|
+
|
652
|
+
@logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}"
|
650
653
|
self.start
|
651
654
|
|
652
655
|
if open?
|
@@ -654,6 +657,9 @@ module Bunny
|
|
654
657
|
|
655
658
|
recover_channels
|
656
659
|
end
|
660
|
+
rescue HostListDepleted
|
661
|
+
reset_host_index
|
662
|
+
retry
|
657
663
|
rescue TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError => e
|
658
664
|
@logger.warn "TCP connection failed, reconnecting in #{@network_recovery_interval} seconds"
|
659
665
|
sleep @network_recovery_interval
|
@@ -990,6 +996,10 @@ module Bunny
|
|
990
996
|
@logger.debug "Heartbeat interval negotiation: client = #{@client_heartbeat}, server = #{connection_tune.heartbeat}, result = #{@heartbeat}"
|
991
997
|
@logger.info "Heartbeat interval used (in seconds): #{@heartbeat}"
|
992
998
|
|
999
|
+
# We set the read_write_timeout to twice the heartbeat value
|
1000
|
+
# This allows us to miss a single heartbeat before we time out the socket.
|
1001
|
+
@transport.read_timeout = @heartbeat * 2
|
1002
|
+
|
993
1003
|
# if there are existing channels we've just recovered from
|
994
1004
|
# a network failure and need to fix the allocated set. See issue 205. MK.
|
995
1005
|
if @channels.empty?
|
@@ -1079,7 +1089,13 @@ module Bunny
|
|
1079
1089
|
def initialize_transport
|
1080
1090
|
if host = @hosts[ @host_index ]
|
1081
1091
|
@host_index_mutex.synchronize { @host_index += 1 }
|
1092
|
+
@transport.close rescue nil # Let's make sure the previous transport socket is closed
|
1082
1093
|
@transport = Transport.new(self, host, @port, @opts.merge(:session_thread => @origin_thread))
|
1094
|
+
|
1095
|
+
# Reset the cached progname for the logger
|
1096
|
+
@logger.progname = to_s if @logger.respond_to?(:progname)
|
1097
|
+
|
1098
|
+
@transport
|
1083
1099
|
else
|
1084
1100
|
raise HostListDepleted
|
1085
1101
|
end
|
data/lib/bunny/transport.rb
CHANGED
@@ -21,12 +21,19 @@ module Bunny
|
|
21
21
|
|
22
22
|
# Default TCP connection timeout
|
23
23
|
DEFAULT_CONNECTION_TIMEOUT = 5.0
|
24
|
+
|
24
25
|
DEFAULT_READ_TIMEOUT = 5.0
|
25
26
|
DEFAULT_WRITE_TIMEOUT = 5.0
|
26
27
|
|
27
|
-
|
28
|
+
# Default TLS protocol version to use.
|
29
|
+
# Currently SSLv3, same as in RabbitMQ Java client
|
30
|
+
DEFAULT_TLS_PROTOCOL = "SSLv3"
|
31
|
+
|
32
|
+
attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout
|
28
33
|
attr_reader :tls_context
|
29
34
|
|
35
|
+
attr_writer :read_timeout
|
36
|
+
|
30
37
|
def initialize(session, host, port, opts)
|
31
38
|
@session = session
|
32
39
|
@session_thread = opts[:session_thread]
|
@@ -37,11 +44,17 @@ module Bunny
|
|
37
44
|
@logger = session.logger
|
38
45
|
@tls_enabled = tls_enabled?(opts)
|
39
46
|
|
40
|
-
@
|
41
|
-
@
|
47
|
+
@read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT
|
48
|
+
@read_timeout = nil if @read_timeout == 0
|
49
|
+
|
50
|
+
@write_timeout = opts[:socket_timeout] # Backwards compatability
|
51
|
+
|
52
|
+
@write_timeout ||= opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT
|
53
|
+
@write_timeout = nil if @write_timeout == 0
|
54
|
+
|
42
55
|
@connect_timeout = self.timeout_from(opts)
|
43
56
|
@connect_timeout = nil if @connect_timeout == 0
|
44
|
-
@disconnect_timeout = @
|
57
|
+
@disconnect_timeout = @write_timeout || @read_timeout || @connect_timeout
|
45
58
|
|
46
59
|
@writes_mutex = @session.mutex_impl.new
|
47
60
|
|
@@ -92,26 +105,20 @@ module Bunny
|
|
92
105
|
block.call(@tls_context) if @tls_context
|
93
106
|
end
|
94
107
|
|
95
|
-
# Writes data to the socket. If read/write timeout was specified
|
96
|
-
#
|
108
|
+
# Writes data to the socket. If read/write timeout was specified the operation will return after that
|
109
|
+
# amount of time has elapsed waiting for the socket.
|
97
110
|
#
|
98
111
|
# @raise [ClientTimeout]
|
99
112
|
def write(data)
|
113
|
+
return write_without_timeout(data) unless @write_timeout
|
114
|
+
|
100
115
|
begin
|
101
|
-
if
|
102
|
-
|
103
|
-
|
104
|
-
@writes_mutex.synchronize { @socket.write(data) }
|
105
|
-
@socket.flush
|
106
|
-
end
|
107
|
-
end
|
108
|
-
else
|
109
|
-
if open?
|
110
|
-
@writes_mutex.synchronize { @socket.write(data) }
|
111
|
-
@socket.flush
|
116
|
+
if open?
|
117
|
+
@writes_mutex.synchronize do
|
118
|
+
@socket.write_nonblock_fully(data, @write_timeout)
|
112
119
|
end
|
113
120
|
end
|
114
|
-
rescue SystemCallError,
|
121
|
+
rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
|
115
122
|
@logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})"
|
116
123
|
close
|
117
124
|
@status = :not_connected
|
@@ -181,8 +188,20 @@ module Bunny
|
|
181
188
|
@socket.flush if @socket
|
182
189
|
end
|
183
190
|
|
184
|
-
def read_fully(
|
185
|
-
|
191
|
+
def read_fully(count)
|
192
|
+
begin
|
193
|
+
@socket.read_fully(count, @read_timeout)
|
194
|
+
rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e
|
195
|
+
@logger.error "Got an exception when receiving data: #{e.message} (#{e.class.name})"
|
196
|
+
close
|
197
|
+
@status = :not_connected
|
198
|
+
|
199
|
+
if @session.automatically_recover?
|
200
|
+
@session.handle_network_failure(e)
|
201
|
+
else
|
202
|
+
@session_thread.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e))
|
203
|
+
end
|
204
|
+
end
|
186
205
|
end
|
187
206
|
|
188
207
|
def read_ready?(timeout = nil)
|
@@ -190,11 +209,10 @@ module Bunny
|
|
190
209
|
io && io[0].include?(@socket)
|
191
210
|
end
|
192
211
|
|
193
|
-
|
194
212
|
# Exposed primarily for Bunny::Channel
|
195
213
|
# @private
|
196
214
|
def read_next_frame(opts = {})
|
197
|
-
header =
|
215
|
+
header = read_fully(7)
|
198
216
|
# TODO: network issues here will sometimes cause
|
199
217
|
# the socket method return an empty string. We need to log
|
200
218
|
# and handle this better.
|
@@ -204,8 +222,8 @@ module Bunny
|
|
204
222
|
# puts "Got AMQ::Protocol::EmptyResponseError, header is #{header.inspect}"
|
205
223
|
# end
|
206
224
|
type, channel, size = AMQ::Protocol::Frame.decode_header(header)
|
207
|
-
payload =
|
208
|
-
frame_end =
|
225
|
+
payload = read_fully(size)
|
226
|
+
frame_end = read_fully(1)
|
209
227
|
|
210
228
|
# 1) the size is miscalculated
|
211
229
|
if payload.bytesize != size
|
@@ -221,7 +239,7 @@ module Bunny
|
|
221
239
|
def self.reacheable?(host, port, timeout)
|
222
240
|
begin
|
223
241
|
s = Bunny::SocketImpl.open(host, port,
|
224
|
-
:
|
242
|
+
:connect_timeout => timeout)
|
225
243
|
|
226
244
|
true
|
227
245
|
rescue SocketError, Timeout::Error => e
|
@@ -239,7 +257,7 @@ module Bunny
|
|
239
257
|
begin
|
240
258
|
@socket = Bunny::SocketImpl.open(@host, @port,
|
241
259
|
:keepalive => @opts[:keepalive],
|
242
|
-
:
|
260
|
+
:connect_timeout => @connect_timeout)
|
243
261
|
rescue StandardError, ClientTimeout => e
|
244
262
|
@status = :not_connected
|
245
263
|
raise Bunny::TCPConnectionFailed.new(e, self.hostname, self.port)
|
@@ -294,7 +312,7 @@ module Bunny
|
|
294
312
|
|
295
313
|
|
296
314
|
def inline_client_certificate_from(opts)
|
297
|
-
opts[:tls_certificate] || opts[:ssl_cert_string]
|
315
|
+
opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert]
|
298
316
|
end
|
299
317
|
|
300
318
|
def inline_client_key_from(opts)
|
@@ -313,7 +331,7 @@ module Bunny
|
|
313
331
|
@tls_ca_certificates = opts.fetch(:tls_ca_certificates, default_tls_certificates)
|
314
332
|
@verify_peer = opts[:verify_ssl] || opts[:verify_peer]
|
315
333
|
|
316
|
-
@tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new
|
334
|
+
@tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new)
|
317
335
|
end
|
318
336
|
|
319
337
|
def wrap_in_tls_socket(socket)
|
@@ -347,7 +365,7 @@ module Bunny
|
|
347
365
|
end
|
348
366
|
end
|
349
367
|
|
350
|
-
def initialize_tls_context(ctx
|
368
|
+
def initialize_tls_context(ctx)
|
351
369
|
ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate) if @tls_certificate
|
352
370
|
ctx.key = OpenSSL::PKey::RSA.new(@tls_key) if @tls_key
|
353
371
|
ctx.cert_store = if @tls_certificate_store
|
@@ -366,15 +384,17 @@ module Bunny
|
|
366
384
|
@logger.warn "Using TLS but no client private key is provided!"
|
367
385
|
end
|
368
386
|
|
387
|
+
# setting TLS/SSL version only works correctly when done
|
388
|
+
# vis set_params. MK.
|
389
|
+
ctx.set_params(:ssl_version => @opts.fetch(:tls_protocol, DEFAULT_TLS_PROTOCOL))
|
390
|
+
|
369
391
|
verify_mode = if @verify_peer
|
370
392
|
OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
371
393
|
else
|
372
394
|
OpenSSL::SSL::VERIFY_NONE
|
373
395
|
end
|
374
|
-
ctx.verify_mode = verify_mode
|
375
396
|
|
376
|
-
|
377
|
-
ctx.ssl_version = ssl_version if ssl_version
|
397
|
+
ctx.set_params(:verify_mode => verify_mode)
|
378
398
|
|
379
399
|
ctx
|
380
400
|
end
|
@@ -397,13 +417,23 @@ module Bunny
|
|
397
417
|
end
|
398
418
|
|
399
419
|
def initialize_tls_certificate_store(certs)
|
400
|
-
|
401
|
-
|
420
|
+
cert_files = []
|
421
|
+
cert_inlines = []
|
422
|
+
certs.each do |cert|
|
423
|
+
if File.readable? cert
|
424
|
+
cert_files.push(cert)
|
425
|
+
else
|
426
|
+
cert_inlines.push(cert)
|
427
|
+
end
|
428
|
+
end
|
429
|
+
@logger.debug "Using CA certificates at #{cert_files.join(', ')}"
|
430
|
+
@logger.debug "Using #{cert_inlines.count} inline ca_certificates"
|
402
431
|
if certs.empty?
|
403
432
|
@logger.error "No CA certificates found, add one with :tls_ca_certificates"
|
404
433
|
end
|
405
434
|
OpenSSL::X509::Store.new.tap do |store|
|
406
|
-
|
435
|
+
cert_files.each { |path| store.add_file(path) }
|
436
|
+
cert_inlines.each { |cert| store.add_cert(OpenSSL::X509::Certificate.new(cert)) }
|
407
437
|
end
|
408
438
|
end
|
409
439
|
|
data/lib/bunny/version.rb
CHANGED
@@ -124,51 +124,4 @@ unless ENV["CI"]
|
|
124
124
|
|
125
125
|
include_examples "successful TLS connection"
|
126
126
|
end
|
127
|
-
|
128
|
-
|
129
|
-
describe "TLS connection to RabbitMQ with ssl_version SSLv3 specified" do
|
130
|
-
let(:connection) do
|
131
|
-
c = Bunny.new(:user => "bunny_gem",
|
132
|
-
:password => "bunny_password",
|
133
|
-
:vhost => "bunny_testbed",
|
134
|
-
:tls => true,
|
135
|
-
:ssl_version => :SSLv3,
|
136
|
-
:tls_ca_certificates => ["./spec/tls/cacert.pem"])
|
137
|
-
c.start
|
138
|
-
c
|
139
|
-
end
|
140
|
-
|
141
|
-
after :each do
|
142
|
-
connection.close
|
143
|
-
end
|
144
|
-
|
145
|
-
include_examples "successful TLS connection"
|
146
|
-
|
147
|
-
it "connects using SSLv3" do
|
148
|
-
connection.transport.socket.ssl_version.should == "SSLv3"
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
describe "TLS connection to RabbitMQ with tls_version TLSv1 specified" do
|
153
|
-
let(:connection) do
|
154
|
-
c = Bunny.new(:user => "bunny_gem",
|
155
|
-
:password => "bunny_password",
|
156
|
-
:vhost => "bunny_testbed",
|
157
|
-
:tls => true,
|
158
|
-
:tls_protocol => :TLSv1,
|
159
|
-
:tls_ca_certificates => ["./spec/tls/cacert.pem"])
|
160
|
-
c.start
|
161
|
-
c
|
162
|
-
end
|
163
|
-
|
164
|
-
after :each do
|
165
|
-
connection.close
|
166
|
-
end
|
167
|
-
|
168
|
-
include_examples "successful TLS connection"
|
169
|
-
|
170
|
-
it "connects using TLSv1" do
|
171
|
-
connection.transport.socket.ssl_version.should == "TLSv1"
|
172
|
-
end
|
173
|
-
end
|
174
127
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bunny
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Duncan
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2014-10-
|
15
|
+
date: 2014-10-21 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: amq-protocol
|
@@ -28,7 +28,7 @@ dependencies:
|
|
28
28
|
- - '>='
|
29
29
|
- !ruby/object:Gem::Version
|
30
30
|
version: 1.9.2
|
31
|
-
description: Easy to use, feature complete Ruby client for RabbitMQ
|
31
|
+
description: Easy to use, feature complete Ruby client for RabbitMQ 3.3 and later
|
32
32
|
versions.
|
33
33
|
email:
|
34
34
|
- celldee@gmail.com
|
@@ -225,9 +225,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
225
225
|
version: '0'
|
226
226
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
227
227
|
requirements:
|
228
|
-
- - '
|
228
|
+
- - '>'
|
229
229
|
- !ruby/object:Gem::Version
|
230
|
-
version:
|
230
|
+
version: 1.3.1
|
231
231
|
requirements: []
|
232
232
|
rubyforge_project:
|
233
233
|
rubygems_version: 2.2.2
|