Add gemini connection endpoint
This commit is contained in:
parent
6622913d52
commit
53bfdbd75d
|
@ -23,8 +23,8 @@ defmodule Chronoscope.Gemini do
|
||||||
|> Enum.map(fn {_, pid, _, _} -> @genserver.call(pid, :list) end)
|
|> Enum.map(fn {_, pid, _, _} -> @genserver.call(pid, :list) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove(host, port) do
|
def remove(host, port, path) do
|
||||||
name = client_name(%{host: host, port: port})
|
name = client_name(%{host: host, port: port, path: path})
|
||||||
|
|
||||||
case @registry.lookup(Gemini.Registry, name) do
|
case @registry.lookup(Gemini.Registry, name) do
|
||||||
[{pid, _}] -> {:ok, @genserver.call(pid, :terminate)}
|
[{pid, _}] -> {:ok, @genserver.call(pid, :terminate)}
|
||||||
|
|
|
@ -18,7 +18,7 @@ defmodule Chronoscope.Gemini.Client do
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
resource: resource,
|
resource: resource,
|
||||||
reponse: {:error, "initializing"},
|
response: {:error, "initializing"},
|
||||||
last_request: DateTime.add(now, -@interval_in_seconds, :second)
|
last_request: DateTime.add(now, -@interval_in_seconds, :second)
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule Chronoscope.Gemini.Response do
|
defmodule Chronoscope.Gemini.Response do
|
||||||
def parse(response) do
|
def parse(response) do
|
||||||
# TODO
|
# TODO
|
||||||
%{status: 20, type: "text/gemini", body: response}
|
%{status: 20, mime_type: "text/gemini", body: to_string(response)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
defmodule ChronoscopeWeb.API.V1.Gemini.ConnectionController do
|
||||||
|
use ChronoscopeWeb, :controller
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
alias Chronoscope.Gemini
|
||||||
|
|
||||||
|
@default_port 1965
|
||||||
|
@default_path "/"
|
||||||
|
@max_host_length 255
|
||||||
|
@gemini Application.compile_env(:chronoscope, :gemini, Gemini)
|
||||||
|
|
||||||
|
def get(conn, %{"host" => host, "port" => port, "path" => path}) do
|
||||||
|
try do
|
||||||
|
handle_get(conn, %{host: host, port: String.to_integer(port), path: path})
|
||||||
|
rescue
|
||||||
|
ArgumentError -> bad_request_response(conn, "invalid port")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(conn, %{"host" => host, "path" => path}) do
|
||||||
|
handle_get(conn, %{host: host, port: @default_port, path: path})
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(conn, %{"host" => host, "port" => port}) do
|
||||||
|
handle_get(conn, %{host: host, port: port, path: @default_path})
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(conn, %{"host" => host}) do
|
||||||
|
handle_get(conn, %{host: host, port: @default_port, path: @default_path})
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(conn, _params) do
|
||||||
|
bad_request_response(conn, "missing host")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_get(conn, %{host: host, port: port, path: path}) when port > 0 and port < 65536 do
|
||||||
|
case connect(host, port, path) do
|
||||||
|
{:ok, response} ->
|
||||||
|
json(conn, %{status: :ok, response: format_response(response)})
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
json(conn, %{status: :error, reason: to_string(error)})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_get(conn, _params) do
|
||||||
|
bad_request_response(conn, "port out of range")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp connect(host, port, path) do
|
||||||
|
# TODO - max path length
|
||||||
|
host
|
||||||
|
|> String.slice(0, @max_host_length)
|
||||||
|
|> @gemini.connect(port, path)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_response(response) do
|
||||||
|
Map.take(response, [:status, :mime_type, :body])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp bad_request_response(conn, message) do
|
||||||
|
conn
|
||||||
|
|> put_status(:bad_request)
|
||||||
|
|> json(%{error: message})
|
||||||
|
end
|
||||||
|
end
|
|
@ -32,6 +32,12 @@ defmodule ChronoscopeWeb.Router do
|
||||||
get "/key-establishment", KeyEstablishmentController, :get
|
get "/key-establishment", KeyEstablishmentController, :get
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scope "/api/v1/gemini", ChronoscopeWeb.API.V1.Gemini do
|
||||||
|
pipe_through :api
|
||||||
|
|
||||||
|
get "/connect", ConnectionController, :get
|
||||||
|
end
|
||||||
|
|
||||||
# Enable LiveDashboard and Swoosh mailbox preview in development
|
# Enable LiveDashboard and Swoosh mailbox preview in development
|
||||||
if Application.compile_env(:chronoscope, :dev_routes) do
|
if Application.compile_env(:chronoscope, :dev_routes) do
|
||||||
# If you want to use the LiveDashboard in production, you should put
|
# If you want to use the LiveDashboard in production, you should put
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
defmodule Chronoscope.Gemini.ClientTest do
|
||||||
|
use Chronoscope.Case, async: true
|
||||||
|
|
||||||
|
alias Chronoscope.Certificate
|
||||||
|
alias Chronoscope.DateTimeMock
|
||||||
|
alias Chronoscope.Gemini.Client
|
||||||
|
alias Chronoscope.SSLMock
|
||||||
|
|
||||||
|
import Mox
|
||||||
|
|
||||||
|
setup :verify_on_exit!
|
||||||
|
|
||||||
|
setup _tags do
|
||||||
|
DateTimeMock
|
||||||
|
|> stub(:utc_now, fn -> ~U[2024-04-21 01:23:45Z] end)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Chronoscope.Gemini.Client.init()" do
|
||||||
|
test "initializes successfully" do
|
||||||
|
assert Client.init(%{host: "localhost", port: 4444, path: "/"}) ==
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
resource: %{host: "localhost", port: 4444, path: "/"},
|
||||||
|
response: {:error, "initializing"},
|
||||||
|
last_request: ~U[2024-04-21 01:23:15Z]
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Chronoscope.Gemini.Client.handle_call()" do
|
||||||
|
test ":terminate" do
|
||||||
|
assert Client.handle_call(:terminate, nil, %{state: true}) == {:stop, :normal, self(), %{state: true}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test ":list" do
|
||||||
|
assert Client.handle_call(:list, nil, %{state: true}) == {:reply, %{state: true}, %{state: true}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test ":connect - cached" do
|
||||||
|
assert Client.handle_call(:connect, nil, %{
|
||||||
|
resource: %{host: "localhost", port: 4444, path: "/"},
|
||||||
|
response: {:error, "initializing"},
|
||||||
|
last_request: ~U[2024-04-21 01:23:45Z]
|
||||||
|
}) ==
|
||||||
|
{:reply, {:error, "initializing"},
|
||||||
|
%{
|
||||||
|
resource: %{host: "localhost", port: 4444, path: "/"},
|
||||||
|
response: {:error, "initializing"},
|
||||||
|
last_request: ~U[2024-04-21 01:23:45Z]
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
|
test ":connect - not cached" do
|
||||||
|
peercert = peercert()
|
||||||
|
peercert_expiration = Certificate.expiration_date(peercert)
|
||||||
|
|
||||||
|
SSLMock
|
||||||
|
|> expect(:connect, fn ~c"localhost", 4444, _, _ -> {:ok, :socket} end)
|
||||||
|
|> expect(:send, fn :socket, _ -> send_ssl_response([]) end)
|
||||||
|
|> expect(:peercert, fn :socket -> {:ok, peercert} end)
|
||||||
|
|> expect(:close, fn :socket -> :ok end)
|
||||||
|
|
||||||
|
assert {:reply, {:ok, %{cert_expiration: ^peercert_expiration}},
|
||||||
|
%{
|
||||||
|
resource: %{host: "localhost", port: 4444, path: "/"},
|
||||||
|
response: {:ok, %{cert_expiration: ^peercert_expiration}},
|
||||||
|
last_request: ~U[2024-04-21 01:23:45Z]
|
||||||
|
}} =
|
||||||
|
Client.handle_call(:connect, nil, %{
|
||||||
|
resource: %{host: "localhost", port: 4444, path: "/"},
|
||||||
|
response: {:error, "initializing"},
|
||||||
|
last_request: ~U[2024-04-21 01:23:00Z]
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,9 +3,9 @@ defmodule Chronoscope.NTS.ClientTest do
|
||||||
|
|
||||||
alias Chronoscope.Certificate
|
alias Chronoscope.Certificate
|
||||||
alias Chronoscope.DateTimeMock
|
alias Chronoscope.DateTimeMock
|
||||||
|
alias Chronoscope.NTS.Client
|
||||||
alias Chronoscope.SSLMock
|
alias Chronoscope.SSLMock
|
||||||
|
|
||||||
import Chronoscope.NTS.Client
|
|
||||||
import Mox
|
import Mox
|
||||||
|
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
@ -19,7 +19,7 @@ defmodule Chronoscope.NTS.ClientTest do
|
||||||
|
|
||||||
describe "Chronoscope.NTS.Client.init()" do
|
describe "Chronoscope.NTS.Client.init()" do
|
||||||
test "initializes successfully" do
|
test "initializes successfully" do
|
||||||
assert init(%{host: "localhost", port: 3333}) ==
|
assert Client.init(%{host: "localhost", port: 3333}) ==
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
server: %{host: "localhost", port: 3333},
|
server: %{host: "localhost", port: 3333},
|
||||||
|
@ -31,26 +31,24 @@ defmodule Chronoscope.NTS.ClientTest do
|
||||||
|
|
||||||
describe "Chronoscope.NTS.Client.handle_call()" do
|
describe "Chronoscope.NTS.Client.handle_call()" do
|
||||||
test ":terminate" do
|
test ":terminate" do
|
||||||
assert handle_call(:terminate, nil, %{state: true}) == {:stop, :normal, self(), %{state: true}}
|
assert Client.handle_call(:terminate, nil, %{state: true}) == {:stop, :normal, self(), %{state: true}}
|
||||||
end
|
end
|
||||||
|
|
||||||
test ":list" do
|
test ":list" do
|
||||||
assert handle_call(:list, nil, %{state: true}) == {:reply, %{state: true}, %{state: true}}
|
assert Client.handle_call(:list, nil, %{state: true}) == {:reply, %{state: true}, %{state: true}}
|
||||||
end
|
end
|
||||||
|
|
||||||
test ":key_establishment - cached" do
|
test ":key_establishment - cached" do
|
||||||
assert handle_call(:key_establishment, nil, %{
|
assert Client.handle_call(:key_establishment, nil, %{
|
||||||
host: "localhost",
|
server: %{host: "localhost", port: 3333},
|
||||||
key_establishment_response: {:error, "initializing"},
|
key_establishment_response: {:error, "initializing"},
|
||||||
last_key_establishment: ~U[2024-03-31 01:23:45Z],
|
last_key_establishment: ~U[2024-03-31 01:23:45Z]
|
||||||
port: 3333
|
|
||||||
}) ==
|
}) ==
|
||||||
{:reply, {:error, "initializing"},
|
{:reply, {:error, "initializing"},
|
||||||
%{
|
%{
|
||||||
host: "localhost",
|
server: %{host: "localhost", port: 3333},
|
||||||
key_establishment_response: {:error, "initializing"},
|
key_establishment_response: {:error, "initializing"},
|
||||||
last_key_establishment: ~U[2024-03-31 01:23:45Z],
|
last_key_establishment: ~U[2024-03-31 01:23:45Z]
|
||||||
port: 3333
|
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -70,7 +68,7 @@ defmodule Chronoscope.NTS.ClientTest do
|
||||||
key_establishment_response: {:ok, %{cert_expiration: ^peercert_expiration}},
|
key_establishment_response: {:ok, %{cert_expiration: ^peercert_expiration}},
|
||||||
last_key_establishment: ~U[2024-03-31 01:23:45Z]
|
last_key_establishment: ~U[2024-03-31 01:23:45Z]
|
||||||
}} =
|
}} =
|
||||||
handle_call(:key_establishment, nil, %{
|
Client.handle_call(:key_establishment, nil, %{
|
||||||
server: %{host: "localhost", port: 3333},
|
server: %{host: "localhost", port: 3333},
|
||||||
key_establishment_response: {:error, "initializing"},
|
key_establishment_response: {:error, "initializing"},
|
||||||
last_key_establishment: ~U[2024-03-31 01:23:00Z]
|
last_key_establishment: ~U[2024-03-31 01:23:00Z]
|
||||||
|
|
Loading…
Reference in New Issue