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