Packages
gnat
1.13.1
1.16.0
1.15.2
1.15.1
1.15.0
1.14.1
1.14.0
1.14.0-rc1
1.14.0-rc0
1.13.1
1.13.0
1.12.1
1.12.0
1.11.1
1.11.0
1.10.2
1.10.2-rc1
1.10.2-rc0
1.10.1
1.10.0
1.9.1
1.9.0
1.8.5
1.8.4
1.8.3
1.8.2
1.8.1
1.7.1
1.7.0
1.6.0
1.5.2
1.5.1
1.5.0
1.4.0
1.3.0
1.3.0-rc0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
0.7.0
0.6.1
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
A nats client in pure elixir. Resilience, Performance, Ease-of-Use.
Current section
Files
Jump to
Current section
Files
lib/gnat/handshake.ex
defmodule Gnat.Handshake do
@moduledoc false
alias Gnat.Parsec
@doc """
This function handles all of the variations of establishing a connection to
a nats server and just returns {:ok, socket} or {:error, reason}
"""
def connect(settings) do
host = settings.host |> to_charlist
case :gen_tcp.connect(host, settings.port, settings.tcp_opts, settings.connection_timeout) do
{:ok, tcp} -> perform_handshake(tcp, settings)
result -> result
end
end
def negotiate_settings(server_settings, user_settings) do
auth_required = server_settings[:auth_required] || user_settings[:auth_required] || false
%{verbose: false}
|> negotiate_auth(server_settings, user_settings, auth_required)
|> negotiate_headers(server_settings, user_settings)
|> negotiate_no_responders(server_settings, user_settings)
end
defp perform_handshake(tcp, user_settings) do
receive do
{:tcp, ^tcp, operation} ->
{_, [{:info, server_settings}]} = Parsec.parse(Parsec.new(), operation)
{:ok, socket} = upgrade_connection(tcp, user_settings)
settings = negotiate_settings(server_settings, user_settings)
:ok = send_connect(user_settings, settings, socket)
{:ok, socket, server_settings}
after
1000 ->
{:error, "timed out waiting for info"}
end
end
defp send_connect(%{tls: true}, settings, socket) do
:ssl.send(socket, "CONNECT " <> Jason.encode!(settings, maps: :strict) <> "\r\n")
end
defp send_connect(_, settings, socket) do
:gen_tcp.send(socket, "CONNECT " <> Jason.encode!(settings, maps: :strict) <> "\r\n")
end
defp negotiate_auth(
settings,
_server,
%{username: username, password: password} = _user,
true = _auth_required
) do
Map.merge(settings, %{user: username, pass: password})
end
defp negotiate_auth(settings, _server, %{token: token} = _user, true = _auth_required) do
Map.merge(settings, %{auth_token: token})
end
defp negotiate_auth(
settings,
%{nonce: nonce} = _server,
%{nkey_seed: seed, jwt: jwt} = _user,
true = _auth_required
) do
{:ok, nkey} = NKEYS.from_seed(seed)
signature = NKEYS.sign(nkey, nonce) |> Base.url_encode64() |> String.replace("=", "")
Map.merge(settings, %{sig: signature, protocol: 1, jwt: jwt})
end
defp negotiate_auth(
settings,
%{nonce: nonce} = _server,
%{nkey_seed: seed} = _user,
true = _auth_required
) do
{:ok, nkey} = NKEYS.from_seed(seed)
signature = NKEYS.sign(nkey, nonce) |> Base.url_encode64() |> String.replace("=", "")
public = NKEYS.public_nkey(nkey)
Map.merge(settings, %{sig: signature, protocol: 1, nkey: public})
end
defp negotiate_auth(settings, _server, _user, _auth_required) do
settings
end
defp negotiate_headers(settings, %{headers: true} = _server, user_settings) do
if Map.get(user_settings, :headers, true) do
Map.put(settings, :headers, true)
else
Map.put(settings, :headers, false)
end
end
defp negotiate_headers(_settings, _server, %{headers: true} = _user) do
raise "NATS Server does not support headers, but your connection settings specify header support"
end
defp negotiate_headers(settings, _server, _user) do
settings
end
defp negotiate_no_responders(%{headers: true} = settings, _server_settings, %{
no_responders: true
}) do
Map.put(settings, :no_responders, true)
end
defp negotiate_no_responders(settings, _server_settings, _user_settings) do
settings
end
defp upgrade_connection(tcp, %{tls: true, ssl_opts: opts}) do
:ok = :inet.setopts(tcp, active: true)
:ssl.connect(tcp, opts, 1_000)
end
defp upgrade_connection(tcp, _settings), do: {:ok, tcp}
end