{
    "file_diffs": [
        {
            "old_file_content": "#\n# Copyright 2014 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Client and server implementations of HTTP/1.x.\n\n.. versionadded:: 4.0\n\"\"\"\n\nimport asyncio\nimport logging\nimport re\nimport types\n\nfrom tornado.concurrent import (\n    Future,\n    future_add_done_callback,\n    future_set_result_unless_cancelled,\n)\nfrom tornado.escape import native_str, utf8\nfrom tornado import gen\nfrom tornado import httputil\nfrom tornado import iostream\nfrom tornado.log import gen_log, app_log\nfrom tornado.util import GzipDecompressor\n\n\nfrom typing import cast, Optional, Type, Awaitable, Callable, Union, Tuple\n\n\nclass _QuietException(Exception):\n    def __init__(self) -> None:\n        pass\n\n\nclass _ExceptionLoggingContext(object):\n    \"\"\"Used with the ``with`` statement when calling delegate methods to\n    log any exceptions with the given logger.  Any exceptions caught are\n    converted to _QuietException\n    \"\"\"\n\n    def __init__(self, logger: logging.Logger) -> None:\n        self.logger = logger\n\n    def __enter__(self) -> None:\n        pass\n\n    def __exit__(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: Optional[BaseException],\n        tb: types.TracebackType,\n    ) -> None:\n        if value is not None:\n            assert typ is not None\n            self.logger.error(\"Uncaught exception\", exc_info=(typ, value, tb))\n            raise _QuietException\n\n\nclass HTTP1ConnectionParameters(object):\n    \"\"\"Parameters for `.HTTP1Connection` and `.HTTP1ServerConnection`.\"\"\"\n\n    def __init__(\n        self,\n        no_keep_alive: bool = False,\n        chunk_size: Optional[int] = None,\n        max_header_size: Optional[int] = None,\n        header_timeout: Optional[float] = None,\n        max_body_size: Optional[int] = None,\n        body_timeout: Optional[float] = None,\n        decompress: bool = False,\n    ) -> None:\n        \"\"\"\n        :arg bool no_keep_alive: If true, always close the connection after\n            one request.\n        :arg int chunk_size: how much data to read into memory at once\n        :arg int max_header_size:  maximum amount of data for HTTP headers\n        :arg float header_timeout: how long to wait for all headers (seconds)\n        :arg int max_body_size: maximum amount of data for body\n        :arg float body_timeout: how long to wait while reading body (seconds)\n        :arg bool decompress: if true, decode incoming\n            ``Content-Encoding: gzip``\n        \"\"\"\n        self.no_keep_alive = no_keep_alive\n        self.chunk_size = chunk_size or 65536\n        self.max_header_size = max_header_size or 65536\n        self.header_timeout = header_timeout\n        self.max_body_size = max_body_size\n        self.body_timeout = body_timeout\n        self.decompress = decompress\n\n\nclass HTTP1Connection(httputil.HTTPConnection):\n    \"\"\"Implements the HTTP/1.x protocol.\n\n    This class can be on its own for clients, or via `HTTP1ServerConnection`\n    for servers.\n    \"\"\"\n\n    def __init__(\n        self,\n        stream: iostream.IOStream,\n        is_client: bool,\n        params: Optional[HTTP1ConnectionParameters] = None,\n        context: Optional[object] = None,\n    ) -> None:\n        \"\"\"\n        :arg stream: an `.IOStream`\n        :arg bool is_client: client or server\n        :arg params: a `.HTTP1ConnectionParameters` instance or ``None``\n        :arg context: an opaque application-defined object that can be accessed\n            as ``connection.context``.\n        \"\"\"\n        self.is_client = is_client\n        self.stream = stream\n        if params is None:\n            params = HTTP1ConnectionParameters()\n        self.params = params\n        self.context = context\n        self.no_keep_alive = params.no_keep_alive\n        # The body limits can be altered by the delegate, so save them\n        # here instead of just referencing self.params later.\n        self._max_body_size = (\n            self.params.max_body_size\n            if self.params.max_body_size is not None\n            else self.stream.max_buffer_size\n        )\n        self._body_timeout = self.params.body_timeout\n        # _write_finished is set to True when finish() has been called,\n        # i.e. there will be no more data sent.  Data may still be in the\n        # stream's write buffer.\n        self._write_finished = False\n        # True when we have read the entire incoming body.\n        self._read_finished = False\n        # _finish_future resolves when all data has been written and flushed\n        # to the IOStream.\n        self._finish_future = Future()  # type: Future[None]\n        # If true, the connection should be closed after this request\n        # (after the response has been written in the server side,\n        # and after it has been read in the client)\n        self._disconnect_on_finish = False\n        self._clear_callbacks()\n        # Save the start lines after we read or write them; they\n        # affect later processing (e.g. 304 responses and HEAD methods\n        # have content-length but no bodies)\n        self._request_start_line = None  # type: Optional[httputil.RequestStartLine]\n        self._response_start_line = None  # type: Optional[httputil.ResponseStartLine]\n        self._request_headers = None  # type: Optional[httputil.HTTPHeaders]\n        # True if we are writing output with chunked encoding.\n        self._chunking_output = False\n        # While reading a body with a content-length, this is the\n        # amount left to read.\n        self._expected_content_remaining = None  # type: Optional[int]\n        # A Future for our outgoing writes, returned by IOStream.write.\n        self._pending_write = None  # type: Optional[Future[None]]\n\n    def read_response(self, delegate: httputil.HTTPMessageDelegate) -> Awaitable[bool]:\n        \"\"\"Read a single HTTP response.\n\n        Typical client-mode usage is to write a request using `write_headers`,\n        `write`, and `finish`, and then call ``read_response``.\n\n        :arg delegate: a `.HTTPMessageDelegate`\n\n        Returns a `.Future` that resolves to a bool after the full response has\n        been read. The result is true if the stream is still open.\n        \"\"\"\n        if self.params.decompress:\n            delegate = _GzipMessageDelegate(delegate, self.params.chunk_size)\n        return self._read_message(delegate)\n\n    async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool:\n        need_delegate_close = False\n        try:\n            header_future = self.stream.read_until_regex(\n                b\"\\r?\\n\\r?\\n\", max_bytes=self.params.max_header_size\n            )\n            if self.params.header_timeout is None:\n                header_data = await header_future\n            else:\n                try:\n                    header_data = await gen.with_timeout(\n                        self.stream.io_loop.time() + self.params.header_timeout,\n                        header_future,\n                        quiet_exceptions=iostream.StreamClosedError,\n                    )\n                except gen.TimeoutError:\n                    self.close()\n                    return False\n            start_line_str, headers = self._parse_headers(header_data)\n            if self.is_client:\n                resp_start_line = httputil.parse_response_start_line(start_line_str)\n                self._response_start_line = resp_start_line\n                start_line = (\n                    resp_start_line\n                )  # type: Union[httputil.RequestStartLine, httputil.ResponseStartLine]\n                # TODO: this will need to change to support client-side keepalive\n                self._disconnect_on_finish = False\n            else:\n                req_start_line = httputil.parse_request_start_line(start_line_str)\n                self._request_start_line = req_start_line\n                self._request_headers = headers\n                start_line = req_start_line\n                self._disconnect_on_finish = not self._can_keep_alive(\n                    req_start_line, headers\n                )\n            need_delegate_close = True\n            with _ExceptionLoggingContext(app_log):\n                header_recv_future = delegate.headers_received(start_line, headers)\n                if header_recv_future is not None:\n                    await header_recv_future\n            if self.stream is None:\n                # We've been detached.\n                need_delegate_close = False\n                return False\n            skip_body = False\n            if self.is_client:\n                assert isinstance(start_line, httputil.ResponseStartLine)\n                if (\n                    self._request_start_line is not None\n                    and self._request_start_line.method == \"HEAD\"\n                ):\n                    skip_body = True\n                code = start_line.code\n                if code == 304:\n                    # 304 responses may include the content-length header\n                    # but do not actually have a body.\n                    # http://tools.ietf.org/html/rfc7230#section-3.3\n                    skip_body = True\n                if 100 <= code < 200:\n                    # 1xx responses should never indicate the presence of\n                    # a body.\n                    if \"Content-Length\" in headers or \"Transfer-Encoding\" in headers:\n                        raise httputil.HTTPInputError(\n                            \"Response code %d cannot have body\" % code\n                        )\n                    # TODO: client delegates will get headers_received twice\n                    # in the case of a 100-continue.  Document or change?\n                    await self._read_message(delegate)\n            else:\n                if headers.get(\"Expect\") == \"100-continue\" and not self._write_finished:\n                    self.stream.write(b\"HTTP/1.1 100 (Continue)\\r\\n\\r\\n\")\n            if not skip_body:\n                body_future = self._read_body(\n                    resp_start_line.code if self.is_client else 0, headers, delegate\n                )\n                if body_future is not None:\n                    if self._body_timeout is None:\n                        await body_future\n                    else:\n                        try:\n                            await gen.with_timeout(\n                                self.stream.io_loop.time() + self._body_timeout,\n                                body_future,\n                                quiet_exceptions=iostream.StreamClosedError,\n                            )\n                        except gen.TimeoutError:\n                            gen_log.info(\"Timeout reading body from %s\", self.context)\n                            self.stream.close()\n                            return False\n            self._read_finished = True\n            if not self._write_finished or self.is_client:\n                need_delegate_close = False\n                with _ExceptionLoggingContext(app_log):\n                    delegate.finish()\n            # If we're waiting for the application to produce an asynchronous\n            # response, and we're not detached, register a close callback\n            # on the stream (we didn't need one while we were reading)\n            if (\n                not self._finish_future.done()\n                and self.stream is not None\n                and not self.stream.closed()\n            ):\n                self.stream.set_close_callback(self._on_connection_close)\n                await self._finish_future\n            if self.is_client and self._disconnect_on_finish:\n                self.close()\n            if self.stream is None:\n                return False\n        except httputil.HTTPInputError as e:\n            gen_log.info(\"Malformed HTTP message from %s: %s\", self.context, e)\n            if not self.is_client:\n                await self.stream.write(b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n            self.close()\n            return False\n        finally:\n            if need_delegate_close:\n                with _ExceptionLoggingContext(app_log):\n                    delegate.on_connection_close()\n            header_future = None  # type: ignore\n            self._clear_callbacks()\n        return True\n\n    def _clear_callbacks(self) -> None:\n        \"\"\"Clears the callback attributes.\n\n        This allows the request handler to be garbage collected more\n        quickly in CPython by breaking up reference cycles.\n        \"\"\"\n        self._write_callback = None\n        self._write_future = None  # type: Optional[Future[None]]\n        self._close_callback = None  # type: Optional[Callable[[], None]]\n        if self.stream is not None:\n            self.stream.set_close_callback(None)\n\n    def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None:\n        \"\"\"Sets a callback that will be run when the connection is closed.\n\n        Note that this callback is slightly different from\n        `.HTTPMessageDelegate.on_connection_close`: The\n        `.HTTPMessageDelegate` method is called when the connection is\n        closed while receiving a message. This callback is used when\n        there is not an active delegate (for example, on the server\n        side this callback is used if the client closes the connection\n        after sending its request but before receiving all the\n        response.\n        \"\"\"\n        self._close_callback = callback\n\n    def _on_connection_close(self) -> None:\n        # Note that this callback is only registered on the IOStream\n        # when we have finished reading the request and are waiting for\n        # the application to produce its response.\n        if self._close_callback is not None:\n            callback = self._close_callback\n            self._close_callback = None\n            callback()\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n        self._clear_callbacks()\n\n    def close(self) -> None:\n        if self.stream is not None:\n            self.stream.close()\n        self._clear_callbacks()\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n\n    def detach(self) -> iostream.IOStream:\n        \"\"\"Take control of the underlying stream.\n\n        Returns the underlying `.IOStream` object and stops all further\n        HTTP processing.  May only be called during\n        `.HTTPMessageDelegate.headers_received`.  Intended for implementing\n        protocols like websockets that tunnel over an HTTP handshake.\n        \"\"\"\n        self._clear_callbacks()\n        stream = self.stream\n        self.stream = None  # type: ignore\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n        return stream\n\n    def set_body_timeout(self, timeout: float) -> None:\n        \"\"\"Sets the body timeout for a single request.\n\n        Overrides the value from `.HTTP1ConnectionParameters`.\n        \"\"\"\n        self._body_timeout = timeout\n\n    def set_max_body_size(self, max_body_size: int) -> None:\n        \"\"\"Sets the body size limit for a single request.\n\n        Overrides the value from `.HTTP1ConnectionParameters`.\n        \"\"\"\n        self._max_body_size = max_body_size\n\n    def write_headers(\n        self,\n        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],\n        headers: httputil.HTTPHeaders,\n        chunk: Optional[bytes] = None,\n    ) -> \"Future[None]\":\n        \"\"\"Implements `.HTTPConnection.write_headers`.\"\"\"\n        lines = []\n        if self.is_client:\n            assert isinstance(start_line, httputil.RequestStartLine)\n            self._request_start_line = start_line\n            lines.append(utf8(\"%s %s HTTP/1.1\" % (start_line[0], start_line[1])))\n            # Client requests with a non-empty body must have either a\n            # Content-Length or a Transfer-Encoding.\n            self._chunking_output = (\n                start_line.method in (\"POST\", \"PUT\", \"PATCH\")\n                and \"Content-Length\" not in headers\n                and (\n                    \"Transfer-Encoding\" not in headers\n                    or headers[\"Transfer-Encoding\"] == \"chunked\"\n                )\n            )\n        else:\n            assert isinstance(start_line, httputil.ResponseStartLine)\n            assert self._request_start_line is not None\n            assert self._request_headers is not None\n            self._response_start_line = start_line\n            lines.append(utf8(\"HTTP/1.1 %d %s\" % (start_line[1], start_line[2])))\n            self._chunking_output = (\n                # TODO: should this use\n                # self._request_start_line.version or\n                # start_line.version?\n                self._request_start_line.version == \"HTTP/1.1\"\n                # Omit payload header field for HEAD request.\n                and self._request_start_line.method != \"HEAD\"\n                # 1xx, 204 and 304 responses have no body (not even a zero-length\n                # body), and so should not have either Content-Length or\n                # Transfer-Encoding headers.\n                and start_line.code not in (204, 304)\n                and (start_line.code < 100 or start_line.code >= 200)\n                # No need to chunk the output if a Content-Length is specified.\n                and \"Content-Length\" not in headers\n                # Applications are discouraged from touching Transfer-Encoding,\n                # but if they do, leave it alone.\n                and \"Transfer-Encoding\" not in headers\n            )\n            # If connection to a 1.1 client will be closed, inform client\n            if (\n                self._request_start_line.version == \"HTTP/1.1\"\n                and self._disconnect_on_finish\n            ):\n                headers[\"Connection\"] = \"close\"\n            # If a 1.0 client asked for keep-alive, add the header.\n            if (\n                self._request_start_line.version == \"HTTP/1.0\"\n                and self._request_headers.get(\"Connection\", \"\").lower() == \"keep-alive\"\n            ):\n                headers[\"Connection\"] = \"Keep-Alive\"\n        if self._chunking_output:\n            headers[\"Transfer-Encoding\"] = \"chunked\"\n        if not self.is_client and (\n            self._request_start_line.method == \"HEAD\"\n            or cast(httputil.ResponseStartLine, start_line).code == 304\n        ):\n            self._expected_content_remaining = 0\n        elif \"Content-Length\" in headers:\n            self._expected_content_remaining = int(headers[\"Content-Length\"])\n        else:\n            self._expected_content_remaining = None\n        # TODO: headers are supposed to be of type str, but we still have some\n        # cases that let bytes slip through. Remove these native_str calls when those\n        # are fixed.\n        header_lines = (\n            native_str(n) + \": \" + native_str(v) for n, v in headers.get_all()\n        )\n        lines.extend(line.encode(\"latin1\") for line in header_lines)\n        for line in lines:\n            if b\"\\n\" in line:\n                raise ValueError(\"Newline in header: \" + repr(line))\n        future = None\n        if self.stream.closed():\n            future = self._write_future = Future()\n            future.set_exception(iostream.StreamClosedError())\n            future.exception()\n        else:\n            future = self._write_future = Future()\n            data = b\"\\r\\n\".join(lines) + b\"\\r\\n\\r\\n\"\n            if chunk:\n                data += self._format_chunk(chunk)\n            self._pending_write = self.stream.write(data)\n            future_add_done_callback(self._pending_write, self._on_write_complete)\n        return future\n\n    def _format_chunk(self, chunk: bytes) -> bytes:\n        if self._expected_content_remaining is not None:\n            self._expected_content_remaining -= len(chunk)\n            if self._expected_content_remaining < 0:\n                # Close the stream now to stop further framing errors.\n                self.stream.close()\n                raise httputil.HTTPOutputError(\n                    \"Tried to write more data than Content-Length\"\n                )\n        if self._chunking_output and chunk:\n            # Don't write out empty chunks because that means END-OF-STREAM\n            # with chunked encoding\n            return utf8(\"%x\" % len(chunk)) + b\"\\r\\n\" + chunk + b\"\\r\\n\"\n        else:\n            return chunk\n\n    def write(self, chunk: bytes) -> \"Future[None]\":\n        \"\"\"Implements `.HTTPConnection.write`.\n\n        For backwards compatibility it is allowed but deprecated to\n        skip `write_headers` and instead call `write()` with a\n        pre-encoded header block.\n        \"\"\"\n        future = None\n        if self.stream.closed():\n            future = self._write_future = Future()\n            self._write_future.set_exception(iostream.StreamClosedError())\n            self._write_future.exception()\n        else:\n            future = self._write_future = Future()\n            self._pending_write = self.stream.write(self._format_chunk(chunk))\n            future_add_done_callback(self._pending_write, self._on_write_complete)\n        return future\n\n    def finish(self) -> None:\n        \"\"\"Implements `.HTTPConnection.finish`.\"\"\"\n        if (\n            self._expected_content_remaining is not None\n            and self._expected_content_remaining != 0\n            and not self.stream.closed()\n        ):\n            self.stream.close()\n            raise httputil.HTTPOutputError(\n                \"Tried to write %d bytes less than Content-Length\"\n                % self._expected_content_remaining\n            )\n        if self._chunking_output:\n            if not self.stream.closed():\n                self._pending_write = self.stream.write(b\"0\\r\\n\\r\\n\")\n                self._pending_write.add_done_callback(self._on_write_complete)\n        self._write_finished = True\n        # If the app finished the request while we're still reading,\n        # divert any remaining data away from the delegate and\n        # close the connection when we're done sending our response.\n        # Closing the connection is the only way to avoid reading the\n        # whole input body.\n        if not self._read_finished:\n            self._disconnect_on_finish = True\n        # No more data is coming, so instruct TCP to send any remaining\n        # data immediately instead of waiting for a full packet or ack.\n        self.stream.set_nodelay(True)\n        if self._pending_write is None:\n            self._finish_request(None)\n        else:\n            future_add_done_callback(self._pending_write, self._finish_request)\n\n    def _on_write_complete(self, future: \"Future[None]\") -> None:\n        exc = future.exception()\n        if exc is not None and not isinstance(exc, iostream.StreamClosedError):\n            future.result()\n        if self._write_callback is not None:\n            callback = self._write_callback\n            self._write_callback = None\n            self.stream.io_loop.add_callback(callback)\n        if self._write_future is not None:\n            future = self._write_future\n            self._write_future = None\n            future_set_result_unless_cancelled(future, None)\n\n    def _can_keep_alive(\n        self, start_line: httputil.RequestStartLine, headers: httputil.HTTPHeaders\n    ) -> bool:\n        if self.params.no_keep_alive:\n            return False\n        connection_header = headers.get(\"Connection\")\n        if connection_header is not None:\n            connection_header = connection_header.lower()\n        if start_line.version == \"HTTP/1.1\":\n            return connection_header != \"close\"\n        elif (\n            \"Content-Length\" in headers\n            or headers.get(\"Transfer-Encoding\", \"\").lower() == \"chunked\"\n            or getattr(start_line, \"method\", None) in (\"HEAD\", \"GET\")\n        ):\n            # start_line may be a request or response start line; only\n            # the former has a method attribute.\n            return connection_header == \"keep-alive\"\n        return False\n\n    def _finish_request(self, future: \"Optional[Future[None]]\") -> None:\n        self._clear_callbacks()\n        if not self.is_client and self._disconnect_on_finish:\n            self.close()\n            return\n        # Turn Nagle's algorithm back on, leaving the stream in its\n        # default state for the next request.\n        self.stream.set_nodelay(False)\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n\n    def _parse_headers(self, data: bytes) -> Tuple[str, httputil.HTTPHeaders]:\n        # The lstrip removes newlines that some implementations sometimes\n        # insert between messages of a reused connection.  Per RFC 7230,\n        # we SHOULD ignore at least one empty line before the request.\n        # http://tools.ietf.org/html/rfc7230#section-3.5\n        data_str = native_str(data.decode(\"latin1\")).lstrip(\"\\r\\n\")\n        # RFC 7230 section allows for both CRLF and bare LF.\n        eol = data_str.find(\"\\n\")\n        start_line = data_str[:eol].rstrip(\"\\r\")\n        headers = httputil.HTTPHeaders.parse(data_str[eol:])\n        return start_line, headers\n\n    def _read_body(\n        self,\n        code: int,\n        headers: httputil.HTTPHeaders,\n        delegate: httputil.HTTPMessageDelegate,\n    ) -> Optional[Awaitable[None]]:\n        if \"Content-Length\" in headers:\n            if \"Transfer-Encoding\" in headers:\n                # Response cannot contain both Content-Length and\n                # Transfer-Encoding headers.\n                # http://tools.ietf.org/html/rfc7230#section-3.3.3\n                raise httputil.HTTPInputError(\n                    \"Response with both Transfer-Encoding and Content-Length\"\n                )\n            if \",\" in headers[\"Content-Length\"]:\n                # Proxies sometimes cause Content-Length headers to get\n                # duplicated.  If all the values are identical then we can\n                # use them but if they differ it's an error.\n                pieces = re.split(r\",\\s*\", headers[\"Content-Length\"])\n                if any(i != pieces[0] for i in pieces):\n                    raise httputil.HTTPInputError(\n                        \"Multiple unequal Content-Lengths: %r\"\n                        % headers[\"Content-Length\"]\n                    )\n                headers[\"Content-Length\"] = pieces[0]\n\n            try:\n                content_length = int(headers[\"Content-Length\"])  # type: Optional[int]\n            except ValueError:\n                # Handles non-integer Content-Length value.\n                raise httputil.HTTPInputError(\n                    \"Only integer Content-Length is allowed: %s\"\n                    % headers[\"Content-Length\"]\n                )\n\n            if cast(int, content_length) > self._max_body_size:\n                raise httputil.HTTPInputError(\"Content-Length too long\")\n        else:\n            content_length = None\n\n        if code == 204:\n            # This response code is not allowed to have a non-empty body,\n            # and has an implicit length of zero instead of read-until-close.\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3\n            if \"Transfer-Encoding\" in headers or content_length not in (None, 0):\n                raise httputil.HTTPInputError(\n                    \"Response with code %d should not have body\" % code\n                )\n            content_length = 0\n\n        if content_length is not None:\n            return self._read_fixed_body(content_length, delegate)\n        if headers.get(\"Transfer-Encoding\", \"\").lower() == \"chunked\":\n            return self._read_chunked_body(delegate)\n        if self.is_client:\n            return self._read_body_until_close(delegate)\n        return None\n\n    async def _read_fixed_body(\n        self, content_length: int, delegate: httputil.HTTPMessageDelegate\n    ) -> None:\n        while content_length > 0:\n            body = await self.stream.read_bytes(\n                min(self.params.chunk_size, content_length), partial=True\n            )\n            content_length -= len(body)\n            if not self._write_finished or self.is_client:\n                with _ExceptionLoggingContext(app_log):\n                    ret = delegate.data_received(body)\n                    if ret is not None:\n                        await ret\n\n    async def _read_chunked_body(self, delegate: httputil.HTTPMessageDelegate) -> None:\n        # TODO: \"chunk extensions\" http://tools.ietf.org/html/rfc2616#section-3.6.1\n        total_size = 0\n        while True:\n            chunk_len_str = await self.stream.read_until(b\"\\r\\n\", max_bytes=64)\n            chunk_len = int(chunk_len_str.strip(), 16)\n            if chunk_len == 0:\n                crlf = await self.stream.read_bytes(2)\n                if crlf != b\"\\r\\n\":\n                    raise httputil.HTTPInputError(\n                        \"improperly terminated chunked request\"\n                    )\n                return\n            total_size += chunk_len\n            if total_size > self._max_body_size:\n                raise httputil.HTTPInputError(\"chunked body too large\")\n            bytes_to_read = chunk_len\n            while bytes_to_read:\n                chunk = await self.stream.read_bytes(\n                    min(bytes_to_read, self.params.chunk_size), partial=True\n                )\n                bytes_to_read -= len(chunk)\n                if not self._write_finished or self.is_client:\n                    with _ExceptionLoggingContext(app_log):\n                        ret = delegate.data_received(chunk)\n                        if ret is not None:\n                            await ret\n            # chunk ends with \\r\\n\n            crlf = await self.stream.read_bytes(2)\n            assert crlf == b\"\\r\\n\"\n\n    async def _read_body_until_close(\n        self, delegate: httputil.HTTPMessageDelegate\n    ) -> None:\n        body = await self.stream.read_until_close()\n        if not self._write_finished or self.is_client:\n            with _ExceptionLoggingContext(app_log):\n                ret = delegate.data_received(body)\n                if ret is not None:\n                    await ret\n\n\nclass _GzipMessageDelegate(httputil.HTTPMessageDelegate):\n    \"\"\"Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``.\"\"\"\n\n    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None:\n        self._delegate = delegate\n        self._chunk_size = chunk_size\n        self._decompressor = None  # type: Optional[GzipDecompressor]\n\n    def headers_received(\n        self,\n        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],\n        headers: httputil.HTTPHeaders,\n    ) -> Optional[Awaitable[None]]:\n        if headers.get(\"Content-Encoding\") == \"gzip\":\n            self._decompressor = GzipDecompressor()\n            # Downstream delegates will only see uncompressed data,\n            # so rename the content-encoding header.\n            # (but note that curl_httpclient doesn't do this).\n            headers.add(\"X-Consumed-Content-Encoding\", headers[\"Content-Encoding\"])\n            del headers[\"Content-Encoding\"]\n        return self._delegate.headers_received(start_line, headers)\n\n    async def data_received(self, chunk: bytes) -> None:\n        if self._decompressor:\n            compressed_data = chunk\n            while compressed_data:\n                decompressed = self._decompressor.decompress(\n                    compressed_data, self._chunk_size\n                )\n                if decompressed:\n                    ret = self._delegate.data_received(decompressed)\n                    if ret is not None:\n                        await ret\n                compressed_data = self._decompressor.unconsumed_tail\n                if compressed_data and not decompressed:\n                    raise httputil.HTTPInputError(\n                        \"encountered unconsumed gzip data without making progress\"\n                    )\n        else:\n            ret = self._delegate.data_received(chunk)\n            if ret is not None:\n                await ret\n\n    def finish(self) -> None:\n        if self._decompressor is not None:\n            tail = self._decompressor.flush()\n            if tail:\n                # The tail should always be empty: decompress returned\n                # all that it can in data_received and the only\n                # purpose of the flush call is to detect errors such\n                # as truncated input. If we did legitimately get a new\n                # chunk at this point we'd need to change the\n                # interface to make finish() a coroutine.\n                raise ValueError(\n                    \"decompressor.flush returned data; possible truncated input\"\n                )\n        return self._delegate.finish()\n\n    def on_connection_close(self) -> None:\n        return self._delegate.on_connection_close()\n\n\nclass HTTP1ServerConnection(object):\n    \"\"\"An HTTP/1.x server.\"\"\"\n\n    def __init__(\n        self,\n        stream: iostream.IOStream,\n        params: Optional[HTTP1ConnectionParameters] = None,\n        context: Optional[object] = None,\n    ) -> None:\n        \"\"\"\n        :arg stream: an `.IOStream`\n        :arg params: a `.HTTP1ConnectionParameters` or None\n        :arg context: an opaque application-defined object that is accessible\n            as ``connection.context``\n        \"\"\"\n        self.stream = stream\n        if params is None:\n            params = HTTP1ConnectionParameters()\n        self.params = params\n        self.context = context\n        self._serving_future = None  # type: Optional[Future[None]]\n\n    async def close(self) -> None:\n        \"\"\"Closes the connection.\n\n        Returns a `.Future` that resolves after the serving loop has exited.\n        \"\"\"\n        self.stream.close()\n        # Block until the serving loop is done, but ignore any exceptions\n        # (start_serving is already responsible for logging them).\n        assert self._serving_future is not None\n        try:\n            await self._serving_future\n        except Exception:\n            pass\n\n    def start_serving(self, delegate: httputil.HTTPServerConnectionDelegate) -> None:\n        \"\"\"Starts serving requests on this connection.\n\n        :arg delegate: a `.HTTPServerConnectionDelegate`\n        \"\"\"\n        assert isinstance(delegate, httputil.HTTPServerConnectionDelegate)\n        fut = gen.convert_yielded(self._server_request_loop(delegate))\n        self._serving_future = fut\n        # Register the future on the IOLoop so its errors get logged.\n        self.stream.io_loop.add_future(fut, lambda f: f.result())\n\n    async def _server_request_loop(\n        self, delegate: httputil.HTTPServerConnectionDelegate\n    ) -> None:\n        try:\n            while True:\n                conn = HTTP1Connection(self.stream, False, self.params, self.context)\n                request_delegate = delegate.start_request(self, conn)\n                try:\n                    ret = await conn.read_response(request_delegate)\n                except (\n                    iostream.StreamClosedError,\n                    iostream.UnsatisfiableReadError,\n                    asyncio.CancelledError,\n                ):\n                    return\n                except _QuietException:\n                    # This exception was already logged.\n                    conn.close()\n                    return\n                except Exception:\n                    gen_log.error(\"Uncaught exception\", exc_info=True)\n                    conn.close()\n                    return\n                if not ret:\n                    return\n                await asyncio.sleep(0)\n        finally:\n            delegate.on_close(self)\n",
            "new_file_content": "#\n# Copyright 2014 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Client and server implementations of HTTP/1.x.\n\n.. versionadded:: 4.0\n\"\"\"\n\nimport asyncio\nimport logging\nimport re\nimport types\n\nfrom tornado.concurrent import (\n    Future,\n    future_add_done_callback,\n    future_set_result_unless_cancelled,\n)\nfrom tornado.escape import native_str, utf8\nfrom tornado import gen\nfrom tornado import httputil\nfrom tornado import iostream\nfrom tornado.log import gen_log, app_log\nfrom tornado.util import GzipDecompressor\n\n\nfrom typing import cast, Optional, Type, Awaitable, Callable, Union, Tuple\n\n\nclass _QuietException(Exception):\n    def __init__(self) -> None:\n        pass\n\n\nclass _ExceptionLoggingContext(object):\n    \"\"\"Used with the ``with`` statement when calling delegate methods to\n    log any exceptions with the given logger.  Any exceptions caught are\n    converted to _QuietException\n    \"\"\"\n\n    def __init__(self, logger: logging.Logger) -> None:\n        self.logger = logger\n\n    def __enter__(self) -> None:\n        pass\n\n    def __exit__(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: Optional[BaseException],\n        tb: types.TracebackType,\n    ) -> None:\n        if value is not None:\n            assert typ is not None\n            self.logger.error(\"Uncaught exception\", exc_info=(typ, value, tb))\n            raise _QuietException\n\n\nclass HTTP1ConnectionParameters(object):\n    \"\"\"Parameters for `.HTTP1Connection` and `.HTTP1ServerConnection`.\"\"\"\n\n    def __init__(\n        self,\n        no_keep_alive: bool = False,\n        chunk_size: Optional[int] = None,\n        max_header_size: Optional[int] = None,\n        header_timeout: Optional[float] = None,\n        max_body_size: Optional[int] = None,\n        body_timeout: Optional[float] = None,\n        decompress: bool = False,\n    ) -> None:\n        \"\"\"\n        :arg bool no_keep_alive: If true, always close the connection after\n            one request.\n        :arg int chunk_size: how much data to read into memory at once\n        :arg int max_header_size:  maximum amount of data for HTTP headers\n        :arg float header_timeout: how long to wait for all headers (seconds)\n        :arg int max_body_size: maximum amount of data for body\n        :arg float body_timeout: how long to wait while reading body (seconds)\n        :arg bool decompress: if true, decode incoming\n            ``Content-Encoding: gzip``\n        \"\"\"\n        self.no_keep_alive = no_keep_alive\n        self.chunk_size = chunk_size or 65536\n        self.max_header_size = max_header_size or 65536\n        self.header_timeout = header_timeout\n        self.max_body_size = max_body_size\n        self.body_timeout = body_timeout\n        self.decompress = decompress\n\n\nclass HTTP1Connection(httputil.HTTPConnection):\n    \"\"\"Implements the HTTP/1.x protocol.\n\n    This class can be on its own for clients, or via `HTTP1ServerConnection`\n    for servers.\n    \"\"\"\n\n    def __init__(\n        self,\n        stream: iostream.IOStream,\n        is_client: bool,\n        params: Optional[HTTP1ConnectionParameters] = None,\n        context: Optional[object] = None,\n    ) -> None:\n        \"\"\"\n        :arg stream: an `.IOStream`\n        :arg bool is_client: client or server\n        :arg params: a `.HTTP1ConnectionParameters` instance or ``None``\n        :arg context: an opaque application-defined object that can be accessed\n            as ``connection.context``.\n        \"\"\"\n        self.is_client = is_client\n        self.stream = stream\n        if params is None:\n            params = HTTP1ConnectionParameters()\n        self.params = params\n        self.context = context\n        self.no_keep_alive = params.no_keep_alive\n        # The body limits can be altered by the delegate, so save them\n        # here instead of just referencing self.params later.\n        self._max_body_size = (\n            self.params.max_body_size\n            if self.params.max_body_size is not None\n            else self.stream.max_buffer_size\n        )\n        self._body_timeout = self.params.body_timeout\n        # _write_finished is set to True when finish() has been called,\n        # i.e. there will be no more data sent.  Data may still be in the\n        # stream's write buffer.\n        self._write_finished = False\n        # True when we have read the entire incoming body.\n        self._read_finished = False\n        # _finish_future resolves when all data has been written and flushed\n        # to the IOStream.\n        self._finish_future = Future()  # type: Future[None]\n        # If true, the connection should be closed after this request\n        # (after the response has been written in the server side,\n        # and after it has been read in the client)\n        self._disconnect_on_finish = False\n        self._clear_callbacks()\n        # Save the start lines after we read or write them; they\n        # affect later processing (e.g. 304 responses and HEAD methods\n        # have content-length but no bodies)\n        self._request_start_line = None  # type: Optional[httputil.RequestStartLine]\n        self._response_start_line = None  # type: Optional[httputil.ResponseStartLine]\n        self._request_headers = None  # type: Optional[httputil.HTTPHeaders]\n        # True if we are writing output with chunked encoding.\n        self._chunking_output = False\n        # While reading a body with a content-length, this is the\n        # amount left to read.\n        self._expected_content_remaining = None  # type: Optional[int]\n        # A Future for our outgoing writes, returned by IOStream.write.\n        self._pending_write = None  # type: Optional[Future[None]]\n\n    def read_response(self, delegate: httputil.HTTPMessageDelegate) -> Awaitable[bool]:\n        \"\"\"Read a single HTTP response.\n\n        Typical client-mode usage is to write a request using `write_headers`,\n        `write`, and `finish`, and then call ``read_response``.\n\n        :arg delegate: a `.HTTPMessageDelegate`\n\n        Returns a `.Future` that resolves to a bool after the full response has\n        been read. The result is true if the stream is still open.\n        \"\"\"\n        if self.params.decompress:\n            delegate = _GzipMessageDelegate(delegate, self.params.chunk_size)\n        return self._read_message(delegate)\n\n    async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool:\n        need_delegate_close = False\n        try:\n            header_future = self.stream.read_until_regex(\n                b\"\\r?\\n\\r?\\n\", max_bytes=self.params.max_header_size\n            )\n            if self.params.header_timeout is None:\n                header_data = await header_future\n            else:\n                try:\n                    header_data = await gen.with_timeout(\n                        self.stream.io_loop.time() + self.params.header_timeout,\n                        header_future,\n                        quiet_exceptions=iostream.StreamClosedError,\n                    )\n                except gen.TimeoutError:\n                    self.close()\n                    return False\n            start_line_str, headers = self._parse_headers(header_data)\n            if self.is_client:\n                resp_start_line = httputil.parse_response_start_line(start_line_str)\n                self._response_start_line = resp_start_line\n                start_line = (\n                    resp_start_line\n                )  # type: Union[httputil.RequestStartLine, httputil.ResponseStartLine]\n                # TODO: this will need to change to support client-side keepalive\n                self._disconnect_on_finish = False\n            else:\n                req_start_line = httputil.parse_request_start_line(start_line_str)\n                self._request_start_line = req_start_line\n                self._request_headers = headers\n                start_line = req_start_line\n                self._disconnect_on_finish = not self._can_keep_alive(\n                    req_start_line, headers\n                )\n            need_delegate_close = True\n            with _ExceptionLoggingContext(app_log):\n                header_recv_future = delegate.headers_received(start_line, headers)\n                if header_recv_future is not None:\n                    await header_recv_future\n            if self.stream is None:\n                # We've been detached.\n                need_delegate_close = False\n                return False\n            skip_body = False\n            if self.is_client:\n                assert isinstance(start_line, httputil.ResponseStartLine)\n                if (\n                    self._request_start_line is not None\n                    and self._request_start_line.method == \"HEAD\"\n                ):\n                    skip_body = True\n                code = start_line.code\n                if code == 304:\n                    # 304 responses may include the content-length header\n                    # but do not actually have a body.\n                    # http://tools.ietf.org/html/rfc7230#section-3.3\n                    skip_body = True\n                if 100 <= code < 200:\n                    # 1xx responses should never indicate the presence of\n                    # a body.\n                    if \"Content-Length\" in headers or \"Transfer-Encoding\" in headers:\n                        raise httputil.HTTPInputError(\n                            \"Response code %d cannot have body\" % code\n                        )\n                    # TODO: client delegates will get headers_received twice\n                    # in the case of a 100-continue.  Document or change?\n                    await self._read_message(delegate)\n            else:\n                if headers.get(\"Expect\") == \"100-continue\" and not self._write_finished:\n                    self.stream.write(b\"HTTP/1.1 100 (Continue)\\r\\n\\r\\n\")\n            if not skip_body:\n                body_future = self._read_body(\n                    resp_start_line.code if self.is_client else 0, headers, delegate\n                )\n                if body_future is not None:\n                    if self._body_timeout is None:\n                        await body_future\n                    else:\n                        try:\n                            await gen.with_timeout(\n                                self.stream.io_loop.time() + self._body_timeout,\n                                body_future,\n                                quiet_exceptions=iostream.StreamClosedError,\n                            )\n                        except gen.TimeoutError:\n                            gen_log.info(\"Timeout reading body from %s\", self.context)\n                            self.stream.close()\n                            return False\n            self._read_finished = True\n            if not self._write_finished or self.is_client:\n                need_delegate_close = False\n                with _ExceptionLoggingContext(app_log):\n                    delegate.finish()\n            # If we're waiting for the application to produce an asynchronous\n            # response, and we're not detached, register a close callback\n            # on the stream (we didn't need one while we were reading)\n            if (\n                not self._finish_future.done()\n                and self.stream is not None\n                and not self.stream.closed()\n            ):\n                self.stream.set_close_callback(self._on_connection_close)\n                await self._finish_future\n            if self.is_client and self._disconnect_on_finish:\n                self.close()\n            if self.stream is None:\n                return False\n        except httputil.HTTPInputError as e:\n            gen_log.info(\"Malformed HTTP message from %s: %s\", self.context, e)\n            if not self.is_client:\n                await self.stream.write(b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n            self.close()\n            return False\n        finally:\n            if need_delegate_close:\n                with _ExceptionLoggingContext(app_log):\n                    delegate.on_connection_close()\n            header_future = None  # type: ignore\n            self._clear_callbacks()\n        return True\n\n    def _clear_callbacks(self) -> None:\n        \"\"\"Clears the callback attributes.\n\n        This allows the request handler to be garbage collected more\n        quickly in CPython by breaking up reference cycles.\n        \"\"\"\n        self._write_callback = None\n        self._write_future = None  # type: Optional[Future[None]]\n        self._close_callback = None  # type: Optional[Callable[[], None]]\n        if self.stream is not None:\n            self.stream.set_close_callback(None)\n\n    def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None:\n        \"\"\"Sets a callback that will be run when the connection is closed.\n\n        Note that this callback is slightly different from\n        `.HTTPMessageDelegate.on_connection_close`: The\n        `.HTTPMessageDelegate` method is called when the connection is\n        closed while receiving a message. This callback is used when\n        there is not an active delegate (for example, on the server\n        side this callback is used if the client closes the connection\n        after sending its request but before receiving all the\n        response.\n        \"\"\"\n        self._close_callback = callback\n\n    def _on_connection_close(self) -> None:\n        # Note that this callback is only registered on the IOStream\n        # when we have finished reading the request and are waiting for\n        # the application to produce its response.\n        if self._close_callback is not None:\n            callback = self._close_callback\n            self._close_callback = None\n            callback()\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n        self._clear_callbacks()\n\n    def close(self) -> None:\n        if self.stream is not None:\n            self.stream.close()\n        self._clear_callbacks()\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n\n    def detach(self) -> iostream.IOStream:\n        \"\"\"Take control of the underlying stream.\n\n        Returns the underlying `.IOStream` object and stops all further\n        HTTP processing.  May only be called during\n        `.HTTPMessageDelegate.headers_received`.  Intended for implementing\n        protocols like websockets that tunnel over an HTTP handshake.\n        \"\"\"\n        self._clear_callbacks()\n        stream = self.stream\n        self.stream = None  # type: ignore\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n        return stream\n\n    def set_body_timeout(self, timeout: float) -> None:\n        \"\"\"Sets the body timeout for a single request.\n\n        Overrides the value from `.HTTP1ConnectionParameters`.\n        \"\"\"\n        self._body_timeout = timeout\n\n    def set_max_body_size(self, max_body_size: int) -> None:\n        \"\"\"Sets the body size limit for a single request.\n\n        Overrides the value from `.HTTP1ConnectionParameters`.\n        \"\"\"\n        self._max_body_size = max_body_size\n\n    def write_headers(\n        self,\n        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],\n        headers: httputil.HTTPHeaders,\n        chunk: Optional[bytes] = None,\n    ) -> \"Future[None]\":\n        \"\"\"Implements `.HTTPConnection.write_headers`.\"\"\"\n        lines = []\n        if self.is_client:\n            assert isinstance(start_line, httputil.RequestStartLine)\n            self._request_start_line = start_line\n            lines.append(utf8(\"%s %s HTTP/1.1\" % (start_line[0], start_line[1])))\n            # Client requests with a non-empty body must have either a\n            # Content-Length or a Transfer-Encoding.\n            self._chunking_output = (\n                start_line.method in (\"POST\", \"PUT\", \"PATCH\")\n                and \"Content-Length\" not in headers\n                and (\n                    \"Transfer-Encoding\" not in headers\n                    or headers[\"Transfer-Encoding\"] == \"chunked\"\n                )\n            )\n        else:\n            assert isinstance(start_line, httputil.ResponseStartLine)\n            assert self._request_start_line is not None\n            assert self._request_headers is not None\n            self._response_start_line = start_line\n            lines.append(utf8(\"HTTP/1.1 %d %s\" % (start_line[1], start_line[2])))\n            self._chunking_output = (\n                # TODO: should this use\n                # self._request_start_line.version or\n                # start_line.version?\n                self._request_start_line.version == \"HTTP/1.1\"\n                # Omit payload header field for HEAD request.\n                and self._request_start_line.method != \"HEAD\"\n                # 1xx, 204 and 304 responses have no body (not even a zero-length\n                # body), and so should not have either Content-Length or\n                # Transfer-Encoding headers.\n                and start_line.code not in (204, 304)\n                and (start_line.code < 100 or start_line.code >= 200)\n                # No need to chunk the output if a Content-Length is specified.\n                and \"Content-Length\" not in headers\n                # Applications are discouraged from touching Transfer-Encoding,\n                # but if they do, leave it alone.\n                and \"Transfer-Encoding\" not in headers\n            )\n            # If connection to a 1.1 client will be closed, inform client\n            if (\n                self._request_start_line.version == \"HTTP/1.1\"\n                and self._disconnect_on_finish\n            ):\n                headers[\"Connection\"] = \"close\"\n            # If a 1.0 client asked for keep-alive, add the header.\n            if (\n                self._request_start_line.version == \"HTTP/1.0\"\n                and self._request_headers.get(\"Connection\", \"\").lower() == \"keep-alive\"\n            ):\n                headers[\"Connection\"] = \"Keep-Alive\"\n        if self._chunking_output:\n            headers[\"Transfer-Encoding\"] = \"chunked\"\n        if not self.is_client and (\n            self._request_start_line.method == \"HEAD\"\n            or cast(httputil.ResponseStartLine, start_line).code == 304\n        ):\n            self._expected_content_remaining = 0\n        elif \"Content-Length\" in headers:\n            self._expected_content_remaining = int(headers[\"Content-Length\"])\n        else:\n            self._expected_content_remaining = None\n        # TODO: headers are supposed to be of type str, but we still have some\n        # cases that let bytes slip through. Remove these native_str calls when those\n        # are fixed.\n        header_lines = (\n            native_str(n) + \": \" + native_str(v) for n, v in headers.get_all()\n        )\n        lines.extend(line.encode(\"latin1\") for line in header_lines)\n        for line in lines:\n            if b\"\\n\" in line:\n                raise ValueError(\"Newline in header: \" + repr(line))\n        future = None\n        if self.stream.closed():\n            future = self._write_future = Future()\n            future.set_exception(iostream.StreamClosedError())\n            future.exception()\n        else:\n            future = self._write_future = Future()\n            data = b\"\\r\\n\".join(lines) + b\"\\r\\n\\r\\n\"\n            if chunk:\n                data += self._format_chunk(chunk)\n            self._pending_write = self.stream.write(data)\n            future_add_done_callback(self._pending_write, self._on_write_complete)\n        return future\n\n    def _format_chunk(self, chunk: bytes) -> bytes:\n        if self._expected_content_remaining is not None:\n            self._expected_content_remaining -= len(chunk)\n            if self._expected_content_remaining < 0:\n                # Close the stream now to stop further framing errors.\n                self.stream.close()\n                raise httputil.HTTPOutputError(\n                    \"Tried to write more data than Content-Length\"\n                )\n        if self._chunking_output and chunk:\n            # Don't write out empty chunks because that means END-OF-STREAM\n            # with chunked encoding\n            return utf8(\"%x\" % len(chunk)) + b\"\\r\\n\" + chunk + b\"\\r\\n\"\n        else:\n            return chunk\n\n    def write(self, chunk: bytes) -> \"Future[None]\":\n        \"\"\"Implements `.HTTPConnection.write`.\n\n        For backwards compatibility it is allowed but deprecated to\n        skip `write_headers` and instead call `write()` with a\n        pre-encoded header block.\n        \"\"\"\n        future = None\n        if self.stream.closed():\n            future = self._write_future = Future()\n            self._write_future.set_exception(iostream.StreamClosedError())\n            self._write_future.exception()\n        else:\n            future = self._write_future = Future()\n            self._pending_write = self.stream.write(self._format_chunk(chunk))\n            future_add_done_callback(self._pending_write, self._on_write_complete)\n        return future\n\n    def finish(self) -> None:\n        \"\"\"Implements `.HTTPConnection.finish`.\"\"\"\n        if (\n            self._expected_content_remaining is not None\n            and self._expected_content_remaining != 0\n            and not self.stream.closed()\n        ):\n            self.stream.close()\n            raise httputil.HTTPOutputError(\n                \"Tried to write %d bytes less than Content-Length\"\n                % self._expected_content_remaining\n            )\n        if self._chunking_output:\n            if not self.stream.closed():\n                self._pending_write = self.stream.write(b\"0\\r\\n\\r\\n\")\n                self._pending_write.add_done_callback(self._on_write_complete)\n        self._write_finished = True\n        # If the app finished the request while we're still reading,\n        # divert any remaining data away from the delegate and\n        # close the connection when we're done sending our response.\n        # Closing the connection is the only way to avoid reading the\n        # whole input body.\n        if not self._read_finished:\n            self._disconnect_on_finish = True\n        # No more data is coming, so instruct TCP to send any remaining\n        # data immediately instead of waiting for a full packet or ack.\n        self.stream.set_nodelay(True)\n        if self._pending_write is None:\n            self._finish_request(None)\n        else:\n            future_add_done_callback(self._pending_write, self._finish_request)\n\n    def _on_write_complete(self, future: \"Future[None]\") -> None:\n        exc = future.exception()\n        if exc is not None and not isinstance(exc, iostream.StreamClosedError):\n            future.result()\n        if self._write_callback is not None:\n            callback = self._write_callback\n            self._write_callback = None\n            self.stream.io_loop.add_callback(callback)\n        if self._write_future is not None:\n            future = self._write_future\n            self._write_future = None\n            future_set_result_unless_cancelled(future, None)\n\n    def _can_keep_alive(\n        self, start_line: httputil.RequestStartLine, headers: httputil.HTTPHeaders\n    ) -> bool:\n        if self.params.no_keep_alive:\n            return False\n        connection_header = headers.get(\"Connection\")\n        if connection_header is not None:\n            connection_header = connection_header.lower()\n        if start_line.version == \"HTTP/1.1\":\n            return connection_header != \"close\"\n        elif (\n            \"Content-Length\" in headers\n            or headers.get(\"Transfer-Encoding\", \"\").lower() == \"chunked\"\n            or getattr(start_line, \"method\", None) in (\"HEAD\", \"GET\")\n        ):\n            # start_line may be a request or response start line; only\n            # the former has a method attribute.\n            return connection_header == \"keep-alive\"\n        return False\n\n    def _finish_request(self, future: \"Optional[Future[None]]\") -> None:\n        self._clear_callbacks()\n        if not self.is_client and self._disconnect_on_finish:\n            self.close()\n            return\n        # Turn Nagle's algorithm back on, leaving the stream in its\n        # default state for the next request.\n        self.stream.set_nodelay(False)\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n\n    def _parse_headers(self, data: bytes) -> Tuple[str, httputil.HTTPHeaders]:\n        # The lstrip removes newlines that some implementations sometimes\n        # insert between messages of a reused connection.  Per RFC 7230,\n        # we SHOULD ignore at least one empty line before the request.\n        # http://tools.ietf.org/html/rfc7230#section-3.5\n        data_str = native_str(data.decode(\"latin1\")).lstrip(\"\\r\\n\")\n        # RFC 7230 section allows for both CRLF and bare LF.\n        eol = data_str.find(\"\\n\")\n        start_line = data_str[:eol].rstrip(\"\\r\")\n        headers = httputil.HTTPHeaders.parse(data_str[eol:])\n        return start_line, headers\n\n    def _read_body(\n        self,\n        code: int,\n        headers: httputil.HTTPHeaders,\n        delegate: httputil.HTTPMessageDelegate,\n    ) -> Optional[Awaitable[None]]:\n        if \"Content-Length\" in headers:\n            if \"Transfer-Encoding\" in headers:\n                # Response cannot contain both Content-Length and\n                # Transfer-Encoding headers.\n                # http://tools.ietf.org/html/rfc7230#section-3.3.3\n                raise httputil.HTTPInputError(\n                    \"Response with both Transfer-Encoding and Content-Length\"\n                )\n            if \",\" in headers[\"Content-Length\"]:\n                # Proxies sometimes cause Content-Length headers to get\n                # duplicated.  If all the values are identical then we can\n                # use them but if they differ it's an error.\n                pieces = re.split(r\",\\s*\", headers[\"Content-Length\"])\n                if any(i != pieces[0] for i in pieces):\n                    raise httputil.HTTPInputError(\n                        \"Multiple unequal Content-Lengths: %r\"\n                        % headers[\"Content-Length\"]\n                    )\n                headers[\"Content-Length\"] = pieces[0]\n\n            try:\n                content_length = int(headers[\"Content-Length\"])  # type: Optional[int]\n            except ValueError:\n                # Handles non-integer Content-Length value.\n                raise httputil.HTTPInputError(\n                    \"Only integer Content-Length is allowed: %s\"\n                    % headers[\"Content-Length\"]\n                )\n\n            if cast(int, content_length) > self._max_body_size:\n                raise httputil.HTTPInputError(\"Content-Length too long\")\n        else:\n            content_length = None\n\n        if code == 204:\n            # This response code is not allowed to have a non-empty body,\n            # and has an implicit length of zero instead of read-until-close.\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3\n            if \"Transfer-Encoding\" in headers or content_length not in (None, 0):\n                raise httputil.HTTPInputError(\n                    \"Response with code %d should not have body\" % code\n                )\n            content_length = 0\n\n        if content_length is not None:\n            return self._read_fixed_body(content_length, delegate)\n        if headers.get(\"Transfer-Encoding\", \"\").lower() == \"chunked\":\n            return self._read_chunked_body(delegate)\n        if self.is_client:\n            return self._read_body_until_close(delegate)\n        return None\n\n    async def _read_fixed_body(\n        self, content_length: int, delegate: httputil.HTTPMessageDelegate\n    ) -> None:\n        while content_length > 0:\n            body = await self.stream.read_bytes(\n                min(self.params.chunk_size, content_length), partial=True\n            )\n            content_length -= len(body)\n            if not self._write_finished or self.is_client:\n                with _ExceptionLoggingContext(app_log):\n                    ret = delegate.data_received(body)\n                    if ret is not None:\n                        await ret\n\n    async def _read_chunked_body(self, delegate: httputil.HTTPMessageDelegate) -> None:\n        # TODO: \"chunk extensions\" http://tools.ietf.org/html/rfc2616#section-3.6.1\n        total_size = 0\n        while True:\n            chunk_len_str = await self.stream.read_until(b\"\\r\\n\", max_bytes=64)\n            chunk_len = int(chunk_len_str.strip(), 16)\n            if chunk_len == 0:\n                crlf = await self.stream.read_bytes(2)\n                if crlf != b\"\\r\\n\":\n                    raise httputil.HTTPInputError(\n                        \"improperly terminated chunked request\"\n                    )\n                return\n            total_size += chunk_len\n            if total_size > self._max_body_size:\n                raise httputil.HTTPInputError(\"chunked body too large\")\n            bytes_to_read = chunk_len\n            while bytes_to_read:\n                chunk = await self.stream.read_bytes(\n                    min(bytes_to_read, self.params.chunk_size), partial=True\n                )\n                bytes_to_read -= len(chunk)\n                if not self._write_finished or self.is_client:\n                    with _ExceptionLoggingContext(app_log):\n                        ret = delegate.data_received(chunk)\n                        if ret is not None:\n                            await ret\n            # chunk ends with \\r\\n\n            crlf = await self.stream.read_bytes(2)\n            assert crlf == b\"\\r\\n\"\n\n    async def _read_body_until_close(\n        self, delegate: httputil.HTTPMessageDelegate\n    ) -> None:\n        body = await self.stream.read_until_close()\n        if not self._write_finished or self.is_client:\n            with _ExceptionLoggingContext(app_log):\n                ret = delegate.data_received(body)\n                if ret is not None:\n                    await ret\n\n\nclass _GzipMessageDelegate(httputil.HTTPMessageDelegate):\n    \"\"\"Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``.\"\"\"\n\n    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None:\n        self._delegate = delegate\n        self._chunk_size = chunk_size\n        self._decompressor = None  # type: Optional[GzipDecompressor]\n\n    def headers_received(\n        self,\n        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],\n        headers: httputil.HTTPHeaders,\n    ) -> Optional[Awaitable[None]]:\n        if headers.get(\"Content-Encoding\", \"\").lower() == \"gzip\":\n            self._decompressor = GzipDecompressor()\n            # Downstream delegates will only see uncompressed data,\n            # so rename the content-encoding header.\n            # (but note that curl_httpclient doesn't do this).\n            headers.add(\"X-Consumed-Content-Encoding\", headers[\"Content-Encoding\"])\n            del headers[\"Content-Encoding\"]\n        return self._delegate.headers_received(start_line, headers)\n\n    async def data_received(self, chunk: bytes) -> None:\n        if self._decompressor:\n            compressed_data = chunk\n            while compressed_data:\n                decompressed = self._decompressor.decompress(\n                    compressed_data, self._chunk_size\n                )\n                if decompressed:\n                    ret = self._delegate.data_received(decompressed)\n                    if ret is not None:\n                        await ret\n                compressed_data = self._decompressor.unconsumed_tail\n                if compressed_data and not decompressed:\n                    raise httputil.HTTPInputError(\n                        \"encountered unconsumed gzip data without making progress\"\n                    )\n        else:\n            ret = self._delegate.data_received(chunk)\n            if ret is not None:\n                await ret\n\n    def finish(self) -> None:\n        if self._decompressor is not None:\n            tail = self._decompressor.flush()\n            if tail:\n                # The tail should always be empty: decompress returned\n                # all that it can in data_received and the only\n                # purpose of the flush call is to detect errors such\n                # as truncated input. If we did legitimately get a new\n                # chunk at this point we'd need to change the\n                # interface to make finish() a coroutine.\n                raise ValueError(\n                    \"decompressor.flush returned data; possible truncated input\"\n                )\n        return self._delegate.finish()\n\n    def on_connection_close(self) -> None:\n        return self._delegate.on_connection_close()\n\n\nclass HTTP1ServerConnection(object):\n    \"\"\"An HTTP/1.x server.\"\"\"\n\n    def __init__(\n        self,\n        stream: iostream.IOStream,\n        params: Optional[HTTP1ConnectionParameters] = None,\n        context: Optional[object] = None,\n    ) -> None:\n        \"\"\"\n        :arg stream: an `.IOStream`\n        :arg params: a `.HTTP1ConnectionParameters` or None\n        :arg context: an opaque application-defined object that is accessible\n            as ``connection.context``\n        \"\"\"\n        self.stream = stream\n        if params is None:\n            params = HTTP1ConnectionParameters()\n        self.params = params\n        self.context = context\n        self._serving_future = None  # type: Optional[Future[None]]\n\n    async def close(self) -> None:\n        \"\"\"Closes the connection.\n\n        Returns a `.Future` that resolves after the serving loop has exited.\n        \"\"\"\n        self.stream.close()\n        # Block until the serving loop is done, but ignore any exceptions\n        # (start_serving is already responsible for logging them).\n        assert self._serving_future is not None\n        try:\n            await self._serving_future\n        except Exception:\n            pass\n\n    def start_serving(self, delegate: httputil.HTTPServerConnectionDelegate) -> None:\n        \"\"\"Starts serving requests on this connection.\n\n        :arg delegate: a `.HTTPServerConnectionDelegate`\n        \"\"\"\n        assert isinstance(delegate, httputil.HTTPServerConnectionDelegate)\n        fut = gen.convert_yielded(self._server_request_loop(delegate))\n        self._serving_future = fut\n        # Register the future on the IOLoop so its errors get logged.\n        self.stream.io_loop.add_future(fut, lambda f: f.result())\n\n    async def _server_request_loop(\n        self, delegate: httputil.HTTPServerConnectionDelegate\n    ) -> None:\n        try:\n            while True:\n                conn = HTTP1Connection(self.stream, False, self.params, self.context)\n                request_delegate = delegate.start_request(self, conn)\n                try:\n                    ret = await conn.read_response(request_delegate)\n                except (\n                    iostream.StreamClosedError,\n                    iostream.UnsatisfiableReadError,\n                    asyncio.CancelledError,\n                ):\n                    return\n                except _QuietException:\n                    # This exception was already logged.\n                    conn.close()\n                    return\n                except Exception:\n                    gen_log.error(\"Uncaught exception\", exc_info=True)\n                    conn.close()\n                    return\n                if not ret:\n                    return\n                await asyncio.sleep(0)\n        finally:\n            delegate.on_close(self)\n",
            "header": {
                "file": {
                    "path": "tornado/http1connection.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "72088d60",
                "new_commit_hash": "5ca91688",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/tornado/http1connection.py"
            },
            "plus_file": {
                "path": "b/tornado/http1connection.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 718,
                            "length": 7
                        },
                        "new_range": {
                            "start": 718,
                            "length": 7
                        },
                        "section": "class _GzipMessageDelegate(httputil.HTTPMessageDelegate):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],",
                                "type": "context"
                            },
                            {
                                "content": "        headers: httputil.HTTPHeaders,",
                                "type": "context"
                            },
                            {
                                "content": "    ) -> Optional[Awaitable[None]]:",
                                "type": "context"
                            },
                            {
                                "content": "        if headers.get(\"Content-Encoding\") == \"gzip\":",
                                "type": "deleted"
                            },
                            {
                                "content": "        if headers.get(\"Content-Encoding\", \"\").lower() == \"gzip\":",
                                "type": "added"
                            },
                            {
                                "content": "            self._decompressor = GzipDecompressor()",
                                "type": "context"
                            },
                            {
                                "content": "            # Downstream delegates will only see uncompressed data,",
                                "type": "context"
                            },
                            {
                                "content": "            # so rename the content-encoding header.",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/http1connection.py",
                            "type": "method",
                            "name": "_GzipMessageDelegate.headers_received",
                            "content": "class _GzipMessageDelegate(httputil.HTTPMessageDelegate):\n    \"\"\"Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``.\"\"\"\n\n    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None:\n        self._delegate = delegate\n        self._chunk_size = chunk_size\n        self._decompressor = None  # type: Optional[GzipDecompressor]\n\n    def headers_received(\n        self,\n        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],\n        headers: httputil.HTTPHeaders,\n    ) -> Optional[Awaitable[None]]:\n        if headers.get(\"Content-Encoding\") == \"gzip\":\n            self._decompressor = GzipDecompressor()\n            # Downstream delegates will only see uncompressed data,\n            # so rename the content-encoding header.\n            # (but note that curl_httpclient doesn't do this).\n            headers.add(\"X-Consumed-Content-Encoding\", headers[\"Content-Encoding\"])\n            del headers[\"Content-Encoding\"]\n        return self._delegate.headers_received(start_line, headers)\n\n    async def data_received(self, chunk: bytes) -> None:\n        if self._decompressor:\n            compressed_data = chunk\n            while compressed_data:\n                decompressed = self._decompressor.decompress(\n                    compressed_data, self._chunk_size\n                )\n                if decompressed:\n                    ret = self._delegate.data_received(decompressed)\n                    if ret is not None:\n                        await ret\n                compressed_data = self._decompressor.unconsumed_tail\n                if compressed_data and not decompressed:\n                    raise httputil.HTTPInputError(\n                        \"encountered unconsumed gzip data without making progress\"\n                    )\n        else:\n            ret = self._delegate.data_received(chunk)\n            if ret is not None:\n                await ret\n\n    def finish(self) -> None:\n        if self._decompressor is not None:\n            tail = self._decompressor.flush()\n            if tail:\n                # The tail should always be empty: decompress returned\n                # all that it can in data_received and the only\n                # purpose of the flush call is to detect errors such\n                # as truncated input. If we did legitimately get a new\n                # chunk at this point we'd need to change the\n                # interface to make finish() a coroutine.\n                raise ValueError(\n                    \"decompressor.flush returned data; possible truncated input\"\n                )\n        return self._delegate.finish()\n\n    def on_connection_close(self) -> None:\n        return self._delegate.on_connection_close()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 716,
                            "end_lineno": 728,
                            "parent": {
                                "file_name": "tornado/http1connection.py",
                                "type": "class",
                                "name": "_GzipMessageDelegate",
                                "content": "class _GzipMessageDelegate(httputil.HTTPMessageDelegate):\n    \"\"\"Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``.\"\"\"\n\n    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None:\n        self._delegate = delegate\n        self._chunk_size = chunk_size\n        self._decompressor = None  # type: Optional[GzipDecompressor]\n\n    def headers_received(\n        self,\n        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],\n        headers: httputil.HTTPHeaders,\n    ) -> Optional[Awaitable[None]]:\n        if headers.get(\"Content-Encoding\") == \"gzip\":\n            self._decompressor = GzipDecompressor()\n            # Downstream delegates will only see uncompressed data,\n            # so rename the content-encoding header.\n            # (but note that curl_httpclient doesn't do this).\n            headers.add(\"X-Consumed-Content-Encoding\", headers[\"Content-Encoding\"])\n            del headers[\"Content-Encoding\"]\n        return self._delegate.headers_received(start_line, headers)\n\n    async def data_received(self, chunk: bytes) -> None:\n        if self._decompressor:\n            compressed_data = chunk\n            while compressed_data:\n                decompressed = self._decompressor.decompress(\n                    compressed_data, self._chunk_size\n                )\n                if decompressed:\n                    ret = self._delegate.data_received(decompressed)\n                    if ret is not None:\n                        await ret\n                compressed_data = self._decompressor.unconsumed_tail\n                if compressed_data and not decompressed:\n                    raise httputil.HTTPInputError(\n                        \"encountered unconsumed gzip data without making progress\"\n                    )\n        else:\n            ret = self._delegate.data_received(chunk)\n            if ret is not None:\n                await ret\n\n    def finish(self) -> None:\n        if self._decompressor is not None:\n            tail = self._decompressor.flush()\n            if tail:\n                # The tail should always be empty: decompress returned\n                # all that it can in data_received and the only\n                # purpose of the flush call is to detect errors such\n                # as truncated input. If we did legitimately get a new\n                # chunk at this point we'd need to change the\n                # interface to make finish() a coroutine.\n                raise ValueError(\n                    \"decompressor.flush returned data; possible truncated input\"\n                )\n        return self._delegate.finish()\n\n    def on_connection_close(self) -> None:\n        return self._delegate.on_connection_close()",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 708,
                                "end_lineno": 767,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "tornado/http1connection.py",
                            "type": "class",
                            "name": "_GzipMessageDelegate",
                            "content": "class _GzipMessageDelegate(httputil.HTTPMessageDelegate):\n    \"\"\"Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``.\"\"\"\n\n    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None:\n        self._delegate = delegate\n        self._chunk_size = chunk_size\n        self._decompressor = None  # type: Optional[GzipDecompressor]\n\n    def headers_received(\n        self,\n        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],\n        headers: httputil.HTTPHeaders,\n    ) -> Optional[Awaitable[None]]:\n        if headers.get(\"Content-Encoding\") == \"gzip\":\n            self._decompressor = GzipDecompressor()\n            # Downstream delegates will only see uncompressed data,\n            # so rename the content-encoding header.\n            # (but note that curl_httpclient doesn't do this).\n            headers.add(\"X-Consumed-Content-Encoding\", headers[\"Content-Encoding\"])\n            del headers[\"Content-Encoding\"]\n        return self._delegate.headers_received(start_line, headers)\n\n    async def data_received(self, chunk: bytes) -> None:\n        if self._decompressor:\n            compressed_data = chunk\n            while compressed_data:\n                decompressed = self._decompressor.decompress(\n                    compressed_data, self._chunk_size\n                )\n                if decompressed:\n                    ret = self._delegate.data_received(decompressed)\n                    if ret is not None:\n                        await ret\n                compressed_data = self._decompressor.unconsumed_tail\n                if compressed_data and not decompressed:\n                    raise httputil.HTTPInputError(\n                        \"encountered unconsumed gzip data without making progress\"\n                    )\n        else:\n            ret = self._delegate.data_received(chunk)\n            if ret is not None:\n                await ret\n\n    def finish(self) -> None:\n        if self._decompressor is not None:\n            tail = self._decompressor.flush()\n            if tail:\n                # The tail should always be empty: decompress returned\n                # all that it can in data_received and the only\n                # purpose of the flush call is to detect errors such\n                # as truncated input. If we did legitimately get a new\n                # chunk at this point we'd need to change the\n                # interface to make finish() a coroutine.\n                raise ValueError(\n                    \"decompressor.flush returned data; possible truncated input\"\n                )\n        return self._delegate.finish()\n\n    def on_connection_close(self) -> None:\n        return self._delegate.on_connection_close()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 708,
                            "end_lineno": 767,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "from tornado import gen, netutil\nfrom tornado.escape import (\n    json_decode,\n    json_encode,\n    utf8,\n    _unicode,\n    recursive_unicode,\n    native_str,\n)\nfrom tornado.http1connection import HTTP1Connection\nfrom tornado.httpclient import HTTPError\nfrom tornado.httpserver import HTTPServer\nfrom tornado.httputil import (\n    HTTPHeaders,\n    HTTPMessageDelegate,\n    HTTPServerConnectionDelegate,\n    ResponseStartLine,\n)\nfrom tornado.iostream import IOStream\nfrom tornado.locks import Event\nfrom tornado.log import gen_log\nfrom tornado.netutil import ssl_options_to_context\nfrom tornado.simple_httpclient import SimpleAsyncHTTPClient\nfrom tornado.testing import (\n    AsyncHTTPTestCase,\n    AsyncHTTPSTestCase,\n    AsyncTestCase,\n    ExpectLog,\n    gen_test,\n)\nfrom tornado.test.util import skipOnTravis\nfrom tornado.web import Application, RequestHandler, stream_request_body\n\nfrom contextlib import closing\nimport datetime\nimport gzip\nimport logging\nimport os\nimport shutil\nimport socket\nimport ssl\nimport sys\nimport tempfile\nimport unittest\nimport urllib.parse\nfrom io import BytesIO\n\nimport typing\n\nif typing.TYPE_CHECKING:\n    from typing import Dict, List  # noqa: F401\n\n\nasync def read_stream_body(stream):\n    \"\"\"Reads an HTTP response from `stream` and returns a tuple of its\n    start_line, headers and body.\"\"\"\n    chunks = []\n\n    class Delegate(HTTPMessageDelegate):\n        def headers_received(self, start_line, headers):\n            self.headers = headers\n            self.start_line = start_line\n\n        def data_received(self, chunk):\n            chunks.append(chunk)\n\n        def finish(self):\n            conn.detach()  # type: ignore\n\n    conn = HTTP1Connection(stream, True)\n    delegate = Delegate()\n    await conn.read_response(delegate)\n    return delegate.start_line, delegate.headers, b\"\".join(chunks)\n\n\nclass HandlerBaseTestCase(AsyncHTTPTestCase):\n    Handler = None\n\n    def get_app(self):\n        return Application([(\"/\", self.__class__.Handler)])\n\n    def fetch_json(self, *args, **kwargs):\n        response = self.fetch(*args, **kwargs)\n        response.rethrow()\n        return json_decode(response.body)\n\n\nclass HelloWorldRequestHandler(RequestHandler):\n    def initialize(self, protocol=\"http\"):\n        self.expected_protocol = protocol\n\n    def get(self):\n        if self.request.protocol != self.expected_protocol:\n            raise Exception(\"unexpected protocol\")\n        self.finish(\"Hello world\")\n\n    def post(self):\n        self.finish(\"Got %d bytes in POST\" % len(self.request.body))\n\n\n# In pre-1.0 versions of openssl, SSLv23 clients always send SSLv2\n# ClientHello messages, which are rejected by SSLv3 and TLSv1\n# servers.  Note that while the OPENSSL_VERSION_INFO was formally\n# introduced in python3.2, it was present but undocumented in\n# python 2.7\nskipIfOldSSL = unittest.skipIf(\n    getattr(ssl, \"OPENSSL_VERSION_INFO\", (0, 0)) < (1, 0),\n    \"old version of ssl module and/or openssl\",\n)\n\n\nclass BaseSSLTest(AsyncHTTPSTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler, dict(protocol=\"https\"))])\n\n\nclass SSLTestMixin(object):\n    def get_ssl_options(self):\n        return dict(\n            ssl_version=self.get_ssl_version(),\n            **AsyncHTTPSTestCase.default_ssl_options()\n        )\n\n    def get_ssl_version(self):\n        raise NotImplementedError()\n\n    def test_ssl(self: typing.Any):\n        response = self.fetch(\"/\")\n        self.assertEqual(response.body, b\"Hello world\")\n\n    def test_large_post(self: typing.Any):\n        response = self.fetch(\"/\", method=\"POST\", body=\"A\" * 5000)\n        self.assertEqual(response.body, b\"Got 5000 bytes in POST\")\n\n    def test_non_ssl_request(self: typing.Any):\n        # Make sure the server closes the connection when it gets a non-ssl\n        # connection, rather than waiting for a timeout or otherwise\n        # misbehaving.\n        with ExpectLog(gen_log, \"(SSL Error|uncaught exception)\"):\n            with ExpectLog(gen_log, \"Uncaught exception\", required=False):\n                with self.assertRaises((IOError, HTTPError)):  # type: ignore\n                    self.fetch(\n                        self.get_url(\"/\").replace(\"https:\", \"http:\"),\n                        request_timeout=3600,\n                        connect_timeout=3600,\n                        raise_error=True,\n                    )\n\n    def test_error_logging(self: typing.Any):\n        # No stack traces are logged for SSL errors.\n        with ExpectLog(gen_log, \"SSL Error\") as expect_log:\n            with self.assertRaises((IOError, HTTPError)):  # type: ignore\n                self.fetch(\n                    self.get_url(\"/\").replace(\"https:\", \"http:\"), raise_error=True\n                )\n        self.assertFalse(expect_log.logged_stack)\n\n\n# Python's SSL implementation differs significantly between versions.\n# For example, SSLv3 and TLSv1 throw an exception if you try to read\n# from the socket before the handshake is complete, but the default\n# of SSLv23 allows it.\n\n\nclass SSLv23Test(BaseSSLTest, SSLTestMixin):\n    def get_ssl_version(self):\n        return ssl.PROTOCOL_SSLv23\n\n\n@skipIfOldSSL\nclass SSLv3Test(BaseSSLTest, SSLTestMixin):\n    def get_ssl_version(self):\n        return ssl.PROTOCOL_SSLv3\n\n\n@skipIfOldSSL\nclass TLSv1Test(BaseSSLTest, SSLTestMixin):\n    def get_ssl_version(self):\n        return ssl.PROTOCOL_TLSv1\n\n\nclass SSLContextTest(BaseSSLTest, SSLTestMixin):\n    def get_ssl_options(self):\n        context = ssl_options_to_context(AsyncHTTPSTestCase.get_ssl_options(self))\n        assert isinstance(context, ssl.SSLContext)\n        return context\n\n\nclass BadSSLOptionsTest(unittest.TestCase):\n    def test_missing_arguments(self):\n        application = Application()\n        self.assertRaises(\n            KeyError,\n            HTTPServer,\n            application,\n            ssl_options={\"keyfile\": \"/__missing__.crt\"},\n        )\n\n    def test_missing_key(self):\n        \"\"\"A missing SSL key should cause an immediate exception.\"\"\"\n\n        application = Application()\n        module_dir = os.path.dirname(__file__)\n        existing_certificate = os.path.join(module_dir, \"test.crt\")\n        existing_key = os.path.join(module_dir, \"test.key\")\n\n        self.assertRaises(\n            (ValueError, IOError),\n            HTTPServer,\n            application,\n            ssl_options={\"certfile\": \"/__mising__.crt\"},\n        )\n        self.assertRaises(\n            (ValueError, IOError),\n            HTTPServer,\n            application,\n            ssl_options={\n                \"certfile\": existing_certificate,\n                \"keyfile\": \"/__missing__.key\",\n            },\n        )\n\n        # This actually works because both files exist\n        HTTPServer(\n            application,\n            ssl_options={\"certfile\": existing_certificate, \"keyfile\": existing_key},\n        )\n\n\nclass MultipartTestHandler(RequestHandler):\n    def post(self):\n        self.finish(\n            {\n                \"header\": self.request.headers[\"X-Header-Encoding-Test\"],\n                \"argument\": self.get_argument(\"argument\"),\n                \"filename\": self.request.files[\"files\"][0].filename,\n                \"filebody\": _unicode(self.request.files[\"files\"][0][\"body\"]),\n            }\n        )\n\n\n# This test is also called from wsgi_test\nclass HTTPConnectionTest(AsyncHTTPTestCase):\n    def get_handlers(self):\n        return [\n            (\"/multipart\", MultipartTestHandler),\n            (\"/hello\", HelloWorldRequestHandler),\n        ]\n\n    def get_app(self):\n        return Application(self.get_handlers())\n\n    def raw_fetch(self, headers, body, newline=b\"\\r\\n\"):\n        with closing(IOStream(socket.socket())) as stream:\n            self.io_loop.run_sync(\n                lambda: stream.connect((\"127.0.0.1\", self.get_http_port()))\n            )\n            stream.write(\n                newline.join(headers + [utf8(\"Content-Length: %d\" % len(body))])\n                + newline\n                + newline\n                + body\n            )\n            start_line, headers, body = self.io_loop.run_sync(\n                lambda: read_stream_body(stream)\n            )\n            return body\n\n    def test_multipart_form(self):\n        # Encodings here are tricky:  Headers are latin1, bodies can be\n        # anything (we use utf8 by default).\n        response = self.raw_fetch(\n            [\n                b\"POST /multipart HTTP/1.0\",\n                b\"Content-Type: multipart/form-data; boundary=1234567890\",\n                b\"X-Header-encoding-test: \\xe9\",\n            ],\n            b\"\\r\\n\".join(\n                [\n                    b\"Content-Disposition: form-data; name=argument\",\n                    b\"\",\n                    u\"\\u00e1\".encode(\"utf-8\"),\n                    b\"--1234567890\",\n                    u'Content-Disposition: form-data; name=\"files\"; filename=\"\\u00f3\"'.encode(\n                        \"utf8\"\n                    ),\n                    b\"\",\n                    u\"\\u00fa\".encode(\"utf-8\"),\n                    b\"--1234567890--\",\n                    b\"\",\n                ]\n            ),\n        )\n        data = json_decode(response)\n        self.assertEqual(u\"\\u00e9\", data[\"header\"])\n        self.assertEqual(u\"\\u00e1\", data[\"argument\"])\n        self.assertEqual(u\"\\u00f3\", data[\"filename\"])\n        self.assertEqual(u\"\\u00fa\", data[\"filebody\"])\n\n    def test_newlines(self):\n        # We support both CRLF and bare LF as line separators.\n        for newline in (b\"\\r\\n\", b\"\\n\"):\n            response = self.raw_fetch([b\"GET /hello HTTP/1.0\"], b\"\", newline=newline)\n            self.assertEqual(response, b\"Hello world\")\n\n    @gen_test\n    def test_100_continue(self):\n        # Run through a 100-continue interaction by hand:\n        # When given Expect: 100-continue, we get a 100 response after the\n        # headers, and then the real response after the body.\n        stream = IOStream(socket.socket())\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        yield stream.write(\n            b\"\\r\\n\".join(\n                [\n                    b\"POST /hello HTTP/1.1\",\n                    b\"Content-Length: 1024\",\n                    b\"Expect: 100-continue\",\n                    b\"Connection: close\",\n                    b\"\\r\\n\",\n                ]\n            )\n        )\n        data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n        self.assertTrue(data.startswith(b\"HTTP/1.1 100 \"), data)\n        stream.write(b\"a\" * 1024)\n        first_line = yield stream.read_until(b\"\\r\\n\")\n        self.assertTrue(first_line.startswith(b\"HTTP/1.1 200\"), first_line)\n        header_data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(native_str(header_data.decode(\"latin1\")))\n        body = yield stream.read_bytes(int(headers[\"Content-Length\"]))\n        self.assertEqual(body, b\"Got 1024 bytes in POST\")\n        stream.close()\n\n\nclass EchoHandler(RequestHandler):\n    def get(self):\n        self.write(recursive_unicode(self.request.arguments))\n\n    def post(self):\n        self.write(recursive_unicode(self.request.arguments))\n\n\nclass TypeCheckHandler(RequestHandler):\n    def prepare(self):\n        self.errors = {}  # type: Dict[str, str]\n        fields = [\n            (\"method\", str),\n            (\"uri\", str),\n            (\"version\", str),\n            (\"remote_ip\", str),\n            (\"protocol\", str),\n            (\"host\", str),\n            (\"path\", str),\n            (\"query\", str),\n        ]\n        for field, expected_type in fields:\n            self.check_type(field, getattr(self.request, field), expected_type)\n\n        self.check_type(\"header_key\", list(self.request.headers.keys())[0], str)\n        self.check_type(\"header_value\", list(self.request.headers.values())[0], str)\n\n        self.check_type(\"cookie_key\", list(self.request.cookies.keys())[0], str)\n        self.check_type(\n            \"cookie_value\", list(self.request.cookies.values())[0].value, str\n        )\n        # secure cookies\n\n        self.check_type(\"arg_key\", list(self.request.arguments.keys())[0], str)\n        self.check_type(\"arg_value\", list(self.request.arguments.values())[0][0], bytes)\n\n    def post(self):\n        self.check_type(\"body\", self.request.body, bytes)\n        self.write(self.errors)\n\n    def get(self):\n        self.write(self.errors)\n\n    def check_type(self, name, obj, expected_type):\n        actual_type = type(obj)\n        if expected_type != actual_type:\n            self.errors[name] = \"expected %s, got %s\" % (expected_type, actual_type)\n\n\nclass PostEchoHandler(RequestHandler):\n    def post(self, *path_args):\n        self.write(dict(echo=self.get_argument(\"data\")))\n\n\nclass PostEchoGBKHandler(PostEchoHandler):\n    def decode_argument(self, value, name=None):\n        try:\n            return value.decode(\"gbk\")\n        except Exception:\n            raise HTTPError(400, \"invalid gbk bytes: %r\" % value)\n\n\nclass HTTPServerTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application(\n            [\n                (\"/echo\", EchoHandler),\n                (\"/typecheck\", TypeCheckHandler),\n                (\"//doubleslash\", EchoHandler),\n                (\"/post_utf8\", PostEchoHandler),\n                (\"/post_gbk\", PostEchoGBKHandler),\n            ]\n        )\n\n    def test_query_string_encoding(self):\n        response = self.fetch(\"/echo?foo=%C3%A9\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {u\"foo\": [u\"\\u00e9\"]})\n\n    def test_empty_query_string(self):\n        response = self.fetch(\"/echo?foo=&foo=\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {u\"foo\": [u\"\", u\"\"]})\n\n    def test_empty_post_parameters(self):\n        response = self.fetch(\"/echo\", method=\"POST\", body=\"foo=&bar=\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {u\"foo\": [u\"\"], u\"bar\": [u\"\"]})\n\n    def test_types(self):\n        headers = {\"Cookie\": \"foo=bar\"}\n        response = self.fetch(\"/typecheck?foo=bar\", headers=headers)\n        data = json_decode(response.body)\n        self.assertEqual(data, {})\n\n        response = self.fetch(\n            \"/typecheck\", method=\"POST\", body=\"foo=bar\", headers=headers\n        )\n        data = json_decode(response.body)\n        self.assertEqual(data, {})\n\n    def test_double_slash(self):\n        # urlparse.urlsplit (which tornado.httpserver used to use\n        # incorrectly) would parse paths beginning with \"//\" as\n        # protocol-relative urls.\n        response = self.fetch(\"//doubleslash\")\n        self.assertEqual(200, response.code)\n        self.assertEqual(json_decode(response.body), {})\n\n    def test_post_encodings(self):\n        headers = {\"Content-Type\": \"application/x-www-form-urlencoded\"}\n        uni_text = \"chinese: \\u5f20\\u4e09\"\n        for enc in (\"utf8\", \"gbk\"):\n            for quote in (True, False):\n                with self.subTest(enc=enc, quote=quote):\n                    bin_text = uni_text.encode(enc)\n                    if quote:\n                        bin_text = urllib.parse.quote(bin_text).encode(\"ascii\")\n                    response = self.fetch(\n                        \"/post_\" + enc,\n                        method=\"POST\",\n                        headers=headers,\n                        body=(b\"data=\" + bin_text),\n                    )\n                    self.assertEqual(json_decode(response.body), {\"echo\": uni_text})\n\n\nclass HTTPServerRawTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/echo\", EchoHandler)])\n\n    def setUp(self):\n        super().setUp()\n        self.stream = IOStream(socket.socket())\n        self.io_loop.run_sync(\n            lambda: self.stream.connect((\"127.0.0.1\", self.get_http_port()))\n        )\n\n    def tearDown(self):\n        self.stream.close()\n        super().tearDown()\n\n    def test_empty_request(self):\n        self.stream.close()\n        self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)\n        self.wait()\n\n    def test_malformed_first_line_response(self):\n        with ExpectLog(gen_log, \".*Malformed HTTP request line\", level=logging.INFO):\n            self.stream.write(b\"asdf\\r\\n\\r\\n\")\n            start_line, headers, response = self.io_loop.run_sync(\n                lambda: read_stream_body(self.stream)\n            )\n            self.assertEqual(\"HTTP/1.1\", start_line.version)\n            self.assertEqual(400, start_line.code)\n            self.assertEqual(\"Bad Request\", start_line.reason)\n\n    def test_malformed_first_line_log(self):\n        with ExpectLog(gen_log, \".*Malformed HTTP request line\", level=logging.INFO):\n            self.stream.write(b\"asdf\\r\\n\\r\\n\")\n            # TODO: need an async version of ExpectLog so we don't need\n            # hard-coded timeouts here.\n            self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop)\n            self.wait()\n\n    def test_malformed_headers(self):\n        with ExpectLog(\n            gen_log,\n            \".*Malformed HTTP message.*no colon in header line\",\n            level=logging.INFO,\n        ):\n            self.stream.write(b\"GET / HTTP/1.0\\r\\nasdf\\r\\n\\r\\n\")\n            self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop)\n            self.wait()\n\n    def test_chunked_request_body(self):\n        # Chunked requests are not widely supported and we don't have a way\n        # to generate them in AsyncHTTPClient, but HTTPServer will read them.\n        self.stream.write(\n            b\"\"\"\\\nPOST /echo HTTP/1.1\nTransfer-Encoding: chunked\nContent-Type: application/x-www-form-urlencoded\n\n4\nfoo=\n3\nbar\n0\n\n\"\"\".replace(\n                b\"\\n\", b\"\\r\\n\"\n            )\n        )\n        start_line, headers, response = self.io_loop.run_sync(\n            lambda: read_stream_body(self.stream)\n        )\n        self.assertEqual(json_decode(response), {u\"foo\": [u\"bar\"]})\n\n    def test_chunked_request_uppercase(self):\n        # As per RFC 2616 section 3.6, \"Transfer-Encoding\" header's value is\n        # case-insensitive.\n        self.stream.write(\n            b\"\"\"\\\nPOST /echo HTTP/1.1\nTransfer-Encoding: Chunked\nContent-Type: application/x-www-form-urlencoded\n\n4\nfoo=\n3\nbar\n0\n\n\"\"\".replace(\n                b\"\\n\", b\"\\r\\n\"\n            )\n        )\n        start_line, headers, response = self.io_loop.run_sync(\n            lambda: read_stream_body(self.stream)\n        )\n        self.assertEqual(json_decode(response), {u\"foo\": [u\"bar\"]})\n\n    @gen_test\n    def test_invalid_content_length(self):\n        with ExpectLog(\n            gen_log, \".*Only integer Content-Length is allowed\", level=logging.INFO\n        ):\n            self.stream.write(\n                b\"\"\"\\\nPOST /echo HTTP/1.1\nContent-Length: foo\n\nbar\n\n\"\"\".replace(\n                    b\"\\n\", b\"\\r\\n\"\n                )\n            )\n            yield self.stream.read_until_close()\n\n\nclass XHeaderTest(HandlerBaseTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.set_header(\"request-version\", self.request.version)\n            self.write(\n                dict(\n                    remote_ip=self.request.remote_ip,\n                    remote_protocol=self.request.protocol,\n                )\n            )\n\n    def get_httpserver_options(self):\n        return dict(xheaders=True, trusted_downstream=[\"5.5.5.5\"])\n\n    def test_ip_headers(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_ip\"], \"127.0.0.1\")\n\n        valid_ipv4 = {\"X-Real-IP\": \"4.4.4.4\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv4)[\"remote_ip\"], \"4.4.4.4\"\n        )\n\n        valid_ipv4_list = {\"X-Forwarded-For\": \"127.0.0.1, 4.4.4.4\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv4_list)[\"remote_ip\"], \"4.4.4.4\"\n        )\n\n        valid_ipv6 = {\"X-Real-IP\": \"2620:0:1cfe:face:b00c::3\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv6)[\"remote_ip\"],\n            \"2620:0:1cfe:face:b00c::3\",\n        )\n\n        valid_ipv6_list = {\"X-Forwarded-For\": \"::1, 2620:0:1cfe:face:b00c::3\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv6_list)[\"remote_ip\"],\n            \"2620:0:1cfe:face:b00c::3\",\n        )\n\n        invalid_chars = {\"X-Real-IP\": \"4.4.4.4<script>\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_chars)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n        invalid_chars_list = {\"X-Forwarded-For\": \"4.4.4.4, 5.5.5.5<script>\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_chars_list)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n        invalid_host = {\"X-Real-IP\": \"www.google.com\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_host)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n    def test_trusted_downstream(self):\n        valid_ipv4_list = {\"X-Forwarded-For\": \"127.0.0.1, 4.4.4.4, 5.5.5.5\"}\n        resp = self.fetch(\"/\", headers=valid_ipv4_list)\n        if resp.headers[\"request-version\"].startswith(\"HTTP/2\"):\n            # This is a hack - there's nothing that fundamentally requires http/1\n            # here but tornado_http2 doesn't support it yet.\n            self.skipTest(\"requires HTTP/1.x\")\n        result = json_decode(resp.body)\n        self.assertEqual(result[\"remote_ip\"], \"4.4.4.4\")\n\n    def test_scheme_headers(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_protocol\"], \"http\")\n\n        https_scheme = {\"X-Scheme\": \"https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_scheme)[\"remote_protocol\"], \"https\"\n        )\n\n        https_forwarded = {\"X-Forwarded-Proto\": \"https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_forwarded)[\"remote_protocol\"], \"https\"\n        )\n\n        https_multi_forwarded = {\"X-Forwarded-Proto\": \"https , http\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_multi_forwarded)[\"remote_protocol\"],\n            \"http\",\n        )\n\n        http_multi_forwarded = {\"X-Forwarded-Proto\": \"http,https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=http_multi_forwarded)[\"remote_protocol\"],\n            \"https\",\n        )\n\n        bad_forwarded = {\"X-Forwarded-Proto\": \"unknown\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=bad_forwarded)[\"remote_protocol\"], \"http\"\n        )\n\n\nclass SSLXHeaderTest(AsyncHTTPSTestCase, HandlerBaseTestCase):\n    def get_app(self):\n        return Application([(\"/\", XHeaderTest.Handler)])\n\n    def get_httpserver_options(self):\n        output = super().get_httpserver_options()\n        output[\"xheaders\"] = True\n        return output\n\n    def test_request_without_xprotocol(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_protocol\"], \"https\")\n\n        http_scheme = {\"X-Scheme\": \"http\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=http_scheme)[\"remote_protocol\"], \"http\"\n        )\n\n        bad_scheme = {\"X-Scheme\": \"unknown\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=bad_scheme)[\"remote_protocol\"], \"https\"\n        )\n\n\nclass ManualProtocolTest(HandlerBaseTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.write(dict(protocol=self.request.protocol))\n\n    def get_httpserver_options(self):\n        return dict(protocol=\"https\")\n\n    def test_manual_protocol(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"protocol\"], \"https\")\n\n\n@unittest.skipIf(\n    not hasattr(socket, \"AF_UNIX\") or sys.platform == \"cygwin\",\n    \"unix sockets not supported on this platform\",\n)\nclass UnixSocketTest(AsyncTestCase):\n    \"\"\"HTTPServers can listen on Unix sockets too.\n\n    Why would you want to do this?  Nginx can proxy to backends listening\n    on unix sockets, for one thing (and managing a namespace for unix\n    sockets can be easier than managing a bunch of TCP port numbers).\n\n    Unfortunately, there's no way to specify a unix socket in a url for\n    an HTTP client, so we have to test this by hand.\n    \"\"\"\n\n    def setUp(self):\n        super().setUp()\n        self.tmpdir = tempfile.mkdtemp()\n        self.sockfile = os.path.join(self.tmpdir, \"test.sock\")\n        sock = netutil.bind_unix_socket(self.sockfile)\n        app = Application([(\"/hello\", HelloWorldRequestHandler)])\n        self.server = HTTPServer(app)\n        self.server.add_socket(sock)\n        self.stream = IOStream(socket.socket(socket.AF_UNIX))\n        self.io_loop.run_sync(lambda: self.stream.connect(self.sockfile))\n\n    def tearDown(self):\n        self.stream.close()\n        self.io_loop.run_sync(self.server.close_all_connections)\n        self.server.stop()\n        shutil.rmtree(self.tmpdir)\n        super().tearDown()\n\n    @gen_test\n    def test_unix_socket(self):\n        self.stream.write(b\"GET /hello HTTP/1.0\\r\\n\\r\\n\")\n        response = yield self.stream.read_until(b\"\\r\\n\")\n        self.assertEqual(response, b\"HTTP/1.1 200 OK\\r\\n\")\n        header_data = yield self.stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(header_data.decode(\"latin1\"))\n        body = yield self.stream.read_bytes(int(headers[\"Content-Length\"]))\n        self.assertEqual(body, b\"Hello world\")\n\n    @gen_test\n    def test_unix_socket_bad_request(self):\n        # Unix sockets don't have remote addresses so they just return an\n        # empty string.\n        with ExpectLog(gen_log, \"Malformed HTTP message from\"):\n            self.stream.write(b\"garbage\\r\\n\\r\\n\")\n            response = yield self.stream.read_until_close()\n        self.assertEqual(response, b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n\n\nclass KeepAliveTest(AsyncHTTPTestCase):\n    \"\"\"Tests various scenarios for HTTP 1.1 keep-alive support.\n\n    These tests don't use AsyncHTTPClient because we want to control\n    connection reuse and closing.\n    \"\"\"\n\n    def get_app(self):\n        class HelloHandler(RequestHandler):\n            def get(self):\n                self.finish(\"Hello world\")\n\n            def post(self):\n                self.finish(\"Hello world\")\n\n        class LargeHandler(RequestHandler):\n            def get(self):\n                # 512KB should be bigger than the socket buffers so it will\n                # be written out in chunks.\n                self.write(\"\".join(chr(i % 256) * 1024 for i in range(512)))\n\n        class TransferEncodingChunkedHandler(RequestHandler):\n            @gen.coroutine\n            def head(self):\n                self.write(\"Hello world\")\n                yield self.flush()\n\n        class FinishOnCloseHandler(RequestHandler):\n            def initialize(self, cleanup_event):\n                self.cleanup_event = cleanup_event\n\n            @gen.coroutine\n            def get(self):\n                self.flush()\n                yield self.cleanup_event.wait()\n\n            def on_connection_close(self):\n                # This is not very realistic, but finishing the request\n                # from the close callback has the right timing to mimic\n                # some errors seen in the wild.\n                self.finish(\"closed\")\n\n        self.cleanup_event = Event()\n        return Application(\n            [\n                (\"/\", HelloHandler),\n                (\"/large\", LargeHandler),\n                (\"/chunked\", TransferEncodingChunkedHandler),\n                (\n                    \"/finish_on_close\",\n                    FinishOnCloseHandler,\n                    dict(cleanup_event=self.cleanup_event),\n                ),\n            ]\n        )\n\n    def setUp(self):\n        super().setUp()\n        self.http_version = b\"HTTP/1.1\"\n\n    def tearDown(self):\n        # We just closed the client side of the socket; let the IOLoop run\n        # once to make sure the server side got the message.\n        self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)\n        self.wait()\n\n        if hasattr(self, \"stream\"):\n            self.stream.close()\n        super().tearDown()\n\n    # The next few methods are a crude manual http client\n    @gen.coroutine\n    def connect(self):\n        self.stream = IOStream(socket.socket())\n        yield self.stream.connect((\"127.0.0.1\", self.get_http_port()))\n\n    @gen.coroutine\n    def read_headers(self):\n        first_line = yield self.stream.read_until(b\"\\r\\n\")\n        self.assertTrue(first_line.startswith(b\"HTTP/1.1 200\"), first_line)\n        header_bytes = yield self.stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(header_bytes.decode(\"latin1\"))\n        raise gen.Return(headers)\n\n    @gen.coroutine\n    def read_response(self):\n        self.headers = yield self.read_headers()\n        body = yield self.stream.read_bytes(int(self.headers[\"Content-Length\"]))\n        self.assertEqual(b\"Hello world\", body)\n\n    def close(self):\n        self.stream.close()\n        del self.stream\n\n    @gen_test\n    def test_two_requests(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_request_close(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\nConnection: close\\r\\n\\r\\n\")\n        yield self.read_response()\n        data = yield self.stream.read_until_close()\n        self.assertTrue(not data)\n        self.assertEqual(self.headers[\"Connection\"], \"close\")\n        self.close()\n\n    # keepalive is supported for http 1.0 too, but it's opt-in\n    @gen_test\n    def test_http10(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\n\\r\\n\")\n        yield self.read_response()\n        data = yield self.stream.read_until_close()\n        self.assertTrue(not data)\n        self.assertTrue(\"Connection\" not in self.headers)\n        self.close()\n\n    @gen_test\n    def test_http10_keepalive(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_http10_keepalive_extra_crlf(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_pipelined_requests(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\nGET / HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_pipelined_cancel(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\nGET / HTTP/1.1\\r\\n\\r\\n\")\n        # only read once\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_cancel_during_download(self):\n        yield self.connect()\n        self.stream.write(b\"GET /large HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        yield self.stream.read_bytes(1024)\n        self.close()\n\n    @gen_test\n    def test_finish_while_closed(self):\n        yield self.connect()\n        self.stream.write(b\"GET /finish_on_close HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        self.close()\n        # Let the hanging coroutine clean up after itself\n        self.cleanup_event.set()\n\n    @gen_test\n    def test_keepalive_chunked(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(\n            b\"POST / HTTP/1.0\\r\\n\"\n            b\"Connection: keep-alive\\r\\n\"\n            b\"Transfer-Encoding: chunked\\r\\n\"\n            b\"\\r\\n\"\n            b\"0\\r\\n\"\n            b\"\\r\\n\"\n        )\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_keepalive_chunked_head_no_body(self):\n        yield self.connect()\n        self.stream.write(b\"HEAD /chunked HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n\n        self.stream.write(b\"HEAD /chunked HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        self.close()\n\n\nclass GzipBaseTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", EchoHandler)])\n\n    def post_gzip(self, body):\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(utf8(body))\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        return self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=compressed_body,\n            headers={\"Content-Encoding\": \"gzip\"},\n        )\n\n    def test_uncompressed(self):\n        response = self.fetch(\"/\", method=\"POST\", body=\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n\nclass GzipTest(GzipBaseTest, AsyncHTTPTestCase):\n    def get_httpserver_options(self):\n        return dict(decompress_request=True)\n\n    def test_gzip(self):\n        response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n\nclass GzipUnsupportedTest(GzipBaseTest, AsyncHTTPTestCase):\n    def test_gzip_unsupported(self):\n        # Gzip support is opt-in; without it the server fails to parse\n        # the body (but parsing form bodies is currently just a log message,\n        # not a fatal error).\n        with ExpectLog(gen_log, \"Unsupported Content-Encoding\"):\n            response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {})\n\n\nclass StreamingChunkSizeTest(AsyncHTTPTestCase):\n    # 50 characters long, and repetitive so it can be compressed.\n    BODY = b\"01234567890123456789012345678901234567890123456789\"\n    CHUNK_SIZE = 16\n\n    def get_http_client(self):\n        # body_producer doesn't work on curl_httpclient, so override the\n        # configured AsyncHTTPClient implementation.\n        return SimpleAsyncHTTPClient()\n\n    def get_httpserver_options(self):\n        return dict(chunk_size=self.CHUNK_SIZE, decompress_request=True)\n\n    class MessageDelegate(HTTPMessageDelegate):\n        def __init__(self, connection):\n            self.connection = connection\n\n        def headers_received(self, start_line, headers):\n            self.chunk_lengths = []  # type: List[int]\n\n        def data_received(self, chunk):\n            self.chunk_lengths.append(len(chunk))\n\n        def finish(self):\n            response_body = utf8(json_encode(self.chunk_lengths))\n            self.connection.write_headers(\n                ResponseStartLine(\"HTTP/1.1\", 200, \"OK\"),\n                HTTPHeaders({\"Content-Length\": str(len(response_body))}),\n            )\n            self.connection.write(response_body)\n            self.connection.finish()\n\n    def get_app(self):\n        class App(HTTPServerConnectionDelegate):\n            def start_request(self, server_conn, request_conn):\n                return StreamingChunkSizeTest.MessageDelegate(request_conn)\n\n        return App()\n\n    def fetch_chunk_sizes(self, **kwargs):\n        response = self.fetch(\"/\", method=\"POST\", **kwargs)\n        response.rethrow()\n        chunks = json_decode(response.body)\n        self.assertEqual(len(self.BODY), sum(chunks))\n        for chunk_size in chunks:\n            self.assertLessEqual(\n                chunk_size, self.CHUNK_SIZE, \"oversized chunk: \" + str(chunks)\n            )\n            self.assertGreater(chunk_size, 0, \"empty chunk: \" + str(chunks))\n        return chunks\n\n    def compress(self, body):\n        bytesio = BytesIO()\n        gzfile = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzfile.write(body)\n        gzfile.close()\n        compressed = bytesio.getvalue()\n        if len(compressed) >= len(body):\n            raise Exception(\"body did not shrink when compressed\")\n        return compressed\n\n    def test_regular_body(self):\n        chunks = self.fetch_chunk_sizes(body=self.BODY)\n        # Without compression we know exactly what to expect.\n        self.assertEqual([16, 16, 16, 2], chunks)\n\n    def test_compressed_body(self):\n        self.fetch_chunk_sizes(\n            body=self.compress(self.BODY), headers={\"Content-Encoding\": \"gzip\"}\n        )\n        # Compression creates irregular boundaries so the assertions\n        # in fetch_chunk_sizes are as specific as we can get.\n\n    def test_chunked_body(self):\n        def body_producer(write):\n            write(self.BODY[:20])\n            write(self.BODY[20:])\n\n        chunks = self.fetch_chunk_sizes(body_producer=body_producer)\n        # HTTP chunk boundaries translate to application-visible breaks\n        self.assertEqual([16, 4, 16, 14], chunks)\n\n    def test_chunked_compressed(self):\n        compressed = self.compress(self.BODY)\n        self.assertGreater(len(compressed), 20)\n\n        def body_producer(write):\n            write(compressed[:20])\n            write(compressed[20:])\n\n        self.fetch_chunk_sizes(\n            body_producer=body_producer, headers={\"Content-Encoding\": \"gzip\"}\n        )\n\n\nclass MaxHeaderSizeTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler)])\n\n    def get_httpserver_options(self):\n        return dict(max_header_size=1024)\n\n    def test_small_headers(self):\n        response = self.fetch(\"/\", headers={\"X-Filler\": \"a\" * 100})\n        response.rethrow()\n        self.assertEqual(response.body, b\"Hello world\")\n\n    def test_large_headers(self):\n        with ExpectLog(gen_log, \"Unsatisfiable read\", required=False):\n            try:\n                self.fetch(\"/\", headers={\"X-Filler\": \"a\" * 1000}, raise_error=True)\n                self.fail(\"did not raise expected exception\")\n            except HTTPError as e:\n                # 431 is \"Request Header Fields Too Large\", defined in RFC\n                # 6585. However, many implementations just close the\n                # connection in this case, resulting in a missing response.\n                if e.response is not None:\n                    self.assertIn(e.response.code, (431, 599))\n\n\n@skipOnTravis\nclass IdleTimeoutTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler)])\n\n    def get_httpserver_options(self):\n        return dict(idle_connection_timeout=0.1)\n\n    def setUp(self):\n        super().setUp()\n        self.streams = []  # type: List[IOStream]\n\n    def tearDown(self):\n        super().tearDown()\n        for stream in self.streams:\n            stream.close()\n\n    @gen.coroutine\n    def connect(self):\n        stream = IOStream(socket.socket())\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        self.streams.append(stream)\n        raise gen.Return(stream)\n\n    @gen_test\n    def test_unused_connection(self):\n        stream = yield self.connect()\n        event = Event()\n        stream.set_close_callback(event.set)\n        yield event.wait()\n\n    @gen_test\n    def test_idle_after_use(self):\n        stream = yield self.connect()\n        event = Event()\n        stream.set_close_callback(event.set)\n\n        # Use the connection twice to make sure keep-alives are working\n        for i in range(2):\n            stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n            yield stream.read_until(b\"\\r\\n\\r\\n\")\n            data = yield stream.read_bytes(11)\n            self.assertEqual(data, b\"Hello world\")\n\n        # Now let the timeout trigger and close the connection.\n        yield event.wait()\n\n\nclass BodyLimitsTest(AsyncHTTPTestCase):\n    def get_app(self):\n        class BufferedHandler(RequestHandler):\n            def put(self):\n                self.write(str(len(self.request.body)))\n\n        @stream_request_body\n        class StreamingHandler(RequestHandler):\n            def initialize(self):\n                self.bytes_read = 0\n\n            def prepare(self):\n                conn = typing.cast(HTTP1Connection, self.request.connection)\n                if \"expected_size\" in self.request.arguments:\n                    conn.set_max_body_size(int(self.get_argument(\"expected_size\")))\n                if \"body_timeout\" in self.request.arguments:\n                    conn.set_body_timeout(float(self.get_argument(\"body_timeout\")))\n\n            def data_received(self, data):\n                self.bytes_read += len(data)\n\n            def put(self):\n                self.write(str(self.bytes_read))\n\n        return Application(\n            [(\"/buffered\", BufferedHandler), (\"/streaming\", StreamingHandler)]\n        )\n\n    def get_httpserver_options(self):\n        return dict(body_timeout=3600, max_body_size=4096)\n\n    def get_http_client(self):\n        # body_producer doesn't work on curl_httpclient, so override the\n        # configured AsyncHTTPClient implementation.\n        return SimpleAsyncHTTPClient()\n\n    def test_small_body(self):\n        response = self.fetch(\"/buffered\", method=\"PUT\", body=b\"a\" * 4096)\n        self.assertEqual(response.body, b\"4096\")\n        response = self.fetch(\"/streaming\", method=\"PUT\", body=b\"a\" * 4096)\n        self.assertEqual(response.body, b\"4096\")\n\n    def test_large_body_buffered(self):\n        with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n            response = self.fetch(\"/buffered\", method=\"PUT\", body=b\"a\" * 10240)\n        self.assertEqual(response.code, 400)\n\n    @unittest.skipIf(os.name == \"nt\", \"flaky on windows\")\n    def test_large_body_buffered_chunked(self):\n        # This test is flaky on windows for unknown reasons.\n        with ExpectLog(gen_log, \".*chunked body too large\", level=logging.INFO):\n            response = self.fetch(\n                \"/buffered\",\n                method=\"PUT\",\n                body_producer=lambda write: write(b\"a\" * 10240),\n            )\n        self.assertEqual(response.code, 400)\n\n    def test_large_body_streaming(self):\n        with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n            response = self.fetch(\"/streaming\", method=\"PUT\", body=b\"a\" * 10240)\n        self.assertEqual(response.code, 400)\n\n    @unittest.skipIf(os.name == \"nt\", \"flaky on windows\")\n    def test_large_body_streaming_chunked(self):\n        with ExpectLog(gen_log, \".*chunked body too large\", level=logging.INFO):\n            response = self.fetch(\n                \"/streaming\",\n                method=\"PUT\",\n                body_producer=lambda write: write(b\"a\" * 10240),\n            )\n        self.assertEqual(response.code, 400)\n\n    def test_large_body_streaming_override(self):\n        response = self.fetch(\n            \"/streaming?expected_size=10240\", method=\"PUT\", body=b\"a\" * 10240\n        )\n        self.assertEqual(response.body, b\"10240\")\n\n    def test_large_body_streaming_chunked_override(self):\n        response = self.fetch(\n            \"/streaming?expected_size=10240\",\n            method=\"PUT\",\n            body_producer=lambda write: write(b\"a\" * 10240),\n        )\n        self.assertEqual(response.body, b\"10240\")\n\n    @gen_test\n    def test_timeout(self):\n        stream = IOStream(socket.socket())\n        try:\n            yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n            # Use a raw stream because AsyncHTTPClient won't let us read a\n            # response without finishing a body.\n            stream.write(\n                b\"PUT /streaming?body_timeout=0.1 HTTP/1.0\\r\\n\"\n                b\"Content-Length: 42\\r\\n\\r\\n\"\n            )\n            with ExpectLog(gen_log, \"Timeout reading body\", level=logging.INFO):\n                response = yield stream.read_until_close()\n            self.assertEqual(response, b\"\")\n        finally:\n            stream.close()\n\n    @gen_test\n    def test_body_size_override_reset(self):\n        # The max_body_size override is reset between requests.\n        stream = IOStream(socket.socket())\n        try:\n            yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n            # Use a raw stream so we can make sure it's all on one connection.\n            stream.write(\n                b\"PUT /streaming?expected_size=10240 HTTP/1.1\\r\\n\"\n                b\"Content-Length: 10240\\r\\n\\r\\n\"\n            )\n            stream.write(b\"a\" * 10240)\n            start_line, headers, response = yield read_stream_body(stream)\n            self.assertEqual(response, b\"10240\")\n            # Without the ?expected_size parameter, we get the old default value\n            stream.write(\n                b\"PUT /streaming HTTP/1.1\\r\\n\" b\"Content-Length: 10240\\r\\n\\r\\n\"\n            )\n            with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n                data = yield stream.read_until_close()\n            self.assertEqual(data, b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n        finally:\n            stream.close()\n\n\nclass LegacyInterfaceTest(AsyncHTTPTestCase):\n    def get_app(self):\n        # The old request_callback interface does not implement the\n        # delegate interface, and writes its response via request.write\n        # instead of request.connection.write_headers.\n        def handle_request(request):\n            self.http1 = request.version.startswith(\"HTTP/1.\")\n            if not self.http1:\n                # This test will be skipped if we're using HTTP/2,\n                # so just close it out cleanly using the modern interface.\n                request.connection.write_headers(\n                    ResponseStartLine(\"\", 200, \"OK\"), HTTPHeaders()\n                )\n                request.connection.finish()\n                return\n            message = b\"Hello world\"\n            request.connection.write(\n                utf8(\"HTTP/1.1 200 OK\\r\\n\" \"Content-Length: %d\\r\\n\\r\\n\" % len(message))\n            )\n            request.connection.write(message)\n            request.connection.finish()\n\n        return handle_request\n\n    def test_legacy_interface(self):\n        response = self.fetch(\"/\")\n        if not self.http1:\n            self.skipTest(\"requires HTTP/1.x\")\n        self.assertEqual(response.body, b\"Hello world\")\n",
            "new_file_content": "from tornado import gen, netutil\nfrom tornado.escape import (\n    json_decode,\n    json_encode,\n    utf8,\n    _unicode,\n    recursive_unicode,\n    native_str,\n)\nfrom tornado.http1connection import HTTP1Connection\nfrom tornado.httpclient import HTTPError\nfrom tornado.httpserver import HTTPServer\nfrom tornado.httputil import (\n    HTTPHeaders,\n    HTTPMessageDelegate,\n    HTTPServerConnectionDelegate,\n    ResponseStartLine,\n)\nfrom tornado.iostream import IOStream\nfrom tornado.locks import Event\nfrom tornado.log import gen_log\nfrom tornado.netutil import ssl_options_to_context\nfrom tornado.simple_httpclient import SimpleAsyncHTTPClient\nfrom tornado.testing import (\n    AsyncHTTPTestCase,\n    AsyncHTTPSTestCase,\n    AsyncTestCase,\n    ExpectLog,\n    gen_test,\n)\nfrom tornado.test.util import skipOnTravis\nfrom tornado.web import Application, RequestHandler, stream_request_body\n\nfrom contextlib import closing\nimport datetime\nimport gzip\nimport logging\nimport os\nimport shutil\nimport socket\nimport ssl\nimport sys\nimport tempfile\nimport unittest\nimport urllib.parse\nfrom io import BytesIO\n\nimport typing\n\nif typing.TYPE_CHECKING:\n    from typing import Dict, List  # noqa: F401\n\n\nasync def read_stream_body(stream):\n    \"\"\"Reads an HTTP response from `stream` and returns a tuple of its\n    start_line, headers and body.\"\"\"\n    chunks = []\n\n    class Delegate(HTTPMessageDelegate):\n        def headers_received(self, start_line, headers):\n            self.headers = headers\n            self.start_line = start_line\n\n        def data_received(self, chunk):\n            chunks.append(chunk)\n\n        def finish(self):\n            conn.detach()  # type: ignore\n\n    conn = HTTP1Connection(stream, True)\n    delegate = Delegate()\n    await conn.read_response(delegate)\n    return delegate.start_line, delegate.headers, b\"\".join(chunks)\n\n\nclass HandlerBaseTestCase(AsyncHTTPTestCase):\n    Handler = None\n\n    def get_app(self):\n        return Application([(\"/\", self.__class__.Handler)])\n\n    def fetch_json(self, *args, **kwargs):\n        response = self.fetch(*args, **kwargs)\n        response.rethrow()\n        return json_decode(response.body)\n\n\nclass HelloWorldRequestHandler(RequestHandler):\n    def initialize(self, protocol=\"http\"):\n        self.expected_protocol = protocol\n\n    def get(self):\n        if self.request.protocol != self.expected_protocol:\n            raise Exception(\"unexpected protocol\")\n        self.finish(\"Hello world\")\n\n    def post(self):\n        self.finish(\"Got %d bytes in POST\" % len(self.request.body))\n\n\n# In pre-1.0 versions of openssl, SSLv23 clients always send SSLv2\n# ClientHello messages, which are rejected by SSLv3 and TLSv1\n# servers.  Note that while the OPENSSL_VERSION_INFO was formally\n# introduced in python3.2, it was present but undocumented in\n# python 2.7\nskipIfOldSSL = unittest.skipIf(\n    getattr(ssl, \"OPENSSL_VERSION_INFO\", (0, 0)) < (1, 0),\n    \"old version of ssl module and/or openssl\",\n)\n\n\nclass BaseSSLTest(AsyncHTTPSTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler, dict(protocol=\"https\"))])\n\n\nclass SSLTestMixin(object):\n    def get_ssl_options(self):\n        return dict(\n            ssl_version=self.get_ssl_version(),\n            **AsyncHTTPSTestCase.default_ssl_options()\n        )\n\n    def get_ssl_version(self):\n        raise NotImplementedError()\n\n    def test_ssl(self: typing.Any):\n        response = self.fetch(\"/\")\n        self.assertEqual(response.body, b\"Hello world\")\n\n    def test_large_post(self: typing.Any):\n        response = self.fetch(\"/\", method=\"POST\", body=\"A\" * 5000)\n        self.assertEqual(response.body, b\"Got 5000 bytes in POST\")\n\n    def test_non_ssl_request(self: typing.Any):\n        # Make sure the server closes the connection when it gets a non-ssl\n        # connection, rather than waiting for a timeout or otherwise\n        # misbehaving.\n        with ExpectLog(gen_log, \"(SSL Error|uncaught exception)\"):\n            with ExpectLog(gen_log, \"Uncaught exception\", required=False):\n                with self.assertRaises((IOError, HTTPError)):  # type: ignore\n                    self.fetch(\n                        self.get_url(\"/\").replace(\"https:\", \"http:\"),\n                        request_timeout=3600,\n                        connect_timeout=3600,\n                        raise_error=True,\n                    )\n\n    def test_error_logging(self: typing.Any):\n        # No stack traces are logged for SSL errors.\n        with ExpectLog(gen_log, \"SSL Error\") as expect_log:\n            with self.assertRaises((IOError, HTTPError)):  # type: ignore\n                self.fetch(\n                    self.get_url(\"/\").replace(\"https:\", \"http:\"), raise_error=True\n                )\n        self.assertFalse(expect_log.logged_stack)\n\n\n# Python's SSL implementation differs significantly between versions.\n# For example, SSLv3 and TLSv1 throw an exception if you try to read\n# from the socket before the handshake is complete, but the default\n# of SSLv23 allows it.\n\n\nclass SSLv23Test(BaseSSLTest, SSLTestMixin):\n    def get_ssl_version(self):\n        return ssl.PROTOCOL_SSLv23\n\n\n@skipIfOldSSL\nclass SSLv3Test(BaseSSLTest, SSLTestMixin):\n    def get_ssl_version(self):\n        return ssl.PROTOCOL_SSLv3\n\n\n@skipIfOldSSL\nclass TLSv1Test(BaseSSLTest, SSLTestMixin):\n    def get_ssl_version(self):\n        return ssl.PROTOCOL_TLSv1\n\n\nclass SSLContextTest(BaseSSLTest, SSLTestMixin):\n    def get_ssl_options(self):\n        context = ssl_options_to_context(AsyncHTTPSTestCase.get_ssl_options(self))\n        assert isinstance(context, ssl.SSLContext)\n        return context\n\n\nclass BadSSLOptionsTest(unittest.TestCase):\n    def test_missing_arguments(self):\n        application = Application()\n        self.assertRaises(\n            KeyError,\n            HTTPServer,\n            application,\n            ssl_options={\"keyfile\": \"/__missing__.crt\"},\n        )\n\n    def test_missing_key(self):\n        \"\"\"A missing SSL key should cause an immediate exception.\"\"\"\n\n        application = Application()\n        module_dir = os.path.dirname(__file__)\n        existing_certificate = os.path.join(module_dir, \"test.crt\")\n        existing_key = os.path.join(module_dir, \"test.key\")\n\n        self.assertRaises(\n            (ValueError, IOError),\n            HTTPServer,\n            application,\n            ssl_options={\"certfile\": \"/__mising__.crt\"},\n        )\n        self.assertRaises(\n            (ValueError, IOError),\n            HTTPServer,\n            application,\n            ssl_options={\n                \"certfile\": existing_certificate,\n                \"keyfile\": \"/__missing__.key\",\n            },\n        )\n\n        # This actually works because both files exist\n        HTTPServer(\n            application,\n            ssl_options={\"certfile\": existing_certificate, \"keyfile\": existing_key},\n        )\n\n\nclass MultipartTestHandler(RequestHandler):\n    def post(self):\n        self.finish(\n            {\n                \"header\": self.request.headers[\"X-Header-Encoding-Test\"],\n                \"argument\": self.get_argument(\"argument\"),\n                \"filename\": self.request.files[\"files\"][0].filename,\n                \"filebody\": _unicode(self.request.files[\"files\"][0][\"body\"]),\n            }\n        )\n\n\n# This test is also called from wsgi_test\nclass HTTPConnectionTest(AsyncHTTPTestCase):\n    def get_handlers(self):\n        return [\n            (\"/multipart\", MultipartTestHandler),\n            (\"/hello\", HelloWorldRequestHandler),\n        ]\n\n    def get_app(self):\n        return Application(self.get_handlers())\n\n    def raw_fetch(self, headers, body, newline=b\"\\r\\n\"):\n        with closing(IOStream(socket.socket())) as stream:\n            self.io_loop.run_sync(\n                lambda: stream.connect((\"127.0.0.1\", self.get_http_port()))\n            )\n            stream.write(\n                newline.join(headers + [utf8(\"Content-Length: %d\" % len(body))])\n                + newline\n                + newline\n                + body\n            )\n            start_line, headers, body = self.io_loop.run_sync(\n                lambda: read_stream_body(stream)\n            )\n            return body\n\n    def test_multipart_form(self):\n        # Encodings here are tricky:  Headers are latin1, bodies can be\n        # anything (we use utf8 by default).\n        response = self.raw_fetch(\n            [\n                b\"POST /multipart HTTP/1.0\",\n                b\"Content-Type: multipart/form-data; boundary=1234567890\",\n                b\"X-Header-encoding-test: \\xe9\",\n            ],\n            b\"\\r\\n\".join(\n                [\n                    b\"Content-Disposition: form-data; name=argument\",\n                    b\"\",\n                    u\"\\u00e1\".encode(\"utf-8\"),\n                    b\"--1234567890\",\n                    u'Content-Disposition: form-data; name=\"files\"; filename=\"\\u00f3\"'.encode(\n                        \"utf8\"\n                    ),\n                    b\"\",\n                    u\"\\u00fa\".encode(\"utf-8\"),\n                    b\"--1234567890--\",\n                    b\"\",\n                ]\n            ),\n        )\n        data = json_decode(response)\n        self.assertEqual(u\"\\u00e9\", data[\"header\"])\n        self.assertEqual(u\"\\u00e1\", data[\"argument\"])\n        self.assertEqual(u\"\\u00f3\", data[\"filename\"])\n        self.assertEqual(u\"\\u00fa\", data[\"filebody\"])\n\n    def test_newlines(self):\n        # We support both CRLF and bare LF as line separators.\n        for newline in (b\"\\r\\n\", b\"\\n\"):\n            response = self.raw_fetch([b\"GET /hello HTTP/1.0\"], b\"\", newline=newline)\n            self.assertEqual(response, b\"Hello world\")\n\n    @gen_test\n    def test_100_continue(self):\n        # Run through a 100-continue interaction by hand:\n        # When given Expect: 100-continue, we get a 100 response after the\n        # headers, and then the real response after the body.\n        stream = IOStream(socket.socket())\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        yield stream.write(\n            b\"\\r\\n\".join(\n                [\n                    b\"POST /hello HTTP/1.1\",\n                    b\"Content-Length: 1024\",\n                    b\"Expect: 100-continue\",\n                    b\"Connection: close\",\n                    b\"\\r\\n\",\n                ]\n            )\n        )\n        data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n        self.assertTrue(data.startswith(b\"HTTP/1.1 100 \"), data)\n        stream.write(b\"a\" * 1024)\n        first_line = yield stream.read_until(b\"\\r\\n\")\n        self.assertTrue(first_line.startswith(b\"HTTP/1.1 200\"), first_line)\n        header_data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(native_str(header_data.decode(\"latin1\")))\n        body = yield stream.read_bytes(int(headers[\"Content-Length\"]))\n        self.assertEqual(body, b\"Got 1024 bytes in POST\")\n        stream.close()\n\n\nclass EchoHandler(RequestHandler):\n    def get(self):\n        self.write(recursive_unicode(self.request.arguments))\n\n    def post(self):\n        self.write(recursive_unicode(self.request.arguments))\n\n\nclass TypeCheckHandler(RequestHandler):\n    def prepare(self):\n        self.errors = {}  # type: Dict[str, str]\n        fields = [\n            (\"method\", str),\n            (\"uri\", str),\n            (\"version\", str),\n            (\"remote_ip\", str),\n            (\"protocol\", str),\n            (\"host\", str),\n            (\"path\", str),\n            (\"query\", str),\n        ]\n        for field, expected_type in fields:\n            self.check_type(field, getattr(self.request, field), expected_type)\n\n        self.check_type(\"header_key\", list(self.request.headers.keys())[0], str)\n        self.check_type(\"header_value\", list(self.request.headers.values())[0], str)\n\n        self.check_type(\"cookie_key\", list(self.request.cookies.keys())[0], str)\n        self.check_type(\n            \"cookie_value\", list(self.request.cookies.values())[0].value, str\n        )\n        # secure cookies\n\n        self.check_type(\"arg_key\", list(self.request.arguments.keys())[0], str)\n        self.check_type(\"arg_value\", list(self.request.arguments.values())[0][0], bytes)\n\n    def post(self):\n        self.check_type(\"body\", self.request.body, bytes)\n        self.write(self.errors)\n\n    def get(self):\n        self.write(self.errors)\n\n    def check_type(self, name, obj, expected_type):\n        actual_type = type(obj)\n        if expected_type != actual_type:\n            self.errors[name] = \"expected %s, got %s\" % (expected_type, actual_type)\n\n\nclass PostEchoHandler(RequestHandler):\n    def post(self, *path_args):\n        self.write(dict(echo=self.get_argument(\"data\")))\n\n\nclass PostEchoGBKHandler(PostEchoHandler):\n    def decode_argument(self, value, name=None):\n        try:\n            return value.decode(\"gbk\")\n        except Exception:\n            raise HTTPError(400, \"invalid gbk bytes: %r\" % value)\n\n\nclass HTTPServerTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application(\n            [\n                (\"/echo\", EchoHandler),\n                (\"/typecheck\", TypeCheckHandler),\n                (\"//doubleslash\", EchoHandler),\n                (\"/post_utf8\", PostEchoHandler),\n                (\"/post_gbk\", PostEchoGBKHandler),\n            ]\n        )\n\n    def test_query_string_encoding(self):\n        response = self.fetch(\"/echo?foo=%C3%A9\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {u\"foo\": [u\"\\u00e9\"]})\n\n    def test_empty_query_string(self):\n        response = self.fetch(\"/echo?foo=&foo=\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {u\"foo\": [u\"\", u\"\"]})\n\n    def test_empty_post_parameters(self):\n        response = self.fetch(\"/echo\", method=\"POST\", body=\"foo=&bar=\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {u\"foo\": [u\"\"], u\"bar\": [u\"\"]})\n\n    def test_types(self):\n        headers = {\"Cookie\": \"foo=bar\"}\n        response = self.fetch(\"/typecheck?foo=bar\", headers=headers)\n        data = json_decode(response.body)\n        self.assertEqual(data, {})\n\n        response = self.fetch(\n            \"/typecheck\", method=\"POST\", body=\"foo=bar\", headers=headers\n        )\n        data = json_decode(response.body)\n        self.assertEqual(data, {})\n\n    def test_double_slash(self):\n        # urlparse.urlsplit (which tornado.httpserver used to use\n        # incorrectly) would parse paths beginning with \"//\" as\n        # protocol-relative urls.\n        response = self.fetch(\"//doubleslash\")\n        self.assertEqual(200, response.code)\n        self.assertEqual(json_decode(response.body), {})\n\n    def test_post_encodings(self):\n        headers = {\"Content-Type\": \"application/x-www-form-urlencoded\"}\n        uni_text = \"chinese: \\u5f20\\u4e09\"\n        for enc in (\"utf8\", \"gbk\"):\n            for quote in (True, False):\n                with self.subTest(enc=enc, quote=quote):\n                    bin_text = uni_text.encode(enc)\n                    if quote:\n                        bin_text = urllib.parse.quote(bin_text).encode(\"ascii\")\n                    response = self.fetch(\n                        \"/post_\" + enc,\n                        method=\"POST\",\n                        headers=headers,\n                        body=(b\"data=\" + bin_text),\n                    )\n                    self.assertEqual(json_decode(response.body), {\"echo\": uni_text})\n\n\nclass HTTPServerRawTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/echo\", EchoHandler)])\n\n    def setUp(self):\n        super().setUp()\n        self.stream = IOStream(socket.socket())\n        self.io_loop.run_sync(\n            lambda: self.stream.connect((\"127.0.0.1\", self.get_http_port()))\n        )\n\n    def tearDown(self):\n        self.stream.close()\n        super().tearDown()\n\n    def test_empty_request(self):\n        self.stream.close()\n        self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)\n        self.wait()\n\n    def test_malformed_first_line_response(self):\n        with ExpectLog(gen_log, \".*Malformed HTTP request line\", level=logging.INFO):\n            self.stream.write(b\"asdf\\r\\n\\r\\n\")\n            start_line, headers, response = self.io_loop.run_sync(\n                lambda: read_stream_body(self.stream)\n            )\n            self.assertEqual(\"HTTP/1.1\", start_line.version)\n            self.assertEqual(400, start_line.code)\n            self.assertEqual(\"Bad Request\", start_line.reason)\n\n    def test_malformed_first_line_log(self):\n        with ExpectLog(gen_log, \".*Malformed HTTP request line\", level=logging.INFO):\n            self.stream.write(b\"asdf\\r\\n\\r\\n\")\n            # TODO: need an async version of ExpectLog so we don't need\n            # hard-coded timeouts here.\n            self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop)\n            self.wait()\n\n    def test_malformed_headers(self):\n        with ExpectLog(\n            gen_log,\n            \".*Malformed HTTP message.*no colon in header line\",\n            level=logging.INFO,\n        ):\n            self.stream.write(b\"GET / HTTP/1.0\\r\\nasdf\\r\\n\\r\\n\")\n            self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop)\n            self.wait()\n\n    def test_chunked_request_body(self):\n        # Chunked requests are not widely supported and we don't have a way\n        # to generate them in AsyncHTTPClient, but HTTPServer will read them.\n        self.stream.write(\n            b\"\"\"\\\nPOST /echo HTTP/1.1\nTransfer-Encoding: chunked\nContent-Type: application/x-www-form-urlencoded\n\n4\nfoo=\n3\nbar\n0\n\n\"\"\".replace(\n                b\"\\n\", b\"\\r\\n\"\n            )\n        )\n        start_line, headers, response = self.io_loop.run_sync(\n            lambda: read_stream_body(self.stream)\n        )\n        self.assertEqual(json_decode(response), {u\"foo\": [u\"bar\"]})\n\n    def test_chunked_request_uppercase(self):\n        # As per RFC 2616 section 3.6, \"Transfer-Encoding\" header's value is\n        # case-insensitive.\n        self.stream.write(\n            b\"\"\"\\\nPOST /echo HTTP/1.1\nTransfer-Encoding: Chunked\nContent-Type: application/x-www-form-urlencoded\n\n4\nfoo=\n3\nbar\n0\n\n\"\"\".replace(\n                b\"\\n\", b\"\\r\\n\"\n            )\n        )\n        start_line, headers, response = self.io_loop.run_sync(\n            lambda: read_stream_body(self.stream)\n        )\n        self.assertEqual(json_decode(response), {u\"foo\": [u\"bar\"]})\n\n    @gen_test\n    def test_invalid_content_length(self):\n        with ExpectLog(\n            gen_log, \".*Only integer Content-Length is allowed\", level=logging.INFO\n        ):\n            self.stream.write(\n                b\"\"\"\\\nPOST /echo HTTP/1.1\nContent-Length: foo\n\nbar\n\n\"\"\".replace(\n                    b\"\\n\", b\"\\r\\n\"\n                )\n            )\n            yield self.stream.read_until_close()\n\n\nclass XHeaderTest(HandlerBaseTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.set_header(\"request-version\", self.request.version)\n            self.write(\n                dict(\n                    remote_ip=self.request.remote_ip,\n                    remote_protocol=self.request.protocol,\n                )\n            )\n\n    def get_httpserver_options(self):\n        return dict(xheaders=True, trusted_downstream=[\"5.5.5.5\"])\n\n    def test_ip_headers(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_ip\"], \"127.0.0.1\")\n\n        valid_ipv4 = {\"X-Real-IP\": \"4.4.4.4\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv4)[\"remote_ip\"], \"4.4.4.4\"\n        )\n\n        valid_ipv4_list = {\"X-Forwarded-For\": \"127.0.0.1, 4.4.4.4\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv4_list)[\"remote_ip\"], \"4.4.4.4\"\n        )\n\n        valid_ipv6 = {\"X-Real-IP\": \"2620:0:1cfe:face:b00c::3\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv6)[\"remote_ip\"],\n            \"2620:0:1cfe:face:b00c::3\",\n        )\n\n        valid_ipv6_list = {\"X-Forwarded-For\": \"::1, 2620:0:1cfe:face:b00c::3\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv6_list)[\"remote_ip\"],\n            \"2620:0:1cfe:face:b00c::3\",\n        )\n\n        invalid_chars = {\"X-Real-IP\": \"4.4.4.4<script>\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_chars)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n        invalid_chars_list = {\"X-Forwarded-For\": \"4.4.4.4, 5.5.5.5<script>\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_chars_list)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n        invalid_host = {\"X-Real-IP\": \"www.google.com\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_host)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n    def test_trusted_downstream(self):\n        valid_ipv4_list = {\"X-Forwarded-For\": \"127.0.0.1, 4.4.4.4, 5.5.5.5\"}\n        resp = self.fetch(\"/\", headers=valid_ipv4_list)\n        if resp.headers[\"request-version\"].startswith(\"HTTP/2\"):\n            # This is a hack - there's nothing that fundamentally requires http/1\n            # here but tornado_http2 doesn't support it yet.\n            self.skipTest(\"requires HTTP/1.x\")\n        result = json_decode(resp.body)\n        self.assertEqual(result[\"remote_ip\"], \"4.4.4.4\")\n\n    def test_scheme_headers(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_protocol\"], \"http\")\n\n        https_scheme = {\"X-Scheme\": \"https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_scheme)[\"remote_protocol\"], \"https\"\n        )\n\n        https_forwarded = {\"X-Forwarded-Proto\": \"https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_forwarded)[\"remote_protocol\"], \"https\"\n        )\n\n        https_multi_forwarded = {\"X-Forwarded-Proto\": \"https , http\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_multi_forwarded)[\"remote_protocol\"],\n            \"http\",\n        )\n\n        http_multi_forwarded = {\"X-Forwarded-Proto\": \"http,https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=http_multi_forwarded)[\"remote_protocol\"],\n            \"https\",\n        )\n\n        bad_forwarded = {\"X-Forwarded-Proto\": \"unknown\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=bad_forwarded)[\"remote_protocol\"], \"http\"\n        )\n\n\nclass SSLXHeaderTest(AsyncHTTPSTestCase, HandlerBaseTestCase):\n    def get_app(self):\n        return Application([(\"/\", XHeaderTest.Handler)])\n\n    def get_httpserver_options(self):\n        output = super().get_httpserver_options()\n        output[\"xheaders\"] = True\n        return output\n\n    def test_request_without_xprotocol(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_protocol\"], \"https\")\n\n        http_scheme = {\"X-Scheme\": \"http\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=http_scheme)[\"remote_protocol\"], \"http\"\n        )\n\n        bad_scheme = {\"X-Scheme\": \"unknown\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=bad_scheme)[\"remote_protocol\"], \"https\"\n        )\n\n\nclass ManualProtocolTest(HandlerBaseTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.write(dict(protocol=self.request.protocol))\n\n    def get_httpserver_options(self):\n        return dict(protocol=\"https\")\n\n    def test_manual_protocol(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"protocol\"], \"https\")\n\n\n@unittest.skipIf(\n    not hasattr(socket, \"AF_UNIX\") or sys.platform == \"cygwin\",\n    \"unix sockets not supported on this platform\",\n)\nclass UnixSocketTest(AsyncTestCase):\n    \"\"\"HTTPServers can listen on Unix sockets too.\n\n    Why would you want to do this?  Nginx can proxy to backends listening\n    on unix sockets, for one thing (and managing a namespace for unix\n    sockets can be easier than managing a bunch of TCP port numbers).\n\n    Unfortunately, there's no way to specify a unix socket in a url for\n    an HTTP client, so we have to test this by hand.\n    \"\"\"\n\n    def setUp(self):\n        super().setUp()\n        self.tmpdir = tempfile.mkdtemp()\n        self.sockfile = os.path.join(self.tmpdir, \"test.sock\")\n        sock = netutil.bind_unix_socket(self.sockfile)\n        app = Application([(\"/hello\", HelloWorldRequestHandler)])\n        self.server = HTTPServer(app)\n        self.server.add_socket(sock)\n        self.stream = IOStream(socket.socket(socket.AF_UNIX))\n        self.io_loop.run_sync(lambda: self.stream.connect(self.sockfile))\n\n    def tearDown(self):\n        self.stream.close()\n        self.io_loop.run_sync(self.server.close_all_connections)\n        self.server.stop()\n        shutil.rmtree(self.tmpdir)\n        super().tearDown()\n\n    @gen_test\n    def test_unix_socket(self):\n        self.stream.write(b\"GET /hello HTTP/1.0\\r\\n\\r\\n\")\n        response = yield self.stream.read_until(b\"\\r\\n\")\n        self.assertEqual(response, b\"HTTP/1.1 200 OK\\r\\n\")\n        header_data = yield self.stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(header_data.decode(\"latin1\"))\n        body = yield self.stream.read_bytes(int(headers[\"Content-Length\"]))\n        self.assertEqual(body, b\"Hello world\")\n\n    @gen_test\n    def test_unix_socket_bad_request(self):\n        # Unix sockets don't have remote addresses so they just return an\n        # empty string.\n        with ExpectLog(gen_log, \"Malformed HTTP message from\"):\n            self.stream.write(b\"garbage\\r\\n\\r\\n\")\n            response = yield self.stream.read_until_close()\n        self.assertEqual(response, b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n\n\nclass KeepAliveTest(AsyncHTTPTestCase):\n    \"\"\"Tests various scenarios for HTTP 1.1 keep-alive support.\n\n    These tests don't use AsyncHTTPClient because we want to control\n    connection reuse and closing.\n    \"\"\"\n\n    def get_app(self):\n        class HelloHandler(RequestHandler):\n            def get(self):\n                self.finish(\"Hello world\")\n\n            def post(self):\n                self.finish(\"Hello world\")\n\n        class LargeHandler(RequestHandler):\n            def get(self):\n                # 512KB should be bigger than the socket buffers so it will\n                # be written out in chunks.\n                self.write(\"\".join(chr(i % 256) * 1024 for i in range(512)))\n\n        class TransferEncodingChunkedHandler(RequestHandler):\n            @gen.coroutine\n            def head(self):\n                self.write(\"Hello world\")\n                yield self.flush()\n\n        class FinishOnCloseHandler(RequestHandler):\n            def initialize(self, cleanup_event):\n                self.cleanup_event = cleanup_event\n\n            @gen.coroutine\n            def get(self):\n                self.flush()\n                yield self.cleanup_event.wait()\n\n            def on_connection_close(self):\n                # This is not very realistic, but finishing the request\n                # from the close callback has the right timing to mimic\n                # some errors seen in the wild.\n                self.finish(\"closed\")\n\n        self.cleanup_event = Event()\n        return Application(\n            [\n                (\"/\", HelloHandler),\n                (\"/large\", LargeHandler),\n                (\"/chunked\", TransferEncodingChunkedHandler),\n                (\n                    \"/finish_on_close\",\n                    FinishOnCloseHandler,\n                    dict(cleanup_event=self.cleanup_event),\n                ),\n            ]\n        )\n\n    def setUp(self):\n        super().setUp()\n        self.http_version = b\"HTTP/1.1\"\n\n    def tearDown(self):\n        # We just closed the client side of the socket; let the IOLoop run\n        # once to make sure the server side got the message.\n        self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)\n        self.wait()\n\n        if hasattr(self, \"stream\"):\n            self.stream.close()\n        super().tearDown()\n\n    # The next few methods are a crude manual http client\n    @gen.coroutine\n    def connect(self):\n        self.stream = IOStream(socket.socket())\n        yield self.stream.connect((\"127.0.0.1\", self.get_http_port()))\n\n    @gen.coroutine\n    def read_headers(self):\n        first_line = yield self.stream.read_until(b\"\\r\\n\")\n        self.assertTrue(first_line.startswith(b\"HTTP/1.1 200\"), first_line)\n        header_bytes = yield self.stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(header_bytes.decode(\"latin1\"))\n        raise gen.Return(headers)\n\n    @gen.coroutine\n    def read_response(self):\n        self.headers = yield self.read_headers()\n        body = yield self.stream.read_bytes(int(self.headers[\"Content-Length\"]))\n        self.assertEqual(b\"Hello world\", body)\n\n    def close(self):\n        self.stream.close()\n        del self.stream\n\n    @gen_test\n    def test_two_requests(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_request_close(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\nConnection: close\\r\\n\\r\\n\")\n        yield self.read_response()\n        data = yield self.stream.read_until_close()\n        self.assertTrue(not data)\n        self.assertEqual(self.headers[\"Connection\"], \"close\")\n        self.close()\n\n    # keepalive is supported for http 1.0 too, but it's opt-in\n    @gen_test\n    def test_http10(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\n\\r\\n\")\n        yield self.read_response()\n        data = yield self.stream.read_until_close()\n        self.assertTrue(not data)\n        self.assertTrue(\"Connection\" not in self.headers)\n        self.close()\n\n    @gen_test\n    def test_http10_keepalive(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_http10_keepalive_extra_crlf(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_pipelined_requests(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\nGET / HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_pipelined_cancel(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\nGET / HTTP/1.1\\r\\n\\r\\n\")\n        # only read once\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_cancel_during_download(self):\n        yield self.connect()\n        self.stream.write(b\"GET /large HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        yield self.stream.read_bytes(1024)\n        self.close()\n\n    @gen_test\n    def test_finish_while_closed(self):\n        yield self.connect()\n        self.stream.write(b\"GET /finish_on_close HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        self.close()\n        # Let the hanging coroutine clean up after itself\n        self.cleanup_event.set()\n\n    @gen_test\n    def test_keepalive_chunked(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(\n            b\"POST / HTTP/1.0\\r\\n\"\n            b\"Connection: keep-alive\\r\\n\"\n            b\"Transfer-Encoding: chunked\\r\\n\"\n            b\"\\r\\n\"\n            b\"0\\r\\n\"\n            b\"\\r\\n\"\n        )\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_keepalive_chunked_head_no_body(self):\n        yield self.connect()\n        self.stream.write(b\"HEAD /chunked HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n\n        self.stream.write(b\"HEAD /chunked HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        self.close()\n\n\nclass GzipBaseTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", EchoHandler)])\n\n    def post_gzip(self, body):\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(utf8(body))\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        return self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=compressed_body,\n            headers={\"Content-Encoding\": \"gzip\"},\n        )\n\n    def test_uncompressed(self):\n        response = self.fetch(\"/\", method=\"POST\", body=\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n\nclass GzipTest(GzipBaseTest, AsyncHTTPTestCase):\n    def get_httpserver_options(self):\n        return dict(decompress_request=True)\n\n    def test_gzip(self):\n        response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n    def test_gzip_case_insensitive(self):\n        # https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.2.1\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(utf8(\"foo=bar\"))\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=compressed_body,\n            headers={\"Content-Encoding\": \"GZIP\"},\n        )\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n\nclass GzipUnsupportedTest(GzipBaseTest, AsyncHTTPTestCase):\n    def test_gzip_unsupported(self):\n        # Gzip support is opt-in; without it the server fails to parse\n        # the body (but parsing form bodies is currently just a log message,\n        # not a fatal error).\n        with ExpectLog(gen_log, \"Unsupported Content-Encoding\"):\n            response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {})\n\n\nclass StreamingChunkSizeTest(AsyncHTTPTestCase):\n    # 50 characters long, and repetitive so it can be compressed.\n    BODY = b\"01234567890123456789012345678901234567890123456789\"\n    CHUNK_SIZE = 16\n\n    def get_http_client(self):\n        # body_producer doesn't work on curl_httpclient, so override the\n        # configured AsyncHTTPClient implementation.\n        return SimpleAsyncHTTPClient()\n\n    def get_httpserver_options(self):\n        return dict(chunk_size=self.CHUNK_SIZE, decompress_request=True)\n\n    class MessageDelegate(HTTPMessageDelegate):\n        def __init__(self, connection):\n            self.connection = connection\n\n        def headers_received(self, start_line, headers):\n            self.chunk_lengths = []  # type: List[int]\n\n        def data_received(self, chunk):\n            self.chunk_lengths.append(len(chunk))\n\n        def finish(self):\n            response_body = utf8(json_encode(self.chunk_lengths))\n            self.connection.write_headers(\n                ResponseStartLine(\"HTTP/1.1\", 200, \"OK\"),\n                HTTPHeaders({\"Content-Length\": str(len(response_body))}),\n            )\n            self.connection.write(response_body)\n            self.connection.finish()\n\n    def get_app(self):\n        class App(HTTPServerConnectionDelegate):\n            def start_request(self, server_conn, request_conn):\n                return StreamingChunkSizeTest.MessageDelegate(request_conn)\n\n        return App()\n\n    def fetch_chunk_sizes(self, **kwargs):\n        response = self.fetch(\"/\", method=\"POST\", **kwargs)\n        response.rethrow()\n        chunks = json_decode(response.body)\n        self.assertEqual(len(self.BODY), sum(chunks))\n        for chunk_size in chunks:\n            self.assertLessEqual(\n                chunk_size, self.CHUNK_SIZE, \"oversized chunk: \" + str(chunks)\n            )\n            self.assertGreater(chunk_size, 0, \"empty chunk: \" + str(chunks))\n        return chunks\n\n    def compress(self, body):\n        bytesio = BytesIO()\n        gzfile = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzfile.write(body)\n        gzfile.close()\n        compressed = bytesio.getvalue()\n        if len(compressed) >= len(body):\n            raise Exception(\"body did not shrink when compressed\")\n        return compressed\n\n    def test_regular_body(self):\n        chunks = self.fetch_chunk_sizes(body=self.BODY)\n        # Without compression we know exactly what to expect.\n        self.assertEqual([16, 16, 16, 2], chunks)\n\n    def test_compressed_body(self):\n        self.fetch_chunk_sizes(\n            body=self.compress(self.BODY), headers={\"Content-Encoding\": \"gzip\"}\n        )\n        # Compression creates irregular boundaries so the assertions\n        # in fetch_chunk_sizes are as specific as we can get.\n\n    def test_chunked_body(self):\n        def body_producer(write):\n            write(self.BODY[:20])\n            write(self.BODY[20:])\n\n        chunks = self.fetch_chunk_sizes(body_producer=body_producer)\n        # HTTP chunk boundaries translate to application-visible breaks\n        self.assertEqual([16, 4, 16, 14], chunks)\n\n    def test_chunked_compressed(self):\n        compressed = self.compress(self.BODY)\n        self.assertGreater(len(compressed), 20)\n\n        def body_producer(write):\n            write(compressed[:20])\n            write(compressed[20:])\n\n        self.fetch_chunk_sizes(\n            body_producer=body_producer, headers={\"Content-Encoding\": \"gzip\"}\n        )\n\n\nclass MaxHeaderSizeTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler)])\n\n    def get_httpserver_options(self):\n        return dict(max_header_size=1024)\n\n    def test_small_headers(self):\n        response = self.fetch(\"/\", headers={\"X-Filler\": \"a\" * 100})\n        response.rethrow()\n        self.assertEqual(response.body, b\"Hello world\")\n\n    def test_large_headers(self):\n        with ExpectLog(gen_log, \"Unsatisfiable read\", required=False):\n            try:\n                self.fetch(\"/\", headers={\"X-Filler\": \"a\" * 1000}, raise_error=True)\n                self.fail(\"did not raise expected exception\")\n            except HTTPError as e:\n                # 431 is \"Request Header Fields Too Large\", defined in RFC\n                # 6585. However, many implementations just close the\n                # connection in this case, resulting in a missing response.\n                if e.response is not None:\n                    self.assertIn(e.response.code, (431, 599))\n\n\n@skipOnTravis\nclass IdleTimeoutTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler)])\n\n    def get_httpserver_options(self):\n        return dict(idle_connection_timeout=0.1)\n\n    def setUp(self):\n        super().setUp()\n        self.streams = []  # type: List[IOStream]\n\n    def tearDown(self):\n        super().tearDown()\n        for stream in self.streams:\n            stream.close()\n\n    @gen.coroutine\n    def connect(self):\n        stream = IOStream(socket.socket())\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        self.streams.append(stream)\n        raise gen.Return(stream)\n\n    @gen_test\n    def test_unused_connection(self):\n        stream = yield self.connect()\n        event = Event()\n        stream.set_close_callback(event.set)\n        yield event.wait()\n\n    @gen_test\n    def test_idle_after_use(self):\n        stream = yield self.connect()\n        event = Event()\n        stream.set_close_callback(event.set)\n\n        # Use the connection twice to make sure keep-alives are working\n        for i in range(2):\n            stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n            yield stream.read_until(b\"\\r\\n\\r\\n\")\n            data = yield stream.read_bytes(11)\n            self.assertEqual(data, b\"Hello world\")\n\n        # Now let the timeout trigger and close the connection.\n        yield event.wait()\n\n\nclass BodyLimitsTest(AsyncHTTPTestCase):\n    def get_app(self):\n        class BufferedHandler(RequestHandler):\n            def put(self):\n                self.write(str(len(self.request.body)))\n\n        @stream_request_body\n        class StreamingHandler(RequestHandler):\n            def initialize(self):\n                self.bytes_read = 0\n\n            def prepare(self):\n                conn = typing.cast(HTTP1Connection, self.request.connection)\n                if \"expected_size\" in self.request.arguments:\n                    conn.set_max_body_size(int(self.get_argument(\"expected_size\")))\n                if \"body_timeout\" in self.request.arguments:\n                    conn.set_body_timeout(float(self.get_argument(\"body_timeout\")))\n\n            def data_received(self, data):\n                self.bytes_read += len(data)\n\n            def put(self):\n                self.write(str(self.bytes_read))\n\n        return Application(\n            [(\"/buffered\", BufferedHandler), (\"/streaming\", StreamingHandler)]\n        )\n\n    def get_httpserver_options(self):\n        return dict(body_timeout=3600, max_body_size=4096)\n\n    def get_http_client(self):\n        # body_producer doesn't work on curl_httpclient, so override the\n        # configured AsyncHTTPClient implementation.\n        return SimpleAsyncHTTPClient()\n\n    def test_small_body(self):\n        response = self.fetch(\"/buffered\", method=\"PUT\", body=b\"a\" * 4096)\n        self.assertEqual(response.body, b\"4096\")\n        response = self.fetch(\"/streaming\", method=\"PUT\", body=b\"a\" * 4096)\n        self.assertEqual(response.body, b\"4096\")\n\n    def test_large_body_buffered(self):\n        with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n            response = self.fetch(\"/buffered\", method=\"PUT\", body=b\"a\" * 10240)\n        self.assertEqual(response.code, 400)\n\n    @unittest.skipIf(os.name == \"nt\", \"flaky on windows\")\n    def test_large_body_buffered_chunked(self):\n        # This test is flaky on windows for unknown reasons.\n        with ExpectLog(gen_log, \".*chunked body too large\", level=logging.INFO):\n            response = self.fetch(\n                \"/buffered\",\n                method=\"PUT\",\n                body_producer=lambda write: write(b\"a\" * 10240),\n            )\n        self.assertEqual(response.code, 400)\n\n    def test_large_body_streaming(self):\n        with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n            response = self.fetch(\"/streaming\", method=\"PUT\", body=b\"a\" * 10240)\n        self.assertEqual(response.code, 400)\n\n    @unittest.skipIf(os.name == \"nt\", \"flaky on windows\")\n    def test_large_body_streaming_chunked(self):\n        with ExpectLog(gen_log, \".*chunked body too large\", level=logging.INFO):\n            response = self.fetch(\n                \"/streaming\",\n                method=\"PUT\",\n                body_producer=lambda write: write(b\"a\" * 10240),\n            )\n        self.assertEqual(response.code, 400)\n\n    def test_large_body_streaming_override(self):\n        response = self.fetch(\n            \"/streaming?expected_size=10240\", method=\"PUT\", body=b\"a\" * 10240\n        )\n        self.assertEqual(response.body, b\"10240\")\n\n    def test_large_body_streaming_chunked_override(self):\n        response = self.fetch(\n            \"/streaming?expected_size=10240\",\n            method=\"PUT\",\n            body_producer=lambda write: write(b\"a\" * 10240),\n        )\n        self.assertEqual(response.body, b\"10240\")\n\n    @gen_test\n    def test_timeout(self):\n        stream = IOStream(socket.socket())\n        try:\n            yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n            # Use a raw stream because AsyncHTTPClient won't let us read a\n            # response without finishing a body.\n            stream.write(\n                b\"PUT /streaming?body_timeout=0.1 HTTP/1.0\\r\\n\"\n                b\"Content-Length: 42\\r\\n\\r\\n\"\n            )\n            with ExpectLog(gen_log, \"Timeout reading body\", level=logging.INFO):\n                response = yield stream.read_until_close()\n            self.assertEqual(response, b\"\")\n        finally:\n            stream.close()\n\n    @gen_test\n    def test_body_size_override_reset(self):\n        # The max_body_size override is reset between requests.\n        stream = IOStream(socket.socket())\n        try:\n            yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n            # Use a raw stream so we can make sure it's all on one connection.\n            stream.write(\n                b\"PUT /streaming?expected_size=10240 HTTP/1.1\\r\\n\"\n                b\"Content-Length: 10240\\r\\n\\r\\n\"\n            )\n            stream.write(b\"a\" * 10240)\n            start_line, headers, response = yield read_stream_body(stream)\n            self.assertEqual(response, b\"10240\")\n            # Without the ?expected_size parameter, we get the old default value\n            stream.write(\n                b\"PUT /streaming HTTP/1.1\\r\\n\" b\"Content-Length: 10240\\r\\n\\r\\n\"\n            )\n            with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n                data = yield stream.read_until_close()\n            self.assertEqual(data, b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n        finally:\n            stream.close()\n\n\nclass LegacyInterfaceTest(AsyncHTTPTestCase):\n    def get_app(self):\n        # The old request_callback interface does not implement the\n        # delegate interface, and writes its response via request.write\n        # instead of request.connection.write_headers.\n        def handle_request(request):\n            self.http1 = request.version.startswith(\"HTTP/1.\")\n            if not self.http1:\n                # This test will be skipped if we're using HTTP/2,\n                # so just close it out cleanly using the modern interface.\n                request.connection.write_headers(\n                    ResponseStartLine(\"\", 200, \"OK\"), HTTPHeaders()\n                )\n                request.connection.finish()\n                return\n            message = b\"Hello world\"\n            request.connection.write(\n                utf8(\"HTTP/1.1 200 OK\\r\\n\" \"Content-Length: %d\\r\\n\\r\\n\" % len(message))\n            )\n            request.connection.write(message)\n            request.connection.finish()\n\n        return handle_request\n\n    def test_legacy_interface(self):\n        response = self.fetch(\"/\")\n        if not self.http1:\n            self.skipTest(\"requires HTTP/1.x\")\n        self.assertEqual(response.body, b\"Hello world\")\n",
            "header": {
                "file": {
                    "path": "tornado/test/httpserver_test.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "614dec7b",
                "new_commit_hash": "58696e23",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/tornado/test/httpserver_test.py"
            },
            "plus_file": {
                "path": "b/tornado/test/httpserver_test.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1000,
                            "length": 6
                        },
                        "new_range": {
                            "start": 1000,
                            "length": 21
                        },
                        "section": "class GzipTest(GzipBaseTest, AsyncHTTPTestCase):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        response = self.post_gzip(\"foo=bar\")",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_gzip_case_insensitive(self):",
                                "type": "added"
                            },
                            {
                                "content": "        # https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.2.1",
                                "type": "added"
                            },
                            {
                                "content": "        bytesio = BytesIO()",
                                "type": "added"
                            },
                            {
                                "content": "        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)",
                                "type": "added"
                            },
                            {
                                "content": "        gzip_file.write(utf8(\"foo=bar\"))",
                                "type": "added"
                            },
                            {
                                "content": "        gzip_file.close()",
                                "type": "added"
                            },
                            {
                                "content": "        compressed_body = bytesio.getvalue()",
                                "type": "added"
                            },
                            {
                                "content": "        response = self.fetch(",
                                "type": "added"
                            },
                            {
                                "content": "            \"/\",",
                                "type": "added"
                            },
                            {
                                "content": "            method=\"POST\",",
                                "type": "added"
                            },
                            {
                                "content": "            body=compressed_body,",
                                "type": "added"
                            },
                            {
                                "content": "            headers={\"Content-Encoding\": \"GZIP\"},",
                                "type": "added"
                            },
                            {
                                "content": "        )",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "class GzipUnsupportedTest(GzipBaseTest, AsyncHTTPTestCase):",
                                "type": "context"
                            },
                            {
                                "content": "    def test_gzip_unsupported(self):",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/test/httpserver_test.py",
                            "type": "class",
                            "name": "GzipTest",
                            "content": "class GzipTest(GzipBaseTest, AsyncHTTPTestCase):\n    def get_httpserver_options(self):\n        return dict(decompress_request=True)\n\n    def test_gzip(self):\n        response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n    def test_gzip_case_insensitive(self):\n        # https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.2.1\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(utf8(\"foo=bar\"))\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=compressed_body,\n            headers={\"Content-Encoding\": \"GZIP\"},\n        )\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 995,
                            "end_lineno": 1016,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "tornado/test/httpserver_test.py",
                            "type": "method",
                            "name": "GzipTest.test_gzip_case_insensitive",
                            "content": "class GzipTest(GzipBaseTest, AsyncHTTPTestCase):\n    def get_httpserver_options(self):\n        return dict(decompress_request=True)\n\n    def test_gzip(self):\n        response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n    def test_gzip_case_insensitive(self):\n        # https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.2.1\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(utf8(\"foo=bar\"))\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=compressed_body,\n            headers={\"Content-Encoding\": \"GZIP\"},\n        )\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 1003,
                            "end_lineno": 1016,
                            "parent": {
                                "file_name": "tornado/test/httpserver_test.py",
                                "type": "class",
                                "name": "GzipTest",
                                "content": "class GzipTest(GzipBaseTest, AsyncHTTPTestCase):\n    def get_httpserver_options(self):\n        return dict(decompress_request=True)\n\n    def test_gzip(self):\n        response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n    def test_gzip_case_insensitive(self):\n        # https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.2.1\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(utf8(\"foo=bar\"))\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=compressed_body,\n            headers={\"Content-Encoding\": \"GZIP\"},\n        )\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 995,
                                "end_lineno": 1016,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "aa9d32d5e89b290dad69e0c6655b7497ea492e45^",
    "new_commit_hash": "aa9d32d5e89b290dad69e0c6655b7497ea492e45",
    "commit_message": "Merge pull request #3041 from samm0ss/sam/feat/encoding-gzip-case-insensitive\n\nFix case-sensitivity for Content-Encoding: gzip. as per rfc7231 the C…",
    "commit_date": "2021-07-09T14:05:14-04:00",
    "metadata": {}
}
{
    "repo_name": "tornado",
    "new_commit_hash": "aa9d32d5e89b290dad69e0c6655b7497ea492e45",
    "test_file_codes": [
        "from tornado import gen, netutil\nfrom tornado.escape import (\n    json_decode,\n    json_encode,\n    utf8,\n    _unicode,\n    recursive_unicode,\n    native_str,\n)\nfrom tornado.http1connection import HTTP1Connection\nfrom tornado.httpclient import HTTPError\nfrom tornado.httpserver import HTTPServer\nfrom tornado.httputil import (\n    HTTPHeaders,\n    HTTPMessageDelegate,\n    HTTPServerConnectionDelegate,\n    ResponseStartLine,\n)\nfrom tornado.iostream import IOStream\nfrom tornado.locks import Event\nfrom tornado.log import gen_log\nfrom tornado.netutil import ssl_options_to_context\nfrom tornado.simple_httpclient import SimpleAsyncHTTPClient\nfrom tornado.testing import (\n    AsyncHTTPTestCase,\n    AsyncHTTPSTestCase,\n    AsyncTestCase,\n    ExpectLog,\n    gen_test,\n)\nfrom tornado.test.util import skipOnTravis\nfrom tornado.web import Application, RequestHandler, stream_request_body\n\nfrom contextlib import closing\nimport datetime\nimport gzip\nimport logging\nimport os\nimport shutil\nimport socket\nimport ssl\nimport sys\nimport tempfile\nimport unittest\nimport urllib.parse\nfrom io import BytesIO\n\nimport typing\n\nif typing.TYPE_CHECKING:\n    from typing import Dict, List  # noqa: F401\n\n\nasync def read_stream_body(stream):\n    \"\"\"Reads an HTTP response from `stream` and returns a tuple of its\n    start_line, headers and body.\"\"\"\n    chunks = []\n\n    class Delegate(HTTPMessageDelegate):\n        def headers_received(self, start_line, headers):\n            self.headers = headers\n            self.start_line = start_line\n\n        def data_received(self, chunk):\n            chunks.append(chunk)\n\n        def finish(self):\n            conn.detach()  # type: ignore\n\n    conn = HTTP1Connection(stream, True)\n    delegate = Delegate()\n    await conn.read_response(delegate)\n    return delegate.start_line, delegate.headers, b\"\".join(chunks)\n\n\nclass HandlerBaseTestCase(AsyncHTTPTestCase):\n    Handler = None\n\n    def get_app(self):\n        return Application([(\"/\", self.__class__.Handler)])\n\n    def fetch_json(self, *args, **kwargs):\n        response = self.fetch(*args, **kwargs)\n        response.rethrow()\n        return json_decode(response.body)\n\n\nclass HelloWorldRequestHandler(RequestHandler):\n    def initialize(self, protocol=\"http\"):\n        self.expected_protocol = protocol\n\n    def get(self):\n        if self.request.protocol != self.expected_protocol:\n            raise Exception(\"unexpected protocol\")\n        self.finish(\"Hello world\")\n\n    def post(self):\n        self.finish(\"Got %d bytes in POST\" % len(self.request.body))\n\n\n# In pre-1.0 versions of openssl, SSLv23 clients always send SSLv2\n# ClientHello messages, which are rejected by SSLv3 and TLSv1\n# servers.  Note that while the OPENSSL_VERSION_INFO was formally\n# introduced in python3.2, it was present but undocumented in\n# python 2.7\nskipIfOldSSL = unittest.skipIf(\n    getattr(ssl, \"OPENSSL_VERSION_INFO\", (0, 0)) < (1, 0),\n    \"old version of ssl module and/or openssl\",\n)\n\n\nclass BaseSSLTest(AsyncHTTPSTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler, dict(protocol=\"https\"))])\n\n\nclass SSLTestMixin(object):\n    def get_ssl_options(self):\n        return dict(\n            ssl_version=self.get_ssl_version(),\n            **AsyncHTTPSTestCase.default_ssl_options()\n        )\n\n    def get_ssl_version(self):\n        raise NotImplementedError()\n\n    def test_ssl(self: typing.Any):\n        response = self.fetch(\"/\")\n        self.assertEqual(response.body, b\"Hello world\")\n\n    def test_large_post(self: typing.Any):\n        response = self.fetch(\"/\", method=\"POST\", body=\"A\" * 5000)\n        self.assertEqual(response.body, b\"Got 5000 bytes in POST\")\n\n    def test_non_ssl_request(self: typing.Any):\n        # Make sure the server closes the connection when it gets a non-ssl\n        # connection, rather than waiting for a timeout or otherwise\n        # misbehaving.\n        with ExpectLog(gen_log, \"(SSL Error|uncaught exception)\"):\n            with ExpectLog(gen_log, \"Uncaught exception\", required=False):\n                with self.assertRaises((IOError, HTTPError)):  # type: ignore\n                    self.fetch(\n                        self.get_url(\"/\").replace(\"https:\", \"http:\"),\n                        request_timeout=3600,\n                        connect_timeout=3600,\n                        raise_error=True,\n                    )\n\n    def test_error_logging(self: typing.Any):\n        # No stack traces are logged for SSL errors.\n        with ExpectLog(gen_log, \"SSL Error\") as expect_log:\n            with self.assertRaises((IOError, HTTPError)):  # type: ignore\n                self.fetch(\n                    self.get_url(\"/\").replace(\"https:\", \"http:\"), raise_error=True\n                )\n        self.assertFalse(expect_log.logged_stack)\n\n\n# Python's SSL implementation differs significantly between versions.\n# For example, SSLv3 and TLSv1 throw an exception if you try to read\n# from the socket before the handshake is complete, but the default\n# of SSLv23 allows it.\n\n\nclass SSLv23Test(BaseSSLTest, SSLTestMixin):\n    def get_ssl_version(self):\n        return ssl.PROTOCOL_SSLv23\n\n\n@skipIfOldSSL\nclass SSLv3Test(BaseSSLTest, SSLTestMixin):\n    def get_ssl_version(self):\n        return ssl.PROTOCOL_SSLv3\n\n\n@skipIfOldSSL\nclass TLSv1Test(BaseSSLTest, SSLTestMixin):\n    def get_ssl_version(self):\n        return ssl.PROTOCOL_TLSv1\n\n\nclass SSLContextTest(BaseSSLTest, SSLTestMixin):\n    def get_ssl_options(self):\n        context = ssl_options_to_context(AsyncHTTPSTestCase.get_ssl_options(self))\n        assert isinstance(context, ssl.SSLContext)\n        return context\n\n\nclass BadSSLOptionsTest(unittest.TestCase):\n    def test_missing_arguments(self):\n        application = Application()\n        self.assertRaises(\n            KeyError,\n            HTTPServer,\n            application,\n            ssl_options={\"keyfile\": \"/__missing__.crt\"},\n        )\n\n    def test_missing_key(self):\n        \"\"\"A missing SSL key should cause an immediate exception.\"\"\"\n\n        application = Application()\n        module_dir = os.path.dirname(__file__)\n        existing_certificate = os.path.join(module_dir, \"test.crt\")\n        existing_key = os.path.join(module_dir, \"test.key\")\n\n        self.assertRaises(\n            (ValueError, IOError),\n            HTTPServer,\n            application,\n            ssl_options={\"certfile\": \"/__mising__.crt\"},\n        )\n        self.assertRaises(\n            (ValueError, IOError),\n            HTTPServer,\n            application,\n            ssl_options={\n                \"certfile\": existing_certificate,\n                \"keyfile\": \"/__missing__.key\",\n            },\n        )\n\n        # This actually works because both files exist\n        HTTPServer(\n            application,\n            ssl_options={\"certfile\": existing_certificate, \"keyfile\": existing_key},\n        )\n\n\nclass MultipartTestHandler(RequestHandler):\n    def post(self):\n        self.finish(\n            {\n                \"header\": self.request.headers[\"X-Header-Encoding-Test\"],\n                \"argument\": self.get_argument(\"argument\"),\n                \"filename\": self.request.files[\"files\"][0].filename,\n                \"filebody\": _unicode(self.request.files[\"files\"][0][\"body\"]),\n            }\n        )\n\n\n# This test is also called from wsgi_test\nclass HTTPConnectionTest(AsyncHTTPTestCase):\n    def get_handlers(self):\n        return [\n            (\"/multipart\", MultipartTestHandler),\n            (\"/hello\", HelloWorldRequestHandler),\n        ]\n\n    def get_app(self):\n        return Application(self.get_handlers())\n\n    def raw_fetch(self, headers, body, newline=b\"\\r\\n\"):\n        with closing(IOStream(socket.socket())) as stream:\n            self.io_loop.run_sync(\n                lambda: stream.connect((\"127.0.0.1\", self.get_http_port()))\n            )\n            stream.write(\n                newline.join(headers + [utf8(\"Content-Length: %d\" % len(body))])\n                + newline\n                + newline\n                + body\n            )\n            start_line, headers, body = self.io_loop.run_sync(\n                lambda: read_stream_body(stream)\n            )\n            return body\n\n    def test_multipart_form(self):\n        # Encodings here are tricky:  Headers are latin1, bodies can be\n        # anything (we use utf8 by default).\n        response = self.raw_fetch(\n            [\n                b\"POST /multipart HTTP/1.0\",\n                b\"Content-Type: multipart/form-data; boundary=1234567890\",\n                b\"X-Header-encoding-test: \\xe9\",\n            ],\n            b\"\\r\\n\".join(\n                [\n                    b\"Content-Disposition: form-data; name=argument\",\n                    b\"\",\n                    u\"\\u00e1\".encode(\"utf-8\"),\n                    b\"--1234567890\",\n                    u'Content-Disposition: form-data; name=\"files\"; filename=\"\\u00f3\"'.encode(\n                        \"utf8\"\n                    ),\n                    b\"\",\n                    u\"\\u00fa\".encode(\"utf-8\"),\n                    b\"--1234567890--\",\n                    b\"\",\n                ]\n            ),\n        )\n        data = json_decode(response)\n        self.assertEqual(u\"\\u00e9\", data[\"header\"])\n        self.assertEqual(u\"\\u00e1\", data[\"argument\"])\n        self.assertEqual(u\"\\u00f3\", data[\"filename\"])\n        self.assertEqual(u\"\\u00fa\", data[\"filebody\"])\n\n    def test_newlines(self):\n        # We support both CRLF and bare LF as line separators.\n        for newline in (b\"\\r\\n\", b\"\\n\"):\n            response = self.raw_fetch([b\"GET /hello HTTP/1.0\"], b\"\", newline=newline)\n            self.assertEqual(response, b\"Hello world\")\n\n    @gen_test\n    def test_100_continue(self):\n        # Run through a 100-continue interaction by hand:\n        # When given Expect: 100-continue, we get a 100 response after the\n        # headers, and then the real response after the body.\n        stream = IOStream(socket.socket())\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        yield stream.write(\n            b\"\\r\\n\".join(\n                [\n                    b\"POST /hello HTTP/1.1\",\n                    b\"Content-Length: 1024\",\n                    b\"Expect: 100-continue\",\n                    b\"Connection: close\",\n                    b\"\\r\\n\",\n                ]\n            )\n        )\n        data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n        self.assertTrue(data.startswith(b\"HTTP/1.1 100 \"), data)\n        stream.write(b\"a\" * 1024)\n        first_line = yield stream.read_until(b\"\\r\\n\")\n        self.assertTrue(first_line.startswith(b\"HTTP/1.1 200\"), first_line)\n        header_data = yield stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(native_str(header_data.decode(\"latin1\")))\n        body = yield stream.read_bytes(int(headers[\"Content-Length\"]))\n        self.assertEqual(body, b\"Got 1024 bytes in POST\")\n        stream.close()\n\n\nclass EchoHandler(RequestHandler):\n    def get(self):\n        self.write(recursive_unicode(self.request.arguments))\n\n    def post(self):\n        self.write(recursive_unicode(self.request.arguments))\n\n\nclass TypeCheckHandler(RequestHandler):\n    def prepare(self):\n        self.errors = {}  # type: Dict[str, str]\n        fields = [\n            (\"method\", str),\n            (\"uri\", str),\n            (\"version\", str),\n            (\"remote_ip\", str),\n            (\"protocol\", str),\n            (\"host\", str),\n            (\"path\", str),\n            (\"query\", str),\n        ]\n        for field, expected_type in fields:\n            self.check_type(field, getattr(self.request, field), expected_type)\n\n        self.check_type(\"header_key\", list(self.request.headers.keys())[0], str)\n        self.check_type(\"header_value\", list(self.request.headers.values())[0], str)\n\n        self.check_type(\"cookie_key\", list(self.request.cookies.keys())[0], str)\n        self.check_type(\n            \"cookie_value\", list(self.request.cookies.values())[0].value, str\n        )\n        # secure cookies\n\n        self.check_type(\"arg_key\", list(self.request.arguments.keys())[0], str)\n        self.check_type(\"arg_value\", list(self.request.arguments.values())[0][0], bytes)\n\n    def post(self):\n        self.check_type(\"body\", self.request.body, bytes)\n        self.write(self.errors)\n\n    def get(self):\n        self.write(self.errors)\n\n    def check_type(self, name, obj, expected_type):\n        actual_type = type(obj)\n        if expected_type != actual_type:\n            self.errors[name] = \"expected %s, got %s\" % (expected_type, actual_type)\n\n\nclass PostEchoHandler(RequestHandler):\n    def post(self, *path_args):\n        self.write(dict(echo=self.get_argument(\"data\")))\n\n\nclass PostEchoGBKHandler(PostEchoHandler):\n    def decode_argument(self, value, name=None):\n        try:\n            return value.decode(\"gbk\")\n        except Exception:\n            raise HTTPError(400, \"invalid gbk bytes: %r\" % value)\n\n\nclass HTTPServerTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application(\n            [\n                (\"/echo\", EchoHandler),\n                (\"/typecheck\", TypeCheckHandler),\n                (\"//doubleslash\", EchoHandler),\n                (\"/post_utf8\", PostEchoHandler),\n                (\"/post_gbk\", PostEchoGBKHandler),\n            ]\n        )\n\n    def test_query_string_encoding(self):\n        response = self.fetch(\"/echo?foo=%C3%A9\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {u\"foo\": [u\"\\u00e9\"]})\n\n    def test_empty_query_string(self):\n        response = self.fetch(\"/echo?foo=&foo=\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {u\"foo\": [u\"\", u\"\"]})\n\n    def test_empty_post_parameters(self):\n        response = self.fetch(\"/echo\", method=\"POST\", body=\"foo=&bar=\")\n        data = json_decode(response.body)\n        self.assertEqual(data, {u\"foo\": [u\"\"], u\"bar\": [u\"\"]})\n\n    def test_types(self):\n        headers = {\"Cookie\": \"foo=bar\"}\n        response = self.fetch(\"/typecheck?foo=bar\", headers=headers)\n        data = json_decode(response.body)\n        self.assertEqual(data, {})\n\n        response = self.fetch(\n            \"/typecheck\", method=\"POST\", body=\"foo=bar\", headers=headers\n        )\n        data = json_decode(response.body)\n        self.assertEqual(data, {})\n\n    def test_double_slash(self):\n        # urlparse.urlsplit (which tornado.httpserver used to use\n        # incorrectly) would parse paths beginning with \"//\" as\n        # protocol-relative urls.\n        response = self.fetch(\"//doubleslash\")\n        self.assertEqual(200, response.code)\n        self.assertEqual(json_decode(response.body), {})\n\n    def test_post_encodings(self):\n        headers = {\"Content-Type\": \"application/x-www-form-urlencoded\"}\n        uni_text = \"chinese: \\u5f20\\u4e09\"\n        for enc in (\"utf8\", \"gbk\"):\n            for quote in (True, False):\n                with self.subTest(enc=enc, quote=quote):\n                    bin_text = uni_text.encode(enc)\n                    if quote:\n                        bin_text = urllib.parse.quote(bin_text).encode(\"ascii\")\n                    response = self.fetch(\n                        \"/post_\" + enc,\n                        method=\"POST\",\n                        headers=headers,\n                        body=(b\"data=\" + bin_text),\n                    )\n                    self.assertEqual(json_decode(response.body), {\"echo\": uni_text})\n\n\nclass HTTPServerRawTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/echo\", EchoHandler)])\n\n    def setUp(self):\n        super().setUp()\n        self.stream = IOStream(socket.socket())\n        self.io_loop.run_sync(\n            lambda: self.stream.connect((\"127.0.0.1\", self.get_http_port()))\n        )\n\n    def tearDown(self):\n        self.stream.close()\n        super().tearDown()\n\n    def test_empty_request(self):\n        self.stream.close()\n        self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)\n        self.wait()\n\n    def test_malformed_first_line_response(self):\n        with ExpectLog(gen_log, \".*Malformed HTTP request line\", level=logging.INFO):\n            self.stream.write(b\"asdf\\r\\n\\r\\n\")\n            start_line, headers, response = self.io_loop.run_sync(\n                lambda: read_stream_body(self.stream)\n            )\n            self.assertEqual(\"HTTP/1.1\", start_line.version)\n            self.assertEqual(400, start_line.code)\n            self.assertEqual(\"Bad Request\", start_line.reason)\n\n    def test_malformed_first_line_log(self):\n        with ExpectLog(gen_log, \".*Malformed HTTP request line\", level=logging.INFO):\n            self.stream.write(b\"asdf\\r\\n\\r\\n\")\n            # TODO: need an async version of ExpectLog so we don't need\n            # hard-coded timeouts here.\n            self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop)\n            self.wait()\n\n    def test_malformed_headers(self):\n        with ExpectLog(\n            gen_log,\n            \".*Malformed HTTP message.*no colon in header line\",\n            level=logging.INFO,\n        ):\n            self.stream.write(b\"GET / HTTP/1.0\\r\\nasdf\\r\\n\\r\\n\")\n            self.io_loop.add_timeout(datetime.timedelta(seconds=0.05), self.stop)\n            self.wait()\n\n    def test_chunked_request_body(self):\n        # Chunked requests are not widely supported and we don't have a way\n        # to generate them in AsyncHTTPClient, but HTTPServer will read them.\n        self.stream.write(\n            b\"\"\"\\\nPOST /echo HTTP/1.1\nTransfer-Encoding: chunked\nContent-Type: application/x-www-form-urlencoded\n\n4\nfoo=\n3\nbar\n0\n\n\"\"\".replace(\n                b\"\\n\", b\"\\r\\n\"\n            )\n        )\n        start_line, headers, response = self.io_loop.run_sync(\n            lambda: read_stream_body(self.stream)\n        )\n        self.assertEqual(json_decode(response), {u\"foo\": [u\"bar\"]})\n\n    def test_chunked_request_uppercase(self):\n        # As per RFC 2616 section 3.6, \"Transfer-Encoding\" header's value is\n        # case-insensitive.\n        self.stream.write(\n            b\"\"\"\\\nPOST /echo HTTP/1.1\nTransfer-Encoding: Chunked\nContent-Type: application/x-www-form-urlencoded\n\n4\nfoo=\n3\nbar\n0\n\n\"\"\".replace(\n                b\"\\n\", b\"\\r\\n\"\n            )\n        )\n        start_line, headers, response = self.io_loop.run_sync(\n            lambda: read_stream_body(self.stream)\n        )\n        self.assertEqual(json_decode(response), {u\"foo\": [u\"bar\"]})\n\n    @gen_test\n    def test_invalid_content_length(self):\n        with ExpectLog(\n            gen_log, \".*Only integer Content-Length is allowed\", level=logging.INFO\n        ):\n            self.stream.write(\n                b\"\"\"\\\nPOST /echo HTTP/1.1\nContent-Length: foo\n\nbar\n\n\"\"\".replace(\n                    b\"\\n\", b\"\\r\\n\"\n                )\n            )\n            yield self.stream.read_until_close()\n\n\nclass XHeaderTest(HandlerBaseTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.set_header(\"request-version\", self.request.version)\n            self.write(\n                dict(\n                    remote_ip=self.request.remote_ip,\n                    remote_protocol=self.request.protocol,\n                )\n            )\n\n    def get_httpserver_options(self):\n        return dict(xheaders=True, trusted_downstream=[\"5.5.5.5\"])\n\n    def test_ip_headers(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_ip\"], \"127.0.0.1\")\n\n        valid_ipv4 = {\"X-Real-IP\": \"4.4.4.4\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv4)[\"remote_ip\"], \"4.4.4.4\"\n        )\n\n        valid_ipv4_list = {\"X-Forwarded-For\": \"127.0.0.1, 4.4.4.4\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv4_list)[\"remote_ip\"], \"4.4.4.4\"\n        )\n\n        valid_ipv6 = {\"X-Real-IP\": \"2620:0:1cfe:face:b00c::3\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv6)[\"remote_ip\"],\n            \"2620:0:1cfe:face:b00c::3\",\n        )\n\n        valid_ipv6_list = {\"X-Forwarded-For\": \"::1, 2620:0:1cfe:face:b00c::3\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=valid_ipv6_list)[\"remote_ip\"],\n            \"2620:0:1cfe:face:b00c::3\",\n        )\n\n        invalid_chars = {\"X-Real-IP\": \"4.4.4.4<script>\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_chars)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n        invalid_chars_list = {\"X-Forwarded-For\": \"4.4.4.4, 5.5.5.5<script>\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_chars_list)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n        invalid_host = {\"X-Real-IP\": \"www.google.com\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=invalid_host)[\"remote_ip\"], \"127.0.0.1\"\n        )\n\n    def test_trusted_downstream(self):\n        valid_ipv4_list = {\"X-Forwarded-For\": \"127.0.0.1, 4.4.4.4, 5.5.5.5\"}\n        resp = self.fetch(\"/\", headers=valid_ipv4_list)\n        if resp.headers[\"request-version\"].startswith(\"HTTP/2\"):\n            # This is a hack - there's nothing that fundamentally requires http/1\n            # here but tornado_http2 doesn't support it yet.\n            self.skipTest(\"requires HTTP/1.x\")\n        result = json_decode(resp.body)\n        self.assertEqual(result[\"remote_ip\"], \"4.4.4.4\")\n\n    def test_scheme_headers(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_protocol\"], \"http\")\n\n        https_scheme = {\"X-Scheme\": \"https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_scheme)[\"remote_protocol\"], \"https\"\n        )\n\n        https_forwarded = {\"X-Forwarded-Proto\": \"https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_forwarded)[\"remote_protocol\"], \"https\"\n        )\n\n        https_multi_forwarded = {\"X-Forwarded-Proto\": \"https , http\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=https_multi_forwarded)[\"remote_protocol\"],\n            \"http\",\n        )\n\n        http_multi_forwarded = {\"X-Forwarded-Proto\": \"http,https\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=http_multi_forwarded)[\"remote_protocol\"],\n            \"https\",\n        )\n\n        bad_forwarded = {\"X-Forwarded-Proto\": \"unknown\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=bad_forwarded)[\"remote_protocol\"], \"http\"\n        )\n\n\nclass SSLXHeaderTest(AsyncHTTPSTestCase, HandlerBaseTestCase):\n    def get_app(self):\n        return Application([(\"/\", XHeaderTest.Handler)])\n\n    def get_httpserver_options(self):\n        output = super().get_httpserver_options()\n        output[\"xheaders\"] = True\n        return output\n\n    def test_request_without_xprotocol(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"remote_protocol\"], \"https\")\n\n        http_scheme = {\"X-Scheme\": \"http\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=http_scheme)[\"remote_protocol\"], \"http\"\n        )\n\n        bad_scheme = {\"X-Scheme\": \"unknown\"}\n        self.assertEqual(\n            self.fetch_json(\"/\", headers=bad_scheme)[\"remote_protocol\"], \"https\"\n        )\n\n\nclass ManualProtocolTest(HandlerBaseTestCase):\n    class Handler(RequestHandler):\n        def get(self):\n            self.write(dict(protocol=self.request.protocol))\n\n    def get_httpserver_options(self):\n        return dict(protocol=\"https\")\n\n    def test_manual_protocol(self):\n        self.assertEqual(self.fetch_json(\"/\")[\"protocol\"], \"https\")\n\n\n@unittest.skipIf(\n    not hasattr(socket, \"AF_UNIX\") or sys.platform == \"cygwin\",\n    \"unix sockets not supported on this platform\",\n)\nclass UnixSocketTest(AsyncTestCase):\n    \"\"\"HTTPServers can listen on Unix sockets too.\n\n    Why would you want to do this?  Nginx can proxy to backends listening\n    on unix sockets, for one thing (and managing a namespace for unix\n    sockets can be easier than managing a bunch of TCP port numbers).\n\n    Unfortunately, there's no way to specify a unix socket in a url for\n    an HTTP client, so we have to test this by hand.\n    \"\"\"\n\n    def setUp(self):\n        super().setUp()\n        self.tmpdir = tempfile.mkdtemp()\n        self.sockfile = os.path.join(self.tmpdir, \"test.sock\")\n        sock = netutil.bind_unix_socket(self.sockfile)\n        app = Application([(\"/hello\", HelloWorldRequestHandler)])\n        self.server = HTTPServer(app)\n        self.server.add_socket(sock)\n        self.stream = IOStream(socket.socket(socket.AF_UNIX))\n        self.io_loop.run_sync(lambda: self.stream.connect(self.sockfile))\n\n    def tearDown(self):\n        self.stream.close()\n        self.io_loop.run_sync(self.server.close_all_connections)\n        self.server.stop()\n        shutil.rmtree(self.tmpdir)\n        super().tearDown()\n\n    @gen_test\n    def test_unix_socket(self):\n        self.stream.write(b\"GET /hello HTTP/1.0\\r\\n\\r\\n\")\n        response = yield self.stream.read_until(b\"\\r\\n\")\n        self.assertEqual(response, b\"HTTP/1.1 200 OK\\r\\n\")\n        header_data = yield self.stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(header_data.decode(\"latin1\"))\n        body = yield self.stream.read_bytes(int(headers[\"Content-Length\"]))\n        self.assertEqual(body, b\"Hello world\")\n\n    @gen_test\n    def test_unix_socket_bad_request(self):\n        # Unix sockets don't have remote addresses so they just return an\n        # empty string.\n        with ExpectLog(gen_log, \"Malformed HTTP message from\"):\n            self.stream.write(b\"garbage\\r\\n\\r\\n\")\n            response = yield self.stream.read_until_close()\n        self.assertEqual(response, b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n\n\nclass KeepAliveTest(AsyncHTTPTestCase):\n    \"\"\"Tests various scenarios for HTTP 1.1 keep-alive support.\n\n    These tests don't use AsyncHTTPClient because we want to control\n    connection reuse and closing.\n    \"\"\"\n\n    def get_app(self):\n        class HelloHandler(RequestHandler):\n            def get(self):\n                self.finish(\"Hello world\")\n\n            def post(self):\n                self.finish(\"Hello world\")\n\n        class LargeHandler(RequestHandler):\n            def get(self):\n                # 512KB should be bigger than the socket buffers so it will\n                # be written out in chunks.\n                self.write(\"\".join(chr(i % 256) * 1024 for i in range(512)))\n\n        class TransferEncodingChunkedHandler(RequestHandler):\n            @gen.coroutine\n            def head(self):\n                self.write(\"Hello world\")\n                yield self.flush()\n\n        class FinishOnCloseHandler(RequestHandler):\n            def initialize(self, cleanup_event):\n                self.cleanup_event = cleanup_event\n\n            @gen.coroutine\n            def get(self):\n                self.flush()\n                yield self.cleanup_event.wait()\n\n            def on_connection_close(self):\n                # This is not very realistic, but finishing the request\n                # from the close callback has the right timing to mimic\n                # some errors seen in the wild.\n                self.finish(\"closed\")\n\n        self.cleanup_event = Event()\n        return Application(\n            [\n                (\"/\", HelloHandler),\n                (\"/large\", LargeHandler),\n                (\"/chunked\", TransferEncodingChunkedHandler),\n                (\n                    \"/finish_on_close\",\n                    FinishOnCloseHandler,\n                    dict(cleanup_event=self.cleanup_event),\n                ),\n            ]\n        )\n\n    def setUp(self):\n        super().setUp()\n        self.http_version = b\"HTTP/1.1\"\n\n    def tearDown(self):\n        # We just closed the client side of the socket; let the IOLoop run\n        # once to make sure the server side got the message.\n        self.io_loop.add_timeout(datetime.timedelta(seconds=0.001), self.stop)\n        self.wait()\n\n        if hasattr(self, \"stream\"):\n            self.stream.close()\n        super().tearDown()\n\n    # The next few methods are a crude manual http client\n    @gen.coroutine\n    def connect(self):\n        self.stream = IOStream(socket.socket())\n        yield self.stream.connect((\"127.0.0.1\", self.get_http_port()))\n\n    @gen.coroutine\n    def read_headers(self):\n        first_line = yield self.stream.read_until(b\"\\r\\n\")\n        self.assertTrue(first_line.startswith(b\"HTTP/1.1 200\"), first_line)\n        header_bytes = yield self.stream.read_until(b\"\\r\\n\\r\\n\")\n        headers = HTTPHeaders.parse(header_bytes.decode(\"latin1\"))\n        raise gen.Return(headers)\n\n    @gen.coroutine\n    def read_response(self):\n        self.headers = yield self.read_headers()\n        body = yield self.stream.read_bytes(int(self.headers[\"Content-Length\"]))\n        self.assertEqual(b\"Hello world\", body)\n\n    def close(self):\n        self.stream.close()\n        del self.stream\n\n    @gen_test\n    def test_two_requests(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_request_close(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\nConnection: close\\r\\n\\r\\n\")\n        yield self.read_response()\n        data = yield self.stream.read_until_close()\n        self.assertTrue(not data)\n        self.assertEqual(self.headers[\"Connection\"], \"close\")\n        self.close()\n\n    # keepalive is supported for http 1.0 too, but it's opt-in\n    @gen_test\n    def test_http10(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\n\\r\\n\")\n        yield self.read_response()\n        data = yield self.stream.read_until_close()\n        self.assertTrue(not data)\n        self.assertTrue(\"Connection\" not in self.headers)\n        self.close()\n\n    @gen_test\n    def test_http10_keepalive(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_http10_keepalive_extra_crlf(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_pipelined_requests(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\nGET / HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_response()\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_pipelined_cancel(self):\n        yield self.connect()\n        self.stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\nGET / HTTP/1.1\\r\\n\\r\\n\")\n        # only read once\n        yield self.read_response()\n        self.close()\n\n    @gen_test\n    def test_cancel_during_download(self):\n        yield self.connect()\n        self.stream.write(b\"GET /large HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        yield self.stream.read_bytes(1024)\n        self.close()\n\n    @gen_test\n    def test_finish_while_closed(self):\n        yield self.connect()\n        self.stream.write(b\"GET /finish_on_close HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        self.close()\n        # Let the hanging coroutine clean up after itself\n        self.cleanup_event.set()\n\n    @gen_test\n    def test_keepalive_chunked(self):\n        self.http_version = b\"HTTP/1.0\"\n        yield self.connect()\n        self.stream.write(\n            b\"POST / HTTP/1.0\\r\\n\"\n            b\"Connection: keep-alive\\r\\n\"\n            b\"Transfer-Encoding: chunked\\r\\n\"\n            b\"\\r\\n\"\n            b\"0\\r\\n\"\n            b\"\\r\\n\"\n        )\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.stream.write(b\"GET / HTTP/1.0\\r\\nConnection: keep-alive\\r\\n\\r\\n\")\n        yield self.read_response()\n        self.assertEqual(self.headers[\"Connection\"], \"Keep-Alive\")\n        self.close()\n\n    @gen_test\n    def test_keepalive_chunked_head_no_body(self):\n        yield self.connect()\n        self.stream.write(b\"HEAD /chunked HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n\n        self.stream.write(b\"HEAD /chunked HTTP/1.1\\r\\n\\r\\n\")\n        yield self.read_headers()\n        self.close()\n\n\nclass GzipBaseTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", EchoHandler)])\n\n    def post_gzip(self, body):\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(utf8(body))\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        return self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=compressed_body,\n            headers={\"Content-Encoding\": \"gzip\"},\n        )\n\n    def test_uncompressed(self):\n        response = self.fetch(\"/\", method=\"POST\", body=\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n\nclass GzipTest(GzipBaseTest, AsyncHTTPTestCase):\n    def get_httpserver_options(self):\n        return dict(decompress_request=True)\n\n    def test_gzip(self):\n        response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n    def test_gzip_case_insensitive(self):\n        # https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.2.1\n        bytesio = BytesIO()\n        gzip_file = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzip_file.write(utf8(\"foo=bar\"))\n        gzip_file.close()\n        compressed_body = bytesio.getvalue()\n        response = self.fetch(\n            \"/\",\n            method=\"POST\",\n            body=compressed_body,\n            headers={\"Content-Encoding\": \"GZIP\"},\n        )\n        self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\n\n\nclass GzipUnsupportedTest(GzipBaseTest, AsyncHTTPTestCase):\n    def test_gzip_unsupported(self):\n        # Gzip support is opt-in; without it the server fails to parse\n        # the body (but parsing form bodies is currently just a log message,\n        # not a fatal error).\n        with ExpectLog(gen_log, \"Unsupported Content-Encoding\"):\n            response = self.post_gzip(\"foo=bar\")\n        self.assertEqual(json_decode(response.body), {})\n\n\nclass StreamingChunkSizeTest(AsyncHTTPTestCase):\n    # 50 characters long, and repetitive so it can be compressed.\n    BODY = b\"01234567890123456789012345678901234567890123456789\"\n    CHUNK_SIZE = 16\n\n    def get_http_client(self):\n        # body_producer doesn't work on curl_httpclient, so override the\n        # configured AsyncHTTPClient implementation.\n        return SimpleAsyncHTTPClient()\n\n    def get_httpserver_options(self):\n        return dict(chunk_size=self.CHUNK_SIZE, decompress_request=True)\n\n    class MessageDelegate(HTTPMessageDelegate):\n        def __init__(self, connection):\n            self.connection = connection\n\n        def headers_received(self, start_line, headers):\n            self.chunk_lengths = []  # type: List[int]\n\n        def data_received(self, chunk):\n            self.chunk_lengths.append(len(chunk))\n\n        def finish(self):\n            response_body = utf8(json_encode(self.chunk_lengths))\n            self.connection.write_headers(\n                ResponseStartLine(\"HTTP/1.1\", 200, \"OK\"),\n                HTTPHeaders({\"Content-Length\": str(len(response_body))}),\n            )\n            self.connection.write(response_body)\n            self.connection.finish()\n\n    def get_app(self):\n        class App(HTTPServerConnectionDelegate):\n            def start_request(self, server_conn, request_conn):\n                return StreamingChunkSizeTest.MessageDelegate(request_conn)\n\n        return App()\n\n    def fetch_chunk_sizes(self, **kwargs):\n        response = self.fetch(\"/\", method=\"POST\", **kwargs)\n        response.rethrow()\n        chunks = json_decode(response.body)\n        self.assertEqual(len(self.BODY), sum(chunks))\n        for chunk_size in chunks:\n            self.assertLessEqual(\n                chunk_size, self.CHUNK_SIZE, \"oversized chunk: \" + str(chunks)\n            )\n            self.assertGreater(chunk_size, 0, \"empty chunk: \" + str(chunks))\n        return chunks\n\n    def compress(self, body):\n        bytesio = BytesIO()\n        gzfile = gzip.GzipFile(mode=\"w\", fileobj=bytesio)\n        gzfile.write(body)\n        gzfile.close()\n        compressed = bytesio.getvalue()\n        if len(compressed) >= len(body):\n            raise Exception(\"body did not shrink when compressed\")\n        return compressed\n\n    def test_regular_body(self):\n        chunks = self.fetch_chunk_sizes(body=self.BODY)\n        # Without compression we know exactly what to expect.\n        self.assertEqual([16, 16, 16, 2], chunks)\n\n    def test_compressed_body(self):\n        self.fetch_chunk_sizes(\n            body=self.compress(self.BODY), headers={\"Content-Encoding\": \"gzip\"}\n        )\n        # Compression creates irregular boundaries so the assertions\n        # in fetch_chunk_sizes are as specific as we can get.\n\n    def test_chunked_body(self):\n        def body_producer(write):\n            write(self.BODY[:20])\n            write(self.BODY[20:])\n\n        chunks = self.fetch_chunk_sizes(body_producer=body_producer)\n        # HTTP chunk boundaries translate to application-visible breaks\n        self.assertEqual([16, 4, 16, 14], chunks)\n\n    def test_chunked_compressed(self):\n        compressed = self.compress(self.BODY)\n        self.assertGreater(len(compressed), 20)\n\n        def body_producer(write):\n            write(compressed[:20])\n            write(compressed[20:])\n\n        self.fetch_chunk_sizes(\n            body_producer=body_producer, headers={\"Content-Encoding\": \"gzip\"}\n        )\n\n\nclass MaxHeaderSizeTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler)])\n\n    def get_httpserver_options(self):\n        return dict(max_header_size=1024)\n\n    def test_small_headers(self):\n        response = self.fetch(\"/\", headers={\"X-Filler\": \"a\" * 100})\n        response.rethrow()\n        self.assertEqual(response.body, b\"Hello world\")\n\n    def test_large_headers(self):\n        with ExpectLog(gen_log, \"Unsatisfiable read\", required=False):\n            try:\n                self.fetch(\"/\", headers={\"X-Filler\": \"a\" * 1000}, raise_error=True)\n                self.fail(\"did not raise expected exception\")\n            except HTTPError as e:\n                # 431 is \"Request Header Fields Too Large\", defined in RFC\n                # 6585. However, many implementations just close the\n                # connection in this case, resulting in a missing response.\n                if e.response is not None:\n                    self.assertIn(e.response.code, (431, 599))\n\n\n@skipOnTravis\nclass IdleTimeoutTest(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([(\"/\", HelloWorldRequestHandler)])\n\n    def get_httpserver_options(self):\n        return dict(idle_connection_timeout=0.1)\n\n    def setUp(self):\n        super().setUp()\n        self.streams = []  # type: List[IOStream]\n\n    def tearDown(self):\n        super().tearDown()\n        for stream in self.streams:\n            stream.close()\n\n    @gen.coroutine\n    def connect(self):\n        stream = IOStream(socket.socket())\n        yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n        self.streams.append(stream)\n        raise gen.Return(stream)\n\n    @gen_test\n    def test_unused_connection(self):\n        stream = yield self.connect()\n        event = Event()\n        stream.set_close_callback(event.set)\n        yield event.wait()\n\n    @gen_test\n    def test_idle_after_use(self):\n        stream = yield self.connect()\n        event = Event()\n        stream.set_close_callback(event.set)\n\n        # Use the connection twice to make sure keep-alives are working\n        for i in range(2):\n            stream.write(b\"GET / HTTP/1.1\\r\\n\\r\\n\")\n            yield stream.read_until(b\"\\r\\n\\r\\n\")\n            data = yield stream.read_bytes(11)\n            self.assertEqual(data, b\"Hello world\")\n\n        # Now let the timeout trigger and close the connection.\n        yield event.wait()\n\n\nclass BodyLimitsTest(AsyncHTTPTestCase):\n    def get_app(self):\n        class BufferedHandler(RequestHandler):\n            def put(self):\n                self.write(str(len(self.request.body)))\n\n        @stream_request_body\n        class StreamingHandler(RequestHandler):\n            def initialize(self):\n                self.bytes_read = 0\n\n            def prepare(self):\n                conn = typing.cast(HTTP1Connection, self.request.connection)\n                if \"expected_size\" in self.request.arguments:\n                    conn.set_max_body_size(int(self.get_argument(\"expected_size\")))\n                if \"body_timeout\" in self.request.arguments:\n                    conn.set_body_timeout(float(self.get_argument(\"body_timeout\")))\n\n            def data_received(self, data):\n                self.bytes_read += len(data)\n\n            def put(self):\n                self.write(str(self.bytes_read))\n\n        return Application(\n            [(\"/buffered\", BufferedHandler), (\"/streaming\", StreamingHandler)]\n        )\n\n    def get_httpserver_options(self):\n        return dict(body_timeout=3600, max_body_size=4096)\n\n    def get_http_client(self):\n        # body_producer doesn't work on curl_httpclient, so override the\n        # configured AsyncHTTPClient implementation.\n        return SimpleAsyncHTTPClient()\n\n    def test_small_body(self):\n        response = self.fetch(\"/buffered\", method=\"PUT\", body=b\"a\" * 4096)\n        self.assertEqual(response.body, b\"4096\")\n        response = self.fetch(\"/streaming\", method=\"PUT\", body=b\"a\" * 4096)\n        self.assertEqual(response.body, b\"4096\")\n\n    def test_large_body_buffered(self):\n        with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n            response = self.fetch(\"/buffered\", method=\"PUT\", body=b\"a\" * 10240)\n        self.assertEqual(response.code, 400)\n\n    @unittest.skipIf(os.name == \"nt\", \"flaky on windows\")\n    def test_large_body_buffered_chunked(self):\n        # This test is flaky on windows for unknown reasons.\n        with ExpectLog(gen_log, \".*chunked body too large\", level=logging.INFO):\n            response = self.fetch(\n                \"/buffered\",\n                method=\"PUT\",\n                body_producer=lambda write: write(b\"a\" * 10240),\n            )\n        self.assertEqual(response.code, 400)\n\n    def test_large_body_streaming(self):\n        with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n            response = self.fetch(\"/streaming\", method=\"PUT\", body=b\"a\" * 10240)\n        self.assertEqual(response.code, 400)\n\n    @unittest.skipIf(os.name == \"nt\", \"flaky on windows\")\n    def test_large_body_streaming_chunked(self):\n        with ExpectLog(gen_log, \".*chunked body too large\", level=logging.INFO):\n            response = self.fetch(\n                \"/streaming\",\n                method=\"PUT\",\n                body_producer=lambda write: write(b\"a\" * 10240),\n            )\n        self.assertEqual(response.code, 400)\n\n    def test_large_body_streaming_override(self):\n        response = self.fetch(\n            \"/streaming?expected_size=10240\", method=\"PUT\", body=b\"a\" * 10240\n        )\n        self.assertEqual(response.body, b\"10240\")\n\n    def test_large_body_streaming_chunked_override(self):\n        response = self.fetch(\n            \"/streaming?expected_size=10240\",\n            method=\"PUT\",\n            body_producer=lambda write: write(b\"a\" * 10240),\n        )\n        self.assertEqual(response.body, b\"10240\")\n\n    @gen_test\n    def test_timeout(self):\n        stream = IOStream(socket.socket())\n        try:\n            yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n            # Use a raw stream because AsyncHTTPClient won't let us read a\n            # response without finishing a body.\n            stream.write(\n                b\"PUT /streaming?body_timeout=0.1 HTTP/1.0\\r\\n\"\n                b\"Content-Length: 42\\r\\n\\r\\n\"\n            )\n            with ExpectLog(gen_log, \"Timeout reading body\", level=logging.INFO):\n                response = yield stream.read_until_close()\n            self.assertEqual(response, b\"\")\n        finally:\n            stream.close()\n\n    @gen_test\n    def test_body_size_override_reset(self):\n        # The max_body_size override is reset between requests.\n        stream = IOStream(socket.socket())\n        try:\n            yield stream.connect((\"127.0.0.1\", self.get_http_port()))\n            # Use a raw stream so we can make sure it's all on one connection.\n            stream.write(\n                b\"PUT /streaming?expected_size=10240 HTTP/1.1\\r\\n\"\n                b\"Content-Length: 10240\\r\\n\\r\\n\"\n            )\n            stream.write(b\"a\" * 10240)\n            start_line, headers, response = yield read_stream_body(stream)\n            self.assertEqual(response, b\"10240\")\n            # Without the ?expected_size parameter, we get the old default value\n            stream.write(\n                b\"PUT /streaming HTTP/1.1\\r\\n\" b\"Content-Length: 10240\\r\\n\\r\\n\"\n            )\n            with ExpectLog(gen_log, \".*Content-Length too long\", level=logging.INFO):\n                data = yield stream.read_until_close()\n            self.assertEqual(data, b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n        finally:\n            stream.close()\n\n\nclass LegacyInterfaceTest(AsyncHTTPTestCase):\n    def get_app(self):\n        # The old request_callback interface does not implement the\n        # delegate interface, and writes its response via request.write\n        # instead of request.connection.write_headers.\n        def handle_request(request):\n            self.http1 = request.version.startswith(\"HTTP/1.\")\n            if not self.http1:\n                # This test will be skipped if we're using HTTP/2,\n                # so just close it out cleanly using the modern interface.\n                request.connection.write_headers(\n                    ResponseStartLine(\"\", 200, \"OK\"), HTTPHeaders()\n                )\n                request.connection.finish()\n                return\n            message = b\"Hello world\"\n            request.connection.write(\n                utf8(\"HTTP/1.1 200 OK\\r\\n\" \"Content-Length: %d\\r\\n\\r\\n\" % len(message))\n            )\n            request.connection.write(message)\n            request.connection.finish()\n\n        return handle_request\n\n    def test_legacy_interface(self):\n        response = self.fetch(\"/\")\n        if not self.http1:\n            self.skipTest(\"requires HTTP/1.x\")\n        self.assertEqual(response.body, b\"Hello world\")\n",
        "#!/usr/bin/env python\nimport unittest\nimport sys\nimport time\nimport traceback\nimport platform\nimport os\n\n\nclass PytestLikeResult(unittest.TestResult):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.start_time = None\n        # Store (test, outcome, info) for each test:\n        #   outcome ∈ { \"passed\", \"failed\", \"error\", \"skipped\" }\n        #   info can hold the exception or skip reason, etc.\n        self.results = []\n\n    def startTestRun(self):\n        \"\"\"Called once before all tests.\"\"\"\n        self.start_time = time.time()\n        # Print a header similar to pytest\n        print(\"=\" * 29 + \" test session starts \" + \"=\" * 29)\n        # You can customize the platform/versions/paths as you like:\n        print(\n            f\"platform {platform.system().lower()} -- Python {platform.python_version()}\"\n        )\n        print(f\"rootdir: {os.path.abspath(os.curdir)}\")\n\n    def stopTestRun(self):\n        \"\"\"Called once after all tests are done.\"\"\"\n        elapsed = time.time() - self.start_time\n        print()  # blank line after final dot\n\n        # Summarize\n        ntests = len(self.results)\n        print(f\"collected {ntests} items\")\n\n        # Show details of failures/errors\n        failures = [r for r in self.results if r[1] == \"failed\"]\n        errors = [r for r in self.results if r[1] == \"error\"]\n\n        if failures:\n            print(\"\\n\" + \"=\" * 35 + \" FAILURES \" + \"=\" * 35)\n            for test, outcome, err_info in failures:\n                test = test.split(\"::\")\n                if len(test) == 3:\n                    test = f\"{test[1]}.{test[2]}\"\n                else:\n                    test = f\"{test[1]}\"\n                print(f\"__________ {test} __________\")\n                self._print_traceback(err_info)\n        if errors:\n            print(\"\\n\" + \"=\" * 35 + \" ERRORS \" + \"=\" * 37)\n            for test, outcome, err_info in errors:\n                test = test.split(\"::\")\n                if len(test) == 3:\n                    test = f\"{test[1]}.{test[2]}\"\n                else:\n                    test = f\"{test[1]}\"\n                print(f\"__________ {test} __________\")\n                self._print_traceback(err_info)\n\n        # Optional short summary lines for passes/fails/skips\n        print(\"\\n==================== short test summary info ====================\")\n        for test, outcome, info in self.results:\n            if outcome in (\"passed\", \"failed\", \"error\", \"skipped\"):\n                print(f\"{outcome.upper()} {test}\")\n\n        # Final one-line summary: “1 failed, 1 errors, 10 passed in 2.04s”\n        passed_count = sum(1 for r in self.results if r[1] == \"passed\")\n        failed_count = len(failures)\n        error_count = len(errors)\n        skipped_count = sum(1 for r in self.results if r[1] == \"skipped\")\n\n        summary_bits = []\n        if failed_count:\n            summary_bits.append(f\"{failed_count} failed\")\n        if error_count:\n            summary_bits.append(f\"{error_count} error{'s' if error_count != 1 else ''}\")\n        if passed_count:\n            summary_bits.append(f\"{passed_count} passed\")\n        if skipped_count:\n            summary_bits.append(f\"{skipped_count} skipped\")\n\n        summary_str = \", \".join(summary_bits) if summary_bits else \"no tests run\"\n        print(\n            f\"=================== {summary_str} in {elapsed:.2f}s ===================\"\n        )\n\n    def startTest(self, test):\n        \"\"\"Called right before each test method.\"\"\"\n        super().startTest(test)\n        # Print a dot or some indicator\n        print(\".\", end=\"\", flush=True)\n\n    def addSuccess(self, test):\n        super().addSuccess(test)\n        self.results.append((self._test_id(test), \"passed\", None))\n\n    def addFailure(self, test, err):\n        super().addFailure(test, err)\n        self.results.append((self._test_id(test), \"failed\", err))\n\n    def addError(self, test, err):\n        super().addError(test, err)\n        self.results.append((self._test_id(test), \"error\", err))\n\n    def addSkip(self, test, reason):\n        super().addSkip(test, reason)\n        self.results.append((self._test_id(test), \"skipped\", reason))\n\n    def _print_traceback(self, err_info):\n        \"\"\"Helper to print the traceback like pytest does.\"\"\"\n        if isinstance(err_info, tuple) and len(err_info) == 3:\n            exc_type, exc_value, tb = err_info\n            traceback.print_exception(exc_type, exc_value, tb, file=sys.stdout)\n        else:\n            print(str(err_info))\n\n    def _test_id(self, test):\n        try:\n            return f\"{test.__class__.__module__}::{test.__class__.__name__}::{test._testMethodName}\"\n        except AttributeError:\n            # Fallback for _ErrorHolder objects\n            return f\"{test.__class__.__module__}::{test.__class__.__name__}\"\n\n\nclass PytestLikeRunner(unittest.TextTestRunner):\n    \"\"\"A custom TextTestRunner that uses the PytestLikeResult.\"\"\"\n\n    resultclass = PytestLikeResult\n\n    def run(self, test):\n        result = self._makeResult()\n        # Overriding run to ensure we call startTestRun/stopTestRun\n        result.startTestRun()\n        test(result)\n        result.stopTestRun()\n        return result\n\n    def tearDown(self):\n        # Reset signal handlers\n        from twisted.internet import reactor\n\n        reactor.removeAll()\n        reactor.stop()\n        super().tearDown()\n\n\ndef main():\n    loader = unittest.TestLoader()\n    suite = loader.discover(\".\", pattern=\"test_*.py\")  # discover tests in r2e_tests/\n    runner = PytestLikeRunner(verbosity=0)\n    result = runner.run(suite)\n    # Exit with code 0 if all tests passed/skipped, 1 if there were fails/errors\n    sys.exit(not result.wasSuccessful())\n\n\nif __name__ == \"__main__\":\n    main()\n"
    ],
    "test_file_names": [
        "test_1.py",
        "tornado_unittest_runner.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 1 package in 738ms\nPrepared 1 package in 1.09s\nInstalled 1 package in 27ms\n + tornado==6.2.dev1 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45)\nResolved 29 packages in 20ms\nInstalled 29 packages in 225ms\n + attrs==24.3.0\n + automat==24.8.1\n + cachetools==5.5.0\n + cffi==1.17.1\n + chardet==5.2.0\n + colorama==0.4.6\n + constantly==23.10.4\n + distlib==0.3.9\n + exceptiongroup==1.2.2\n + filelock==3.16.1\n + hyperlink==21.0.0\n + idna==3.10\n + incremental==24.7.2\n + iniconfig==2.0.0\n + packaging==24.2\n + platformdirs==4.3.6\n + pluggy==1.5.0\n + pycares==4.5.0\n + pycparser==2.22\n + pycurl==7.45.4\n + pyproject-api==1.8.0\n + pytest==8.3.4\n + setuptools==75.6.0\n + tomli==2.2.1\n + tox==4.23.2\n + twisted==24.11.0\n + typing-extensions==4.12.2\n + virtualenv==20.28.0\n + zope-interface==7.2\n",
    "new_commit_res_code": 1,
    "new_commit_res_stdout": "============================= test session starts =============================\nplatform linux -- Python 3.9.21\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45\n............................................................................\ncollected 76 items\n\n=================================== ERRORS =====================================\n__________ BadSSLOptionsTest.test_missing_key __________\nTraceback (most recent call last):\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 59, in testPartExecutor\n    yield\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 592, in run\n    self._callTestMethod(testMethod)\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 550, in _callTestMethod\n    method()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/r2e_tests/test_1.py\", line 224, in test_missing_key\n    HTTPServer(\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/util.py\", line 280, in __new__\n    instance.initialize(*args, **init_kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/httpserver.py\", line 187, in initialize\n    TCPServer.__init__(\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/tcpserver.py\", line 133, in __init__\n    raise ValueError(\nValueError: certfile \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/r2e_tests/test.crt\" does not exist\n__________ UnixSocketTest.test_unix_socket_bad_request __________\nTraceback (most recent call last):\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 59, in testPartExecutor\n    yield\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 592, in run\n    self._callTestMethod(testMethod)\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 550, in _callTestMethod\n    method()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/testing.py\", line 100, in __call__\n    result = self.orig_method(*args, **kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/testing.py\", line 601, in post_coroutine\n    return self.io_loop.run_sync(\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/ioloop.py\", line 531, in run_sync\n    return future_cell[0].result()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/gen.py\", line 779, in run\n    yielded = self.gen.send(value)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/r2e_tests/test_1.py\", line 757, in test_unix_socket_bad_request\n    response = yield self.stream.read_until_close()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/testing.py\", line 724, in __exit__\n    raise Exception(\"did not get expected log message\")\nException: did not get expected log message\n\n==================== short test summary info ====================\nPASSED r2e_tests.test_1::BadSSLOptionsTest::test_missing_arguments\nERROR r2e_tests.test_1::BadSSLOptionsTest::test_missing_key\nPASSED r2e_tests.test_1::BodyLimitsTest::test_body_size_override_reset\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_buffered\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_buffered_chunked\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_streaming\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_streaming_chunked\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_streaming_chunked_override\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_streaming_override\nPASSED r2e_tests.test_1::BodyLimitsTest::test_small_body\nPASSED r2e_tests.test_1::BodyLimitsTest::test_timeout\nPASSED r2e_tests.test_1::GzipBaseTest::test_uncompressed\nPASSED r2e_tests.test_1::GzipTest::test_gzip\nPASSED r2e_tests.test_1::GzipTest::test_gzip_case_insensitive\nPASSED r2e_tests.test_1::GzipTest::test_uncompressed\nPASSED r2e_tests.test_1::GzipUnsupportedTest::test_gzip_unsupported\nPASSED r2e_tests.test_1::GzipUnsupportedTest::test_uncompressed\nPASSED r2e_tests.test_1::HTTPConnectionTest::test_100_continue\nPASSED r2e_tests.test_1::HTTPConnectionTest::test_multipart_form\nPASSED r2e_tests.test_1::HTTPConnectionTest::test_newlines\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_chunked_request_body\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_chunked_request_uppercase\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_empty_request\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_invalid_content_length\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_malformed_first_line_log\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_malformed_first_line_response\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_malformed_headers\nPASSED r2e_tests.test_1::HTTPServerTest::test_double_slash\nPASSED r2e_tests.test_1::HTTPServerTest::test_empty_post_parameters\nPASSED r2e_tests.test_1::HTTPServerTest::test_empty_query_string\nPASSED r2e_tests.test_1::HTTPServerTest::test_post_encodings\nPASSED r2e_tests.test_1::HTTPServerTest::test_query_string_encoding\nPASSED r2e_tests.test_1::HTTPServerTest::test_types\nPASSED r2e_tests.test_1::IdleTimeoutTest::test_idle_after_use\nPASSED r2e_tests.test_1::IdleTimeoutTest::test_unused_connection\nPASSED r2e_tests.test_1::KeepAliveTest::test_cancel_during_download\nPASSED r2e_tests.test_1::KeepAliveTest::test_finish_while_closed\nPASSED r2e_tests.test_1::KeepAliveTest::test_http10\nPASSED r2e_tests.test_1::KeepAliveTest::test_http10_keepalive\nPASSED r2e_tests.test_1::KeepAliveTest::test_http10_keepalive_extra_crlf\nPASSED r2e_tests.test_1::KeepAliveTest::test_keepalive_chunked\nPASSED r2e_tests.test_1::KeepAliveTest::test_keepalive_chunked_head_no_body\nPASSED r2e_tests.test_1::KeepAliveTest::test_pipelined_cancel\nPASSED r2e_tests.test_1::KeepAliveTest::test_pipelined_requests\nPASSED r2e_tests.test_1::KeepAliveTest::test_request_close\nPASSED r2e_tests.test_1::KeepAliveTest::test_two_requests\nPASSED r2e_tests.test_1::LegacyInterfaceTest::test_legacy_interface\nPASSED r2e_tests.test_1::ManualProtocolTest::test_manual_protocol\nPASSED r2e_tests.test_1::MaxHeaderSizeTest::test_large_headers\nPASSED r2e_tests.test_1::MaxHeaderSizeTest::test_small_headers\nPASSED r2e_tests.test_1::SSLContextTest::test_error_logging\nPASSED r2e_tests.test_1::SSLContextTest::test_large_post\nPASSED r2e_tests.test_1::SSLContextTest::test_non_ssl_request\nPASSED r2e_tests.test_1::SSLContextTest::test_ssl\nPASSED r2e_tests.test_1::SSLXHeaderTest::test_request_without_xprotocol\nPASSED r2e_tests.test_1::SSLv23Test::test_error_logging\nPASSED r2e_tests.test_1::SSLv23Test::test_large_post\nPASSED r2e_tests.test_1::SSLv23Test::test_non_ssl_request\nPASSED r2e_tests.test_1::SSLv23Test::test_ssl\nPASSED r2e_tests.test_1::SSLv3Test::test_error_logging\nPASSED r2e_tests.test_1::SSLv3Test::test_large_post\nPASSED r2e_tests.test_1::SSLv3Test::test_non_ssl_request\nPASSED r2e_tests.test_1::SSLv3Test::test_ssl\nPASSED r2e_tests.test_1::StreamingChunkSizeTest::test_chunked_body\nPASSED r2e_tests.test_1::StreamingChunkSizeTest::test_chunked_compressed\nPASSED r2e_tests.test_1::StreamingChunkSizeTest::test_compressed_body\nPASSED r2e_tests.test_1::StreamingChunkSizeTest::test_regular_body\nPASSED r2e_tests.test_1::TLSv1Test::test_error_logging\nPASSED r2e_tests.test_1::TLSv1Test::test_large_post\nPASSED r2e_tests.test_1::TLSv1Test::test_non_ssl_request\nPASSED r2e_tests.test_1::TLSv1Test::test_ssl\nPASSED r2e_tests.test_1::UnixSocketTest::test_unix_socket\nERROR r2e_tests.test_1::UnixSocketTest::test_unix_socket_bad_request\nPASSED r2e_tests.test_1::XHeaderTest::test_ip_headers\nPASSED r2e_tests.test_1::XHeaderTest::test_scheme_headers\nPASSED r2e_tests.test_1::XHeaderTest::test_trusted_downstream\n=================== 2 errors, 74 passed in 0.96s ===================\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts =============================\nplatform linux -- Python 3.9.21\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45\n............................................................................\ncollected 76 items\n\n=================================== FAILURES ===================================\n__________ GzipTest.test_gzip_case_insensitive __________\nTraceback (most recent call last):\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 59, in testPartExecutor\n    yield\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 592, in run\n    self._callTestMethod(testMethod)\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 550, in _callTestMethod\n    method()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/testing.py\", line 100, in __call__\n    result = self.orig_method(*args, **kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/r2e_tests/test_1.py\", line 1016, in test_gzip_case_insensitive\n    self.assertEqual(json_decode(response.body), {u\"foo\": [u\"bar\"]})\nAssertionError: {} != {'foo': ['bar']}\n- {}\n+ {'foo': ['bar']}\n\n=================================== ERRORS =====================================\n__________ BadSSLOptionsTest.test_missing_key __________\nTraceback (most recent call last):\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 59, in testPartExecutor\n    yield\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 592, in run\n    self._callTestMethod(testMethod)\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 550, in _callTestMethod\n    method()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/r2e_tests/test_1.py\", line 224, in test_missing_key\n    HTTPServer(\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/util.py\", line 280, in __new__\n    instance.initialize(*args, **init_kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/httpserver.py\", line 187, in initialize\n    TCPServer.__init__(\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/tcpserver.py\", line 133, in __init__\n    raise ValueError(\nValueError: certfile \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/r2e_tests/test.crt\" does not exist\n__________ UnixSocketTest.test_unix_socket_bad_request __________\nTraceback (most recent call last):\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 59, in testPartExecutor\n    yield\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 592, in run\n    self._callTestMethod(testMethod)\n  File \"/home/gcpuser/.local/share/uv/python/cpython-3.9.21-linux-x86_64-gnu/lib/python3.9/unittest/case.py\", line 550, in _callTestMethod\n    method()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/testing.py\", line 100, in __call__\n    result = self.orig_method(*args, **kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/testing.py\", line 601, in post_coroutine\n    return self.io_loop.run_sync(\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/ioloop.py\", line 531, in run_sync\n    return future_cell[0].result()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/gen.py\", line 779, in run\n    yielded = self.gen.send(value)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/r2e_tests/test_1.py\", line 757, in test_unix_socket_bad_request\n    response = yield self.stream.read_until_close()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_aa9d32d5e89b290dad69e0c6655b7497ea492e45/tornado/testing.py\", line 724, in __exit__\n    raise Exception(\"did not get expected log message\")\nException: did not get expected log message\n\n==================== short test summary info ====================\nPASSED r2e_tests.test_1::BadSSLOptionsTest::test_missing_arguments\nERROR r2e_tests.test_1::BadSSLOptionsTest::test_missing_key\nPASSED r2e_tests.test_1::BodyLimitsTest::test_body_size_override_reset\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_buffered\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_buffered_chunked\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_streaming\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_streaming_chunked\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_streaming_chunked_override\nPASSED r2e_tests.test_1::BodyLimitsTest::test_large_body_streaming_override\nPASSED r2e_tests.test_1::BodyLimitsTest::test_small_body\nPASSED r2e_tests.test_1::BodyLimitsTest::test_timeout\nPASSED r2e_tests.test_1::GzipBaseTest::test_uncompressed\nPASSED r2e_tests.test_1::GzipTest::test_gzip\nFAILED r2e_tests.test_1::GzipTest::test_gzip_case_insensitive\nPASSED r2e_tests.test_1::GzipTest::test_uncompressed\nPASSED r2e_tests.test_1::GzipUnsupportedTest::test_gzip_unsupported\nPASSED r2e_tests.test_1::GzipUnsupportedTest::test_uncompressed\nPASSED r2e_tests.test_1::HTTPConnectionTest::test_100_continue\nPASSED r2e_tests.test_1::HTTPConnectionTest::test_multipart_form\nPASSED r2e_tests.test_1::HTTPConnectionTest::test_newlines\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_chunked_request_body\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_chunked_request_uppercase\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_empty_request\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_invalid_content_length\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_malformed_first_line_log\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_malformed_first_line_response\nPASSED r2e_tests.test_1::HTTPServerRawTest::test_malformed_headers\nPASSED r2e_tests.test_1::HTTPServerTest::test_double_slash\nPASSED r2e_tests.test_1::HTTPServerTest::test_empty_post_parameters\nPASSED r2e_tests.test_1::HTTPServerTest::test_empty_query_string\nPASSED r2e_tests.test_1::HTTPServerTest::test_post_encodings\nPASSED r2e_tests.test_1::HTTPServerTest::test_query_string_encoding\nPASSED r2e_tests.test_1::HTTPServerTest::test_types\nPASSED r2e_tests.test_1::IdleTimeoutTest::test_idle_after_use\nPASSED r2e_tests.test_1::IdleTimeoutTest::test_unused_connection\nPASSED r2e_tests.test_1::KeepAliveTest::test_cancel_during_download\nPASSED r2e_tests.test_1::KeepAliveTest::test_finish_while_closed\nPASSED r2e_tests.test_1::KeepAliveTest::test_http10\nPASSED r2e_tests.test_1::KeepAliveTest::test_http10_keepalive\nPASSED r2e_tests.test_1::KeepAliveTest::test_http10_keepalive_extra_crlf\nPASSED r2e_tests.test_1::KeepAliveTest::test_keepalive_chunked\nPASSED r2e_tests.test_1::KeepAliveTest::test_keepalive_chunked_head_no_body\nPASSED r2e_tests.test_1::KeepAliveTest::test_pipelined_cancel\nPASSED r2e_tests.test_1::KeepAliveTest::test_pipelined_requests\nPASSED r2e_tests.test_1::KeepAliveTest::test_request_close\nPASSED r2e_tests.test_1::KeepAliveTest::test_two_requests\nPASSED r2e_tests.test_1::LegacyInterfaceTest::test_legacy_interface\nPASSED r2e_tests.test_1::ManualProtocolTest::test_manual_protocol\nPASSED r2e_tests.test_1::MaxHeaderSizeTest::test_large_headers\nPASSED r2e_tests.test_1::MaxHeaderSizeTest::test_small_headers\nPASSED r2e_tests.test_1::SSLContextTest::test_error_logging\nPASSED r2e_tests.test_1::SSLContextTest::test_large_post\nPASSED r2e_tests.test_1::SSLContextTest::test_non_ssl_request\nPASSED r2e_tests.test_1::SSLContextTest::test_ssl\nPASSED r2e_tests.test_1::SSLXHeaderTest::test_request_without_xprotocol\nPASSED r2e_tests.test_1::SSLv23Test::test_error_logging\nPASSED r2e_tests.test_1::SSLv23Test::test_large_post\nPASSED r2e_tests.test_1::SSLv23Test::test_non_ssl_request\nPASSED r2e_tests.test_1::SSLv23Test::test_ssl\nPASSED r2e_tests.test_1::SSLv3Test::test_error_logging\nPASSED r2e_tests.test_1::SSLv3Test::test_large_post\nPASSED r2e_tests.test_1::SSLv3Test::test_non_ssl_request\nPASSED r2e_tests.test_1::SSLv3Test::test_ssl\nPASSED r2e_tests.test_1::StreamingChunkSizeTest::test_chunked_body\nPASSED r2e_tests.test_1::StreamingChunkSizeTest::test_chunked_compressed\nPASSED r2e_tests.test_1::StreamingChunkSizeTest::test_compressed_body\nPASSED r2e_tests.test_1::StreamingChunkSizeTest::test_regular_body\nPASSED r2e_tests.test_1::TLSv1Test::test_error_logging\nPASSED r2e_tests.test_1::TLSv1Test::test_large_post\nPASSED r2e_tests.test_1::TLSv1Test::test_non_ssl_request\nPASSED r2e_tests.test_1::TLSv1Test::test_ssl\nPASSED r2e_tests.test_1::UnixSocketTest::test_unix_socket\nERROR r2e_tests.test_1::UnixSocketTest::test_unix_socket_bad_request\nPASSED r2e_tests.test_1::XHeaderTest::test_ip_headers\nPASSED r2e_tests.test_1::XHeaderTest::test_scheme_headers\nPASSED r2e_tests.test_1::XHeaderTest::test_trusted_downstream\n=================== 1 failed, 2 errors, 73 passed in 0.86s ===================\n",
    "old_commit_res_stderr": "WARNING:tornado.general:Unsupported Content-Encoding: GZIP\n"
}
tornado/http1connection.py tornado/test/httpserver_test.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Server Fails to Decode GZIP Content with Uppercase Content-Encoding Header

**Description:**
When sending a request with the `Content-Encoding` header set to `"GZIP"` (uppercase), the server does not decode the gzip-compressed body as expected. This behavior deviates from the HTTP specification, which states that header field values should be treated case-insensitively.

**Example Buggy Code:**
```python
import gzip
from io import BytesIO
from tornado.testing import AsyncHTTPTestCase
from tornado.web import Application, RequestHandler

class TestGzipEncoding(AsyncHTTPTestCase):
    def get_app(self):
        return Application([("/", GzipHandler)])

    def test_gzip_case_insensitive(self):
        bytes_io = BytesIO()
        gzip_file = gzip.GzipFile(mode="w", fileobj=bytes_io)
        gzip_file.write(b"foo=bar")
        gzip_file.close()
        compressed_body = bytes_io.getvalue()
        
        response = self.fetch(
            "/",
            method="POST",
            body=compressed_body,
            headers={"Content-Encoding": "GZIP"},  # Uppercase
        )
        # Expected to decode and return {'foo': ['bar']}
```

**Expected Behavior:**
When the server receives a request with the `Content-Encoding` header set to any case variation of `"gzip"` (e.g., `"gzip"`, `"GZIP"`, `"GzIp"`), it should correctly recognize the encoding and decompress the request body, returning the expected data structure.

**Actual Behavior:**
With the `Content-Encoding` header set to `"GZIP"`, the server fails to recognize the encoding due to case sensitivity, resulting in an empty response `{}` instead of the expected `{'foo': ['bar']}`.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### tornado/http1connection.py
#
# Copyright 2014 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Client and server implementations of HTTP/1.x.

.. versionadded:: 4.0
"""

import asyncio
import logging
import re
import types

from tornado.concurrent import (
    Future,
    future_add_done_callback,
    future_set_result_unless_cancelled,
)
from tornado.escape import native_str, utf8
from tornado import gen
from tornado import httputil
from tornado import iostream
from tornado.log import gen_log, app_log
from tornado.util import GzipDecompressor


from typing import cast, Optional, Type, Awaitable, Callable, Union, Tuple


class _QuietException(Exception):
    def __init__(self) -> None:
        pass


class _ExceptionLoggingContext(object):
    """Used with the ``with`` statement when calling delegate methods to
    log any exceptions with the given logger.  Any exceptions caught are
    converted to _QuietException
    """

    def __init__(self, logger: logging.Logger) -> None:
        self.logger = logger

    def __enter__(self) -> None:
        pass

    def __exit__(
        self,
        typ: "Optional[Type[BaseException]]",
        value: Optional[BaseException],
        tb: types.TracebackType,
    ) -> None:
        if value is not None:
            assert typ is not None
            self.logger.error("Uncaught exception", exc_info=(typ, value, tb))
            raise _QuietException


class HTTP1ConnectionParameters(object):
    """Parameters for `.HTTP1Connection` and `.HTTP1ServerConnection`."""

    def __init__(
        self,
        no_keep_alive: bool = False,
        chunk_size: Optional[int] = None,
        max_header_size: Optional[int] = None,
        header_timeout: Optional[float] = None,
        max_body_size: Optional[int] = None,
        body_timeout: Optional[float] = None,
        decompress: bool = False,
    ) -> None:
        """
        :arg bool no_keep_alive: If true, always close the connection after
            one request.
        :arg int chunk_size: how much data to read into memory at once
        :arg int max_header_size:  maximum amount of data for HTTP headers
        :arg float header_timeout: how long to wait for all headers (seconds)
        :arg int max_body_size: maximum amount of data for body
        :arg float body_timeout: how long to wait while reading body (seconds)
        :arg bool decompress: if true, decode incoming
            ``Content-Encoding: gzip``
        """
        self.no_keep_alive = no_keep_alive
        self.chunk_size = chunk_size or 65536
        self.max_header_size = max_header_size or 65536
        self.header_timeout = header_timeout
        self.max_body_size = max_body_size
        self.body_timeout = body_timeout
        self.decompress = decompress


class HTTP1Connection(httputil.HTTPConnection):
    """Implements the HTTP/1.x protocol.

    This class can be on its own for clients, or via `HTTP1ServerConnection`
    for servers.
    """

    def __init__(
        self,
        stream: iostream.IOStream,
        is_client: bool,
        params: Optional[HTTP1ConnectionParameters] = None,
        context: Optional[object] = None,
    ) -> None:
        """
        :arg stream: an `.IOStream`
        :arg bool is_client: client or server
        :arg params: a `.HTTP1ConnectionParameters` instance or ``None``
        :arg context: an opaque application-defined object that can be accessed
            as ``connection.context``.
        """
        self.is_client = is_client
        self.stream = stream
        if params is None:
            params = HTTP1ConnectionParameters()
        self.params = params
        self.context = context
        self.no_keep_alive = params.no_keep_alive
        # The body limits can be altered by the delegate, so save them
        # here instead of just referencing self.params later.
        self._max_body_size = (
            self.params.max_body_size
            if self.params.max_body_size is not None
            else self.stream.max_buffer_size
        )
        self._body_timeout = self.params.body_timeout
        # _write_finished is set to True when finish() has been called,
        # i.e. there will be no more data sent.  Data may still be in the
        # stream's write buffer.
        self._write_finished = False
        # True when we have read the entire incoming body.
        self._read_finished = False
        # _finish_future resolves when all data has been written and flushed
        # to the IOStream.
        self._finish_future = Future()  # type: Future[None]
        # If true, the connection should be closed after this request
        # (after the response has been written in the server side,
        # and after it has been read in the client)
        self._disconnect_on_finish = False
        self._clear_callbacks()
        # Save the start lines after we read or write them; they
        # affect later processing (e.g. 304 responses and HEAD methods
        # have content-length but no bodies)
        self._request_start_line = None  # type: Optional[httputil.RequestStartLine]
        self._response_start_line = None  # type: Optional[httputil.ResponseStartLine]
        self._request_headers = None  # type: Optional[httputil.HTTPHeaders]
        # True if we are writing output with chunked encoding.
        self._chunking_output = False
        # While reading a body with a content-length, this is the
        # amount left to read.
        self._expected_content_remaining = None  # type: Optional[int]
        # A Future for our outgoing writes, returned by IOStream.write.
        self._pending_write = None  # type: Optional[Future[None]]

    def read_response(self, delegate: httputil.HTTPMessageDelegate) -> Awaitable[bool]:
        """Read a single HTTP response.

        Typical client-mode usage is to write a request using `write_headers`,
        `write`, and `finish`, and then call ``read_response``.

        :arg delegate: a `.HTTPMessageDelegate`

        Returns a `.Future` that resolves to a bool after the full response has
        been read. The result is true if the stream is still open.
        """
        if self.params.decompress:
            delegate = _GzipMessageDelegate(delegate, self.params.chunk_size)
        return self._read_message(delegate)

    async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool:
        need_delegate_close = False
        try:
            header_future = self.stream.read_until_regex(
                b"\r?\n\r?\n", max_bytes=self.params.max_header_size
            )
            if self.params.header_timeout is None:
                header_data = await header_future
            else:
                try:
                    header_data = await gen.with_timeout(
                        self.stream.io_loop.time() + self.params.header_timeout,
                        header_future,
                        quiet_exceptions=iostream.StreamClosedError,
                    )
                except gen.TimeoutError:
                    self.close()
                    return False
            start_line_str, headers = self._parse_headers(header_data)
            if self.is_client:
                resp_start_line = httputil.parse_response_start_line(start_line_str)
                self._response_start_line = resp_start_line
                start_line = (
                    resp_start_line
                )  # type: Union[httputil.RequestStartLine, httputil.ResponseStartLine]
                # TODO: this will need to change to support client-side keepalive
                self._disconnect_on_finish = False
            else:
                req_start_line = httputil.parse_request_start_line(start_line_str)
                self._request_start_line = req_start_line
                self._request_headers = headers
                start_line = req_start_line
                self._disconnect_on_finish = not self._can_keep_alive(
                    req_start_line, headers
                )
            need_delegate_close = True
            with _ExceptionLoggingContext(app_log):
                header_recv_future = delegate.headers_received(start_line, headers)
                if header_recv_future is not None:
                    await header_recv_future
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                return False
            skip_body = False
            if self.is_client:
                assert isinstance(start_line, httputil.ResponseStartLine)
                if (
                    self._request_start_line is not None
                    and self._request_start_line.method == "HEAD"
                ):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    # 304 responses may include the content-length header
                    # but do not actually have a body.
                    # http://tools.ietf.org/html/rfc7230#section-3.3
                    skip_body = True
                if 100 <= code < 200:
                    # 1xx responses should never indicate the presence of
                    # a body.
                    if "Content-Length" in headers or "Transfer-Encoding" in headers:
                        raise httputil.HTTPInputError(
                            "Response code %d cannot have body" % code
                        )
                    # TODO: client delegates will get headers_received twice
                    # in the case of a 100-continue.  Document or change?
                    await self._read_message(delegate)
            else:
                if headers.get("Expect") == "100-continue" and not self._write_finished:
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
            if not skip_body:
                body_future = self._read_body(
                    resp_start_line.code if self.is_client else 0, headers, delegate
                )
                if body_future is not None:
                    if self._body_timeout is None:
                        await body_future
                    else:
                        try:
                            await gen.with_timeout(
                                self.stream.io_loop.time() + self._body_timeout,
                                body_future,
                                quiet_exceptions=iostream.StreamClosedError,
                            )
                        except gen.TimeoutError:
                            gen_log.info("Timeout reading body from %s", self.context)
                            self.stream.close()
                            return False
            self._read_finished = True
            if not self._write_finished or self.is_client:
                need_delegate_close = False
                with _ExceptionLoggingContext(app_log):
                    delegate.finish()
            # If we're waiting for the application to produce an asynchronous
            # response, and we're not detached, register a close callback
            # on the stream (we didn't need one while we were reading)
            if (
                not self._finish_future.done()
                and self.stream is not None
                and not self.stream.closed()
            ):
                self.stream.set_close_callback(self._on_connection_close)
                await self._finish_future
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                return False
        except httputil.HTTPInputError as e:
            gen_log.info("Malformed HTTP message from %s: %s", self.context, e)
            if not self.is_client:
                await self.stream.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
            self.close()
            return False
        finally:
            if need_delegate_close:
                with _ExceptionLoggingContext(app_log):
                    delegate.on_connection_close()
            header_future = None  # type: ignore
            self._clear_callbacks()
        return True

    def _clear_callbacks(self) -> None:
        """Clears the callback attributes.

        This allows the request handler to be garbage collected more
        quickly in CPython by breaking up reference cycles.
        """
        self._write_callback = None
        self._write_future = None  # type: Optional[Future[None]]
        self._close_callback = None  # type: Optional[Callable[[], None]]
        if self.stream is not None:
            self.stream.set_close_callback(None)

    def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None:
        """Sets a callback that will be run when the connection is closed.

        Note that this callback is slightly different from
        `.HTTPMessageDelegate.on_connection_close`: The
        `.HTTPMessageDelegate` method is called when the connection is
        closed while receiving a message. This callback is used when
        there is not an active delegate (for example, on the server
        side this callback is used if the client closes the connection
        after sending its request but before receiving all the
        response.
        """
        self._close_callback = callback

    def _on_connection_close(self) -> None:
        # Note that this callback is only registered on the IOStream
        # when we have finished reading the request and are waiting for
        # the application to produce its response.
        if self._close_callback is not None:
            callback = self._close_callback
            self._close_callback = None
            callback()
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)
        self._clear_callbacks()

    def close(self) -> None:
        if self.stream is not None:
            self.stream.close()
        self._clear_callbacks()
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)

    def detach(self) -> iostream.IOStream:
        """Take control of the underlying stream.

        Returns the underlying `.IOStream` object and stops all further
        HTTP processing.  May only be called during
        `.HTTPMessageDelegate.headers_received`.  Intended for implementing
        protocols like websockets that tunnel over an HTTP handshake.
        """
        self._clear_callbacks()
        stream = self.stream
        self.stream = None  # type: ignore
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)
        return stream

    def set_body_timeout(self, timeout: float) -> None:
        """Sets the body timeout for a single request.

        Overrides the value from `.HTTP1ConnectionParameters`.
        """
        self._body_timeout = timeout

    def set_max_body_size(self, max_body_size: int) -> None:
        """Sets the body size limit for a single request.

        Overrides the value from `.HTTP1ConnectionParameters`.
        """
        self._max_body_size = max_body_size

    def write_headers(
        self,
        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
        headers: httputil.HTTPHeaders,
        chunk: Optional[bytes] = None,
    ) -> "Future[None]":
        """Implements `.HTTPConnection.write_headers`."""
        lines = []
        if self.is_client:
            assert isinstance(start_line, httputil.RequestStartLine)
            self._request_start_line = start_line
            lines.append(utf8("%s %s HTTP/1.1" % (start_line[0], start_line[1])))
            # Client requests with a non-empty body must have either a
            # Content-Length or a Transfer-Encoding.
            self._chunking_output = (
                start_line.method in ("POST", "PUT", "PATCH")
                and "Content-Length" not in headers
                and (
                    "Transfer-Encoding" not in headers
                    or headers["Transfer-Encoding"] == "chunked"
                )
            )
        else:
            assert isinstance(start_line, httputil.ResponseStartLine)
            assert self._request_start_line is not None
            assert self._request_headers is not None
            self._response_start_line = start_line
            lines.append(utf8("HTTP/1.1 %d %s" % (start_line[1], start_line[2])))
            self._chunking_output = (
                # TODO: should this use
                # self._request_start_line.version or
                # start_line.version?
                self._request_start_line.version == "HTTP/1.1"
                # Omit payload header field for HEAD request.
                and self._request_start_line.method != "HEAD"
                # 1xx, 204 and 304 responses have no body (not even a zero-length
                # body), and so should not have either Content-Length or
                # Transfer-Encoding headers.
                and start_line.code not in (204, 304)
                and (start_line.code < 100 or start_line.code >= 200)
                # No need to chunk the output if a Content-Length is specified.
                and "Content-Length" not in headers
                # Applications are discouraged from touching Transfer-Encoding,
                # but if they do, leave it alone.
                and "Transfer-Encoding" not in headers
            )
            # If connection to a 1.1 client will be closed, inform client
            if (
                self._request_start_line.version == "HTTP/1.1"
                and self._disconnect_on_finish
            ):
                headers["Connection"] = "close"
            # If a 1.0 client asked for keep-alive, add the header.
            if (
                self._request_start_line.version == "HTTP/1.0"
                and self._request_headers.get("Connection", "").lower() == "keep-alive"
            ):
                headers["Connection"] = "Keep-Alive"
        if self._chunking_output:
            headers["Transfer-Encoding"] = "chunked"
        if not self.is_client and (
            self._request_start_line.method == "HEAD"
            or cast(httputil.ResponseStartLine, start_line).code == 304
        ):
            self._expected_content_remaining = 0
        elif "Content-Length" in headers:
            self._expected_content_remaining = int(headers["Content-Length"])
        else:
            self._expected_content_remaining = None
        # TODO: headers are supposed to be of type str, but we still have some
        # cases that let bytes slip through. Remove these native_str calls when those
        # are fixed.
        header_lines = (
            native_str(n) + ": " + native_str(v) for n, v in headers.get_all()
        )
        lines.extend(line.encode("latin1") for line in header_lines)
        for line in lines:
            if b"\n" in line:
                raise ValueError("Newline in header: " + repr(line))
        future = None
        if self.stream.closed():
            future = self._write_future = Future()
            future.set_exception(iostream.StreamClosedError())
            future.exception()
        else:
            future = self._write_future = Future()
            data = b"\r\n".join(lines) + b"\r\n\r\n"
            if chunk:
                data += self._format_chunk(chunk)
            self._pending_write = self.stream.write(data)
            future_add_done_callback(self._pending_write, self._on_write_complete)
        return future

    def _format_chunk(self, chunk: bytes) -> bytes:
        if self._expected_content_remaining is not None:
            self._expected_content_remaining -= len(chunk)
            if self._expected_content_remaining < 0:
                # Close the stream now to stop further framing errors.
                self.stream.close()
                raise httputil.HTTPOutputError(
                    "Tried to write more data than Content-Length"
                )
        if self._chunking_output and chunk:
            # Don't write out empty chunks because that means END-OF-STREAM
            # with chunked encoding
            return utf8("%x" % len(chunk)) + b"\r\n" + chunk + b"\r\n"
        else:
            return chunk

    def write(self, chunk: bytes) -> "Future[None]":
        """Implements `.HTTPConnection.write`.

        For backwards compatibility it is allowed but deprecated to
        skip `write_headers` and instead call `write()` with a
        pre-encoded header block.
        """
        future = None
        if self.stream.closed():
            future = self._write_future = Future()
            self._write_future.set_exception(iostream.StreamClosedError())
            self._write_future.exception()
        else:
            future = self._write_future = Future()
            self._pending_write = self.stream.write(self._format_chunk(chunk))
            future_add_done_callback(self._pending_write, self._on_write_complete)
        return future

    def finish(self) -> None:
        """Implements `.HTTPConnection.finish`."""
        if (
            self._expected_content_remaining is not None
            and self._expected_content_remaining != 0
            and not self.stream.closed()
        ):
            self.stream.close()
            raise httputil.HTTPOutputError(
                "Tried to write %d bytes less than Content-Length"
                % self._expected_content_remaining
            )
        if self._chunking_output:
            if not self.stream.closed():
                self._pending_write = self.stream.write(b"0\r\n\r\n")
                self._pending_write.add_done_callback(self._on_write_complete)
        self._write_finished = True
        # If the app finished the request while we're still reading,
        # divert any remaining data away from the delegate and
        # close the connection when we're done sending our response.
        # Closing the connection is the only way to avoid reading the
        # whole input body.
        if not self._read_finished:
            self._disconnect_on_finish = True
        # No more data is coming, so instruct TCP to send any remaining
        # data immediately instead of waiting for a full packet or ack.
        self.stream.set_nodelay(True)
        if self._pending_write is None:
            self._finish_request(None)
        else:
            future_add_done_callback(self._pending_write, self._finish_request)

    def _on_write_complete(self, future: "Future[None]") -> None:
        exc = future.exception()
        if exc is not None and not isinstance(exc, iostream.StreamClosedError):
            future.result()
        if self._write_callback is not None:
            callback = self._write_callback
            self._write_callback = None
            self.stream.io_loop.add_callback(callback)
        if self._write_future is not None:
            future = self._write_future
            self._write_future = None
            future_set_result_unless_cancelled(future, None)

    def _can_keep_alive(
        self, start_line: httputil.RequestStartLine, headers: httputil.HTTPHeaders
    ) -> bool:
        if self.params.no_keep_alive:
            return False
        connection_header = headers.get("Connection")
        if connection_header is not None:
            connection_header = connection_header.lower()
        if start_line.version == "HTTP/1.1":
            return connection_header != "close"
        elif (
            "Content-Length" in headers
            or headers.get("Transfer-Encoding", "").lower() == "chunked"
            or getattr(start_line, "method", None) in ("HEAD", "GET")
        ):
            # start_line may be a request or response start line; only
            # the former has a method attribute.
            return connection_header == "keep-alive"
        return False

    def _finish_request(self, future: "Optional[Future[None]]") -> None:
        self._clear_callbacks()
        if not self.is_client and self._disconnect_on_finish:
            self.close()
            return
        # Turn Nagle's algorithm back on, leaving the stream in its
        # default state for the next request.
        self.stream.set_nodelay(False)
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)

    def _parse_headers(self, data: bytes) -> Tuple[str, httputil.HTTPHeaders]:
        # The lstrip removes newlines that some implementations sometimes
        # insert between messages of a reused connection.  Per RFC 7230,
        # we SHOULD ignore at least one empty line before the request.
        # http://tools.ietf.org/html/rfc7230#section-3.5
        data_str = native_str(data.decode("latin1")).lstrip("\r\n")
        # RFC 7230 section allows for both CRLF and bare LF.
        eol = data_str.find("\n")
        start_line = data_str[:eol].rstrip("\r")
        headers = httputil.HTTPHeaders.parse(data_str[eol:])
        return start_line, headers

    def _read_body(
        self,
        code: int,
        headers: httputil.HTTPHeaders,
        delegate: httputil.HTTPMessageDelegate,
    ) -> Optional[Awaitable[None]]:
        if "Content-Length" in headers:
            if "Transfer-Encoding" in headers:
                # Response cannot contain both Content-Length and
                # Transfer-Encoding headers.
                # http://tools.ietf.org/html/rfc7230#section-3.3.3
                raise httputil.HTTPInputError(
                    "Response with both Transfer-Encoding and Content-Length"
                )
            if "," in headers["Content-Length"]:
                # Proxies sometimes cause Content-Length headers to get
                # duplicated.  If all the values are identical then we can
                # use them but if they differ it's an error.
                pieces = re.split(r",\s*", headers["Content-Length"])
                if any(i != pieces[0] for i in pieces):
                    raise httputil.HTTPInputError(
                        "Multiple unequal Content-Lengths: %r"
                        % headers["Content-Length"]
                    )
                headers["Content-Length"] = pieces[0]

            try:
                content_length = int(headers["Content-Length"])  # type: Optional[int]
            except ValueError:
                # Handles non-integer Content-Length value.
                raise httputil.HTTPInputError(
                    "Only integer Content-Length is allowed: %s"
                    % headers["Content-Length"]
                )

            if cast(int, content_length) > self._max_body_size:
                raise httputil.HTTPInputError("Content-Length too long")
        else:
            content_length = None

        if code == 204:
            # This response code is not allowed to have a non-empty body,
            # and has an implicit length of zero instead of read-until-close.
            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
            if "Transfer-Encoding" in headers or content_length not in (None, 0):
                raise httputil.HTTPInputError(
                    "Response with code %d should not have body" % code
                )
            content_length = 0

        if content_length is not None:
            return self._read_fixed_body(content_length, delegate)
        if headers.get("Transfer-Encoding", "").lower() == "chunked":
            return self._read_chunked_body(delegate)
        if self.is_client:
            return self._read_body_until_close(delegate)
        return None

    async def _read_fixed_body(
        self, content_length: int, delegate: httputil.HTTPMessageDelegate
    ) -> None:
        while content_length > 0:
            body = await self.stream.read_bytes(
                min(self.params.chunk_size, content_length), partial=True
            )
            content_length -= len(body)
            if not self._write_finished or self.is_client:
                with _ExceptionLoggingContext(app_log):
                    ret = delegate.data_received(body)
                    if ret is not None:
                        await ret

    async def _read_chunked_body(self, delegate: httputil.HTTPMessageDelegate) -> None:
        # TODO: "chunk extensions" http://tools.ietf.org/html/rfc2616#section-3.6.1
        total_size = 0
        while True:
            chunk_len_str = await self.stream.read_until(b"\r\n", max_bytes=64)
            chunk_len = int(chunk_len_str.strip(), 16)
            if chunk_len == 0:
                crlf = await self.stream.read_bytes(2)
                if crlf != b"\r\n":
                    raise httputil.HTTPInputError(
                        "improperly terminated chunked request"
                    )
                return
            total_size += chunk_len
            if total_size > self._max_body_size:
                raise httputil.HTTPInputError("chunked body too large")
            bytes_to_read = chunk_len
            while bytes_to_read:
                chunk = await self.stream.read_bytes(
                    min(bytes_to_read, self.params.chunk_size), partial=True
                )
                bytes_to_read -= len(chunk)
                if not self._write_finished or self.is_client:
                    with _ExceptionLoggingContext(app_log):
                        ret = delegate.data_received(chunk)
                        if ret is not None:
                            await ret
            # chunk ends with \r\n
            crlf = await self.stream.read_bytes(2)
            assert crlf == b"\r\n"

    async def _read_body_until_close(
        self, delegate: httputil.HTTPMessageDelegate
    ) -> None:
        body = await self.stream.read_until_close()
        if not self._write_finished or self.is_client:
            with _ExceptionLoggingContext(app_log):
                ret = delegate.data_received(body)
                if ret is not None:
                    await ret


class _GzipMessageDelegate(httputil.HTTPMessageDelegate):
    """Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``."""

    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None:
        self._delegate = delegate
        self._chunk_size = chunk_size
        self._decompressor = None  # type: Optional[GzipDecompressor]

    def headers_received(
        self,
        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
        headers: httputil.HTTPHeaders,
    ) -> Optional[Awaitable[None]]:
        if headers.get("Content-Encoding") == "gzip":
            self._decompressor = GzipDecompressor()
            # Downstream delegates will only see uncompressed data,
            # so rename the content-encoding header.
            # (but note that curl_httpclient doesn't do this).
            headers.add("X-Consumed-Content-Encoding", headers["Content-Encoding"])
            del headers["Content-Encoding"]
        return self._delegate.headers_received(start_line, headers)

    async def data_received(self, chunk: bytes) -> None:
        if self._decompressor:
            compressed_data = chunk
            while compressed_data:
                decompressed = self._decompressor.decompress(
                    compressed_data, self._chunk_size
                )
                if decompressed:
                    ret = self._delegate.data_received(decompressed)
                    if ret is not None:
                        await ret
                compressed_data = self._decompressor.unconsumed_tail
                if compressed_data and not decompressed:
                    raise httputil.HTTPInputError(
                        "encountered unconsumed gzip data without making progress"
                    )
        else:
            ret = self._delegate.data_received(chunk)
            if ret is not None:
                await ret

    def finish(self) -> None:
        if self._decompressor is not None:
            tail = self._decompressor.flush()
            if tail:
                # The tail should always be empty: decompress returned
                # all that it can in data_received and the only
                # purpose of the flush call is to detect errors such
                # as truncated input. If we did legitimately get a new
                # chunk at this point we'd need to change the
                # interface to make finish() a coroutine.
                raise ValueError(
                    "decompressor.flush returned data; possible truncated input"
                )
        return self._delegate.finish()

    def on_connection_close(self) -> None:
        return self._delegate.on_connection_close()


class HTTP1ServerConnection(object):
    """An HTTP/1.x server."""

    def __init__(
        self,
        stream: iostream.IOStream,
        params: Optional[HTTP1ConnectionParameters] = None,
        context: Optional[object] = None,
    ) -> None:
        """
        :arg stream: an `.IOStream`
        :arg params: a `.HTTP1ConnectionParameters` or None
        :arg context: an opaque application-defined object that is accessible
            as ``connection.context``
        """
        self.stream = stream
        if params is None:
            params = HTTP1ConnectionParameters()
        self.params = params
        self.context = context
        self._serving_future = None  # type: Optional[Future[None]]

    async def close(self) -> None:
        """Closes the connection.

        Returns a `.Future` that resolves after the serving loop has exited.
        """
        self.stream.close()
        # Block until the serving loop is done, but ignore any exceptions
        # (start_serving is already responsible for logging them).
        assert self._serving_future is not None
        try:
            await self._serving_future
        except Exception:
            pass

    def start_serving(self, delegate: httputil.HTTPServerConnectionDelegate) -> None:
        """Starts serving requests on this connection.

        :arg delegate: a `.HTTPServerConnectionDelegate`
        """
        assert isinstance(delegate, httputil.HTTPServerConnectionDelegate)
        fut = gen.convert_yielded(self._server_request_loop(delegate))
        self._serving_future = fut
        # Register the future on the IOLoop so its errors get logged.
        self.stream.io_loop.add_future(fut, lambda f: f.result())

    async def _server_request_loop(
        self, delegate: httputil.HTTPServerConnectionDelegate
    ) -> None:
        try:
            while True:
                conn = HTTP1Connection(self.stream, False, self.params, self.context)
                request_delegate = delegate.start_request(self, conn)
                try:
                    ret = await conn.read_response(request_delegate)
                except (
                    iostream.StreamClosedError,
                    iostream.UnsatisfiableReadError,
                    asyncio.CancelledError,
                ):
                    return
                except _QuietException:
                    # This exception was already logged.
                    conn.close()
                    return
                except Exception:
                    gen_log.error("Uncaught exception", exc_info=True)
                    conn.close()
                    return
                if not ret:
                    return
                await asyncio.sleep(0)
        finally:
            delegate.on_close(self)

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** Server Fails to Decode GZIP Content with Uppercase Content-Encoding Header

**Description:**
When sending a request with the `Content-Encoding` header set to `"GZIP"` (uppercase), the server does not decode the gzip-compressed body as expected. This behavior deviates from the HTTP specification, which states that header field values should be treated case-insensitively.

**Example Buggy Code:**
```python
import gzip
from io import BytesIO
from tornado.testing import AsyncHTTPTestCase
from tornado.web import Application, RequestHandler

class TestGzipEncoding(AsyncHTTPTestCase):
    def get_app(self):
        return Application([("/", GzipHandler)])

    def test_gzip_case_insensitive(self):
        bytes_io = BytesIO()
        gzip_file = gzip.GzipFile(mode="w", fileobj=bytes_io)
        gzip_file.write(b"foo=bar")
        gzip_file.close()
        compressed_body = bytes_io.getvalue()
        
        response = self.fetch(
            "/",
            method="POST",
            body=compressed_body,
            headers={"Content-Encoding": "GZIP"},  # Uppercase
        )
        # Expected to decode and return {'foo': ['bar']}
```

**Expected Behavior:**
When the server receives a request with the `Content-Encoding` header set to any case variation of `"gzip"` (e.g., `"gzip"`, `"GZIP"`, `"GzIp"`), it should correctly recognize the encoding and decompress the request body, returning the expected data structure.

**Actual Behavior:**
With the `Content-Encoding` header set to `"GZIP"`, the server fails to recognize the encoding due to case sensitivity, resulting in an empty response `{}` instead of the expected `{'foo': ['bar']}`.

[/ISSUE]
{
    "BadSSLOptionsTest.test_missing_arguments": "PASSED",
    "BadSSLOptionsTest.test_missing_key": "ERROR",
    "BodyLimitsTest.test_body_size_override_reset": "PASSED",
    "BodyLimitsTest.test_large_body_buffered": "PASSED",
    "BodyLimitsTest.test_large_body_buffered_chunked": "PASSED",
    "BodyLimitsTest.test_large_body_streaming": "PASSED",
    "BodyLimitsTest.test_large_body_streaming_chunked": "PASSED",
    "BodyLimitsTest.test_large_body_streaming_chunked_override": "PASSED",
    "BodyLimitsTest.test_large_body_streaming_override": "PASSED",
    "BodyLimitsTest.test_small_body": "PASSED",
    "BodyLimitsTest.test_timeout": "PASSED",
    "GzipBaseTest.test_uncompressed": "PASSED",
    "GzipTest.test_gzip": "PASSED",
    "GzipTest.test_gzip_case_insensitive": "PASSED",
    "GzipTest.test_uncompressed": "PASSED",
    "GzipUnsupportedTest.test_gzip_unsupported": "PASSED",
    "GzipUnsupportedTest.test_uncompressed": "PASSED",
    "HTTPConnectionTest.test_100_continue": "PASSED",
    "HTTPConnectionTest.test_multipart_form": "PASSED",
    "HTTPConnectionTest.test_newlines": "PASSED",
    "HTTPServerRawTest.test_chunked_request_body": "PASSED",
    "HTTPServerRawTest.test_chunked_request_uppercase": "PASSED",
    "HTTPServerRawTest.test_empty_request": "PASSED",
    "HTTPServerRawTest.test_invalid_content_length": "PASSED",
    "HTTPServerRawTest.test_malformed_first_line_log": "PASSED",
    "HTTPServerRawTest.test_malformed_first_line_response": "PASSED",
    "HTTPServerRawTest.test_malformed_headers": "PASSED",
    "HTTPServerTest.test_double_slash": "PASSED",
    "HTTPServerTest.test_empty_post_parameters": "PASSED",
    "HTTPServerTest.test_empty_query_string": "PASSED",
    "HTTPServerTest.test_post_encodings": "PASSED",
    "HTTPServerTest.test_query_string_encoding": "PASSED",
    "HTTPServerTest.test_types": "PASSED",
    "IdleTimeoutTest.test_idle_after_use": "PASSED",
    "IdleTimeoutTest.test_unused_connection": "PASSED",
    "KeepAliveTest.test_cancel_during_download": "PASSED",
    "KeepAliveTest.test_finish_while_closed": "PASSED",
    "KeepAliveTest.test_http10": "PASSED",
    "KeepAliveTest.test_http10_keepalive": "PASSED",
    "KeepAliveTest.test_http10_keepalive_extra_crlf": "PASSED",
    "KeepAliveTest.test_keepalive_chunked": "PASSED",
    "KeepAliveTest.test_keepalive_chunked_head_no_body": "PASSED",
    "KeepAliveTest.test_pipelined_cancel": "PASSED",
    "KeepAliveTest.test_pipelined_requests": "PASSED",
    "KeepAliveTest.test_request_close": "PASSED",
    "KeepAliveTest.test_two_requests": "PASSED",
    "LegacyInterfaceTest.test_legacy_interface": "PASSED",
    "ManualProtocolTest.test_manual_protocol": "PASSED",
    "MaxHeaderSizeTest.test_large_headers": "PASSED",
    "MaxHeaderSizeTest.test_small_headers": "PASSED",
    "SSLContextTest.test_error_logging": "PASSED",
    "SSLContextTest.test_large_post": "PASSED",
    "SSLContextTest.test_non_ssl_request": "PASSED",
    "SSLContextTest.test_ssl": "PASSED",
    "SSLXHeaderTest.test_request_without_xprotocol": "PASSED",
    "SSLv23Test.test_error_logging": "PASSED",
    "SSLv23Test.test_large_post": "PASSED",
    "SSLv23Test.test_non_ssl_request": "PASSED",
    "SSLv23Test.test_ssl": "PASSED",
    "SSLv3Test.test_error_logging": "PASSED",
    "SSLv3Test.test_large_post": "PASSED",
    "SSLv3Test.test_non_ssl_request": "PASSED",
    "SSLv3Test.test_ssl": "PASSED",
    "StreamingChunkSizeTest.test_chunked_body": "PASSED",
    "StreamingChunkSizeTest.test_chunked_compressed": "PASSED",
    "StreamingChunkSizeTest.test_compressed_body": "PASSED",
    "StreamingChunkSizeTest.test_regular_body": "PASSED",
    "TLSv1Test.test_error_logging": "PASSED",
    "TLSv1Test.test_large_post": "PASSED",
    "TLSv1Test.test_non_ssl_request": "PASSED",
    "TLSv1Test.test_ssl": "PASSED",
    "UnixSocketTest.test_unix_socket": "PASSED",
    "UnixSocketTest.test_unix_socket_bad_request": "ERROR",
    "XHeaderTest.test_ip_headers": "PASSED",
    "XHeaderTest.test_scheme_headers": "PASSED",
    "XHeaderTest.test_trusted_downstream": "PASSED"
}
diff --git a/tornado/http1connection.py b/tornado/http1connection.py
index 72088d60..5ca91688 100644
--- a/tornado/http1connection.py
+++ b/tornado/http1connection.py
@@ -718,7 +718,7 @@ class _GzipMessageDelegate(httputil.HTTPMessageDelegate):
         start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
         headers: httputil.HTTPHeaders,
     ) -> Optional[Awaitable[None]]:
-        if headers.get("Content-Encoding") == "gzip":
+        if headers.get("Content-Encoding", "").lower() == "gzip":
             self._decompressor = GzipDecompressor()
             # Downstream delegates will only see uncompressed data,
             # so rename the content-encoding header.

{"tornado/http1connection.py": "#\n# Copyright 2014 Facebook\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\"); you may\n# not use this file except in compliance with the License. You may obtain\n# a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\n# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\n# License for the specific language governing permissions and limitations\n# under the License.\n\n\"\"\"Client and server implementations of HTTP/1.x.\n\n.. versionadded:: 4.0\n\"\"\"\n\nimport asyncio\nimport logging\nimport re\nimport types\n\nfrom tornado.concurrent import (\n    Future,\n    future_add_done_callback,\n    future_set_result_unless_cancelled,\n)\nfrom tornado.escape import native_str, utf8\nfrom tornado import gen\nfrom tornado import httputil\nfrom tornado import iostream\nfrom tornado.log import gen_log, app_log\nfrom tornado.util import GzipDecompressor\n\n\nfrom typing import cast, Optional, Type, Awaitable, Callable, Union, Tuple\n\n\nclass _QuietException(Exception):\n    def __init__(self) -> None:\n        pass\n\n\nclass _ExceptionLoggingContext(object):\n    \"\"\"Used with the ``with`` statement when calling delegate methods to\n    log any exceptions with the given logger.  Any exceptions caught are\n    converted to _QuietException\n    \"\"\"\n\n    def __init__(self, logger: logging.Logger) -> None:\n        self.logger = logger\n\n    def __enter__(self) -> None:\n        pass\n\n    def __exit__(\n        self,\n        typ: \"Optional[Type[BaseException]]\",\n        value: Optional[BaseException],\n        tb: types.TracebackType,\n    ) -> None:\n        if value is not None:\n            assert typ is not None\n            self.logger.error(\"Uncaught exception\", exc_info=(typ, value, tb))\n            raise _QuietException\n\n\nclass HTTP1ConnectionParameters(object):\n    \"\"\"Parameters for `.HTTP1Connection` and `.HTTP1ServerConnection`.\"\"\"\n\n    def __init__(\n        self,\n        no_keep_alive: bool = False,\n        chunk_size: Optional[int] = None,\n        max_header_size: Optional[int] = None,\n        header_timeout: Optional[float] = None,\n        max_body_size: Optional[int] = None,\n        body_timeout: Optional[float] = None,\n        decompress: bool = False,\n    ) -> None:\n        \"\"\"\n        :arg bool no_keep_alive: If true, always close the connection after\n            one request.\n        :arg int chunk_size: how much data to read into memory at once\n        :arg int max_header_size:  maximum amount of data for HTTP headers\n        :arg float header_timeout: how long to wait for all headers (seconds)\n        :arg int max_body_size: maximum amount of data for body\n        :arg float body_timeout: how long to wait while reading body (seconds)\n        :arg bool decompress: if true, decode incoming\n            ``Content-Encoding: gzip``\n        \"\"\"\n        self.no_keep_alive = no_keep_alive\n        self.chunk_size = chunk_size or 65536\n        self.max_header_size = max_header_size or 65536\n        self.header_timeout = header_timeout\n        self.max_body_size = max_body_size\n        self.body_timeout = body_timeout\n        self.decompress = decompress\n\n\nclass HTTP1Connection(httputil.HTTPConnection):\n    \"\"\"Implements the HTTP/1.x protocol.\n\n    This class can be on its own for clients, or via `HTTP1ServerConnection`\n    for servers.\n    \"\"\"\n\n    def __init__(\n        self,\n        stream: iostream.IOStream,\n        is_client: bool,\n        params: Optional[HTTP1ConnectionParameters] = None,\n        context: Optional[object] = None,\n    ) -> None:\n        \"\"\"\n        :arg stream: an `.IOStream`\n        :arg bool is_client: client or server\n        :arg params: a `.HTTP1ConnectionParameters` instance or ``None``\n        :arg context: an opaque application-defined object that can be accessed\n            as ``connection.context``.\n        \"\"\"\n        self.is_client = is_client\n        self.stream = stream\n        if params is None:\n            params = HTTP1ConnectionParameters()\n        self.params = params\n        self.context = context\n        self.no_keep_alive = params.no_keep_alive\n        # The body limits can be altered by the delegate, so save them\n        # here instead of just referencing self.params later.\n        self._max_body_size = (\n            self.params.max_body_size\n            if self.params.max_body_size is not None\n            else self.stream.max_buffer_size\n        )\n        self._body_timeout = self.params.body_timeout\n        # _write_finished is set to True when finish() has been called,\n        # i.e. there will be no more data sent.  Data may still be in the\n        # stream's write buffer.\n        self._write_finished = False\n        # True when we have read the entire incoming body.\n        self._read_finished = False\n        # _finish_future resolves when all data has been written and flushed\n        # to the IOStream.\n        self._finish_future = Future()  # type: Future[None]\n        # If true, the connection should be closed after this request\n        # (after the response has been written in the server side,\n        # and after it has been read in the client)\n        self._disconnect_on_finish = False\n        self._clear_callbacks()\n        # Save the start lines after we read or write them; they\n        # affect later processing (e.g. 304 responses and HEAD methods\n        # have content-length but no bodies)\n        self._request_start_line = None  # type: Optional[httputil.RequestStartLine]\n        self._response_start_line = None  # type: Optional[httputil.ResponseStartLine]\n        self._request_headers = None  # type: Optional[httputil.HTTPHeaders]\n        # True if we are writing output with chunked encoding.\n        self._chunking_output = False\n        # While reading a body with a content-length, this is the\n        # amount left to read.\n        self._expected_content_remaining = None  # type: Optional[int]\n        # A Future for our outgoing writes, returned by IOStream.write.\n        self._pending_write = None  # type: Optional[Future[None]]\n\n    def read_response(self, delegate: httputil.HTTPMessageDelegate) -> Awaitable[bool]:\n        \"\"\"Read a single HTTP response.\n\n        Typical client-mode usage is to write a request using `write_headers`,\n        `write`, and `finish`, and then call ``read_response``.\n\n        :arg delegate: a `.HTTPMessageDelegate`\n\n        Returns a `.Future` that resolves to a bool after the full response has\n        been read. The result is true if the stream is still open.\n        \"\"\"\n        if self.params.decompress:\n            delegate = _GzipMessageDelegate(delegate, self.params.chunk_size)\n        return self._read_message(delegate)\n\n    async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool:\n        need_delegate_close = False\n        try:\n            header_future = self.stream.read_until_regex(\n                b\"\\r?\\n\\r?\\n\", max_bytes=self.params.max_header_size\n            )\n            if self.params.header_timeout is None:\n                header_data = await header_future\n            else:\n                try:\n                    header_data = await gen.with_timeout(\n                        self.stream.io_loop.time() + self.params.header_timeout,\n                        header_future,\n                        quiet_exceptions=iostream.StreamClosedError,\n                    )\n                except gen.TimeoutError:\n                    self.close()\n                    return False\n            start_line_str, headers = self._parse_headers(header_data)\n            if self.is_client:\n                resp_start_line = httputil.parse_response_start_line(start_line_str)\n                self._response_start_line = resp_start_line\n                start_line = (\n                    resp_start_line\n                )  # type: Union[httputil.RequestStartLine, httputil.ResponseStartLine]\n                # TODO: this will need to change to support client-side keepalive\n                self._disconnect_on_finish = False\n            else:\n                req_start_line = httputil.parse_request_start_line(start_line_str)\n                self._request_start_line = req_start_line\n                self._request_headers = headers\n                start_line = req_start_line\n                self._disconnect_on_finish = not self._can_keep_alive(\n                    req_start_line, headers\n                )\n            need_delegate_close = True\n            with _ExceptionLoggingContext(app_log):\n                header_recv_future = delegate.headers_received(start_line, headers)\n                if header_recv_future is not None:\n                    await header_recv_future\n            if self.stream is None:\n                # We've been detached.\n                need_delegate_close = False\n                return False\n            skip_body = False\n            if self.is_client:\n                assert isinstance(start_line, httputil.ResponseStartLine)\n                if (\n                    self._request_start_line is not None\n                    and self._request_start_line.method == \"HEAD\"\n                ):\n                    skip_body = True\n                code = start_line.code\n                if code == 304:\n                    # 304 responses may include the content-length header\n                    # but do not actually have a body.\n                    # http://tools.ietf.org/html/rfc7230#section-3.3\n                    skip_body = True\n                if 100 <= code < 200:\n                    # 1xx responses should never indicate the presence of\n                    # a body.\n                    if \"Content-Length\" in headers or \"Transfer-Encoding\" in headers:\n                        raise httputil.HTTPInputError(\n                            \"Response code %d cannot have body\" % code\n                        )\n                    # TODO: client delegates will get headers_received twice\n                    # in the case of a 100-continue.  Document or change?\n                    await self._read_message(delegate)\n            else:\n                if headers.get(\"Expect\") == \"100-continue\" and not self._write_finished:\n                    self.stream.write(b\"HTTP/1.1 100 (Continue)\\r\\n\\r\\n\")\n            if not skip_body:\n                body_future = self._read_body(\n                    resp_start_line.code if self.is_client else 0, headers, delegate\n                )\n                if body_future is not None:\n                    if self._body_timeout is None:\n                        await body_future\n                    else:\n                        try:\n                            await gen.with_timeout(\n                                self.stream.io_loop.time() + self._body_timeout,\n                                body_future,\n                                quiet_exceptions=iostream.StreamClosedError,\n                            )\n                        except gen.TimeoutError:\n                            gen_log.info(\"Timeout reading body from %s\", self.context)\n                            self.stream.close()\n                            return False\n            self._read_finished = True\n            if not self._write_finished or self.is_client:\n                need_delegate_close = False\n                with _ExceptionLoggingContext(app_log):\n                    delegate.finish()\n            # If we're waiting for the application to produce an asynchronous\n            # response, and we're not detached, register a close callback\n            # on the stream (we didn't need one while we were reading)\n            if (\n                not self._finish_future.done()\n                and self.stream is not None\n                and not self.stream.closed()\n            ):\n                self.stream.set_close_callback(self._on_connection_close)\n                await self._finish_future\n            if self.is_client and self._disconnect_on_finish:\n                self.close()\n            if self.stream is None:\n                return False\n        except httputil.HTTPInputError as e:\n            gen_log.info(\"Malformed HTTP message from %s: %s\", self.context, e)\n            if not self.is_client:\n                await self.stream.write(b\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\")\n            self.close()\n            return False\n        finally:\n            if need_delegate_close:\n                with _ExceptionLoggingContext(app_log):\n                    delegate.on_connection_close()\n            header_future = None  # type: ignore\n            self._clear_callbacks()\n        return True\n\n    def _clear_callbacks(self) -> None:\n        \"\"\"Clears the callback attributes.\n\n        This allows the request handler to be garbage collected more\n        quickly in CPython by breaking up reference cycles.\n        \"\"\"\n        self._write_callback = None\n        self._write_future = None  # type: Optional[Future[None]]\n        self._close_callback = None  # type: Optional[Callable[[], None]]\n        if self.stream is not None:\n            self.stream.set_close_callback(None)\n\n    def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None:\n        \"\"\"Sets a callback that will be run when the connection is closed.\n\n        Note that this callback is slightly different from\n        `.HTTPMessageDelegate.on_connection_close`: The\n        `.HTTPMessageDelegate` method is called when the connection is\n        closed while receiving a message. This callback is used when\n        there is not an active delegate (for example, on the server\n        side this callback is used if the client closes the connection\n        after sending its request but before receiving all the\n        response.\n        \"\"\"\n        self._close_callback = callback\n\n    def _on_connection_close(self) -> None:\n        # Note that this callback is only registered on the IOStream\n        # when we have finished reading the request and are waiting for\n        # the application to produce its response.\n        if self._close_callback is not None:\n            callback = self._close_callback\n            self._close_callback = None\n            callback()\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n        self._clear_callbacks()\n\n    def close(self) -> None:\n        if self.stream is not None:\n            self.stream.close()\n        self._clear_callbacks()\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n\n    def detach(self) -> iostream.IOStream:\n        \"\"\"Take control of the underlying stream.\n\n        Returns the underlying `.IOStream` object and stops all further\n        HTTP processing.  May only be called during\n        `.HTTPMessageDelegate.headers_received`.  Intended for implementing\n        protocols like websockets that tunnel over an HTTP handshake.\n        \"\"\"\n        self._clear_callbacks()\n        stream = self.stream\n        self.stream = None  # type: ignore\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n        return stream\n\n    def set_body_timeout(self, timeout: float) -> None:\n        \"\"\"Sets the body timeout for a single request.\n\n        Overrides the value from `.HTTP1ConnectionParameters`.\n        \"\"\"\n        self._body_timeout = timeout\n\n    def set_max_body_size(self, max_body_size: int) -> None:\n        \"\"\"Sets the body size limit for a single request.\n\n        Overrides the value from `.HTTP1ConnectionParameters`.\n        \"\"\"\n        self._max_body_size = max_body_size\n\n    def write_headers(\n        self,\n        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],\n        headers: httputil.HTTPHeaders,\n        chunk: Optional[bytes] = None,\n    ) -> \"Future[None]\":\n        \"\"\"Implements `.HTTPConnection.write_headers`.\"\"\"\n        lines = []\n        if self.is_client:\n            assert isinstance(start_line, httputil.RequestStartLine)\n            self._request_start_line = start_line\n            lines.append(utf8(\"%s %s HTTP/1.1\" % (start_line[0], start_line[1])))\n            # Client requests with a non-empty body must have either a\n            # Content-Length or a Transfer-Encoding.\n            self._chunking_output = (\n                start_line.method in (\"POST\", \"PUT\", \"PATCH\")\n                and \"Content-Length\" not in headers\n                and (\n                    \"Transfer-Encoding\" not in headers\n                    or headers[\"Transfer-Encoding\"] == \"chunked\"\n                )\n            )\n        else:\n            assert isinstance(start_line, httputil.ResponseStartLine)\n            assert self._request_start_line is not None\n            assert self._request_headers is not None\n            self._response_start_line = start_line\n            lines.append(utf8(\"HTTP/1.1 %d %s\" % (start_line[1], start_line[2])))\n            self._chunking_output = (\n                # TODO: should this use\n                # self._request_start_line.version or\n                # start_line.version?\n                self._request_start_line.version == \"HTTP/1.1\"\n                # Omit payload header field for HEAD request.\n                and self._request_start_line.method != \"HEAD\"\n                # 1xx, 204 and 304 responses have no body (not even a zero-length\n                # body), and so should not have either Content-Length or\n                # Transfer-Encoding headers.\n                and start_line.code not in (204, 304)\n                and (start_line.code < 100 or start_line.code >= 200)\n                # No need to chunk the output if a Content-Length is specified.\n                and \"Content-Length\" not in headers\n                # Applications are discouraged from touching Transfer-Encoding,\n                # but if they do, leave it alone.\n                and \"Transfer-Encoding\" not in headers\n            )\n            # If connection to a 1.1 client will be closed, inform client\n            if (\n                self._request_start_line.version == \"HTTP/1.1\"\n                and self._disconnect_on_finish\n            ):\n                headers[\"Connection\"] = \"close\"\n            # If a 1.0 client asked for keep-alive, add the header.\n            if (\n                self._request_start_line.version == \"HTTP/1.0\"\n                and self._request_headers.get(\"Connection\", \"\").lower() == \"keep-alive\"\n            ):\n                headers[\"Connection\"] = \"Keep-Alive\"\n        if self._chunking_output:\n            headers[\"Transfer-Encoding\"] = \"chunked\"\n        if not self.is_client and (\n            self._request_start_line.method == \"HEAD\"\n            or cast(httputil.ResponseStartLine, start_line).code == 304\n        ):\n            self._expected_content_remaining = 0\n        elif \"Content-Length\" in headers:\n            self._expected_content_remaining = int(headers[\"Content-Length\"])\n        else:\n            self._expected_content_remaining = None\n        # TODO: headers are supposed to be of type str, but we still have some\n        # cases that let bytes slip through. Remove these native_str calls when those\n        # are fixed.\n        header_lines = (\n            native_str(n) + \": \" + native_str(v) for n, v in headers.get_all()\n        )\n        lines.extend(line.encode(\"latin1\") for line in header_lines)\n        for line in lines:\n            if b\"\\n\" in line:\n                raise ValueError(\"Newline in header: \" + repr(line))\n        future = None\n        if self.stream.closed():\n            future = self._write_future = Future()\n            future.set_exception(iostream.StreamClosedError())\n            future.exception()\n        else:\n            future = self._write_future = Future()\n            data = b\"\\r\\n\".join(lines) + b\"\\r\\n\\r\\n\"\n            if chunk:\n                data += self._format_chunk(chunk)\n            self._pending_write = self.stream.write(data)\n            future_add_done_callback(self._pending_write, self._on_write_complete)\n        return future\n\n    def _format_chunk(self, chunk: bytes) -> bytes:\n        if self._expected_content_remaining is not None:\n            self._expected_content_remaining -= len(chunk)\n            if self._expected_content_remaining < 0:\n                # Close the stream now to stop further framing errors.\n                self.stream.close()\n                raise httputil.HTTPOutputError(\n                    \"Tried to write more data than Content-Length\"\n                )\n        if self._chunking_output and chunk:\n            # Don't write out empty chunks because that means END-OF-STREAM\n            # with chunked encoding\n            return utf8(\"%x\" % len(chunk)) + b\"\\r\\n\" + chunk + b\"\\r\\n\"\n        else:\n            return chunk\n\n    def write(self, chunk: bytes) -> \"Future[None]\":\n        \"\"\"Implements `.HTTPConnection.write`.\n\n        For backwards compatibility it is allowed but deprecated to\n        skip `write_headers` and instead call `write()` with a\n        pre-encoded header block.\n        \"\"\"\n        future = None\n        if self.stream.closed():\n            future = self._write_future = Future()\n            self._write_future.set_exception(iostream.StreamClosedError())\n            self._write_future.exception()\n        else:\n            future = self._write_future = Future()\n            self._pending_write = self.stream.write(self._format_chunk(chunk))\n            future_add_done_callback(self._pending_write, self._on_write_complete)\n        return future\n\n    def finish(self) -> None:\n        \"\"\"Implements `.HTTPConnection.finish`.\"\"\"\n        if (\n            self._expected_content_remaining is not None\n            and self._expected_content_remaining != 0\n            and not self.stream.closed()\n        ):\n            self.stream.close()\n            raise httputil.HTTPOutputError(\n                \"Tried to write %d bytes less than Content-Length\"\n                % self._expected_content_remaining\n            )\n        if self._chunking_output:\n            if not self.stream.closed():\n                self._pending_write = self.stream.write(b\"0\\r\\n\\r\\n\")\n                self._pending_write.add_done_callback(self._on_write_complete)\n        self._write_finished = True\n        # If the app finished the request while we're still reading,\n        # divert any remaining data away from the delegate and\n        # close the connection when we're done sending our response.\n        # Closing the connection is the only way to avoid reading the\n        # whole input body.\n        if not self._read_finished:\n            self._disconnect_on_finish = True\n        # No more data is coming, so instruct TCP to send any remaining\n        # data immediately instead of waiting for a full packet or ack.\n        self.stream.set_nodelay(True)\n        if self._pending_write is None:\n            self._finish_request(None)\n        else:\n            future_add_done_callback(self._pending_write, self._finish_request)\n\n    def _on_write_complete(self, future: \"Future[None]\") -> None:\n        exc = future.exception()\n        if exc is not None and not isinstance(exc, iostream.StreamClosedError):\n            future.result()\n        if self._write_callback is not None:\n            callback = self._write_callback\n            self._write_callback = None\n            self.stream.io_loop.add_callback(callback)\n        if self._write_future is not None:\n            future = self._write_future\n            self._write_future = None\n            future_set_result_unless_cancelled(future, None)\n\n    def _can_keep_alive(\n        self, start_line: httputil.RequestStartLine, headers: httputil.HTTPHeaders\n    ) -> bool:\n        if self.params.no_keep_alive:\n            return False\n        connection_header = headers.get(\"Connection\")\n        if connection_header is not None:\n            connection_header = connection_header.lower()\n        if start_line.version == \"HTTP/1.1\":\n            return connection_header != \"close\"\n        elif (\n            \"Content-Length\" in headers\n            or headers.get(\"Transfer-Encoding\", \"\").lower() == \"chunked\"\n            or getattr(start_line, \"method\", None) in (\"HEAD\", \"GET\")\n        ):\n            # start_line may be a request or response start line; only\n            # the former has a method attribute.\n            return connection_header == \"keep-alive\"\n        return False\n\n    def _finish_request(self, future: \"Optional[Future[None]]\") -> None:\n        self._clear_callbacks()\n        if not self.is_client and self._disconnect_on_finish:\n            self.close()\n            return\n        # Turn Nagle's algorithm back on, leaving the stream in its\n        # default state for the next request.\n        self.stream.set_nodelay(False)\n        if not self._finish_future.done():\n            future_set_result_unless_cancelled(self._finish_future, None)\n\n    def _parse_headers(self, data: bytes) -> Tuple[str, httputil.HTTPHeaders]:\n        # The lstrip removes newlines that some implementations sometimes\n        # insert between messages of a reused connection.  Per RFC 7230,\n        # we SHOULD ignore at least one empty line before the request.\n        # http://tools.ietf.org/html/rfc7230#section-3.5\n        data_str = native_str(data.decode(\"latin1\")).lstrip(\"\\r\\n\")\n        # RFC 7230 section allows for both CRLF and bare LF.\n        eol = data_str.find(\"\\n\")\n        start_line = data_str[:eol].rstrip(\"\\r\")\n        headers = httputil.HTTPHeaders.parse(data_str[eol:])\n        return start_line, headers\n\n    def _read_body(\n        self,\n        code: int,\n        headers: httputil.HTTPHeaders,\n        delegate: httputil.HTTPMessageDelegate,\n    ) -> Optional[Awaitable[None]]:\n        if \"Content-Length\" in headers:\n            if \"Transfer-Encoding\" in headers:\n                # Response cannot contain both Content-Length and\n                # Transfer-Encoding headers.\n                # http://tools.ietf.org/html/rfc7230#section-3.3.3\n                raise httputil.HTTPInputError(\n                    \"Response with both Transfer-Encoding and Content-Length\"\n                )\n            if \",\" in headers[\"Content-Length\"]:\n                # Proxies sometimes cause Content-Length headers to get\n                # duplicated.  If all the values are identical then we can\n                # use them but if they differ it's an error.\n                pieces = re.split(r\",\\s*\", headers[\"Content-Length\"])\n                if any(i != pieces[0] for i in pieces):\n                    raise httputil.HTTPInputError(\n                        \"Multiple unequal Content-Lengths: %r\"\n                        % headers[\"Content-Length\"]\n                    )\n                headers[\"Content-Length\"] = pieces[0]\n\n            try:\n                content_length = int(headers[\"Content-Length\"])  # type: Optional[int]\n            except ValueError:\n                # Handles non-integer Content-Length value.\n                raise httputil.HTTPInputError(\n                    \"Only integer Content-Length is allowed: %s\"\n                    % headers[\"Content-Length\"]\n                )\n\n            if cast(int, content_length) > self._max_body_size:\n                raise httputil.HTTPInputError(\"Content-Length too long\")\n        else:\n            content_length = None\n\n        if code == 204:\n            # This response code is not allowed to have a non-empty body,\n            # and has an implicit length of zero instead of read-until-close.\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3\n            if \"Transfer-Encoding\" in headers or content_length not in (None, 0):\n                raise httputil.HTTPInputError(\n                    \"Response with code %d should not have body\" % code\n                )\n            content_length = 0\n\n        if content_length is not None:\n            return self._read_fixed_body(content_length, delegate)\n        if headers.get(\"Transfer-Encoding\", \"\").lower() == \"chunked\":\n            return self._read_chunked_body(delegate)\n        if self.is_client:\n            return self._read_body_until_close(delegate)\n        return None\n\n    async def _read_fixed_body(\n        self, content_length: int, delegate: httputil.HTTPMessageDelegate\n    ) -> None:\n        while content_length > 0:\n            body = await self.stream.read_bytes(\n                min(self.params.chunk_size, content_length), partial=True\n            )\n            content_length -= len(body)\n            if not self._write_finished or self.is_client:\n                with _ExceptionLoggingContext(app_log):\n                    ret = delegate.data_received(body)\n                    if ret is not None:\n                        await ret\n\n    async def _read_chunked_body(self, delegate: httputil.HTTPMessageDelegate) -> None:\n        # TODO: \"chunk extensions\" http://tools.ietf.org/html/rfc2616#section-3.6.1\n        total_size = 0\n        while True:\n            chunk_len_str = await self.stream.read_until(b\"\\r\\n\", max_bytes=64)\n            chunk_len = int(chunk_len_str.strip(), 16)\n            if chunk_len == 0:\n                crlf = await self.stream.read_bytes(2)\n                if crlf != b\"\\r\\n\":\n                    raise httputil.HTTPInputError(\n                        \"improperly terminated chunked request\"\n                    )\n                return\n            total_size += chunk_len\n            if total_size > self._max_body_size:\n                raise httputil.HTTPInputError(\"chunked body too large\")\n            bytes_to_read = chunk_len\n            while bytes_to_read:\n                chunk = await self.stream.read_bytes(\n                    min(bytes_to_read, self.params.chunk_size), partial=True\n                )\n                bytes_to_read -= len(chunk)\n                if not self._write_finished or self.is_client:\n                    with _ExceptionLoggingContext(app_log):\n                        ret = delegate.data_received(chunk)\n                        if ret is not None:\n                            await ret\n            # chunk ends with \\r\\n\n            crlf = await self.stream.read_bytes(2)\n            assert crlf == b\"\\r\\n\"\n\n    async def _read_body_until_close(\n        self, delegate: httputil.HTTPMessageDelegate\n    ) -> None:\n        body = await self.stream.read_until_close()\n        if not self._write_finished or self.is_client:\n            with _ExceptionLoggingContext(app_log):\n                ret = delegate.data_received(body)\n                if ret is not None:\n                    await ret\n\n\nclass _GzipMessageDelegate(httputil.HTTPMessageDelegate):\n    \"\"\"Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``.\"\"\"\n\n    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None:\n        self._delegate = delegate\n        self._chunk_size = chunk_size\n        self._decompressor = None  # type: Optional[GzipDecompressor]\n\n    def headers_received(\n        self,\n        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],\n        headers: httputil.HTTPHeaders,\n    ) -> Optional[Awaitable[None]]:\n        if headers.get(\"Content-Encoding\") == \"gzip\":\n            self._decompressor = GzipDecompressor()\n            # Downstream delegates will only see uncompressed data,\n            # so rename the content-encoding header.\n            # (but note that curl_httpclient doesn't do this).\n            headers.add(\"X-Consumed-Content-Encoding\", headers[\"Content-Encoding\"])\n            del headers[\"Content-Encoding\"]\n        return self._delegate.headers_received(start_line, headers)\n\n    async def data_received(self, chunk: bytes) -> None:\n        if self._decompressor:\n            compressed_data = chunk\n            while compressed_data:\n                decompressed = self._decompressor.decompress(\n                    compressed_data, self._chunk_size\n                )\n                if decompressed:\n                    ret = self._delegate.data_received(decompressed)\n                    if ret is not None:\n                        await ret\n                compressed_data = self._decompressor.unconsumed_tail\n                if compressed_data and not decompressed:\n                    raise httputil.HTTPInputError(\n                        \"encountered unconsumed gzip data without making progress\"\n                    )\n        else:\n            ret = self._delegate.data_received(chunk)\n            if ret is not None:\n                await ret\n\n    def finish(self) -> None:\n        if self._decompressor is not None:\n            tail = self._decompressor.flush()\n            if tail:\n                # The tail should always be empty: decompress returned\n                # all that it can in data_received and the only\n                # purpose of the flush call is to detect errors such\n                # as truncated input. If we did legitimately get a new\n                # chunk at this point we'd need to change the\n                # interface to make finish() a coroutine.\n                raise ValueError(\n                    \"decompressor.flush returned data; possible truncated input\"\n                )\n        return self._delegate.finish()\n\n    def on_connection_close(self) -> None:\n        return self._delegate.on_connection_close()\n\n\nclass HTTP1ServerConnection(object):\n    \"\"\"An HTTP/1.x server.\"\"\"\n\n    def __init__(\n        self,\n        stream: iostream.IOStream,\n        params: Optional[HTTP1ConnectionParameters] = None,\n        context: Optional[object] = None,\n    ) -> None:\n        \"\"\"\n        :arg stream: an `.IOStream`\n        :arg params: a `.HTTP1ConnectionParameters` or None\n        :arg context: an opaque application-defined object that is accessible\n            as ``connection.context``\n        \"\"\"\n        self.stream = stream\n        if params is None:\n            params = HTTP1ConnectionParameters()\n        self.params = params\n        self.context = context\n        self._serving_future = None  # type: Optional[Future[None]]\n\n    async def close(self) -> None:\n        \"\"\"Closes the connection.\n\n        Returns a `.Future` that resolves after the serving loop has exited.\n        \"\"\"\n        self.stream.close()\n        # Block until the serving loop is done, but ignore any exceptions\n        # (start_serving is already responsible for logging them).\n        assert self._serving_future is not None\n        try:\n            await self._serving_future\n        except Exception:\n            pass\n\n    def start_serving(self, delegate: httputil.HTTPServerConnectionDelegate) -> None:\n        \"\"\"Starts serving requests on this connection.\n\n        :arg delegate: a `.HTTPServerConnectionDelegate`\n        \"\"\"\n        assert isinstance(delegate, httputil.HTTPServerConnectionDelegate)\n        fut = gen.convert_yielded(self._server_request_loop(delegate))\n        self._serving_future = fut\n        # Register the future on the IOLoop so its errors get logged.\n        self.stream.io_loop.add_future(fut, lambda f: f.result())\n\n    async def _server_request_loop(\n        self, delegate: httputil.HTTPServerConnectionDelegate\n    ) -> None:\n        try:\n            while True:\n                conn = HTTP1Connection(self.stream, False, self.params, self.context)\n                request_delegate = delegate.start_request(self, conn)\n                try:\n                    ret = await conn.read_response(request_delegate)\n                except (\n                    iostream.StreamClosedError,\n                    iostream.UnsatisfiableReadError,\n                    asyncio.CancelledError,\n                ):\n                    return\n                except _QuietException:\n                    # This exception was already logged.\n                    conn.close()\n                    return\n                except Exception:\n                    gen_log.error(\"Uncaught exception\", exc_info=True)\n                    conn.close()\n                    return\n                if not ret:\n                    return\n                await asyncio.sleep(0)\n        finally:\n            delegate.on_close(self)\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Server Fails to Decode GZIP Content with Uppercase Content-Encoding Header

**Description:**
When sending a request with the `Content-Encoding` header set to `"GZIP"` (uppercase), the server does not decode the gzip-compressed body as expected. This behavior deviates from the HTTP specification, which states that header field values should be treated case-insensitively.

**Example Buggy Code:**
```python
import gzip
from io import BytesIO
from tornado.testing import AsyncHTTPTestCase
from tornado.web import Application, RequestHandler

class TestGzipEncoding(AsyncHTTPTestCase):
    def get_app(self):
        return Application([("/", GzipHandler)])

    def test_gzip_case_insensitive(self):
        bytes_io = BytesIO()
        gzip_file = gzip.GzipFile(mode="w", fileobj=bytes_io)
        gzip_file.write(b"foo=bar")
        gzip_file.close()
        compressed_body = bytes_io.getvalue()
        
        response = self.fetch(
            "/",
            method="POST",
            body=compressed_body,
            headers={"Content-Encoding": "GZIP"},  # Uppercase
        )
        # Expected to decode and return {'foo': ['bar']}
```

**Expected Behavior:**
When the server receives a request with the `Content-Encoding` header set to any case variation of `"gzip"` (e.g., `"gzip"`, `"GZIP"`, `"GzIp"`), it should correctly recognize the encoding and decompress the request body, returning the expected data structure.

**Actual Behavior:**
With the `Content-Encoding` header set to `"GZIP"`, the server fails to recognize the encoding due to case sensitivity, resulting in an empty response `{}` instead of the expected `{'foo': ['bar']}`.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### tornado/http1connection.py
#
# Copyright 2014 Facebook
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Client and server implementations of HTTP/1.x.

.. versionadded:: 4.0
"""

import asyncio
import logging
import re
import types

from tornado.concurrent import (
    Future,
    future_add_done_callback,
    future_set_result_unless_cancelled,
)
from tornado.escape import native_str, utf8
from tornado import gen
from tornado import httputil
from tornado import iostream
from tornado.log import gen_log, app_log
from tornado.util import GzipDecompressor


from typing import cast, Optional, Type, Awaitable, Callable, Union, Tuple


class _QuietException(Exception):
    def __init__(self) -> None:
        pass


class _ExceptionLoggingContext(object):
    """Used with the ``with`` statement when calling delegate methods to
    log any exceptions with the given logger.  Any exceptions caught are
    converted to _QuietException
    """

    def __init__(self, logger: logging.Logger) -> None:
        self.logger = logger

    def __enter__(self) -> None:
        pass

    def __exit__(
        self,
        typ: "Optional[Type[BaseException]]",
        value: Optional[BaseException],
        tb: types.TracebackType,
    ) -> None:
        if value is not None:
            assert typ is not None
            self.logger.error("Uncaught exception", exc_info=(typ, value, tb))
            raise _QuietException


class HTTP1ConnectionParameters(object):
    """Parameters for `.HTTP1Connection` and `.HTTP1ServerConnection`."""

    def __init__(
        self,
        no_keep_alive: bool = False,
        chunk_size: Optional[int] = None,
        max_header_size: Optional[int] = None,
        header_timeout: Optional[float] = None,
        max_body_size: Optional[int] = None,
        body_timeout: Optional[float] = None,
        decompress: bool = False,
    ) -> None:
        """
        :arg bool no_keep_alive: If true, always close the connection after
            one request.
        :arg int chunk_size: how much data to read into memory at once
        :arg int max_header_size:  maximum amount of data for HTTP headers
        :arg float header_timeout: how long to wait for all headers (seconds)
        :arg int max_body_size: maximum amount of data for body
        :arg float body_timeout: how long to wait while reading body (seconds)
        :arg bool decompress: if true, decode incoming
            ``Content-Encoding: gzip``
        """
        self.no_keep_alive = no_keep_alive
        self.chunk_size = chunk_size or 65536
        self.max_header_size = max_header_size or 65536
        self.header_timeout = header_timeout
        self.max_body_size = max_body_size
        self.body_timeout = body_timeout
        self.decompress = decompress


class HTTP1Connection(httputil.HTTPConnection):
    """Implements the HTTP/1.x protocol.

    This class can be on its own for clients, or via `HTTP1ServerConnection`
    for servers.
    """

    def __init__(
        self,
        stream: iostream.IOStream,
        is_client: bool,
        params: Optional[HTTP1ConnectionParameters] = None,
        context: Optional[object] = None,
    ) -> None:
        """
        :arg stream: an `.IOStream`
        :arg bool is_client: client or server
        :arg params: a `.HTTP1ConnectionParameters` instance or ``None``
        :arg context: an opaque application-defined object that can be accessed
            as ``connection.context``.
        """
        self.is_client = is_client
        self.stream = stream
        if params is None:
            params = HTTP1ConnectionParameters()
        self.params = params
        self.context = context
        self.no_keep_alive = params.no_keep_alive
        # The body limits can be altered by the delegate, so save them
        # here instead of just referencing self.params later.
        self._max_body_size = (
            self.params.max_body_size
            if self.params.max_body_size is not None
            else self.stream.max_buffer_size
        )
        self._body_timeout = self.params.body_timeout
        # _write_finished is set to True when finish() has been called,
        # i.e. there will be no more data sent.  Data may still be in the
        # stream's write buffer.
        self._write_finished = False
        # True when we have read the entire incoming body.
        self._read_finished = False
        # _finish_future resolves when all data has been written and flushed
        # to the IOStream.
        self._finish_future = Future()  # type: Future[None]
        # If true, the connection should be closed after this request
        # (after the response has been written in the server side,
        # and after it has been read in the client)
        self._disconnect_on_finish = False
        self._clear_callbacks()
        # Save the start lines after we read or write them; they
        # affect later processing (e.g. 304 responses and HEAD methods
        # have content-length but no bodies)
        self._request_start_line = None  # type: Optional[httputil.RequestStartLine]
        self._response_start_line = None  # type: Optional[httputil.ResponseStartLine]
        self._request_headers = None  # type: Optional[httputil.HTTPHeaders]
        # True if we are writing output with chunked encoding.
        self._chunking_output = False
        # While reading a body with a content-length, this is the
        # amount left to read.
        self._expected_content_remaining = None  # type: Optional[int]
        # A Future for our outgoing writes, returned by IOStream.write.
        self._pending_write = None  # type: Optional[Future[None]]

    def read_response(self, delegate: httputil.HTTPMessageDelegate) -> Awaitable[bool]:
        """Read a single HTTP response.

        Typical client-mode usage is to write a request using `write_headers`,
        `write`, and `finish`, and then call ``read_response``.

        :arg delegate: a `.HTTPMessageDelegate`

        Returns a `.Future` that resolves to a bool after the full response has
        been read. The result is true if the stream is still open.
        """
        if self.params.decompress:
            delegate = _GzipMessageDelegate(delegate, self.params.chunk_size)
        return self._read_message(delegate)

    async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool:
        need_delegate_close = False
        try:
            header_future = self.stream.read_until_regex(
                b"\r?\n\r?\n", max_bytes=self.params.max_header_size
            )
            if self.params.header_timeout is None:
                header_data = await header_future
            else:
                try:
                    header_data = await gen.with_timeout(
                        self.stream.io_loop.time() + self.params.header_timeout,
                        header_future,
                        quiet_exceptions=iostream.StreamClosedError,
                    )
                except gen.TimeoutError:
                    self.close()
                    return False
            start_line_str, headers = self._parse_headers(header_data)
            if self.is_client:
                resp_start_line = httputil.parse_response_start_line(start_line_str)
                self._response_start_line = resp_start_line
                start_line = (
                    resp_start_line
                )  # type: Union[httputil.RequestStartLine, httputil.ResponseStartLine]
                # TODO: this will need to change to support client-side keepalive
                self._disconnect_on_finish = False
            else:
                req_start_line = httputil.parse_request_start_line(start_line_str)
                self._request_start_line = req_start_line
                self._request_headers = headers
                start_line = req_start_line
                self._disconnect_on_finish = not self._can_keep_alive(
                    req_start_line, headers
                )
            need_delegate_close = True
            with _ExceptionLoggingContext(app_log):
                header_recv_future = delegate.headers_received(start_line, headers)
                if header_recv_future is not None:
                    await header_recv_future
            if self.stream is None:
                # We've been detached.
                need_delegate_close = False
                return False
            skip_body = False
            if self.is_client:
                assert isinstance(start_line, httputil.ResponseStartLine)
                if (
                    self._request_start_line is not None
                    and self._request_start_line.method == "HEAD"
                ):
                    skip_body = True
                code = start_line.code
                if code == 304:
                    # 304 responses may include the content-length header
                    # but do not actually have a body.
                    # http://tools.ietf.org/html/rfc7230#section-3.3
                    skip_body = True
                if 100 <= code < 200:
                    # 1xx responses should never indicate the presence of
                    # a body.
                    if "Content-Length" in headers or "Transfer-Encoding" in headers:
                        raise httputil.HTTPInputError(
                            "Response code %d cannot have body" % code
                        )
                    # TODO: client delegates will get headers_received twice
                    # in the case of a 100-continue.  Document or change?
                    await self._read_message(delegate)
            else:
                if headers.get("Expect") == "100-continue" and not self._write_finished:
                    self.stream.write(b"HTTP/1.1 100 (Continue)\r\n\r\n")
            if not skip_body:
                body_future = self._read_body(
                    resp_start_line.code if self.is_client else 0, headers, delegate
                )
                if body_future is not None:
                    if self._body_timeout is None:
                        await body_future
                    else:
                        try:
                            await gen.with_timeout(
                                self.stream.io_loop.time() + self._body_timeout,
                                body_future,
                                quiet_exceptions=iostream.StreamClosedError,
                            )
                        except gen.TimeoutError:
                            gen_log.info("Timeout reading body from %s", self.context)
                            self.stream.close()
                            return False
            self._read_finished = True
            if not self._write_finished or self.is_client:
                need_delegate_close = False
                with _ExceptionLoggingContext(app_log):
                    delegate.finish()
            # If we're waiting for the application to produce an asynchronous
            # response, and we're not detached, register a close callback
            # on the stream (we didn't need one while we were reading)
            if (
                not self._finish_future.done()
                and self.stream is not None
                and not self.stream.closed()
            ):
                self.stream.set_close_callback(self._on_connection_close)
                await self._finish_future
            if self.is_client and self._disconnect_on_finish:
                self.close()
            if self.stream is None:
                return False
        except httputil.HTTPInputError as e:
            gen_log.info("Malformed HTTP message from %s: %s", self.context, e)
            if not self.is_client:
                await self.stream.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
            self.close()
            return False
        finally:
            if need_delegate_close:
                with _ExceptionLoggingContext(app_log):
                    delegate.on_connection_close()
            header_future = None  # type: ignore
            self._clear_callbacks()
        return True

    def _clear_callbacks(self) -> None:
        """Clears the callback attributes.

        This allows the request handler to be garbage collected more
        quickly in CPython by breaking up reference cycles.
        """
        self._write_callback = None
        self._write_future = None  # type: Optional[Future[None]]
        self._close_callback = None  # type: Optional[Callable[[], None]]
        if self.stream is not None:
            self.stream.set_close_callback(None)

    def set_close_callback(self, callback: Optional[Callable[[], None]]) -> None:
        """Sets a callback that will be run when the connection is closed.

        Note that this callback is slightly different from
        `.HTTPMessageDelegate.on_connection_close`: The
        `.HTTPMessageDelegate` method is called when the connection is
        closed while receiving a message. This callback is used when
        there is not an active delegate (for example, on the server
        side this callback is used if the client closes the connection
        after sending its request but before receiving all the
        response.
        """
        self._close_callback = callback

    def _on_connection_close(self) -> None:
        # Note that this callback is only registered on the IOStream
        # when we have finished reading the request and are waiting for
        # the application to produce its response.
        if self._close_callback is not None:
            callback = self._close_callback
            self._close_callback = None
            callback()
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)
        self._clear_callbacks()

    def close(self) -> None:
        if self.stream is not None:
            self.stream.close()
        self._clear_callbacks()
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)

    def detach(self) -> iostream.IOStream:
        """Take control of the underlying stream.

        Returns the underlying `.IOStream` object and stops all further
        HTTP processing.  May only be called during
        `.HTTPMessageDelegate.headers_received`.  Intended for implementing
        protocols like websockets that tunnel over an HTTP handshake.
        """
        self._clear_callbacks()
        stream = self.stream
        self.stream = None  # type: ignore
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)
        return stream

    def set_body_timeout(self, timeout: float) -> None:
        """Sets the body timeout for a single request.

        Overrides the value from `.HTTP1ConnectionParameters`.
        """
        self._body_timeout = timeout

    def set_max_body_size(self, max_body_size: int) -> None:
        """Sets the body size limit for a single request.

        Overrides the value from `.HTTP1ConnectionParameters`.
        """
        self._max_body_size = max_body_size

    def write_headers(
        self,
        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
        headers: httputil.HTTPHeaders,
        chunk: Optional[bytes] = None,
    ) -> "Future[None]":
        """Implements `.HTTPConnection.write_headers`."""
        lines = []
        if self.is_client:
            assert isinstance(start_line, httputil.RequestStartLine)
            self._request_start_line = start_line
            lines.append(utf8("%s %s HTTP/1.1" % (start_line[0], start_line[1])))
            # Client requests with a non-empty body must have either a
            # Content-Length or a Transfer-Encoding.
            self._chunking_output = (
                start_line.method in ("POST", "PUT", "PATCH")
                and "Content-Length" not in headers
                and (
                    "Transfer-Encoding" not in headers
                    or headers["Transfer-Encoding"] == "chunked"
                )
            )
        else:
            assert isinstance(start_line, httputil.ResponseStartLine)
            assert self._request_start_line is not None
            assert self._request_headers is not None
            self._response_start_line = start_line
            lines.append(utf8("HTTP/1.1 %d %s" % (start_line[1], start_line[2])))
            self._chunking_output = (
                # TODO: should this use
                # self._request_start_line.version or
                # start_line.version?
                self._request_start_line.version == "HTTP/1.1"
                # Omit payload header field for HEAD request.
                and self._request_start_line.method != "HEAD"
                # 1xx, 204 and 304 responses have no body (not even a zero-length
                # body), and so should not have either Content-Length or
                # Transfer-Encoding headers.
                and start_line.code not in (204, 304)
                and (start_line.code < 100 or start_line.code >= 200)
                # No need to chunk the output if a Content-Length is specified.
                and "Content-Length" not in headers
                # Applications are discouraged from touching Transfer-Encoding,
                # but if they do, leave it alone.
                and "Transfer-Encoding" not in headers
            )
            # If connection to a 1.1 client will be closed, inform client
            if (
                self._request_start_line.version == "HTTP/1.1"
                and self._disconnect_on_finish
            ):
                headers["Connection"] = "close"
            # If a 1.0 client asked for keep-alive, add the header.
            if (
                self._request_start_line.version == "HTTP/1.0"
                and self._request_headers.get("Connection", "").lower() == "keep-alive"
            ):
                headers["Connection"] = "Keep-Alive"
        if self._chunking_output:
            headers["Transfer-Encoding"] = "chunked"
        if not self.is_client and (
            self._request_start_line.method == "HEAD"
            or cast(httputil.ResponseStartLine, start_line).code == 304
        ):
            self._expected_content_remaining = 0
        elif "Content-Length" in headers:
            self._expected_content_remaining = int(headers["Content-Length"])
        else:
            self._expected_content_remaining = None
        # TODO: headers are supposed to be of type str, but we still have some
        # cases that let bytes slip through. Remove these native_str calls when those
        # are fixed.
        header_lines = (
            native_str(n) + ": " + native_str(v) for n, v in headers.get_all()
        )
        lines.extend(line.encode("latin1") for line in header_lines)
        for line in lines:
            if b"\n" in line:
                raise ValueError("Newline in header: " + repr(line))
        future = None
        if self.stream.closed():
            future = self._write_future = Future()
            future.set_exception(iostream.StreamClosedError())
            future.exception()
        else:
            future = self._write_future = Future()
            data = b"\r\n".join(lines) + b"\r\n\r\n"
            if chunk:
                data += self._format_chunk(chunk)
            self._pending_write = self.stream.write(data)
            future_add_done_callback(self._pending_write, self._on_write_complete)
        return future

    def _format_chunk(self, chunk: bytes) -> bytes:
        if self._expected_content_remaining is not None:
            self._expected_content_remaining -= len(chunk)
            if self._expected_content_remaining < 0:
                # Close the stream now to stop further framing errors.
                self.stream.close()
                raise httputil.HTTPOutputError(
                    "Tried to write more data than Content-Length"
                )
        if self._chunking_output and chunk:
            # Don't write out empty chunks because that means END-OF-STREAM
            # with chunked encoding
            return utf8("%x" % len(chunk)) + b"\r\n" + chunk + b"\r\n"
        else:
            return chunk

    def write(self, chunk: bytes) -> "Future[None]":
        """Implements `.HTTPConnection.write`.

        For backwards compatibility it is allowed but deprecated to
        skip `write_headers` and instead call `write()` with a
        pre-encoded header block.
        """
        future = None
        if self.stream.closed():
            future = self._write_future = Future()
            self._write_future.set_exception(iostream.StreamClosedError())
            self._write_future.exception()
        else:
            future = self._write_future = Future()
            self._pending_write = self.stream.write(self._format_chunk(chunk))
            future_add_done_callback(self._pending_write, self._on_write_complete)
        return future

    def finish(self) -> None:
        """Implements `.HTTPConnection.finish`."""
        if (
            self._expected_content_remaining is not None
            and self._expected_content_remaining != 0
            and not self.stream.closed()
        ):
            self.stream.close()
            raise httputil.HTTPOutputError(
                "Tried to write %d bytes less than Content-Length"
                % self._expected_content_remaining
            )
        if self._chunking_output:
            if not self.stream.closed():
                self._pending_write = self.stream.write(b"0\r\n\r\n")
                self._pending_write.add_done_callback(self._on_write_complete)
        self._write_finished = True
        # If the app finished the request while we're still reading,
        # divert any remaining data away from the delegate and
        # close the connection when we're done sending our response.
        # Closing the connection is the only way to avoid reading the
        # whole input body.
        if not self._read_finished:
            self._disconnect_on_finish = True
        # No more data is coming, so instruct TCP to send any remaining
        # data immediately instead of waiting for a full packet or ack.
        self.stream.set_nodelay(True)
        if self._pending_write is None:
            self._finish_request(None)
        else:
            future_add_done_callback(self._pending_write, self._finish_request)

    def _on_write_complete(self, future: "Future[None]") -> None:
        exc = future.exception()
        if exc is not None and not isinstance(exc, iostream.StreamClosedError):
            future.result()
        if self._write_callback is not None:
            callback = self._write_callback
            self._write_callback = None
            self.stream.io_loop.add_callback(callback)
        if self._write_future is not None:
            future = self._write_future
            self._write_future = None
            future_set_result_unless_cancelled(future, None)

    def _can_keep_alive(
        self, start_line: httputil.RequestStartLine, headers: httputil.HTTPHeaders
    ) -> bool:
        if self.params.no_keep_alive:
            return False
        connection_header = headers.get("Connection")
        if connection_header is not None:
            connection_header = connection_header.lower()
        if start_line.version == "HTTP/1.1":
            return connection_header != "close"
        elif (
            "Content-Length" in headers
            or headers.get("Transfer-Encoding", "").lower() == "chunked"
            or getattr(start_line, "method", None) in ("HEAD", "GET")
        ):
            # start_line may be a request or response start line; only
            # the former has a method attribute.
            return connection_header == "keep-alive"
        return False

    def _finish_request(self, future: "Optional[Future[None]]") -> None:
        self._clear_callbacks()
        if not self.is_client and self._disconnect_on_finish:
            self.close()
            return
        # Turn Nagle's algorithm back on, leaving the stream in its
        # default state for the next request.
        self.stream.set_nodelay(False)
        if not self._finish_future.done():
            future_set_result_unless_cancelled(self._finish_future, None)

    def _parse_headers(self, data: bytes) -> Tuple[str, httputil.HTTPHeaders]:
        # The lstrip removes newlines that some implementations sometimes
        # insert between messages of a reused connection.  Per RFC 7230,
        # we SHOULD ignore at least one empty line before the request.
        # http://tools.ietf.org/html/rfc7230#section-3.5
        data_str = native_str(data.decode("latin1")).lstrip("\r\n")
        # RFC 7230 section allows for both CRLF and bare LF.
        eol = data_str.find("\n")
        start_line = data_str[:eol].rstrip("\r")
        headers = httputil.HTTPHeaders.parse(data_str[eol:])
        return start_line, headers

    def _read_body(
        self,
        code: int,
        headers: httputil.HTTPHeaders,
        delegate: httputil.HTTPMessageDelegate,
    ) -> Optional[Awaitable[None]]:
        if "Content-Length" in headers:
            if "Transfer-Encoding" in headers:
                # Response cannot contain both Content-Length and
                # Transfer-Encoding headers.
                # http://tools.ietf.org/html/rfc7230#section-3.3.3
                raise httputil.HTTPInputError(
                    "Response with both Transfer-Encoding and Content-Length"
                )
            if "," in headers["Content-Length"]:
                # Proxies sometimes cause Content-Length headers to get
                # duplicated.  If all the values are identical then we can
                # use them but if they differ it's an error.
                pieces = re.split(r",\s*", headers["Content-Length"])
                if any(i != pieces[0] for i in pieces):
                    raise httputil.HTTPInputError(
                        "Multiple unequal Content-Lengths: %r"
                        % headers["Content-Length"]
                    )
                headers["Content-Length"] = pieces[0]

            try:
                content_length = int(headers["Content-Length"])  # type: Optional[int]
            except ValueError:
                # Handles non-integer Content-Length value.
                raise httputil.HTTPInputError(
                    "Only integer Content-Length is allowed: %s"
                    % headers["Content-Length"]
                )

            if cast(int, content_length) > self._max_body_size:
                raise httputil.HTTPInputError("Content-Length too long")
        else:
            content_length = None

        if code == 204:
            # This response code is not allowed to have a non-empty body,
            # and has an implicit length of zero instead of read-until-close.
            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
            if "Transfer-Encoding" in headers or content_length not in (None, 0):
                raise httputil.HTTPInputError(
                    "Response with code %d should not have body" % code
                )
            content_length = 0

        if content_length is not None:
            return self._read_fixed_body(content_length, delegate)
        if headers.get("Transfer-Encoding", "").lower() == "chunked":
            return self._read_chunked_body(delegate)
        if self.is_client:
            return self._read_body_until_close(delegate)
        return None

    async def _read_fixed_body(
        self, content_length: int, delegate: httputil.HTTPMessageDelegate
    ) -> None:
        while content_length > 0:
            body = await self.stream.read_bytes(
                min(self.params.chunk_size, content_length), partial=True
            )
            content_length -= len(body)
            if not self._write_finished or self.is_client:
                with _ExceptionLoggingContext(app_log):
                    ret = delegate.data_received(body)
                    if ret is not None:
                        await ret

    async def _read_chunked_body(self, delegate: httputil.HTTPMessageDelegate) -> None:
        # TODO: "chunk extensions" http://tools.ietf.org/html/rfc2616#section-3.6.1
        total_size = 0
        while True:
            chunk_len_str = await self.stream.read_until(b"\r\n", max_bytes=64)
            chunk_len = int(chunk_len_str.strip(), 16)
            if chunk_len == 0:
                crlf = await self.stream.read_bytes(2)
                if crlf != b"\r\n":
                    raise httputil.HTTPInputError(
                        "improperly terminated chunked request"
                    )
                return
            total_size += chunk_len
            if total_size > self._max_body_size:
                raise httputil.HTTPInputError("chunked body too large")
            bytes_to_read = chunk_len
            while bytes_to_read:
                chunk = await self.stream.read_bytes(
                    min(bytes_to_read, self.params.chunk_size), partial=True
                )
                bytes_to_read -= len(chunk)
                if not self._write_finished or self.is_client:
                    with _ExceptionLoggingContext(app_log):
                        ret = delegate.data_received(chunk)
                        if ret is not None:
                            await ret
            # chunk ends with \r\n
            crlf = await self.stream.read_bytes(2)
            assert crlf == b"\r\n"

    async def _read_body_until_close(
        self, delegate: httputil.HTTPMessageDelegate
    ) -> None:
        body = await self.stream.read_until_close()
        if not self._write_finished or self.is_client:
            with _ExceptionLoggingContext(app_log):
                ret = delegate.data_received(body)
                if ret is not None:
                    await ret


class _GzipMessageDelegate(httputil.HTTPMessageDelegate):
    """Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``."""

    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None:
        self._delegate = delegate
        self._chunk_size = chunk_size
        self._decompressor = None  # type: Optional[GzipDecompressor]

    def headers_received(
        self,
        start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
        headers: httputil.HTTPHeaders,
    ) -> Optional[Awaitable[None]]:
        if headers.get("Content-Encoding") == "gzip":
            self._decompressor = GzipDecompressor()
            # Downstream delegates will only see uncompressed data,
            # so rename the content-encoding header.
            # (but note that curl_httpclient doesn't do this).
            headers.add("X-Consumed-Content-Encoding", headers["Content-Encoding"])
            del headers["Content-Encoding"]
        return self._delegate.headers_received(start_line, headers)

    async def data_received(self, chunk: bytes) -> None:
        if self._decompressor:
            compressed_data = chunk
            while compressed_data:
                decompressed = self._decompressor.decompress(
                    compressed_data, self._chunk_size
                )
                if decompressed:
                    ret = self._delegate.data_received(decompressed)
                    if ret is not None:
                        await ret
                compressed_data = self._decompressor.unconsumed_tail
                if compressed_data and not decompressed:
                    raise httputil.HTTPInputError(
                        "encountered unconsumed gzip data without making progress"
                    )
        else:
            ret = self._delegate.data_received(chunk)
            if ret is not None:
                await ret

    def finish(self) -> None:
        if self._decompressor is not None:
            tail = self._decompressor.flush()
            if tail:
                # The tail should always be empty: decompress returned
                # all that it can in data_received and the only
                # purpose of the flush call is to detect errors such
                # as truncated input. If we did legitimately get a new
                # chunk at this point we'd need to change the
                # interface to make finish() a coroutine.
                raise ValueError(
                    "decompressor.flush returned data; possible truncated input"
                )
        return self._delegate.finish()

    def on_connection_close(self) -> None:
        return self._delegate.on_connection_close()


class HTTP1ServerConnection(object):
    """An HTTP/1.x server."""

    def __init__(
        self,
        stream: iostream.IOStream,
        params: Optional[HTTP1ConnectionParameters] = None,
        context: Optional[object] = None,
    ) -> None:
        """
        :arg stream: an `.IOStream`
        :arg params: a `.HTTP1ConnectionParameters` or None
        :arg context: an opaque application-defined object that is accessible
            as ``connection.context``
        """
        self.stream = stream
        if params is None:
            params = HTTP1ConnectionParameters()
        self.params = params
        self.context = context
        self._serving_future = None  # type: Optional[Future[None]]

    async def close(self) -> None:
        """Closes the connection.

        Returns a `.Future` that resolves after the serving loop has exited.
        """
        self.stream.close()
        # Block until the serving loop is done, but ignore any exceptions
        # (start_serving is already responsible for logging them).
        assert self._serving_future is not None
        try:
            await self._serving_future
        except Exception:
            pass

    def start_serving(self, delegate: httputil.HTTPServerConnectionDelegate) -> None:
        """Starts serving requests on this connection.

        :arg delegate: a `.HTTPServerConnectionDelegate`
        """
        assert isinstance(delegate, httputil.HTTPServerConnectionDelegate)
        fut = gen.convert_yielded(self._server_request_loop(delegate))
        self._serving_future = fut
        # Register the future on the IOLoop so its errors get logged.
        self.stream.io_loop.add_future(fut, lambda f: f.result())

    async def _server_request_loop(
        self, delegate: httputil.HTTPServerConnectionDelegate
    ) -> None:
        try:
            while True:
                conn = HTTP1Connection(self.stream, False, self.params, self.context)
                request_delegate = delegate.start_request(self, conn)
                try:
                    ret = await conn.read_response(request_delegate)
                except (
                    iostream.StreamClosedError,
                    iostream.UnsatisfiableReadError,
                    asyncio.CancelledError,
                ):
                    return
                except _QuietException:
                    # This exception was already logged.
                    conn.close()
                    return
                except Exception:
                    gen_log.error("Uncaught exception", exc_info=True)
                    conn.close()
                    return
                if not ret:
                    return
                await asyncio.sleep(0)
        finally:
            delegate.on_close(self)

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user