Async::HTTPSourceAsyncHTTPEndpoint

class Endpoint

Represents a way to connect to a remote HTTP server.

Definitions

def self.parse(string, endpoint = nil, **options)

Parse a URL string into an endpoint.

Signature

parameter string String

The URL to parse.

parameter endpoint IO::Endpoint::Generic | Nil

An optional underlying endpoint to use.

parameter options Hash

Additional options to pass to Async::HTTP::Endpoint#initialize.

returns Endpoint

The parsed endpoint.

Implementation

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

def self.for(scheme, hostname, path = "/", **options)

Construct an endpoint with a specified scheme, hostname, optional path, and options.

Signature

parameter scheme String

The scheme to use, e.g. "http" or "https".

parameter hostname String

The hostname to connect to (or bind to).

parameter *options Hash

Additional options, passed to Async::HTTP::Endpoint#initialize.

Implementation

def self.for(scheme, hostname, path = "/", **options)
	# TODO: Consider using URI.for once it becomes available:
	uri_klass = SCHEMES.fetch(scheme.downcase) do
		raise ArgumentError, "Unsupported scheme: #{scheme.inspect}"
	end
	
	self.new(
		uri_klass.new(scheme, nil, hostname, nil, nil, path, nil, nil, nil).normalize,
		**options
	)
end

def self.[](url)

Coerce the given object into an endpoint.

Signature

parameter url String | Endpoint

The URL or endpoint to convert.

Implementation

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

def initialize(url, endpoint = nil, **options)

Signature

option scheme String

the scheme to use, overrides the URL scheme.

option hostname String

the hostname to connect to (or bind to), overrides the URL hostname (used for SNI).

option port Integer

the port to bind to, overrides the URL port.

option ssl_context OpenSSL::SSL::SSLContext

the context to use for TLS.

option alpn_protocols Array(String)

the alpn protocols to negotiate.

Implementation

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

def to_url

Signature

returns URI

The URL representation of this endpoint, including port if non-default.

Implementation

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

def to_s

Signature

returns String

A short string representation of this endpoint.

Implementation

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

def inspect

Signature

returns String

A detailed string representation of this endpoint.

Implementation

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

def address

Signature

returns Addrinfo

The address of the underlying endpoint.

Implementation

def address
	endpoint.address
end

def secure?

Signature

returns Boolean

Whether this endpoint uses a secure protocol (HTTPS or WSS).

Implementation

def secure?
	["https", "wss"].include?(self.scheme)
end

def protocol

Signature

returns Protocol

The protocol to use for this endpoint.

Implementation

def protocol
	@options.fetch(:protocol) do
		if secure?
			Protocol::HTTPS
		else
			Protocol::HTTP
		end
	end
end

def default_port

Signature

returns Integer

The default port for this endpoint's scheme.

Implementation

def default_port
	secure? ? 443 : 80
end

def default_port?

Signature

returns Boolean

Whether the endpoint's port is the default for its scheme.

Implementation

def default_port?
	port == default_port
end

def port

Signature

returns Integer

The port number for this endpoint.

Implementation

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

def hostname

The hostname is the server we are connecting to:

Implementation

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

def scheme

Signature

returns String

The URL scheme, e.g. "http" or "https".

Implementation

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

def authority(ignore_default_port = true)

Signature

returns String

The authority component (hostname and optional port).

Implementation

def authority(ignore_default_port = true)
	if ignore_default_port and default_port?
		@url.hostname
	else
		"#{@url.hostname}:#{port}"
	end
end

def path

Return the path and query components of the given URL.

Implementation

def path
	buffer = @url.path || "/"
	
	if query = @url.query
		buffer = "#{buffer}?#{query}"
	end
	
	return buffer
end

def alpn_protocols

Signature

returns Array(String)

The ALPN protocol names for TLS negotiation.

Implementation

def alpn_protocols
	@options.fetch(:alpn_protocols){self.protocol.names}
end

def localhost?

Signature

returns Boolean

Whether the endpoint refers to a localhost address.

Implementation

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

def ssl_verify_mode

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

Implementation

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

def ssl_context

Signature

returns OpenSSL::SSL::SSLContext

The SSL context for TLS connections.

Implementation

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

def build_endpoint(endpoint = nil)

Build a suitable endpoint, optionally wrapping in TLS for secure connections.

Signature

parameter endpoint IO::Endpoint::Generic | Nil

An optional underlying endpoint to wrap.

returns IO::Endpoint::Generic

The constructed endpoint.

Implementation

def build_endpoint(endpoint = nil)
	endpoint ||= tcp_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

def endpoint

Signature

returns IO::Endpoint::Generic

The resolved endpoint, built on demand.

Implementation

def endpoint
	@endpoint ||= build_endpoint
end

def endpoint=(endpoint)

Set the underlying endpoint, wrapping it as needed.

Signature

parameter endpoint IO::Endpoint::Generic

The endpoint to assign.

Implementation

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

def bind(*arguments, &block)

Bind to the endpoint.

Implementation

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

def connect(&block)

Connect to the endpoint.

Implementation

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

def each

Enumerate all resolved endpoints.

Signature

yields {|endpoint| ...}

Each resolved endpoint.

parameter endpoint Endpoint

The resolved endpoint.

Implementation

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

def key

Signature

returns Array

A key suitable for identifying this endpoint in a hash.

Implementation

def key
	[@url, @options]
end

def eql?(other)

Signature

returns Boolean

Whether two endpoints are equal.

Implementation

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

def hash

Signature

returns Integer

The hash code for this endpoint.

Implementation

def hash
	self.key.hash
end