Skip to content
Merged
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
7 changes: 6 additions & 1 deletion src/stac_auth_proxy/handlers/reverse_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ async def proxy_request(self, request: Request) -> Response:
logger.debug(f"Proxying request to {rp_req.url}")

start_time = time.perf_counter()
rp_resp = await self.client.send(rp_req, stream=True)
try:
rp_resp = await self.client.send(rp_req, stream=True)
except httpx.TimeoutException:
return Response(status_code=504, content=b"Upstream timed out")
except httpx.ConnectError:
return Response(status_code=502, content=b"Upstream unreachable")
proxy_time = time.perf_counter() - start_time
rp_resp.headers["Server-Timing"] = build_server_timing_header(
rp_resp.headers.get("Server-Timing"),
Expand Down
32 changes: 31 additions & 1 deletion tests/test_reverse_proxy.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
"""Tests for the reverse proxy handler's header functionality."""

import httpx
import pytest
from fastapi import Request

from stac_auth_proxy.handlers.reverse_proxy import ReverseProxyHandler


async def empty_body():
"""Receive channel that yields an empty request body."""
return {"type": "http.request", "body": b"", "more_body": False}


def create_request(scope_overrides=None, headers=None):
"""Create a mock FastAPI request with custom scope and headers."""
default_scope = {
Expand All @@ -25,7 +31,7 @@ def create_request(scope_overrides=None, headers=None):
if headers:
default_scope["headers"] = headers

return Request(default_scope)
return Request(default_scope, receive=empty_body)


@pytest.fixture
Expand Down Expand Up @@ -310,3 +316,27 @@ async def test_x_forwarded_port_in_forwarded_header(legacy_headers):

# Check that the x-forwarded-port header is preserved
assert result_headers["X-Forwarded-Port"] == "443"


@pytest.mark.parametrize(
"exception,expected_status",
[
(httpx.ConnectTimeout("Timed out"), 504),
(httpx.ConnectError("Connection refused"), 502),
],
)
async def test_upstream_transport_errors(exception, expected_status):
"""Transport failures become gateway responses rather than unhandled errors."""

def raise_error(request):
raise exception

handler = ReverseProxyHandler(
upstream="http://upstream-api.com",
client=httpx.AsyncClient(
base_url="http://upstream-api.com",
transport=httpx.MockTransport(raise_error),
),
)
response = await handler.proxy_request(create_request())
assert response.status_code == expected_status
Loading