From bf37a4855d06554f944bd984c17235bcd035c960 Mon Sep 17 00:00:00 2001 From: Neel Shah Date: Tue, 13 Jan 2026 16:01:48 +0100 Subject: [PATCH 1/2] Handle proxy tunnels in httlib integration --- sentry_sdk/integrations/stdlib.py | 18 ++++++++- tests/integrations/stdlib/test_httplib.py | 49 ++++++++++++++++++++++- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/sentry_sdk/integrations/stdlib.py b/sentry_sdk/integrations/stdlib.py index bf0c626fa8..b94aad7cf0 100644 --- a/sentry_sdk/integrations/stdlib.py +++ b/sentry_sdk/integrations/stdlib.py @@ -69,9 +69,18 @@ def _install_httplib() -> None: def putrequest( self: "HTTPConnection", method: str, url: str, *args: "Any", **kwargs: "Any" ) -> "Any": - host = self.host - port = self.port + # proxy info is in _tunnel_host/_tunnel_port + host = getattr(self, "_tunnel_host", None) or self.host + default_port = self.default_port + tunnel_port = getattr(self, "_tunnel_port", None) + if tunnel_port: + port = tunnel_port + # need to override default_port for correct url recording + if tunnel_port == 443: + default_port = 443 + else: + port = self.port client = sentry_sdk.get_client() if client.get_integration(StdlibIntegration) is None or is_sentry_url( @@ -104,6 +113,11 @@ def putrequest( span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query) span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment) + # Set network peer address and port (the actual connection endpoint) + # for proxies, these point to the proxy host/port + span.set_data(SPANDATA.NETWORK_PEER_ADDRESS, self.host) + span.set_data(SPANDATA.NETWORK_PEER_PORT, self.port) + rv = real_putrequest(self, method, url, *args, **kwargs) if should_propagate_trace(client, real_url): diff --git a/tests/integrations/stdlib/test_httplib.py b/tests/integrations/stdlib/test_httplib.py index 588c3b34f4..38e0eed246 100644 --- a/tests/integrations/stdlib/test_httplib.py +++ b/tests/integrations/stdlib/test_httplib.py @@ -1,7 +1,10 @@ import os import datetime +import socket from http.client import HTTPConnection, HTTPSConnection +from http.server import BaseHTTPRequestHandler, HTTPServer from socket import SocketIO +from threading import Thread from urllib.error import HTTPError from urllib.request import urlopen from unittest import mock @@ -12,11 +15,35 @@ from sentry_sdk.consts import MATCH_ALL, SPANDATA from sentry_sdk.integrations.stdlib import StdlibIntegration -from tests.conftest import ApproxDict, create_mock_http_server +from tests.conftest import ApproxDict, create_mock_http_server, get_free_port PORT = create_mock_http_server() +class MockProxyRequestHandler(BaseHTTPRequestHandler): + def do_CONNECT(self): + self.send_response(200, "Connection Established") + self.end_headers() + + self.rfile.readline() + + response = b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" + self.wfile.write(response) + self.wfile.flush() + + +def create_mock_proxy_server(): + proxy_port = get_free_port() + proxy_server = HTTPServer(("localhost", proxy_port), MockProxyRequestHandler) + proxy_thread = Thread(target=proxy_server.serve_forever) + proxy_thread.daemon = True + proxy_thread.start() + return proxy_port + + +PROXY_PORT = create_mock_proxy_server() + + def test_crumb_capture(sentry_init, capture_events): sentry_init(integrations=[StdlibIntegration()]) events = capture_events() @@ -642,3 +669,23 @@ def test_http_timeout(monkeypatch, sentry_init, capture_envelopes): span = transaction["spans"][0] assert span["op"] == "http.client" assert span["description"] == f"GET http://localhost:{PORT}/bla" # noqa: E231 + + +def test_proxy_https_tunnel(sentry_init, capture_events): + sentry_init(traces_sample_rate=1.0) + events = capture_events() + + with start_transaction(name="test_transaction"): + conn = HTTPConnection("localhost", PROXY_PORT) + conn.set_tunnel("api.example.com", 443) + conn.request("GET", "/foo") + conn.getresponse() + + (event,) = events + (span,) = event["spans"] + + assert span["description"] == "GET https://api.example.com/foo" + assert span["data"]["url"] == "https://api.example.com/foo" + assert span["data"][SPANDATA.HTTP_METHOD] == "GET" + assert span["data"][SPANDATA.NETWORK_PEER_ADDRESS] == "localhost" + assert span["data"][SPANDATA.NETWORK_PEER_PORT] == PROXY_PORT From 791cfd376a53f604c1455edb49639fd199ba5fe8 Mon Sep 17 00:00:00 2001 From: Neel Shah Date: Tue, 13 Jan 2026 16:37:31 +0100 Subject: [PATCH 2/2] only add span attrs for proxies --- sentry_sdk/integrations/stdlib.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sentry_sdk/integrations/stdlib.py b/sentry_sdk/integrations/stdlib.py index b94aad7cf0..9458d266b3 100644 --- a/sentry_sdk/integrations/stdlib.py +++ b/sentry_sdk/integrations/stdlib.py @@ -70,7 +70,8 @@ def putrequest( self: "HTTPConnection", method: str, url: str, *args: "Any", **kwargs: "Any" ) -> "Any": # proxy info is in _tunnel_host/_tunnel_port - host = getattr(self, "_tunnel_host", None) or self.host + tunnel_host = getattr(self, "_tunnel_host", None) + host = tunnel_host or self.host default_port = self.default_port tunnel_port = getattr(self, "_tunnel_port", None) @@ -113,10 +114,10 @@ def putrequest( span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query) span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment) - # Set network peer address and port (the actual connection endpoint) - # for proxies, these point to the proxy host/port - span.set_data(SPANDATA.NETWORK_PEER_ADDRESS, self.host) - span.set_data(SPANDATA.NETWORK_PEER_PORT, self.port) + if tunnel_host: + # for proxies, these point to the proxy host/port + span.set_data(SPANDATA.NETWORK_PEER_ADDRESS, self.host) + span.set_data(SPANDATA.NETWORK_PEER_PORT, self.port) rv = real_putrequest(self, method, url, *args, **kwargs)