pleroma/lib/pleroma/web/fed_sockets.ex

186 lines
6.3 KiB
Elixir

# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.FedSockets do
@moduledoc """
This documents the FedSockets framework. A framework for federating
ActivityPub objects between servers via persistant WebSocket connections.
FedSockets allow servers to authenticate on first contact and maintain that
connection, eliminating the need to authenticate every time data needs to be shared.
## Protocol
FedSockets currently support 2 types of data transfer:
* `publish` method which doesn't require a response
* `fetch` method requires a response be sent
### Publish
The publish operation sends a json encoded map of the shape:
%{action: :publish, data: json}
and accepts (but does not require) a reply of form:
%{"action" => "publish_reply"}
The outgoing params represent
* data: ActivityPub object encoded into json
### Fetch
The fetch operation sends a json encoded map of the shape:
%{action: :fetch, data: id, uuid: fetch_uuid}
and requires a reply of form:
%{"action" => "fetch_reply", "uuid" => uuid, "data" => data}
The outgoing params represent
* id: an ActivityPub object URI
* uuid: a unique uuid generated by the sender
The reply params represent
* data: an ActivityPub object encoded into json
* uuid: the uuid sent along with the fetch request
## Examples
Clients of FedSocket transfers shouldn't need to use any of the functions outside of this module.
A typical publish operation can be performed through the following code, and a fetch operation in a similar manner.
case FedSockets.get_or_create_fed_socket(inbox) do
{:ok, fedsocket} ->
FedSockets.publish(fedsocket, json)
_ ->
alternative_publish(inbox, actor, json, params)
end
## Configuration
FedSockets have the following config settings
config :pleroma, :fed_sockets,
enabled: true,
ping_interval: :timer.seconds(15),
connection_duration: :timer.hours(1),
rejection_duration: :timer.hours(1),
fed_socket_fetches: [
default: 12_000,
interval: 3_000,
lazy: false
]
* enabled - turn FedSockets on or off with this flag. Can be toggled at runtime.
* connection_duration - How long a FedSocket can sit idle before it's culled.
* rejection_duration - After failing to make a FedSocket connection a host will be excluded
from further connections for this amount of time
* fed_socket_fetches - Use these parameters to pass options to the Cachex queue backing the FetchRegistry
* fed_socket_rejections - Use these parameters to pass options to the Cachex queue backing the FedRegistry
Cachex options are
* default: the minimum amount of time a fetch can wait before it times out.
* interval: the interval between checks for timed out entries. This plus the default represent the maximum time allowed
* lazy: leave at false for consistant and fast lookups, set to true for stricter timeout enforcement
"""
require Logger
alias Pleroma.Web.FedSockets.FedRegistry
alias Pleroma.Web.FedSockets.FedSocket
alias Pleroma.Web.FedSockets.SocketInfo
@doc """
returns a FedSocket for the given origin. Will reuse an existing one or create a new one.
address is expected to be a fully formed URL such as:
"http://www.example.com" or "http://www.example.com:8080"
It can and usually does include additional path parameters,
but these are ignored as the FedSockets are organized by host and port info alone.
"""
def get_or_create_fed_socket(address) do
with {:cache, {:error, :missing}} <- {:cache, get_fed_socket(address)},
{:connect, {:ok, _pid}} <- {:connect, FedSocket.connect_to_host(address)},
{:cache, {:ok, fed_socket}} <- {:cache, get_fed_socket(address)} do
Logger.debug("fedsocket created for - #{inspect(address)}")
{:ok, fed_socket}
else
{:cache, {:ok, socket}} ->
Logger.debug("fedsocket found in cache - #{inspect(address)}")
{:ok, socket}
{:cache, {:error, :rejected} = e} ->
e
{:connect, {:error, _host}} ->
Logger.debug("set host rejected for - #{inspect(address)}")
FedRegistry.set_host_rejected(address)
{:error, :rejected}
{_, {:error, :disabled}} ->
{:error, :disabled}
{_, {:error, reason}} ->
Logger.warn("get_or_create_fed_socket error - #{inspect(reason)}")
{:error, reason}
end
end
@doc """
returns a FedSocket for the given origin. Will not create a new FedSocket if one does not exist.
address is expected to be a fully formed URL such as:
"http://www.example.com" or "http://www.example.com:8080"
"""
def get_fed_socket(address) do
origin = SocketInfo.origin(address)
with {:config, true} <- {:config, Pleroma.Config.get([:fed_sockets, :enabled], false)},
{:ok, socket} <- FedRegistry.get_fed_socket(origin) do
{:ok, socket}
else
{:config, _} ->
{:error, :disabled}
{:error, :rejected} ->
Logger.debug("FedSocket previously rejected - #{inspect(origin)}")
{:error, :rejected}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Sends the supplied data via the publish protocol.
It will not block waiting for a reply.
Returns :ok but this is not an indication of a successful transfer.
the data is expected to be JSON encoded binary data.
"""
def publish(%SocketInfo{} = fed_socket, json) do
FedSocket.publish(fed_socket, json)
end
@doc """
Sends the supplied data via the fetch protocol.
It will block waiting for a reply or timeout.
Returns {:ok, object} where object is the requested object (or nil)
{:error, :timeout} in the event the message was not responded to
the id is expected to be the URI of an ActivityPub object.
"""
def fetch(%SocketInfo{} = fed_socket, id) do
FedSocket.fetch(fed_socket, id)
end
@doc """
Disconnect all and restart FedSockets.
This is mainly used in development and testing but could be useful in production.
"""
def reset do
FedRegistry
|> Process.whereis()
|> Process.exit(:testing)
end
def uri_for_origin(origin),
do: "ws://#{origin}/api/fedsocket/v1"
end