lib/async/io/ssl_socket.rb in async-io-1.4.0 vs lib/async/io/ssl_socket.rb in async-io-1.5.0

- old
+ new

@@ -26,72 +26,104 @@ module IO SSLError = OpenSSL::SSL::SSLError # Asynchronous TCP socket wrapper. class SSLSocket < Generic - wraps ::OpenSSL::SSL::SSLSocket, :alpn_protocol, :cert, :cipher, :client_ca, :hostname=, :npn_protocol, :peer_cert, :peer_cert_chain, :pending, :post_connection_check, :session, :session=, :session_reused?, :ssl_version, :state + wraps ::OpenSSL::SSL::SSLSocket, :alpn_protocol, :cert, :cipher, :client_ca, :close, :context, :hostname, :hostname=, :npn_protocol, :peer_cert, :peer_cert_chain, :pending, :post_connection_check, :session, :session=, :session_reused?, :ssl_version, :state, :sync_close, :sync_close=, :sysclose, :verify_result, :tmp_key wrap_blocking_method :accept, :accept_nonblock wrap_blocking_method :connect, :connect_nonblock + alias syswrite write + alias sysread read + + # It's hard to know what #to_io / #io should do. So, they are omitted. + + def self.connect(socket, context, hostname = nil, &block) + client = self.wrap(socket, context) + + # Used for SNI: + if hostname + client.hostname = hostname + end + + begin + client.connect + rescue + # If the connection fails (e.g. certificates are invalid), the caller never sees the socket, so we close it and raise the exception up the chain. + client.close + + raise + end + + return client unless block_given? + + begin + yield client + ensure + client.close + end + end + def local_address @io.to_io.local_address end def remote_address @io.to_io.remote_address end - # This method/implementation might change in the future, don't depend on it :) - def self.connect_socket(socket, context) - io = wrapped_klass.new(socket.to_io, context) + def self.wrap(socket, context) + io = @wrapped_klass.new(socket.to_io, context) # This ensures that when the internal IO is closed, it also closes the internal socket: io.sync_close = true return self.new(io, socket.reactor) end end + # We reimplement this from scratch because the native implementation doesn't expose the underlying server/context that we need to implement non-blocking accept. class SSLServer extend Forwardable def initialize(server, context) @server = server @context = context end - def_delegators :@server, :local_address, :setsockopt, :getsockopt + def_delegators :@server, :local_address, :setsockopt, :getsockopt, :close attr :server attr :context - include ServerSocket - def listen(*args) @server.listen(*args) end def accept(task: Task.current) peer, address = @server.accept - wrapper = SSLSocket.connect_socket(peer, @context) + wrapper = SSLSocket.wrap(peer, @context) return wrapper, address unless block_given? task.async do task.annotate "accepting secure connection #{address}" begin + # You want to do this in a nested async task or you might suffer from head-of-line blocking. wrapper.accept yield wrapper, address rescue SSLError Async.logger.error($!.class) {$!} ensure wrapper.close end end end + + include Server end end end