Current section
Files
Jump to
Current section
Files
lib/fast_ci/servers/websocket/initialization_logic.ex
defmodule FastCi.Servers.Websocket.InitializationLogic do
alias FastCi.Servers.Websocket
alias FastCi.Loggers.Websocket, as: Logger
@moduledoc """
Module containing the logic for the initialization process of websockets.
This is placed here so that it dosn't clutter the main socket module.
"""
@websocket_receive_timeout 10_000
defp socket_path(params) do
params
|> format_maps_to_json()
|> Map.from_struct()
|> URI.encode_query()
|> append_query_to_test_orchestrators()
end
# It won't work if fully translated to json, only maps
# need to be encoded into json while the top-level query
# params need to be standard URL query ones.
defp format_maps_to_json(params) when is_map(params) do
params
|> Map.to_list()
|> Enum.map(fn {k, v} ->
{k, if(is_map(v), do: Jason.encode!(v), else: v)}
end)
|> Map.new()
end
defp append_query_to_test_orchestrators(query) do
"/test_orchestrators/socket/websocket?" <> query
end
# This is giving back tuple for scheme: (http/ws) or (https/wss) in general
def protocol_schemes(%URI{} = uri) do
{protocol_scheme(uri), protocol_scheme(uri, :ws, :wss)}
end
defp protocol_scheme(%URI{} = uri, non_secure \\ :http, secure \\ :https) do
case uri.scheme do
"ws" -> non_secure
"wss" -> secure
end
end
# Boilerplate code from Mint to connect to a websocket, it's ugly but it's how it should be.
def connect_to_websocket(join_request, uri, http_scheme, ws_scheme, uuid) do
with {:ok, conn} <- Mint.HTTP.connect(http_scheme, uri.host, uri.port, protocols: [:http1]),
{:ok, conn, ref} <-
Mint.WebSocket.upgrade(ws_scheme, conn, socket_path(join_request), []),
{:ok, http_get_message} <- receive_connection_message(uuid),
{:ok, conn, [{:status, ^ref, status}, {:headers, ^ref, resp_headers}, {:done, ^ref}]} <-
Mint.WebSocket.stream(conn, http_get_message),
{:ok, conn, websocket} <- Mint.WebSocket.new(conn, ref, status, resp_headers) do
Logger.info(%Websocket{uuid: uuid}, "Node connected to server: #{uri.host}")
{:ok, conn, websocket, ref}
end
end
# Receive the status from the server during the initial connection setup,
# this is after the upgrade connection.
defp receive_connection_message(uuid) do
receive do
message ->
{:ok, message}
after
@websocket_receive_timeout ->
Logger.warning(%Websocket{uuid: uuid}, "Node failed to connect to server.")
{:error, :timeout}
end
end
# Parse the application environment to use the correct value.
def server_uri() do
with server when is_bitstring(server) <- Application.get_env(:fast_ci, :server, "wss://apx.fast.ci:443") do
{:ok, URI.parse(server)}
else
_ ->
{:error, :invalid_server_url}
end
end
# Phoenix topic for the base connection.
def topic(join_request) do
"test_orchestrator:#{join_request.run_key}-#{join_request.build_id}"
end
end