defmodule ThousandIsland.Socket do
@moduledoc """
Encapsulates a client connection's underlying socket, providing a facility to
read, write, and otherwise manipulate a connection from a client.
"""
defstruct socket: nil,
transport_module: nil,
read_timeout: nil,
span: nil
@typedoc "A reference to a socket along with metadata describing how to use it"
@type t :: %__MODULE__{
socket: ThousandIsland.Transport.socket(),
transport_module: module(),
read_timeout: timeout(),
span: ThousandIsland.Telemetry.t()
}
@doc false
@spec new(
ThousandIsland.Transport.socket(),
ThousandIsland.ServerConfig.t(),
ThousandIsland.Telemetry.t()
) ::
t()
def new(socket, server_config, span) do
%__MODULE__{
socket: socket,
transport_module: server_config.transport_module,
read_timeout: server_config.read_timeout,
span: span
}
end
@doc """
Handshakes the underlying socket if it is required (as in the case of SSL sockets, for example).
This is normally called internally by `ThousandIsland.Handler` and does not need to be
called by implementations which are based on `ThousandIsland.Handler`
"""
@spec handshake(t()) :: ThousandIsland.Transport.on_handshake()
def handshake(%__MODULE__{} = socket) do
case socket.transport_module.handshake(socket.socket) do
{:ok, _} ->
{:ok, socket}
{:error, error} ->
ThousandIsland.Telemetry.stop_span(socket.span, %{}, %{error: error})
{:error, error}
end
end
@doc """
Returns available bytes on the given socket. Up to `length` bytes will be
returned (0 can be passed in to get the next 'available' bytes, typically the
next packet). If insufficient bytes are available, the function can wait `timeout`
milliseconds for data to arrive.
"""
@spec recv(t(), non_neg_integer(), timeout() | nil) :: ThousandIsland.Transport.on_recv()
def recv(%__MODULE__{} = socket, length \\ 0, timeout \\ nil) do
case socket.transport_module.recv(socket.socket, length, timeout || socket.read_timeout) do
{:ok, data} ->
ThousandIsland.Telemetry.untimed_span_event(socket.span, :recv, %{data: data})
{:ok, data}
{:error, error} ->
ThousandIsland.Telemetry.span_event(socket.span, :recv_error, %{error: error})
{:error, error}
end
end
@doc """
Sends the given data (specified as a binary or an IO list) on the given socket.
"""
@spec send(t(), IO.chardata()) :: ThousandIsland.Transport.on_send()
def send(%__MODULE__{} = socket, data) do
case socket.transport_module.send(socket.socket, data) do
:ok ->
ThousandIsland.Telemetry.untimed_span_event(socket.span, :send, %{data: data})
:ok
{:error, error} ->
ThousandIsland.Telemetry.span_event(socket.span, :send_error, %{data: data, error: error})
{:error, error}
end
end
@doc """
Sends the contents of the given file based on the provided offset & length
"""
@spec sendfile(t(), String.t(), non_neg_integer(), non_neg_integer()) ::
ThousandIsland.Transport.on_sendfile()
def sendfile(%__MODULE__{} = socket, filename, offset, length) do
case socket.transport_module.sendfile(socket.socket, filename, offset, length) do
{:ok, bytes_written} ->
measurements = %{filename: filename, offset: offset, bytes_written: bytes_written}
ThousandIsland.Telemetry.untimed_span_event(socket.span, :sendfile, measurements)
{:ok, bytes_written}
{:error, error} ->
measurements = %{filename: filename, offset: offset, length: length, error: error}
ThousandIsland.Telemetry.span_event(socket.span, :sendfile_error, measurements)
{:error, error}
end
end
@doc """
Shuts down the socket in the given direction.
"""
@spec shutdown(t(), ThousandIsland.Transport.way()) :: ThousandIsland.Transport.on_shutdown()
def shutdown(%__MODULE__{} = socket, way) do
ThousandIsland.Telemetry.span_event(socket.span, :socket_shutdown, %{way: way})
socket.transport_module.shutdown(socket.socket, way)
end
@doc """
Closes the given socket. Note that a socket is automatically closed when the handler
process which owns it terminates
"""
@spec close(t()) :: ThousandIsland.Transport.on_close()
def close(%__MODULE__{} = socket) do
socket.transport_module.close(socket.socket)
end
@doc """
Gets the given flags on the socket
Errors are usually from :inet.posix(), however, SSL module defines return type as any()
"""
@spec getopts(t(), ThousandIsland.Transport.socket_get_options()) ::
ThousandIsland.Transport.on_getopts()
def getopts(%__MODULE__{} = socket, options) do
socket.transport_module.getopts(socket.socket, options)
end
@doc """
Sets the given flags on the socket
Errors are usually from :inet.posix(), however, SSL module defines return type as any()
"""
@spec setopts(t(), ThousandIsland.Transport.socket_set_options()) ::
ThousandIsland.Transport.on_setopts()
def setopts(%__MODULE__{} = socket, options) do
socket.transport_module.setopts(socket.socket, options)
end
@doc """
Returns information in the form of `t:ThousandIsland.Transport.socket_info()` about the local end of the socket.
"""
@spec local_info(t()) :: ThousandIsland.Transport.socket_info()
def local_info(%__MODULE__{} = socket) do
socket.transport_module.local_info(socket.socket)
end
@doc """
Returns information in the form of `t:ThousandIsland.Transport.socket_info()` about the remote end of the socket.
"""
@spec peer_info(t()) :: ThousandIsland.Transport.socket_info()
def peer_info(%__MODULE__{} = socket) do
socket.transport_module.peer_info(socket.socket)
end
@doc """
Returns whether or not this protocol is secure.
"""
@spec secure?(t()) :: boolean()
def secure?(%__MODULE__{} = socket) do
socket.transport_module.secure?()
end
@doc """
Returns statistics about the connection.
"""
@spec getstat(t()) :: ThousandIsland.Transport.socket_stats()
def getstat(%__MODULE__{} = socket) do
socket.transport_module.getstat(socket.socket)
end
@doc """
Returns information about the protocol negotiated during transport handshaking (if any).
"""
@spec negotiated_protocol(t()) :: ThousandIsland.Transport.negotiated_protocol_info()
def negotiated_protocol(%__MODULE__{} = socket) do
socket.transport_module.negotiated_protocol(socket.socket)
end
@doc """
Returns the telemetry span representing the lifetime of this socket
"""
@spec telemetry_span(t()) :: ThousandIsland.Telemetry.t()
def telemetry_span(%__MODULE__{} = socket) do
socket.span
end
end