Packages
thousand_island
0.5.8
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
test/thousand_island/socket_test.exs
defmodule ThousandIsland.SocketTest do
# False due to telemetry raciness
use ExUnit.Case, async: false
def gen_tcp_setup(_context) do
{:ok, %{client_mod: :gen_tcp, client_opts: [active: false], server_opts: []}}
end
def ssl_setup(_context) do
{:ok,
%{
client_mod: :ssl,
client_opts: [
active: false,
verify: :verify_peer,
cacertfile: Path.join(__DIR__, "../support/ca.pem")
],
server_opts: [
transport_module: ThousandIsland.Transports.SSL,
transport_options: [
certfile: Path.join(__DIR__, "../support/cert.pem"),
keyfile: Path.join(__DIR__, "../support/key.pem"),
alpn_preferred_protocols: ["foo"]
]
]
}}
end
defmodule Echo do
use ThousandIsland.Handler
@impl ThousandIsland.Handler
def handle_connection(socket, state) do
{:ok, data} = ThousandIsland.Socket.recv(socket, 0)
ThousandIsland.Socket.send(socket, data)
{:close, state}
end
end
defmodule Sendfile do
use ThousandIsland.Handler
@impl ThousandIsland.Handler
def handle_connection(socket, state) do
ThousandIsland.Socket.sendfile(socket, Path.join(__DIR__, "../support/sendfile"), 0, 6)
ThousandIsland.Socket.sendfile(socket, Path.join(__DIR__, "../support/sendfile"), 1, 3)
{:close, state}
end
end
defmodule Closer do
use ThousandIsland.Handler
@impl ThousandIsland.Handler
def handle_connection(_socket, state) do
{:close, state}
end
end
defmodule Info do
use ThousandIsland.Handler
@impl ThousandIsland.Handler
def handle_connection(socket, state) do
peer_info = ThousandIsland.Socket.peer_info(socket)
local_info = ThousandIsland.Socket.local_info(socket)
negotiated_protocol = ThousandIsland.Socket.negotiated_protocol(socket)
ThousandIsland.Socket.send(
socket,
"#{inspect([local_info, peer_info, negotiated_protocol])}"
)
{:close, state}
end
end
[:gen_tcp_setup, :ssl_setup]
|> Enum.each(fn setup_fn ->
describe "common behaviour using #{setup_fn}" do
setup setup_fn
test "should send and receive", context do
{:ok, port} = start_handler(Echo, context.server_opts)
{:ok, client} = context.client_mod.connect('localhost', port, context.client_opts)
assert context.client_mod.send(client, "HELLO") == :ok
assert context.client_mod.recv(client, 0) == {:ok, 'HELLO'}
end
test "it should send files", context do
{:ok, port} = start_handler(Sendfile, context.server_opts)
{:ok, client} = context.client_mod.connect('localhost', port, context.client_opts)
assert context.client_mod.recv(client, 9) == {:ok, 'ABCDEFBCD'}
end
test "it should close connections", context do
{:ok, port} = start_handler(Closer, context.server_opts)
{:ok, client} = context.client_mod.connect('localhost', port, context.client_opts)
assert context.client_mod.recv(client, 0) == {:error, :closed}
end
test "it should emit telemetry events as expected", context do
{:ok, collector_pid} = start_collector()
{:ok, port} = start_handler(Echo, context.server_opts)
{:ok, client} = context.client_mod.connect('localhost', port, context.client_opts)
:ok = context.client_mod.send(client, "HELLO")
{:ok, 'HELLO'} = context.client_mod.recv(client, 0)
context.client_mod.close(client)
# Give the server process a chance to shut down
Process.sleep(100)
events = ThousandIsland.TelemetryCollector.get_events(collector_pid)
assert length(events) == 4
assert {[:socket, :handshake], %{}, %{connection_id: connection_id}} = Enum.at(events, 0)
assert {[:socket, :recv], %{result: {:ok, "HELLO"}}, %{connection_id: ^connection_id}} =
Enum.at(events, 1)
assert {[:socket, :send], %{data: "HELLO", result: :ok}, %{connection_id: ^connection_id}} =
Enum.at(events, 2)
assert {[:socket, :close],
%{octets_recv: _, octets_sent: _, packets_recv: _, packets_sent: _},
%{connection_id: ^connection_id}} = Enum.at(events, 3)
end
end
end)
describe "behaviour specific to gen_tcp" do
setup :gen_tcp_setup
test "it should provide correct connection info", context do
{:ok, port} = start_handler(Info, context.server_opts)
{:ok, client} = context.client_mod.connect('localhost', port, context.client_opts)
{:ok, resp} = context.client_mod.recv(client, 0)
{:ok, local_port} = :inet.port(client)
expected = [
%{address: {127, 0, 0, 1}, port: port, ssl_cert: nil},
%{address: {127, 0, 0, 1}, port: local_port, ssl_cert: nil},
{:error, :protocol_not_negotiated}
]
assert to_string(resp) == inspect(expected)
context.client_mod.close(client)
end
end
describe "behaviour specific to ssl" do
setup :ssl_setup
test "it should provide correct connection info", context do
{:ok, port} = start_handler(Info, context.server_opts)
{:ok, client} =
context.client_mod.connect('localhost', port,
active: false,
verify: :verify_peer,
cacertfile: Path.join(__DIR__, "../support/ca.pem"),
alpn_advertised_protocols: ["foo"]
)
{:ok, {_, local_port}} = context.client_mod.sockname(client)
{:ok, resp} = context.client_mod.recv(client, 0)
expected = [
%{address: {127, 0, 0, 1}, port: port, ssl_cert: nil},
%{address: {127, 0, 0, 1}, port: local_port, ssl_cert: nil},
{:ok, "foo"}
]
assert to_string(resp) == inspect(expected)
context.client_mod.close(client)
end
end
defp start_handler(handler, server_args) do
resolved_args = server_args |> Keyword.merge(port: 0, handler_module: handler)
{:ok, server_pid} = start_supervised({ThousandIsland, resolved_args})
{:ok, %{port: port}} = ThousandIsland.listener_info(server_pid)
{:ok, port}
end
defp start_collector do
start_supervised(
{ThousandIsland.TelemetryCollector,
[
[:socket, :handshake],
[:socket, :recv],
[:socket, :send],
[:socket, :sendfile],
[:socket, :shutdown],
[:socket, :close]
]}
)
end
end