diff --git a/src/urllib3/http2.py b/src/urllib3/http2.py index d3a764c676..9c9c7edc92 100644 --- a/src/urllib3/http2.py +++ b/src/urllib3/http2.py @@ -61,6 +61,9 @@ def __init__( super().__init__(host, port, **kwargs) + if self._tunnel_host is not None: + raise NotImplementedError("Tunneling isn't supported with HTTP/2") + def _new_h2_conn(self) -> _LockedObject[h2.connection.H2Connection]: config = h2.config.H2Configuration(client_side=True) return _LockedObject(h2.connection.H2Connection(config=config)) @@ -118,6 +121,17 @@ def send(self, data: bytes) -> None: # type: ignore[override] # Defensive: return raise NotImplementedError("Sending data isn't supported yet") + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + raise NotImplementedError( + "HTTP/2 does not support setting up a tunnel through a proxy" + ) + def getresponse( # type: ignore[override] self, ) -> HTTP2Response: diff --git a/test/with_dummyserver/test_connectionpool.py b/test/with_dummyserver/test_connectionpool.py index 4fbe6a4f74..04c4b5307f 100644 --- a/test/with_dummyserver/test_connectionpool.py +++ b/test/with_dummyserver/test_connectionpool.py @@ -437,33 +437,44 @@ def test_socket_timeout_updated_on_reuse_parameter( [mock.call(x) for x in expect_settimeout_calls] ) - def test_tunnel(self) -> None: + def test_tunnel(self, http_version: str) -> None: # note the actual httplib.py has no tests for this functionality timeout = Timeout(total=None) with HTTPConnectionPool(self.host, self.port, timeout=timeout) as pool: conn = pool._get_conn() - try: - conn.set_tunnel(self.host, self.port) - with mock.patch.object( - conn, "_tunnel", create=True, return_value=None - ) as conn_tunnel: - pool._make_request(conn, "GET", "/") - conn_tunnel.assert_called_once_with() - finally: - conn.close() + if http_version == "h2": + with pytest.raises(NotImplementedError) as e: + conn.set_tunnel(self.host, self.port) + assert ( + str(e.value) + == "HTTP/2 does not support setting up a tunnel through a proxy" + ) + else: + try: + conn.set_tunnel(self.host, self.port) + with mock.patch.object( + conn, "_tunnel", create=True, return_value=None + ) as conn_tunnel: + pool._make_request(conn, "GET", "/") + conn_tunnel.assert_called_once_with() + finally: + conn.close() # test that it's not called when tunnel is not set timeout = Timeout(total=None) with HTTPConnectionPool(self.host, self.port, timeout=timeout) as pool: conn = pool._get_conn() - try: - with mock.patch.object( - conn, "_tunnel", create=True, return_value=None - ) as conn_tunnel: - pool._make_request(conn, "GET", "/") - assert not conn_tunnel.called - finally: - conn.close() + if http_version == "h2": + pass + else: + try: + with mock.patch.object( + conn, "_tunnel", create=True, return_value=None + ) as conn_tunnel: + pool._make_request(conn, "GET", "/") + assert not conn_tunnel.called + finally: + conn.close() def test_redirect_relative_url_no_deprecation(self) -> None: with HTTPConnectionPool(self.host, self.port) as pool: diff --git a/test/with_dummyserver/test_https.py b/test/with_dummyserver/test_https.py index aa22f11879..b80345ba91 100644 --- a/test/with_dummyserver/test_https.py +++ b/test/with_dummyserver/test_https.py @@ -603,7 +603,7 @@ def test_https_timeout(self) -> None: with pytest.warns(InsecureRequestWarning): https_pool.request("GET", "/") - def test_tunnel(self) -> None: + def test_tunnel(self, http_version: str) -> None: """test the _tunnel behavior""" timeout = Timeout(total=None) with HTTPSConnectionPool( @@ -614,13 +614,21 @@ def test_tunnel(self) -> None: ssl_minimum_version=self.tls_version(), ) as https_pool: with contextlib.closing(https_pool._new_conn()) as conn: - conn.set_tunnel(self.host, self.port) - with mock.patch.object( - conn, "_tunnel", create=True, return_value=None - ) as conn_tunnel: - with pytest.warns(InsecureRequestWarning): - https_pool._make_request(conn, "GET", "/") - conn_tunnel.assert_called_once_with() + if http_version == "h2": + with pytest.raises(NotImplementedError) as e: + conn.set_tunnel(self.host, self.port) + assert ( + str(e.value) + == "HTTP/2 does not support setting up a tunnel through a proxy" + ) + else: + conn.set_tunnel(self.host, self.port) + with mock.patch.object( + conn, "_tunnel", create=True, return_value=None + ) as conn_tunnel: + with pytest.warns(InsecureRequestWarning): + https_pool._make_request(conn, "GET", "/") + conn_tunnel.assert_called_once_with() @requires_network() def test_enhanced_timeout(self) -> None: diff --git a/test/with_dummyserver/test_socketlevel.py b/test/with_dummyserver/test_socketlevel.py index 69d8070b8b..634df64171 100644 --- a/test/with_dummyserver/test_socketlevel.py +++ b/test/with_dummyserver/test_socketlevel.py @@ -1133,6 +1133,34 @@ def echo_socket_handler(listener: socket.socket) -> None: retries=False, ) + def test_alpn_protocol_in_first_request_packet(self) -> None: + self.buf = b"" + + def echo_socket_handler(listener: socket.socket) -> None: + sock = listener.accept()[0] + self.buf = b"" + while not self.buf.endswith(b"\r\n\r\n"): + self.buf += sock.recv(65536) + + sock.send( + ( + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %d\r\n" + "\r\n" + "%s" % (len(self.buf), self.buf.decode("utf-8")) + ).encode("utf-8") + ) + sock.close() + + self._start_server(echo_socket_handler) + base_url = f"http://{self.host}:{self.port}" + with proxy_from_url(base_url) as proxy: + r = proxy.request("GET", "http://google.com/") + assert r.status == 200 + assert b"HTTP/1.1" in self.buf + assert b"h2" not in self.buf + def test_connect_reconn(self) -> None: def proxy_ssl_one(listener: socket.socket) -> None: sock = listener.accept()[0]