Merge branch 'websocket-data-fed' into 'develop'
[akkoma] / lib / pleroma / web / fed_sockets / 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 {:connect, {:error, _host}} ->
108 Logger.debug("set host rejected for - #{inspect(address)}")
109 FedRegistry.set_host_rejected(address)
110 {:error, :rejected}
111
112 {_, {:error, :disabled}} ->
113 {:error, :disabled}
114
115 {_, {:error, reason}} ->
116 Logger.warn("get_or_create_fed_socket error - #{inspect(reason)}")
117 {:error, reason}
118 end
119 end
120
121 @doc """
122 returns a FedSocket for the given origin. Will not create a new FedSocket if one does not exist.
123
124 address is expected to be a fully formed URL such as:
125 "http://www.example.com" or "http://www.example.com:8080"
126 """
127 def get_fed_socket(address) do
128 origin = SocketInfo.origin(address)
129
130 with {:config, true} <- {:config, Pleroma.Config.get([:fed_sockets, :enabled], false)},
131 {:ok, socket} <- FedRegistry.get_fed_socket(origin) do
132 {:ok, socket}
133 else
134 {:config, _} ->
135 {:error, :disabled}
136
137 {:error, :rejected} ->
138 Logger.debug("FedSocket previously rejected - #{inspect(origin)}")
139 {:error, :rejected}
140
141 {:error, reason} ->
142 {:error, reason}
143 end
144 end
145
146 @doc """
147 Sends the supplied data via the publish protocol.
148 It will not block waiting for a reply.
149 Returns :ok but this is not an indication of a successful transfer.
150
151 the data is expected to be JSON encoded binary data.
152 """
153 def publish(%SocketInfo{} = fed_socket, json) do
154 FedSocket.publish(fed_socket, json)
155 end
156
157 @doc """
158 Sends the supplied data via the fetch protocol.
159 It will block waiting for a reply or timeout.
160
161 Returns {:ok, object} where object is the requested object (or nil)
162 {:error, :timeout} in the event the message was not responded to
163
164 the id is expected to be the URI of an ActivityPub object.
165 """
166 def fetch(%SocketInfo{} = fed_socket, id) do
167 FedSocket.fetch(fed_socket, id)
168 end
169
170 @doc """
171 Disconnect all and restart FedSockets.
172 This is mainly used in development and testing but could be useful in production.
173 """
174 def reset do
175 FedRegistry
176 |> Process.whereis()
177 |> Process.exit(:testing)
178 end
179
180 def uri_for_origin(origin),
181 do: "ws://#{origin}/api/fedsocket/v1"
182 end