diff options
author | Andrew Morgan <andrew@amorgan.xyz> | 2023-02-23 23:25:57 +0000 |
---|---|---|
committer | Andrew Morgan <andrew@amorgan.xyz> | 2023-02-23 23:40:07 +0000 |
commit | 29cc17fde7f7438ccf826abe282bfbf8403c0d26 (patch) | |
tree | aebce3a91a8dbe329e66d8c9588f6f7b0101a209 | |
parent | Fix typo in federation_verify_certificates in config documentation. (#15139) (diff) | |
download | synapse-29cc17fde7f7438ccf826abe282bfbf8403c0d26.tar.xz |
Add a method for Synapse modules to carry out HTTP federation requests
Provides a fairly basic interface for Synapse modules to complete HTTP federation requests. Custom error types were used in order to prevent stabilising any of the internal MatrixFederationHttpClient code.
-rw-r--r-- | synapse/module_api/__init__.py | 132 | ||||
-rw-r--r-- | synapse/module_api/errors.py | 56 |
2 files changed, 187 insertions, 1 deletions
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", ] |