Class: Async::Redis::Endpoint

Inherits:
IO::Endpoint::Generic
  • Object
show all
Defined in:
lib/async/redis/endpoint.rb

Overview

Represents a way to connect to a remote Redis server.

Constant Summary collapse

LOCALHOST =
URI::Generic.build(scheme: "redis", host: "localhost").freeze
SCHEMES =
{
	"redis" => URI::Generic,
	"rediss" => URI::Generic,
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(url, endpoint = nil, **options) ⇒ Endpoint

Create a new endpoint.

Parameters:

  • scheme (Hash)

    a customizable set of options

  • hostname (Hash)

    a customizable set of options

  • port (Hash)

    a customizable set of options

  • ssl_context (Hash)

    a customizable set of options

Raises:

  • (ArgumentError)


122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/async/redis/endpoint.rb', line 122

def initialize(url, endpoint = nil, **options)
	super(**options)
	
	raise ArgumentError, "URL must be absolute (include scheme, host): #{url}" unless url.absolute?
	
	@url = url
	
	if endpoint
		@endpoint = self.build_endpoint(endpoint)
	else
		@endpoint = nil
	end
end

Instance Attribute Details

#urlObject (readonly)

Returns the value of attribute url.



160
161
162
# File 'lib/async/redis/endpoint.rb', line 160

def url
  @url
end

Class Method Details

.[](object) ⇒ Object

Coerce the given object into an endpoint.



106
107
108
109
110
111
112
# File 'lib/async/redis/endpoint.rb', line 106

def self.[](object)
	if object.is_a?(self)
		return object
	else
		self.parse(object.to_s)
	end
end

.for(scheme, host, port: nil, database: nil, **options) ⇒ Object

Construct an endpoint with a specified scheme, hostname, optional path, and options. If no scheme is provided, it will be auto-detected based on SSL context.



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
# File 'lib/async/redis/endpoint.rb', line 77

def self.for(scheme, host, port: nil, database: nil, **options)
	# Auto-detect scheme if not provided:
	if default_scheme = options.delete(:scheme)
		scheme ||= default_scheme
	end
	
	scheme ||= options[:ssl_context] ? "rediss" : "redis"
	
	uri_klass = SCHEMES.fetch(scheme.downcase) do
		raise ArgumentError, "Unsupported scheme: #{scheme.inspect}"
	end
	
	if database
		path = "/#{database}"
	end
	
	self.new(
		uri_klass.build(
			scheme: scheme,
			host: host,
			port: port,
			path: path,
		),
		**options
	)
end

.local(**options) ⇒ Object

Create a local Redis endpoint.



32
33
34
# File 'lib/async/redis/endpoint.rb', line 32

def self.local(**options)
	self.new(LOCALHOST, **options)
end

.parse(string, endpoint = nil, **options) ⇒ Object

Parse a Redis URL string into an endpoint.



65
66
67
68
69
# File 'lib/async/redis/endpoint.rb', line 65

def self.parse(string, endpoint = nil, **options)
	url = URI.parse(string)
	
	return self.new(url, endpoint, **options)
end

.remote(host, port = 6379, **options) ⇒ Object

Create a remote Redis endpoint.



41
42
43
44
# File 'lib/async/redis/endpoint.rb', line 41

def self.remote(host, port = 6379, **options)
	# URI::Generic.build automatically handles IPv6 addresses correctly:
	self.new(URI::Generic.build(scheme: "redis", host: host, port: port), **options)
end

.unix(path, **options) ⇒ Object

Create a local Redis endpoint from a UNIX socket path.



50
51
52
53
# File 'lib/async/redis/endpoint.rb', line 50

def self.unix(path, **options)
	endpoint = ::IO::Endpoint.unix(path)
	self.new(URI::Generic.build(scheme: "redis", path:), endpoint, **options)
end

Instance Method Details

#addressObject

Get the address of the underlying endpoint.



164
165
166
# File 'lib/async/redis/endpoint.rb', line 164

def address
	endpoint.address
end

#bind(*arguments, &block) ⇒ Object

Bind to the endpoint and yield the server socket.



305
306
307
# File 'lib/async/redis/endpoint.rb', line 305

def bind(*arguments, &block)
	endpoint.bind(*arguments, &block)
end

#build_endpoint(endpoint = nil) ⇒ Object

Build the underlying endpoint with optional SSL wrapping.



275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/async/redis/endpoint.rb', line 275

def build_endpoint(endpoint = nil)
	endpoint ||= make_endpoint
	
	if secure?
		# Wrap it in SSL:
		return ::IO::Endpoint::SSLEndpoint.new(endpoint,
			ssl_context: self.ssl_context,
			hostname: @url.hostname,
			timeout: self.timeout,
		)
	end
	
	return endpoint
end

#connect(&block) ⇒ Object

Connect to the endpoint and yield the client socket.



311
312
313
# File 'lib/async/redis/endpoint.rb', line 311

def connect(&block)
	endpoint.connect(&block)
end

#credentialsObject

Get the credentials for authentication.



233
234
235
# File 'lib/async/redis/endpoint.rb', line 233

def credentials
	@options[:credentials] || extract_userinfo(@url.userinfo)
end

#databaseObject

Get the database number for this endpoint.



221
222
223
# File 'lib/async/redis/endpoint.rb', line 221

def database
	@options[:database] || extract_database(@url.path)
end

#default_portObject

Get the default port for Redis connections.



192
193
194
# File 'lib/async/redis/endpoint.rb', line 192

def default_port
	6379
end

#default_port?Boolean

Check if the endpoint is using the default port.

Returns:

  • (Boolean)


198
199
200
# File 'lib/async/redis/endpoint.rb', line 198

def default_port?
	port == default_port
end

#eachObject

Iterate over each possible endpoint variation.



317
318
319
320
321
322
323
# File 'lib/async/redis/endpoint.rb', line 317

def each
	return to_enum unless block_given?
	
	self.tcp_endpoint.each do |endpoint|
		yield self.class.new(@url, endpoint, **@options)
	end
end

#endpointObject

Get the underlying endpoint, building it if necessary.



292
293
294
# File 'lib/async/redis/endpoint.rb', line 292

def endpoint
	@endpoint ||= build_endpoint
end

#endpoint=(endpoint) ⇒ Object

Set the underlying endpoint.



298
299
300
# File 'lib/async/redis/endpoint.rb', line 298

def endpoint=(endpoint)
	@endpoint = build_endpoint(endpoint)
end

#eql?(other) ⇒ Boolean

Check if this endpoint is equal to another.

Returns:

  • (Boolean)


334
335
336
# File 'lib/async/redis/endpoint.rb', line 334

def eql? other
	self.key.eql? other.key
end

#hashObject

Get the hash code for this endpoint.



340
341
342
# File 'lib/async/redis/endpoint.rb', line 340

def hash
	self.key.hash
end

#hostnameObject

The hostname is the server we are connecting to:



209
210
211
# File 'lib/async/redis/endpoint.rb', line 209

def hostname
	@options[:hostname] || @url.hostname
end

#inspectObject

Convert the endpoint to an inspectable string.



156
157
158
# File 'lib/async/redis/endpoint.rb', line 156

def inspect
	"\#<#{self.class} #{self.to_url} #{@options.inspect}>"
end

#keyObject

Get the key for hashing and equality comparison.



327
328
329
# File 'lib/async/redis/endpoint.rb', line 327

def key
	[@url, @options]
end

#localhost?Boolean

Check if the endpoint is connecting to localhost.

Returns:

  • (Boolean)


249
250
251
# File 'lib/async/redis/endpoint.rb', line 249

def localhost?
	@url.hostname =~ /^(.*?\.)?localhost\.?$/
end

#portObject

Get the port for this endpoint.



204
205
206
# File 'lib/async/redis/endpoint.rb', line 204

def port
	@options[:port] || @url.port || default_port
end

#protocolObject

Get the protocol for this endpoint.



176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/async/redis/endpoint.rb', line 176

def protocol
	protocol = @options.fetch(:protocol, Protocol::RESP2)
	
	if credentials = self.credentials
		protocol = Protocol::Authenticated.new(credentials, protocol)
	end
	
	if database = self.database
		protocol = Protocol::Selected.new(database, protocol)
	end
	
	return protocol
end

#schemeObject

Get the scheme for this endpoint.



215
216
217
# File 'lib/async/redis/endpoint.rb', line 215

def scheme
	@options[:scheme] || @url.scheme
end

#secure?Boolean

Check if the connection is secure (using TLS).

Returns:

  • (Boolean)


170
171
172
# File 'lib/async/redis/endpoint.rb', line 170

def secure?
	["rediss"].include?(self.scheme)
end

#ssl_contextObject

Get the SSL context for secure connections.



264
265
266
267
268
269
270
# File 'lib/async/redis/endpoint.rb', line 264

def ssl_context
	@options[:ssl_context] || OpenSSL::SSL::SSLContext.new.tap do |context|
		context.set_params(
			verify_mode: self.ssl_verify_mode
		)
	end
end

#ssl_verify_modeObject

We don't try to validate peer certificates when talking to localhost because they would always be self-signed.



254
255
256
257
258
259
260
# File 'lib/async/redis/endpoint.rb', line 254

def ssl_verify_mode
	if self.localhost?
		OpenSSL::SSL::VERIFY_NONE
	else
		OpenSSL::SSL::VERIFY_PEER
	end
end

#to_sObject

Convert the endpoint to a string representation.



150
151
152
# File 'lib/async/redis/endpoint.rb', line 150

def to_s
	"\#<#{self.class} #{self.to_url} #{@options}>"
end

#to_urlObject

Convert the endpoint to a URL.



138
139
140
141
142
143
144
145
146
# File 'lib/async/redis/endpoint.rb', line 138

def to_url
	url = @url.dup
	
	unless default_port?
		url.port = self.port
	end
	
	return url
end