Permalink
Browse files

Refactor sockets and corresponding tests

  • Loading branch information...
1 parent 853abe2 commit 8f56d1b61409f9b8c2a50d1f445fa1bec168b94f @durran durran committed Feb 12, 2014
@@ -15,51 +15,93 @@
module Mongo
class Pool
- # This class models the database connections and their behavior.
+ # This class models the socket connections and their behavior.
+ #
+ # @since 3.0.0
class Connection
+ # The default time in seconds to timeout a connection attempt.
+ #
+ # @since 3.0.0
TIMEOUT = 5
- attr_reader :host, :port, :timeout
+ # @return [ String ] host The host to connect to.
+ attr_reader :host
- def initialize(host, port, timeout = nil, options = {})
- @host = host
- @port = port
- @timeout = timeout || TIMEOUT
- @socket = nil
- @ssl_opts = options.reject { |k, v| !k.to_s.start_with?('ssl') }
- end
+ # @return [ Integer ] port The port to connect on.
+ attr_reader :port
- def connect
- # if host && port.nil?
- # @socket = Socket::Unix.new(host, timeout)
- # else
- # if ssl_opts && !ssl_opts.empty?
- # socket = Socket::SSL.new(host, port, timeout, ssl_opts)
- # else
- # socket = Socket::TCP.new(host, port, timeout)
- # end
- # end
+ # @return [ Float ] timeout The connection timeout.
+ attr_reader :timeout
+
+ # Tell the underlying socket to establish a connection to the host.
+ #
+ # @example Connect to the host.
+ # connection.connect!
+ #
+ # @note This method mutates the connection class by setting a socket if
+ # one previously did not exist.
+ #
+ # @return [ true ] If the connection succeeded.
+ #
+ # @since 3.0.0
+ def connect!
+ @socket = Socket.create(host, port, timeout, ssl_opts) unless socket
+ socket.connect! and true
end
- def disconnect
- if @socket
- @socket.close
+ # Disconnect the connection.
+ #
+ # @example Disconnect from the host.
+ # connection.disconnect!
+ #
+ # @note This method mutates the connection by setting the socket to nil
+ # if the closing succeeded.
+ #
+ # @return [ true ] If the disconnect succeeded.
+ #
+ # @since 3.0.0
+ def disconnect!
+ if socket
+ socket.close
@socket = nil
end
+ true
+ end
+
+ # Initialize a new socket connection from the client to the server.
+ #
+ # @example Create the connection.
+ # Connection.new('127.0.0.1', 27017, 10)
+ #
+ # @param [ String ] host The host to connect to.
+ # @param [ Integer ] port The port to connect to.
+ # @param [ Float ] timeout The connection timeout.
+ # @param [ Hash ] options The connection options.
+ #
+ # @since 3.0.0
+ def initialize(host, port, timeout = nil, options = {})
+ @host = host
+ @port = port
+ @timeout = timeout || TIMEOUT
+ @ssl_opts = options.reject { |k, v| !k.to_s.start_with?('ssl') }
+ @socket = nil
end
def read
- # Protocol::Reply.deserialize(@socket).documents
end
def write(message)
- # @socket.write(message.serialize)
end
private
attr_reader :socket, :ssl_opts
+
+ def connected
+ connect! if socket.nil? || !socket.alive?
+ yield socket
+ end
end
end
end
@@ -24,6 +24,40 @@ module Socket
module Base
include ::Socket::Constants
+ # @return [ String ] host The host to connect to.
+ attr_reader :host
+
+ # @return [ Float ] timeout The connection timeout.
+ attr_reader :timeout
+
+ # Determine if the socket is alive.
+ #
+ # @example Is the socket alive?
+ # socket.alive?
+ #
+ # @return [ true, false ] If the socket is alive.
+ #
+ # @since 3.0.0
+ def alive?
+ begin
+ Kernel::select([ @socket ], nil, [ @socket ], 0) ? !eof? : true
+ rescue
+ false
+ end
+ end
+
+ # Close the wrapped socket.
+ #
+ # @example Close the wrapped socket.
+ # socket.close
+ #
+ # @return [ true ] True if the socket completed closing.
+ #
+ # @since 3.0.0
+ def close
+ @socket.close and true
+ end
+
# Reads data from the socket instance.
#
# @example
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+require 'mongo/pool/socket/ssl/context'
+
module Mongo
class Pool
module Socket
@@ -21,86 +23,86 @@ class SSL
include Socket::Base
include OpenSSL
+ # @return [ OpenSSL::SSL::SSLContext ] context The SSL context.
+ attr_reader :context
+
+ # @return [ Integer ] port The port to connect to.
+ attr_reader :port
+
+ # Establishes the socket connection and performs
+ # optional SSL valiation.
+ #
+ # @example Connect the SSL socket.
+ # sock.connect
+ #
+ # @note This method is mutable in that the wrapped socket is set.
+ #
+ # @return [ SSL ] The connected socket instance.
+ #
+ # @since 3.0.0
+ def connect!
+ Timeout.timeout(timeout, Mongo::SocketTimeoutError) do
+ @socket = handle_connect
+
+ # Apply ssl wrapper and perform handshake.
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket, context)
+ ssl_socket.sync_close = true
+ ssl_socket.connect
+
+ # Perform peer cert validation if needed.
+ if verifying_certificate?
+ unless OpenSSL::SSL.verify_certificate_identity(ssl_socket.peer_cert, host)
+ raise Mongo::SocketError, 'SSL handshake failed due to a hostname mismatch.'
+ end
+ end
+ self
+ end
+ end
+
# Initializes a new TCP socket with SSL support.
#
- # @example
+ # @example Create the new SSL socket.
# SSL.new('::1', 30, 27017)
# SSL.new('127.0.0.1', 30, 27017)
- # SSL.new('127.0.0.1', 30, 27017, :connect => false)
+ # SSL.new('127.0.0.1', 30, 27017)
#
- # @param host [String] The hostname or IP address.
- # @param port [Integer] The port number.
- # @param timeout [Integer] The socket timeout value.
- # @param opts [Hash] Optional settings and configuration values.
+ # @param host [ String ] The hostname or IP address.
+ # @param port [ Integer ] The port number.
+ # @param timeout [ Integer ] The socket timeout value.
+ # @param opts [ Hash ] Optional settings and configuration values.
#
- # @option opts [true, false] :connect (true) If true calls connect
+ # @option opts [ true, false ] :connect (true) If true calls connect
# before returning the object instance.
- # @option opts [String] :ssl_cert (nil) Path to the certificate file
+ # @option opts [ String ] :ssl_cert (nil) Path to the certificate file
# used to identify the local connection against MongoDB.
- # @option opts [String] :ssl_key (nil) Path to the private key file
+ # @option opts [ String ] :ssl_key (nil) Path to the private key file
# used to identify the local connection against MongoDB. If included
# in the ssl certificate file then only :ssl_cert is needed.
- # @option opts [true, false] :ssl_verify (nil) Specifies whether or
+ # @option opts [ true, false ] :ssl_verify (nil) Specifies whether or
# not peer certificate validation should occur.
- # @option opts [String] :ssl_ca_cert (nil) Path to the :ca_certs file
+ # @option opts [ String ] :ssl_ca_cert (nil) Path to the :ca_certs file
# containing a set of concatenated "certification authority"
# certificates, which are used to validate the certificates returned
# from the other end of the socket connection. Implies :ssl_verify.
#
- # @return [SSL] The SSL socket instance.
+ # @since 3.0.0
def initialize(host, port, timeout, opts = {})
@host = host
@port = port
@timeout = timeout
-
- @context = OpenSSL::SSL::SSLContext.new
-
- # client SSL certificate
- if opts[:ssl_cert]
- @context.cert =
- OpenSSL::X509::Certificate.new(File.open(opts[:ssl_cert]))
- end
-
- # client private key file (optional if included in cert)
- if opts[:ssl_key]
- @context.key = OpenSSL::PKey::RSA.new(File.open(opts[:ssl_key]))
- end
-
- # peer certificate validation
- if opts[:ssl_verify] || opts[:ssl_ca_cert]
- @ssl_verify = true
- @context.ca_file = opts[:ca_cert]
- @context.verify_mode = OpenSSL::SSL::VERIFY_PEER
- end
+ @context = Context.create(opts)
end
- # Establishes the socket connection and performs
- # optional SSL valiation.
+ # Does this socket verify it's certificate on connection?
#
- # @example
- # sock = SSL.new('::1', 27017, 30)
- # sock.connect
+ # @example Is the socket verifying it's certificate?
+ # socket.verifying_certificate?
#
- # @return [Socket] The connected socket instance.
- def connect
- Timeout.timeout(@timeout, Mongo::SocketTimeoutError) do
- @socket = handle_connect
-
- # apply ssl wrapper and perform handshake
- @ssl_socket = OpenSSL::SSL::SSLSocket.new(@socket, @context)
- @ssl_socket.sync_close = true
- @ssl_socket.connect
-
- # perform peer cert validation if needed
- if @ssl_verify
- unless OpenSSL::SSL.verify_certificate_identity(
- @ssl_socket.peer_cert, @host)
-
- raise Mongo::SocketError, 'SSL handshake failed due ' +
- 'to a hostname mismatch.'
- end
- end
- end
+ # @return [ true, false ] If the certificate is verified.
+ #
+ # @since 3.0.0
+ def verifying_certificate?
+ context.verify_mode == OpenSSL::SSL::VERIFY_PEER
end
end
end
@@ -0,0 +1,63 @@
+# Copyright (C) 2009-2014 MongoDB, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module Mongo
+ class Pool
+ module Socket
+ class SSL
+
+ # Factory module for creating SSL context objects.
+ #
+ # @since 3.0.0
+ module Context
+
+ class << self
+
+ # Create the new SSL context based off the provided options.
+ #
+ # @example Create the SSL context.
+ # Context.create(ssl_cert: '/path/to/file')
+ #
+ # @param [ Hash ] options The SSL options.
+ #
+ # @return [ OpenSSL::SSL::SSLContext ] The created context.
+ #
+ # @since 3.0.0
+ def create(opts = {})
+ context = OpenSSL::SSL::SSLContext.new
+
+ # Client SSL certificate.
+ if opts[:ssl_cert]
+ context.cert = OpenSSL::X509::Certificate.new(File.open(opts[:ssl_cert]))
+ end
+
+ # Client private key file (optional if included in cert).
+ if opts[:ssl_key]
+ context.key = OpenSSL::PKey::RSA.new(File.open(opts[:ssl_key]))
+ end
+
+ # Peer certificate validation.
+ if opts[:ssl_verify] || opts[:ssl_ca_cert]
+ context.ca_file = opts[:ssl_ca_cert]
+ context.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ end
+
+ context
+ end
+ end
+ end
+ end
+ end
+ end
+end
Oops, something went wrong.

2 comments on commit 8f56d1b

Contributor

TylerBrock replied Feb 12, 2014

Slow Clap

Contributor

durran replied Feb 12, 2014

Ha!

Please sign in to comment.