Fixed en error preventing Thrift::NonblockingServer from working with SSL
diff --git a/lib/rb/lib/thrift/transport/server_socket.rb b/lib/rb/lib/thrift/transport/server_socket.rb
index 60e4b8a..b21c65f 100644
--- a/lib/rb/lib/thrift/transport/server_socket.rb
+++ b/lib/rb/lib/thrift/transport/server_socket.rb
@@ -60,7 +60,7 @@
end
def to_io
- @handle || raise(IOError, 'closed stream')
+ @handle&.to_io || raise(IOError, 'closed stream')
end
def to_s
diff --git a/lib/rb/lib/thrift/transport/socket.rb b/lib/rb/lib/thrift/transport/socket.rb
index b3476ea..7d01e2e 100644
--- a/lib/rb/lib/thrift/transport/socket.rb
+++ b/lib/rb/lib/thrift/transport/socket.rb
@@ -134,7 +134,9 @@
@handle = nil
end
- alias to_io handle
+ def to_io
+ @handle&.to_io || raise(IOError, 'closed stream')
+ end
def to_s
"socket(#{@host}:#{@port})"
diff --git a/lib/rb/spec/nonblocking_server_spec.rb b/lib/rb/spec/nonblocking_server_spec.rb
index abf1597..cfc60ff 100644
--- a/lib/rb/spec/nonblocking_server_spec.rb
+++ b/lib/rb/spec/nonblocking_server_spec.rb
@@ -18,6 +18,7 @@
#
require 'spec_helper'
+require 'timeout'
describe 'NonblockingServer' do
@@ -260,4 +261,102 @@
expect(@server_thread.join(2)).not_to be_nil
end
end
+
+ describe "#{Thrift::NonblockingServer} with TLS transport" do
+ before(:each) do
+ @port = available_port
+ handler = Handler.new
+ processor = SpecNamespace::NonblockingService::Processor.new(handler)
+ @transport = Thrift::SSLServerSocket.new('localhost', @port, create_server_ssl_context)
+ transport_factory = Thrift::FramedTransportFactory.new
+ logger = Logger.new(STDERR)
+ logger.level = Logger::WARN
+ @server = Thrift::NonblockingServer.new(processor, @transport, transport_factory, nil, 5, logger)
+ handler.server = @server
+
+ @server_thread = Thread.new(Thread.current) do |master_thread|
+ begin
+ @server.serve
+ rescue => e
+ master_thread.raise e
+ end
+ end
+
+ @clients = []
+ wait_until_listening
+ end
+
+ after(:each) do
+ @clients.each(&:close)
+ @server.shutdown if @server
+ @server_thread.join(2) if @server_thread
+ @transport.close if @transport
+ end
+
+ it "should handle requests over TLS" do
+ expect(@server_thread).to be_alive
+
+ client = setup_tls_client
+ expect(client.greeting(true)).to eq(SpecNamespace::Hello.new)
+
+ @server.shutdown
+ expect(@server_thread.join(2)).to be_an_instance_of(Thread)
+ end
+
+ def setup_tls_client
+ transport = Thrift::FramedTransport.new(
+ Thrift::SSLSocket.new('localhost', @port, nil, create_client_ssl_context)
+ )
+ protocol = Thrift::BinaryProtocol.new(transport)
+ client = SpecNamespace::NonblockingService::Client.new(protocol)
+ transport.open
+ @clients << transport
+ client
+ end
+
+ def wait_until_listening
+ Timeout.timeout(2) do
+ until @transport.handle
+ raise "Server thread exited unexpectedly" unless @server_thread.alive?
+ sleep 0.01
+ end
+ end
+ end
+
+ def available_port
+ TCPServer.open('localhost', 0) { |server| server.addr[1] }
+ end
+
+ def ssl_keys_dir
+ File.expand_path('../../../test/keys', __dir__)
+ end
+
+ def create_server_ssl_context
+ OpenSSL::SSL::SSLContext.new.tap do |ctx|
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ if ctx.respond_to?(:min_version=) && OpenSSL::SSL.const_defined?(:TLS1_2_VERSION)
+ ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
+ end
+ ctx.ca_file = File.join(ssl_keys_dir, 'CA.pem')
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(File.join(ssl_keys_dir, 'server.crt')))
+ ctx.cert_store = OpenSSL::X509::Store.new
+ ctx.cert_store.add_file(File.join(ssl_keys_dir, 'client.pem'))
+ ctx.key = OpenSSL::PKey::RSA.new(File.read(File.join(ssl_keys_dir, 'server.key')))
+ end
+ end
+
+ def create_client_ssl_context
+ OpenSSL::SSL::SSLContext.new.tap do |ctx|
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ if ctx.respond_to?(:min_version=) && OpenSSL::SSL.const_defined?(:TLS1_2_VERSION)
+ ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
+ end
+ ctx.ca_file = File.join(ssl_keys_dir, 'CA.pem')
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(File.join(ssl_keys_dir, 'client.crt')))
+ ctx.cert_store = OpenSSL::X509::Store.new
+ ctx.cert_store.add_file(File.join(ssl_keys_dir, 'server.pem'))
+ ctx.key = OpenSSL::PKey::RSA.new(File.read(File.join(ssl_keys_dir, 'client.key')))
+ end
+ end
+ end
end
diff --git a/lib/rb/spec/ssl_server_socket_spec.rb b/lib/rb/spec/ssl_server_socket_spec.rb
index 82e6518..20cfdf4 100644
--- a/lib/rb/spec/ssl_server_socket_spec.rb
+++ b/lib/rb/spec/ssl_server_socket_spec.rb
@@ -27,6 +27,18 @@
@socket = Thrift::SSLServerSocket.new(1234)
end
+ it "should delegate to_io to the underlying SSL server handle" do
+ tcp_server = double("TCPServer")
+ ssl_server = double("SSLServer")
+
+ allow(TCPServer).to receive(:new).with(nil, 1234).and_return(tcp_server)
+ allow(OpenSSL::SSL::SSLServer).to receive(:new).with(tcp_server, nil).and_return(ssl_server)
+ allow(ssl_server).to receive(:to_io).and_return(tcp_server)
+
+ @socket.listen
+ expect(@socket.to_io).to eq(tcp_server)
+ end
+
it "should provide a reasonable to_s" do
expect(@socket.to_s).to eq("ssl(socket(:1234))")
end
diff --git a/lib/rb/spec/ssl_socket_spec.rb b/lib/rb/spec/ssl_socket_spec.rb
index 808d8d5..eb68bcf 100644
--- a/lib/rb/spec/ssl_socket_spec.rb
+++ b/lib/rb/spec/ssl_socket_spec.rb
@@ -35,6 +35,7 @@
allow(@handle).to receive(:connect_nonblock)
allow(@handle).to receive(:close)
allow(@handle).to receive(:post_connection_check)
+ allow(@handle).to receive(:to_io).and_return(@simple_socket_handle)
allow(::Socket).to receive(:new).and_return(@simple_socket_handle)
allow(OpenSSL::SSL::SSLSocket).to receive(:new).and_return(@handle)
@@ -71,6 +72,15 @@
expect(Thrift::SSLSocket.new('localhost', 8080, 5, @context).ssl_context).to eq(@context)
end
+ it "should delegate to_io to the underlying SSL socket handle" do
+ @socket.open
+ expect(@socket.to_io).to eq(@simple_socket_handle)
+ end
+
+ it "should raise IOError when to_io is called on a closed stream" do
+ expect { @socket.to_io }.to raise_error(IOError, 'closed stream')
+ end
+
it "should provide a reasonable to_s" do
expect(Thrift::SSLSocket.new('myhost', 8090).to_s).to eq("ssl(socket(myhost:8090))")
end