diff --git a/docs/project/changelog.rst b/docs/project/changelog.rst index 7e2968826..12fc8c32e 100644 --- a/docs/project/changelog.rst +++ b/docs/project/changelog.rst @@ -32,6 +32,11 @@ notice. *In development* +Improvements +............ + +* Added support for HTTP/1.0 proxies. + 15.0.1 ------ diff --git a/src/websockets/asyncio/client.py b/src/websockets/asyncio/client.py index 38a56ddda..aa0d6f774 100644 --- a/src/websockets/asyncio/client.py +++ b/src/websockets/asyncio/client.py @@ -754,7 +754,7 @@ def __init__( self.reader.read_line, self.reader.read_exact, self.reader.read_to_eof, - include_body=False, + proxy=True, ) loop = asyncio.get_running_loop() diff --git a/src/websockets/http11.py b/src/websockets/http11.py index 177c927fb..290ef087e 100644 --- a/src/websockets/http11.py +++ b/src/websockets/http11.py @@ -213,7 +213,7 @@ def parse( read_line: Callable[[int], Generator[None, None, bytes]], read_exact: Callable[[int], Generator[None, None, bytes]], read_to_eof: Callable[[int], Generator[None, None, bytes]], - include_body: bool = True, + proxy: bool = False, ) -> Generator[None, None, Response]: """ Parse a WebSocket handshake response. @@ -249,10 +249,17 @@ def parse( protocol, raw_status_code, raw_reason = status_line.split(b" ", 2) except ValueError: # not enough values to unpack (expected 3, got 1-2) raise ValueError(f"invalid HTTP status line: {d(status_line)}") from None - if protocol != b"HTTP/1.1": - raise ValueError( - f"unsupported protocol; expected HTTP/1.1: {d(status_line)}" - ) + if proxy: # some proxies still use HTTP/1.0 + if protocol not in [b"HTTP/1.1", b"HTTP/1.0"]: + raise ValueError( + f"unsupported protocol; expected HTTP/1.1 or HTTP/1.0: " + f"{d(status_line)}" + ) + else: + if protocol != b"HTTP/1.1": + raise ValueError( + f"unsupported protocol; expected HTTP/1.1: {d(status_line)}" + ) try: status_code = int(raw_status_code) except ValueError: # invalid literal for int() with base 10 @@ -269,12 +276,12 @@ def parse( headers = yield from parse_headers(read_line) - if include_body: + if proxy: + body = b"" + else: body = yield from read_body( status_code, headers, read_line, read_exact, read_to_eof ) - else: - body = b"" return cls(status_code, reason, headers, body) diff --git a/src/websockets/sync/client.py b/src/websockets/sync/client.py index 58cb84710..b161523f8 100644 --- a/src/websockets/sync/client.py +++ b/src/websockets/sync/client.py @@ -498,7 +498,7 @@ def read_connect_response(sock: socket.socket, deadline: Deadline) -> Response: reader.read_line, reader.read_exact, reader.read_to_eof, - include_body=False, + proxy=True, ) try: while True: diff --git a/tests/test_http11.py b/tests/test_http11.py index ca7a1bc86..3328b3b5e 100644 --- a/tests/test_http11.py +++ b/tests/test_http11.py @@ -328,11 +328,28 @@ def test_parse_body_not_modified(self): response = self.assertGeneratorReturns(self.parse()) self.assertEqual(response.body, b"") - def test_parse_without_body(self): + def test_parse_proxy_response_does_not_read_body(self): self.reader.feed_data(b"HTTP/1.1 200 Connection Established\r\n\r\n") - response = self.assertGeneratorReturns(self.parse(include_body=False)) + response = self.assertGeneratorReturns(self.parse(proxy=True)) self.assertEqual(response.body, b"") + def test_parse_proxy_http10(self): + self.reader.feed_data(b"HTTP/1.0 200 OK\r\nContent-Length: 0\r\n\r\n") + response = self.assertGeneratorReturns(self.parse(proxy=True)) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.reason_phrase, "OK") + self.assertEqual(response.body, b"") + + def test_parse_proxy_unsupported_protocol(self): + self.reader.feed_data(b"HTTP/1.2 400 Bad Request\r\n\r\n") + with self.assertRaises(ValueError) as raised: + next(self.parse(proxy=True)) + self.assertEqual( + str(raised.exception), + "unsupported protocol; expected HTTP/1.1 or HTTP/1.0: " + "HTTP/1.2 400 Bad Request", + ) + def test_serialize(self): # Example from the protocol overview in RFC 6455 response = Response(