Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 21 additions & 9 deletions matrix/utils/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,45 @@
import requests


async def post_url(session, url, data=None):
"""Send a POST request to a given URL with optional data, returning status and content."""
DEFAULT_HTTP_TIMEOUT_SECONDS = 30


async def post_url(session, url, data=None, timeout=DEFAULT_HTTP_TIMEOUT_SECONDS):
"""Send a POST request and return ``(status_code, text_content)``.

A default timeout is applied to avoid indefinitely hanging control-plane calls.
"""
try:
async with session.post(url, json=data) as response:
async with session.post(url, json=data, timeout=timeout) as response:
status = response.status # Get the HTTP status code
content = await response.text() # Get the response body as text
return status, content
except Exception as e:
return None, repr(e) # Return None for status and error message as content


async def fetch_url(url, headers=None):
"""Asynchronously fetch data from a single URL, returning status code and content."""
async def fetch_url(url, headers=None, timeout=DEFAULT_HTTP_TIMEOUT_SECONDS):
"""Asynchronously fetch a URL and return ``(status_code, text_content)``.

A default timeout is applied to avoid indefinitely hanging health checks.
"""
try:
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as response:
async with session.get(url, headers=headers, timeout=timeout) as response:
status = response.status # Get the status code
content = await response.text() # Get response body as text
return status, content
except Exception as e: # Catch-all for unexpected errors
return None, f"Unexpected error: {str(e)}"


def fetch_url_sync(url, headers=None):
"""Synchronously fetch data from a single URL, returning status code and content."""
def fetch_url_sync(url, headers=None, timeout=DEFAULT_HTTP_TIMEOUT_SECONDS):
"""Synchronously fetch a URL and return ``(status_code, text_content)``.

A default timeout is applied to avoid indefinitely hanging health checks.
"""
try:
response = requests.get(url, headers=headers)
response = requests.get(url, headers=headers, timeout=timeout)
status = response.status_code # Get the status code
content = response.text # Get response body as text
return status, content
Expand Down
66 changes: 66 additions & 0 deletions tests/unit/utils/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,69 @@ def log_message(self, format, *args): # pragma: no cover
status, content = fetch_url_sync("http://127.0.0.1:1")
assert status is None
assert "boom" in content


@pytest.mark.asyncio
async def test_post_url_forwards_timeout():
async with aiohttp.ClientSession() as session:
with patch.object(session, "post", side_effect=Exception("boom")) as mock_post:
await post_url(session, "http://example.com", timeout=1.25)

mock_post.assert_called_once_with(
"http://example.com", json=None, timeout=1.25
)


@pytest.mark.asyncio
async def test_fetch_url_forwards_timeout():
class _DummyResponse:
status = 200

async def text(self):
return "ok"

async def __aenter__(self):
return self

async def __aexit__(self, exc_type, exc, tb):
return False

class _DummySession:
def __init__(self):
self.calls = []

def get(self, url, headers=None, timeout=None):
self.calls.append((url, headers, timeout))
return _DummyResponse()

async def __aenter__(self):
return self

async def __aexit__(self, exc_type, exc, tb):
return False

dummy_session = _DummySession()
with patch("matrix.utils.http.aiohttp.ClientSession", return_value=dummy_session):
status, content = await fetch_url(
"http://example.com", headers={"x": "1"}, timeout=2.5
)

assert status == 200
assert content == "ok"
assert dummy_session.calls == [
("http://example.com", {"x": "1"}, 2.5)
]


def test_fetch_url_sync_forwards_timeout():
with patch("matrix.utils.http.requests.get") as mock_get:
mock_get.return_value.status_code = 200
mock_get.return_value.text = "ok"

status, content = fetch_url_sync("http://example.com", timeout=3.0)

assert status == 200
assert content == "ok"
mock_get.assert_called_once_with(
"http://example.com", headers=None, timeout=3.0
)