1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
# frozen_string_literal: true
require 'openssl'
require 'rbconfig'
module Dalli
##
# Various socket implementations used by Dalli.
##
module Socket
##
# Common methods for all socket implementations.
##
module InstanceMethods
def readfull(count)
value = +''
loop do
result = read_nonblock(count - value.bytesize, exception: false)
value << result if append_to_buffer?(result)
break if value.bytesize == count
end
value
end
def read_available
value = +''
loop do
result = read_nonblock(8196, exception: false)
break if WAIT_RCS.include?(result)
raise Errno::ECONNRESET, "Connection reset: #{logged_options.inspect}" unless result
value << result
end
value
end
WAIT_RCS = %i[wait_writable wait_readable].freeze
def append_to_buffer?(result)
raise Timeout::Error, "IO timeout: #{logged_options.inspect}" if nonblock_timed_out?(result)
raise Errno::ECONNRESET, "Connection reset: #{logged_options.inspect}" unless result
!WAIT_RCS.include?(result)
end
def nonblock_timed_out?(result)
return true if result == :wait_readable && !wait_readable(options[:socket_timeout])
# TODO: Do we actually need this? Looks to be only used in read_nonblock
result == :wait_writable && !wait_writable(options[:socket_timeout])
end
FILTERED_OUT_OPTIONS = %i[username password].freeze
def logged_options
options.reject { |k, _| FILTERED_OUT_OPTIONS.include? k }
end
end
##
# Wraps the below TCP socket class in the case where the client
# has configured a TLS/SSL connection between Dalli and the
# Memcached server.
##
class SSLSocket < ::OpenSSL::SSL::SSLSocket
include Dalli::Socket::InstanceMethods
def options
io.options
end
unless method_defined?(:wait_readable)
def wait_readable(timeout = nil)
to_io.wait_readable(timeout)
end
end
unless method_defined?(:wait_writable)
def wait_writable(timeout = nil)
to_io.wait_writable(timeout)
end
end
end
##
# A standard TCP socket between the Dalli client and the Memcached server.
##
class TCP < TCPSocket
include Dalli::Socket::InstanceMethods
attr_accessor :options, :server
def self.open(host, port, server, options = {})
Timeout.timeout(options[:socket_timeout]) do
sock = new(host, port)
sock.options = { host: host, port: port }.merge(options)
sock.server = server
init_socket_options(sock, options)
options[:ssl_context] ? wrapping_ssl_socket(sock, host, options[:ssl_context]) : sock
end
end
def self.init_socket_options(sock, options)
sock.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true)
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options[:keepalive]
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_RCVBUF, options[:rcvbuf]) if options[:rcvbuf]
sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_SNDBUF, options[:sndbuf]) if options[:sndbuf]
end
def self.wrapping_ssl_socket(tcp_socket, host, ssl_context)
ssl_socket = Dalli::Socket::SSLSocket.new(tcp_socket, ssl_context)
ssl_socket.hostname = host
ssl_socket.sync_close = true
ssl_socket.connect
ssl_socket
end
end
if /mingw|mswin/.match?(RbConfig::CONFIG['host_os'])
##
# UNIX domain sockets are not supported on Windows platforms.
##
class UNIX
def initialize(*_args)
raise Dalli::DalliError, 'Unix sockets are not supported on Windows platform.'
end
end
else
##
# UNIX represents a UNIX domain socket, which is an interprocess communication
# mechanism between processes on the same host. Used when the Memcached server
# is running on the same machine as the Dalli client.
##
class UNIX < UNIXSocket
include Dalli::Socket::InstanceMethods
attr_accessor :options, :server
def self.open(path, server, options = {})
Timeout.timeout(options[:socket_timeout]) do
sock = new(path)
sock.options = { path: path }.merge(options)
sock.server = server
sock
end
end
end
end
end
end
|