Skip to content

Commit 570059a

Browse files
Fix HTTP redirect handling in urllib instrumentation REPLAY mode
When urllib follows redirects, response.geturl() should return the final URL after all redirects. The instrumentation now: - Captures finalUrl in RECORD mode when it differs from request URL - Uses finalUrl in REPLAY mode so MockHTTPResponse.geturl() returns the correct final URL instead of the original request URL This ensures application code that checks the final URL after redirects behaves the same in RECORD and REPLAY modes.
1 parent 63b20fa commit 570059a

1 file changed

Lines changed: 18 additions & 3 deletions

File tree

drift/instrumentation/urllib/instrumentation.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,7 @@ def _create_mock_response(self, mock_data: dict[str, Any], url: str) -> MockHTTP
756756
757757
Args:
758758
mock_data: Mock response data from CLI
759-
url: Request URL
759+
url: Request URL (original request URL)
760760
761761
Returns:
762762
MockHTTPResponse object
@@ -765,6 +765,10 @@ def _create_mock_response(self, mock_data: dict[str, Any], url: str) -> MockHTTP
765765
reason = mock_data.get("statusMessage", "OK")
766766
headers = dict(mock_data.get("headers", {}))
767767

768+
# Use finalUrl from recorded response if present (indicates redirect occurred)
769+
# Otherwise fall back to the original request URL
770+
response_url = mock_data.get("finalUrl", url)
771+
768772
# Remove content-encoding and transfer-encoding headers since the body
769773
# was already decompressed when recorded
770774
headers_to_remove = []
@@ -792,13 +796,13 @@ def _create_mock_response(self, mock_data: dict[str, Any], url: str) -> MockHTTP
792796
else:
793797
body_bytes = json.dumps(body).encode("utf-8")
794798

795-
logger.debug(f"Created mock response: {status_code} for {url}")
799+
logger.debug(f"Created mock response: {status_code} for {response_url}")
796800
return MockHTTPResponse(
797801
status_code=status_code,
798802
reason=reason,
799803
headers=headers,
800804
body=body_bytes,
801-
url=url,
805+
url=response_url,
802806
)
803807

804808
def _raise_http_error_from_mock(self, mock_data: dict[str, Any], url: str) -> None:
@@ -943,12 +947,23 @@ def _finalize_span(
943947
response_body_base64 = None
944948
response_body_size = 0
945949

950+
# Capture final URL (important for redirect scenarios)
951+
# urllib sets response.url to the final URL after all redirects
952+
final_url = getattr(response, "url", None)
953+
if callable(final_url):
954+
# Some response objects have geturl() method
955+
final_url = final_url()
956+
946957
output_value = {
947958
"statusCode": response_status,
948959
"statusMessage": response_reason,
949960
"headers": response_headers,
950961
}
951962

963+
# Store final URL if different from request URL (indicates redirect)
964+
if final_url and final_url != url:
965+
output_value["finalUrl"] = final_url
966+
952967
# Add body fields only if body exists
953968
if response_body_base64 is not None:
954969
output_value["body"] = response_body_base64

0 commit comments

Comments
 (0)