Read an HTTP/1.1 response and return ``(status_code, reason, headers)``. ``reason`` and ``headers`` are expected to contain only ASCII characters. Other characters are represented with surrogate escapes. :func:`read_request` doesn't attempt to read the response body because We
(stream: asyncio.StreamReader)
| 92 | |
| 93 | |
| 94 | async def read_response(stream: asyncio.StreamReader) -> tuple[int, str, Headers]: |
| 95 | """ |
| 96 | Read an HTTP/1.1 response and return ``(status_code, reason, headers)``. |
| 97 | |
| 98 | ``reason`` and ``headers`` are expected to contain only ASCII characters. |
| 99 | Other characters are represented with surrogate escapes. |
| 100 | |
| 101 | :func:`read_request` doesn't attempt to read the response body because |
| 102 | WebSocket handshake responses don't have one. If the response contains a |
| 103 | body, it may be read from ``stream`` after this coroutine returns. |
| 104 | |
| 105 | Args: |
| 106 | stream: Input to read the response from. |
| 107 | |
| 108 | Raises: |
| 109 | EOFError: If the connection is closed without a full HTTP response. |
| 110 | SecurityError: If the response exceeds a security limit. |
| 111 | ValueError: If the response isn't well formatted. |
| 112 | |
| 113 | """ |
| 114 | # https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2 |
| 115 | |
| 116 | # As in read_request, parsing is simple because a fixed value is expected |
| 117 | # for version, status_code is a 3-digit number, and reason can be ignored. |
| 118 | |
| 119 | try: |
| 120 | status_line = await read_line(stream) |
| 121 | except EOFError as exc: |
| 122 | raise EOFError("connection closed while reading HTTP status line") from exc |
| 123 | |
| 124 | try: |
| 125 | version, raw_status_code, raw_reason = status_line.split(b" ", 2) |
| 126 | except ValueError: # not enough values to unpack (expected 3, got 1-2) |
| 127 | raise ValueError(f"invalid HTTP status line: {d(status_line)}") from None |
| 128 | |
| 129 | if version != b"HTTP/1.1": |
| 130 | raise ValueError(f"unsupported HTTP version: {d(version)}") |
| 131 | try: |
| 132 | status_code = int(raw_status_code) |
| 133 | except ValueError: # invalid literal for int() with base 10 |
| 134 | raise ValueError(f"invalid HTTP status code: {d(raw_status_code)}") from None |
| 135 | if not 100 <= status_code < 1000: |
| 136 | raise ValueError(f"unsupported HTTP status code: {d(raw_status_code)}") |
| 137 | if not _value_re.fullmatch(raw_reason): |
| 138 | raise ValueError(f"invalid HTTP reason phrase: {d(raw_reason)}") |
| 139 | reason = raw_reason.decode() |
| 140 | |
| 141 | headers = await read_headers(stream) |
| 142 | |
| 143 | return status_code, reason, headers |
| 144 | |
| 145 | |
| 146 | async def read_headers(stream: asyncio.StreamReader) -> Headers: |
searching dependent graphs…