Packages
thousand_island
1.0.0-pre.6
1.5.0
1.4.3
1.4.2
1.4.1
1.4.0
1.3.14
1.3.13
1.3.12
1.3.11
1.3.10
1.3.9
1.3.8
1.3.7
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.0
1.1.0
1.0.0
1.0.0-pre.7
1.0.0-pre.6
1.0.0-pre.5
1.0.0-pre.4
1.0.0-pre.3
1.0.0-pre.2
1.0.0-pre.1
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.17
0.5.16
0.5.15
0.5.14
0.5.13
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
A simple & modern pure Elixir socket server
Current section
Files
Jump to
Current section
Files
lib/thousand_island/socket.ex
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.
"""
@enforce_keys [:socket, :transport_module, :read_timeout, :span]
defstruct @enforce_keys
@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(raw_socket, server_config, span) do
%__MODULE__{
socket: raw_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, reason} = err ->
ThousandIsland.Telemetry.stop_span(socket.span, %{}, %{error: reason})
err
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} = ok ->
ThousandIsland.Telemetry.untimed_span_event(socket.span, :recv, %{data: data})
ok
{:error, reason} = err ->
ThousandIsland.Telemetry.span_event(socket.span, :recv_error, %{error: reason})
err
end
end
@doc """
Sends the given data (specified as a binary or an IO list) on the given socket.
"""
@spec send(t(), iodata()) :: 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, reason} = err ->
ThousandIsland.Telemetry.span_event(socket.span, :send_error, %{data: data, error: reason})
err
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} = ok ->
measurements = %{filename: filename, offset: offset, bytes_written: bytes_written}
ThousandIsland.Telemetry.untimed_span_event(socket.span, :sendfile, measurements)
ok
{:error, reason} = err ->
measurements = %{filename: filename, offset: offset, length: length, error: reason}
ThousandIsland.Telemetry.span_event(socket.span, :sendfile_error, measurements)
err
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 sockname(t()) :: ThousandIsland.Transport.on_sockname()
def sockname(%__MODULE__{} = socket) do
socket.transport_module.sockname(socket.socket)
end
@doc """
Returns information in the form of `t:ThousandIsland.Transport.socket_info()` about the remote end of the socket.
"""
@spec peername(t()) :: ThousandIsland.Transport.on_peername()
def peername(%__MODULE__{} = socket) do
socket.transport_module.peername(socket.socket)
end
@doc """
Returns information in the form of `t:public_key.der_encoded()` about the peer certificate of the socket.
"""
@spec peercert(t()) :: ThousandIsland.Transport.on_peercert()
def peercert(%__MODULE__{} = socket) do
socket.transport_module.peercert(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.on_negotiated_protocol()
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