diff --git a/synapse/module_api/__init__.py b/synapse/module_api/__init__.py
index 1964276a54..af4dc3b1d5 100644
--- a/synapse/module_api/__init__.py
+++ b/synapse/module_api/__init__.py
@@ -37,7 +37,12 @@ from twisted.internet import defer
from twisted.web.resource import Resource
from synapse.api import errors
-from synapse.api.errors import SynapseError
+from synapse.api.errors import (
+ FederationDeniedError,
+ HttpResponseException,
+ RequestSendFailed,
+ SynapseError,
+)
from synapse.events import EventBase
from synapse.events.presence_router import (
GET_INTERESTED_USERS_CALLBACK,
@@ -129,6 +134,14 @@ from synapse.util import Clock
from synapse.util.async_helpers import maybe_awaitable
from synapse.util.caches.descriptors import CachedFunction, cached as _cached
from synapse.util.frozenutils import freeze
+from synapse.util.retryutils import NotRetryingDestination
+
+from .errors import (
+ FederationHttpDeniedException,
+ FederationHttpNotRetryingDestinationException,
+ FederationHttpRequestSendFailedException,
+ FederationHttpResponseException,
+)
if TYPE_CHECKING:
from synapse.app.generic_worker import GenericWorkerSlavedStore
@@ -1612,6 +1625,123 @@ class ModuleApi:
deactivation=deactivation,
)
+ async def _try_federation_http_request(
+ self,
+ method: str,
+ remote_server_name: str,
+ path: str,
+ query_parameters: Optional[Dict[str, Any]],
+ body: Optional[JsonDict] = None,
+ timeout: Optional[int] = None,
+ ) -> Union[JsonDict, List]:
+ """
+ Send a federation request to a remote homeserver and return the response.
+
+ This method assumes the `method` argument is fully capitalised.
+
+ A helper method for self.send_federation_http_request, see that method for
+ more details.
+ """
+ assert method in ["GET", "PUT", "POST", "DELETE"]
+
+ fed_client = self._hs.get_federation_http_client()
+
+ if method == "GET":
+ return await fed_client.get_json(
+ destination=remote_server_name,
+ path=path,
+ args=query_parameters,
+ timeout=timeout,
+ )
+ elif method == "PUT":
+ return await fed_client.put_json(
+ destination=remote_server_name,
+ path=path,
+ args=query_parameters,
+ data=body,
+ timeout=timeout,
+ )
+ elif method == "POST":
+ return await fed_client.post_json(
+ destination=remote_server_name,
+ path=path,
+ args=query_parameters,
+ data=body,
+ timeout=timeout,
+ )
+ elif method == "DELETE":
+ return await fed_client.delete_json(
+ destination=remote_server_name,
+ path=path,
+ args=query_parameters,
+ timeout=timeout,
+ )
+
+ return {}
+
+ async def send_federation_http_request(
+ self,
+ method: str,
+ remote_server_name: str,
+ path: str,
+ query_parameters: Optional[Dict[str, Any]],
+ body: Optional[JsonDict] = None,
+ timeout: Optional[int] = None,
+ ) -> Union[JsonDict, List]:
+ """
+ Send an HTTP federation request to a remote homeserver.
+
+ Added in Synapse v1.79.0.
+
+ If the request is successful, the parsed response body will be returned. If
+ unsuccessful, an exception will be raised. Callers are expected to handle the
+ possible exception cases. See exception class docstrings for a more detailed
+ explanation of each.
+
+ Args:
+ method: The HTTP method to use. Must be one of: "GET", "PUT", "POST",
+ "DELETE".
+ remote_server_name: The remote server to send the request to. This method
+ will resolve delegated homeserver URLs automatically (well-known etc).
+ path: The HTTP path for the request.
+ query_parameters: Any query parameters for the request.
+ body: The body of the request.
+ timeout: The timeout in seconds to wait before giving up on a request.
+
+ Returns:
+ The response to the request as a Python object.
+
+ Raises:
+ FederationHttpResponseException: If we get an HTTP response code >= 300
+ (except 429).
+ FederationHttpNotRetryingDestinationException: If the homeserver believes the
+ remote homeserver is down and is not yet ready to attempt to contact it.
+ FederationHttpDeniedException: If this destination is not on the local
+ homeserver's configured federation whitelist.
+ FederationHttpRequestSendFailedException: If there were problems connecting
+ to the remote, due to e.g. DNS failures, connection timeouts etc.
+ """
+ try:
+ return await self._try_federation_http_request(
+ method.upper(), remote_server_name, path, query_parameters, body, timeout
+ )
+ except HttpResponseException as e:
+ raise FederationHttpResponseException(
+ remote_server_name,
+ status_code=e.code,
+ msg=e.msg,
+ response_body=e.response,
+ )
+ except NotRetryingDestination:
+ raise FederationHttpNotRetryingDestinationException(remote_server_name)
+ except FederationDeniedError:
+ raise FederationHttpDeniedException(remote_server_name)
+ except RequestSendFailed as e:
+ raise FederationHttpRequestSendFailedException(
+ remote_server_name,
+ can_retry=e.can_retry,
+ )
+
class PublicRoomListManager:
"""Contains methods for adding to, removing from and querying whether a room
diff --git a/synapse/module_api/errors.py b/synapse/module_api/errors.py
index bedd045d6f..984fc8b3e5 100644
--- a/synapse/module_api/errors.py
+++ b/synapse/module_api/errors.py
@@ -13,6 +13,7 @@
# limitations under the License.
"""Exception types which are exposed as part of the stable module API"""
+import attr
from synapse.api.errors import (
Codes,
@@ -24,6 +25,57 @@ from synapse.config._base import ConfigError
from synapse.handlers.push_rules import InvalidRuleException
from synapse.storage.push_rule import RuleNotFoundException
+
+@attr.s(auto_attribs=True)
+class FederationHttpResponseException(Exception):
+ """
+ Raised when an HTTP request over federation returns a status code > 300 (and not 429).
+ """
+
+ remote_server_name: str
+ # The HTTP status code of the response.
+ status_code: int
+ # A human-readable explanation for the error.
+ msg: str
+ # The non-parsed HTTP response body.
+ response_body: bytes
+
+
+@attr.s(auto_attribs=True)
+class FederationHttpNotRetryingDestinationException(Exception):
+ """
+ Raised when the local homeserver refuses to send traffic to a remote homeserver that
+ it believes is experiencing an outage.
+ """
+
+ remote_server_name: str
+
+
+@attr.s(auto_attribs=True)
+class FederationHttpDeniedException(Exception):
+ """
+ Raised when the local homeserver refuses to send federation traffic to a remote
+ homeserver. This is due to the remote homeserver not being on the configured
+ federation whitelist.
+ """
+
+ remote_server_name: str
+
+
+@attr.s(auto_attribs=True)
+class FederationHttpRequestSendFailedException(Exception):
+ """
+ Raised when there are problems connecting to the remote homeserver due to e.g.
+ DNS failures, connection timeouts, etc.
+ """
+
+ remote_server_name: str
+ # Whether the request can be retried with a chance of success. This will be True
+ # if the failure occurred due to e.g. timeouts, a disruption in the connection etc.
+ # Will be false in the case of e.g. a malformed response from the remote homeserver.
+ can_retry: bool
+
+
__all__ = [
"Codes",
"InvalidClientCredentialsError",
@@ -32,4 +84,8 @@ __all__ = [
"ConfigError",
"InvalidRuleException",
"RuleNotFoundException",
+ "FederationHttpResponseException",
+ "FederationHttpNotRetryingDestinationException",
+ "FederationHttpDeniedException",
+ "FederationHttpRequestSendFailedException",
]
|