Compare commits

...

2 Commits

Author SHA1 Message Date
Andrew Morgan
2ac38a1109 changelog 2023-02-23 23:40:07 +00:00
Andrew Morgan
29cc17fde7 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.
2023-02-23 23:40:07 +00:00
3 changed files with 188 additions and 1 deletions

View File

@@ -0,0 +1 @@
Add a new `send_federation_http_request` method to the Module API to allow Synapse modules to make matrix federation requests over HTTP.

View File

@@ -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

View File

@@ -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",
]