1fd5899c802317b89b388a1615a00fdc527d8b8f
[akkoma] / lib / pleroma / web / fed_sockets.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.FedSockets do
6 @moduledoc """
7 This documents the FedSockets framework. A framework for federating
8 ActivityPub objects between servers via persistant WebSocket connections.
9
10 FedSockets allow servers to authenticate on first contact and maintain that
11 connection, eliminating the need to authenticate every time data needs to be shared.
12
13 ## Protocol
14 FedSockets currently support 2 types of data transfer:
15 * `publish` method which doesn't require a response
16 * `fetch` method requires a response be sent
17
18 ### Publish
19 The publish operation sends a json encoded map of the shape:
20 %{action: :publish, data: json}
21 and accepts (but does not require) a reply of form:
22 %{"action" => "publish_reply"}
23
24 The outgoing params represent
25 * data: ActivityPub object encoded into json
26
27
28 ### Fetch
29 The fetch operation sends a json encoded map of the shape:
30 %{action: :fetch, data: id, uuid: fetch_uuid}
31 and requires a reply of form:
32 %{"action" => "fetch_reply", "uuid" => uuid, "data" => data}
33
34 The outgoing params represent
35 * id: an ActivityPub object URI
36 * uuid: a unique uuid generated by the sender
37
38 The reply params represent
39 * data: an ActivityPub object encoded into json
40 * uuid: the uuid sent along with the fetch request
41
42 ## Examples
43 Clients of FedSocket transfers shouldn't need to use any of the functions outside of this module.
44
45 A typical publish operation can be performed through the following code, and a fetch operation in a similar manner.
46
47 case FedSockets.get_or_create_fed_socket(inbox) do
48 {:ok, fedsocket} ->
49 FedSockets.publish(fedsocket, json)
50
51 _ ->
52 alternative_publish(inbox, actor, json, params)
53 end
54
55 ## Configuration
56 FedSockets have the following config settings
57
58 config :pleroma, :fed_sockets,
59 enabled: true,
60 ping_interval: :timer.seconds(15),
61 connection_duration: :timer.hours(1),
62 rejection_duration: :timer.hours(1),
63 fed_socket_fetches: [
64 default: 12_000,
65 interval: 3_000,
66 lazy: false
67 ]
68 * enabled - turn FedSockets on or off with this flag. Can be toggled at runtime.
69 * connection_duration - How long a FedSocket can sit idle before it's culled.
70 * rejection_duration - After failing to make a FedSocket connection a host will be excluded
71 from further connections for this amount of time
72 * fed_socket_fetches - Use these parameters to pass options to the Cachex queue backing the FetchRegistry
73 * fed_socket_rejections - Use these parameters to pass options to the Cachex queue backing the FedRegistry
74
75 Cachex options are
76 * default: the minimum amount of time a fetch can wait before it times out.
77 * interval: the interval between checks for timed out entries. This plus the default represent the maximum time allowed
78 * lazy: leave at false for consistant and fast lookups, set to true for stricter timeout enforcement
79
80 """
81 require Logger
82
83 alias Pleroma.Web.FedSockets.FedRegistry
84 alias Pleroma.Web.FedSockets.FedSocket
85 alias Pleroma.Web.FedSockets.SocketInfo
86
87 @doc """
88 returns a FedSocket for the given origin. Will reuse an existing one or create a new one.
89
90 address is expected to be a fully formed URL such as:
91 "http://www.example.com" or "http://www.example.com:8080"
92
93 It can and usually does include additional path parameters,
94 but these are ignored as the FedSockets are organized by host and port info alone.
95 """
96 def get_or_create_fed_socket(address) do
97 with {:cache, {:error, :missing}} <- {:cache, get_fed_socket(address)},
98 {:connect, {:ok, _pid}} <- {:connect, FedSocket.connect_to_host(address)},
99 {:cache, {:ok, fed_socket}} <- {:cache, get_fed_socket(address)} do
100 Logger.debug("fedsocket created for - #{inspect(address)}")
101 {:ok, fed_socket}
102 else
103 {:cache, {:ok, socket}} ->
104 Logger.debug("fedsocket found in cache - #{inspect(address)}")
105 {:ok, socket}
106
107 {:cache, {:error, :rejected} = e} ->
108 e
109
110 {:connect, {:error, _host}} ->
111 Logger.debug("set host rejected for - #{inspect(address)}")
112 FedRegistry.set_host_rejected(address)
113 {:error, :rejected}
114
115 {_, {:error, :disabled}} ->
116 {:error, :disabled}
117
118 {_, {:error, reason}} ->
119 Logger.warn("get_or_create_fed_socket error - #{inspect(reason)}")
120 {:error, reason}
121 end
122 end
123
124 @doc """
125 returns a FedSocket for the given origin. Will not create a new FedSocket if one does not exist.
126
127 address is expected to be a fully formed URL such as:
128 "http://www.example.com" or "http://www.example.com:8080"
129 """
130 def get_fed_socket(address) do
131 origin = SocketInfo.origin(address)
132
133 with {:config, true} <- {:config, Pleroma.Config.get([:fed_sockets, :enabled], false)},
134 {:ok, socket} <- FedRegistry.get_fed_socket(origin) do
135 {:ok, socket}
136 else
137 {:config, _} ->
138 {:error, :disabled}
139
140 {:error, :rejected} ->
141 Logger.debug("FedSocket previously rejected - #{inspect(origin)}")
142 {:error, :rejected}
143
144 {:error, reason} ->
145 {:error, reason}
146 end
147 end
148
149 @doc """
150 Sends the supplied data via the publish protocol.
151 It will not block waiting for a reply.
152 Returns :ok but this is not an indication of a successful transfer.
153
154 the data is expected to be JSON encoded binary data.
155 """
156 def publish(%SocketInfo{} = fed_socket, json) do
157 FedSocket.publish(fed_socket, json)
158 end
159
160 @doc """
161 Sends the supplied data via the fetch protocol.
162 It will block waiting for a reply or timeout.
163
164 Returns {:ok, object} where object is the requested object (or nil)
165 {:error, :timeout} in the event the message was not responded to
166
167 the id is expected to be the URI of an ActivityPub object.
168 """
169 def fetch(%SocketInfo{} = fed_socket, id) do
170 FedSocket.fetch(fed_socket, id)
171 end
172
173 @doc """
174 Disconnect all and restart FedSockets.
175 This is mainly used in development and testing but could be useful in production.
176 """
177 def reset do
178 FedRegistry
179 |> Process.whereis()
180 |> Process.exit(:testing)
181 end
182
183 def uri_for_origin(origin),
184 do: "ws://#{origin}/api/fedsocket/v1"
185 end