https://github.com/python/cpython/commit/690928748c92c32b259f44e872b9ac4f5759e884
commit: 690928748c92c32b259f44e872b9ac4f5759e884
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-10-13T23:53:21Z
summary:

[3.14] gh-159044: Restore string conversion for HTTP tunnel header values 
(GH-159216) (GH-159383)

(cherry picked from commit 8138bf11a5d32e8db003a86606be8e4c61e14d4b)

Co-authored-by: João Gabriel Pieroli da Silva <[email protected]>
Co-authored-by: LorenaMBorges <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-10-12-00-00-00.gh-issue-159044.Tr8kXv.rst
M Doc/library/http.client.rst
M Lib/http/client.py
M Lib/test/test_httplib.py

diff --git a/Doc/library/http.client.rst b/Doc/library/http.client.rst
index 68fa4a77efb869d..72049d07d435ca6 100644
--- a/Doc/library/http.client.rst
+++ b/Doc/library/http.client.rst
@@ -362,7 +362,8 @@ HTTPConnection Objects
    proxy server).
 
    The *headers* argument should be a mapping of extra HTTP headers to send 
with
-   the CONNECT request.
+   the CONNECT request. Non-string header values are converted to strings via
+   :func:`str`.
 
    As HTTP/1.1 is used for HTTP CONNECT tunnelling request, `as per the RFC
    <https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.6>`_, a HTTP 
``Host:``
diff --git a/Lib/http/client.py b/Lib/http/client.py
index 50f7862d3d2ab86..82122be02a16370 100644
--- a/Lib/http/client.py
+++ b/Lib/http/client.py
@@ -1001,7 +1001,7 @@ def _tunnel(self):
         headers = [connect]
         for header, value in self._tunnel_headers.items():
             header_bytes = header.encode("latin-1")
-            value_bytes = value.encode("latin-1")
+            value_bytes = str(value).encode("latin-1")
             if not _is_legal_header_name(header_bytes):
                 raise ValueError('Invalid header name %r' % (header_bytes,))
             if _is_illegal_header_value(value_bytes):
diff --git a/Lib/test/test_httplib.py b/Lib/test/test_httplib.py
index 0aaf09b26315ed0..705f636d422a747 100644
--- a/Lib/test/test_httplib.py
+++ b/Lib/test/test_httplib.py
@@ -2691,6 +2691,23 @@ def _create_connection(address, timeout=None, 
source_address=None):
         self.assertIsNotNone(exc)
         self.assertTrue(sock.file_closed)
 
+    def test_tunnel_nonstr_headers(self):
+        class CustomObject:
+            def __str__(self):
+                return 'custom-str'
+
+        self.conn.set_tunnel('destination.com', headers={
+            'None-Value': None,
+            'Integer-Value': 9,
+            'Custom-Value': CustomObject(),
+        })
+
+        self.conn.request('HEAD', '/', '')
+
+        self.assertIn(b'None-Value: None\r\n', self.conn.sock.data)
+        self.assertIn(b'Integer-Value: 9\r\n', self.conn.sock.data)
+        self.assertIn(b'Custom-Value: custom-str\r\n', self.conn.sock.data)
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-12-00-00-00.gh-issue-159044.Tr8kXv.rst 
b/Misc/NEWS.d/next/Library/2026-10-12-00-00-00.gh-issue-159044.Tr8kXv.rst
new file mode 100644
index 000000000000000..ef51dc8eca1066c
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-12-00-00-00.gh-issue-159044.Tr8kXv.rst
@@ -0,0 +1,4 @@
+Restore the previous behaviour of :meth:`http.client.HTTPConnection.set_tunnel`
+where header values that are not strings (such as :const:`None`, :class:`int`
+or other objects) are converted with :class:`str` instead of raising
+:exc:`AttributeError` when the request is sent.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to