{
    "file_diffs": [
        {
            "old_file_content": "#!/usr/bin/env python\n#\n# Copyright 2009 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\"\"\"Non-blocking HTTP client implementation using pycurl.\"\"\"\n\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nimport collections\nimport logging\nimport pycurl\nimport threading\nimport time\n\nfrom tornado import httputil\nfrom tornado import ioloop\nfrom tornado.log import gen_log\nfrom tornado import stack_context\n\nfrom tornado.escape import utf8, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main\nfrom tornado.util import bytes_type\n\ntry:\n    from io import BytesIO  # py3\nexcept ImportError:\n    from cStringIO import StringIO as BytesIO  # py2\n\n\nclass CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, io_loop, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [_curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        try:\n            self._socket_action = self._multi.socket_action\n        except AttributeError:\n            # socket_action is found in pycurl since 7.18.2 (it's been\n            # in libcurl longer than that but wasn't accessible to\n            # python).\n            gen_log.warning(\"socket_action method missing from pycurl; \"\n                            \"falling back to socket_all. Upgrading \"\n                            \"libcurl and pycurl will improve performance\")\n            self._socket_action = \\\n                lambda fd, action: self._multi.socket_all()\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000, io_loop=io_loop)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        self._closed = True\n        super(CurlAsyncHTTPClient, self).close()\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    # Disable IPv6 to mitigate the effects of this bug\n                    # on curl versions <= 7.21.0\n                    # http://sourceforge.net/tracker/?func=detail&aid=3017819&group_id=976&atid=100976\n                    if pycurl.version_info()[2] <= 0x71500:  # 7.21.0\n                        curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n                    _curl_setup_request(curl, request, curl.info[\"buffer\"],\n                                        curl.info[\"headers\"])\n                    self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n\nclass CurlError(HTTPError):\n    def __init__(self, errno, message):\n        HTTPError.__init__(self, 599, message)\n        self.errno = errno\n\n\ndef _curl_create():\n    curl = pycurl.Curl()\n    if gen_log.isEnabledFor(logging.DEBUG):\n        curl.setopt(pycurl.VERBOSE, 1)\n        curl.setopt(pycurl.DEBUGFUNCTION, _curl_debug)\n    return curl\n\n\ndef _curl_setup_request(curl, request, buffer, headers):\n    curl.setopt(pycurl.URL, native_str(request.url))\n\n    # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n    # with servers that don't support it (which include, among others,\n    # Google's OpenID endpoint).  Additionally, this behavior has\n    # a bug in conjunction with the curl_multi_socket_action API\n    # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n    # which increases the delays.  It's more trouble than it's worth,\n    # so just turn off the feature (yes, setting Expect: to an empty\n    # value is the official way to disable this)\n    if \"Expect\" not in request.headers:\n        request.headers[\"Expect\"] = \"\"\n\n    # libcurl adds Pragma: no-cache by default; disable that too\n    if \"Pragma\" not in request.headers:\n        request.headers[\"Pragma\"] = \"\"\n\n    # Request headers may be either a regular dict or HTTPHeaders object\n    if isinstance(request.headers, httputil.HTTPHeaders):\n        curl.setopt(pycurl.HTTPHEADER,\n                    [native_str(\"%s: %s\" % i) for i in request.headers.get_all()])\n    else:\n        curl.setopt(pycurl.HTTPHEADER,\n                    [native_str(\"%s: %s\" % i) for i in request.headers.items()])\n\n    if request.header_callback:\n        curl.setopt(pycurl.HEADERFUNCTION, request.header_callback)\n    else:\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    lambda line: _curl_header_callback(headers, line))\n    if request.streaming_callback:\n        write_function = request.streaming_callback\n    else:\n        write_function = buffer.write\n    if bytes_type is str:  # py2\n        curl.setopt(pycurl.WRITEFUNCTION, write_function)\n    else:  # py3\n        # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n        # a fork/port.  That version has a bug in which it passes unicode\n        # strings instead of bytes to the WRITEFUNCTION.  This means that\n        # if you use a WRITEFUNCTION (which tornado always does), you cannot\n        # download arbitrary binary data.  This needs to be fixed in the\n        # ported pycurl package, but in the meantime this lambda will\n        # make it work for downloading (utf8) text.\n        curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n    curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n    curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n    curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n    curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n    if request.user_agent:\n        curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n    else:\n        curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n    if request.network_interface:\n        curl.setopt(pycurl.INTERFACE, request.network_interface)\n    if request.use_gzip:\n        curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n    else:\n        curl.setopt(pycurl.ENCODING, \"none\")\n    if request.proxy_host and request.proxy_port:\n        curl.setopt(pycurl.PROXY, request.proxy_host)\n        curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n        if request.proxy_username:\n            credentials = '%s:%s' % (request.proxy_username,\n                                     request.proxy_password)\n            curl.setopt(pycurl.PROXYUSERPWD, credentials)\n    else:\n        curl.setopt(pycurl.PROXY, '')\n        curl.unsetopt(pycurl.PROXYUSERPWD)\n    if request.validate_cert:\n        curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n        curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n    else:\n        curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n        curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n    if request.ca_certs is not None:\n        curl.setopt(pycurl.CAINFO, request.ca_certs)\n    else:\n        # There is no way to restore pycurl.CAINFO to its default value\n        # (Using unsetopt makes it reject all certificates).\n        # I don't see any way to read the default value from python so it\n        # can be restored later.  We'll have to just leave CAINFO untouched\n        # if no ca_certs file was specified, and require that if any\n        # request uses a custom ca_certs file, they all must.\n        pass\n\n    if request.allow_ipv6 is False:\n        # Curl behaves reasonably when DNS resolution gives an ipv6 address\n        # that we can't reach, so allow ipv6 unless the user asks to disable.\n        # (but see version check in _process_queue above)\n        curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n    else:\n        curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n    # Set the request method through curl's irritating interface which makes\n    # up names for almost every single method\n    curl_options = {\n        \"GET\": pycurl.HTTPGET,\n        \"POST\": pycurl.POST,\n        \"PUT\": pycurl.UPLOAD,\n        \"HEAD\": pycurl.NOBODY,\n    }\n    custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n    for o in curl_options.values():\n        curl.setopt(o, False)\n    if request.method in curl_options:\n        curl.unsetopt(pycurl.CUSTOMREQUEST)\n        curl.setopt(curl_options[request.method], True)\n    elif request.allow_nonstandard_methods or request.method in custom_methods:\n        curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n    else:\n        raise KeyError('unknown method ' + request.method)\n\n    # Handle curl's cryptic options for every individual HTTP method\n    if request.method in (\"POST\", \"PUT\"):\n        request_buffer = BytesIO(utf8(request.body))\n        curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n        if request.method == \"POST\":\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            curl.setopt(pycurl.POSTFIELDSIZE, len(request.body))\n        else:\n            curl.setopt(pycurl.INFILESIZE, len(request.body))\n\n    if request.auth_username is not None:\n        userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n        if request.auth_mode is None or request.auth_mode == \"basic\":\n            curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n        elif request.auth_mode == \"digest\":\n            curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n        else:\n            raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n        curl.setopt(pycurl.USERPWD, native_str(userpwd))\n        gen_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                      request.auth_username)\n    else:\n        curl.unsetopt(pycurl.USERPWD)\n        gen_log.debug(\"%s %s\", request.method, request.url)\n\n    if request.client_cert is not None:\n        curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n    if request.client_key is not None:\n        curl.setopt(pycurl.SSLKEY, request.client_key)\n\n    if threading.activeCount() > 1:\n        # libcurl/pycurl is not thread-safe by default.  When multiple threads\n        # are used, signals should be disabled.  This has the side effect\n        # of disabling DNS timeouts in some environments (when libcurl is\n        # not linked against ares), so we don't do it when there is only one\n        # thread.  Applications that use many short-lived threads may need\n        # to set NOSIGNAL manually in a prepare_curl_callback since\n        # there may not be any other threads running at the time we call\n        # threading.activeCount.\n        curl.setopt(pycurl.NOSIGNAL, 1)\n    if request.prepare_curl_callback is not None:\n        request.prepare_curl_callback(curl)\n\n\ndef _curl_header_callback(headers, header_line):\n    # header_line as returned by curl includes the end-of-line characters.\n    header_line = header_line.strip()\n    if header_line.startswith(\"HTTP/\"):\n        headers.clear()\n        return\n    if not header_line:\n        return\n    headers.parse_line(header_line)\n\n\ndef _curl_debug(debug_type, debug_msg):\n    debug_types = ('I', '<', '>', '<', '>')\n    if debug_type == 0:\n        gen_log.debug('%s', debug_msg.strip())\n    elif debug_type in (1, 2):\n        for line in debug_msg.splitlines():\n            gen_log.debug('%s %s', debug_types[debug_type], line)\n    elif debug_type == 4:\n        gen_log.debug('%s %r', debug_types[debug_type], debug_msg)\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(CurlAsyncHTTPClient)\n    main()\n",
            "new_file_content": "#!/usr/bin/env python\n#\n# Copyright 2009 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\"\"\"Non-blocking HTTP client implementation using pycurl.\"\"\"\n\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nimport collections\nimport logging\nimport pycurl\nimport threading\nimport time\n\nfrom tornado import httputil\nfrom tornado import ioloop\nfrom tornado.log import gen_log\nfrom tornado import stack_context\n\nfrom tornado.escape import utf8, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main\nfrom tornado.util import bytes_type\n\ntry:\n    from io import BytesIO  # py3\nexcept ImportError:\n    from cStringIO import StringIO as BytesIO  # py2\n\n\nclass CurlAsyncHTTPClient(AsyncHTTPClient):\n    def initialize(self, io_loop, max_clients=10, defaults=None):\n        super(CurlAsyncHTTPClient, self).initialize(io_loop, defaults=defaults)\n        self._multi = pycurl.CurlMulti()\n        self._multi.setopt(pycurl.M_TIMERFUNCTION, self._set_timeout)\n        self._multi.setopt(pycurl.M_SOCKETFUNCTION, self._handle_socket)\n        self._curls = [_curl_create() for i in range(max_clients)]\n        self._free_list = self._curls[:]\n        self._requests = collections.deque()\n        self._fds = {}\n        self._timeout = None\n\n        try:\n            self._socket_action = self._multi.socket_action\n        except AttributeError:\n            # socket_action is found in pycurl since 7.18.2 (it's been\n            # in libcurl longer than that but wasn't accessible to\n            # python).\n            gen_log.warning(\"socket_action method missing from pycurl; \"\n                            \"falling back to socket_all. Upgrading \"\n                            \"libcurl and pycurl will improve performance\")\n            self._socket_action = \\\n                lambda fd, action: self._multi.socket_all()\n\n        # libcurl has bugs that sometimes cause it to not report all\n        # relevant file descriptors and timeouts to TIMERFUNCTION/\n        # SOCKETFUNCTION.  Mitigate the effects of such bugs by\n        # forcing a periodic scan of all active requests.\n        self._force_timeout_callback = ioloop.PeriodicCallback(\n            self._handle_force_timeout, 1000, io_loop=io_loop)\n        self._force_timeout_callback.start()\n\n        # Work around a bug in libcurl 7.29.0: Some fields in the curl\n        # multi object are initialized lazily, and its destructor will\n        # segfault if it is destroyed without having been used.  Add\n        # and remove a dummy handle to make sure everything is\n        # initialized.\n        dummy_curl_handle = pycurl.Curl()\n        self._multi.add_handle(dummy_curl_handle)\n        self._multi.remove_handle(dummy_curl_handle)\n\n    def close(self):\n        self._force_timeout_callback.stop()\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        for curl in self._curls:\n            curl.close()\n        self._multi.close()\n        self._closed = True\n        super(CurlAsyncHTTPClient, self).close()\n\n    def fetch_impl(self, request, callback):\n        self._requests.append((request, callback))\n        self._process_queue()\n        self._set_timeout(0)\n\n    def _handle_socket(self, event, fd, multi, data):\n        \"\"\"Called by libcurl when it wants to change the file descriptors\n        it cares about.\n        \"\"\"\n        event_map = {\n            pycurl.POLL_NONE: ioloop.IOLoop.NONE,\n            pycurl.POLL_IN: ioloop.IOLoop.READ,\n            pycurl.POLL_OUT: ioloop.IOLoop.WRITE,\n            pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE\n        }\n        if event == pycurl.POLL_REMOVE:\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n                del self._fds[fd]\n        else:\n            ioloop_event = event_map[event]\n            # libcurl sometimes closes a socket and then opens a new\n            # one using the same FD without giving us a POLL_NONE in\n            # between.  This is a problem with the epoll IOLoop,\n            # because the kernel can tell when a socket is closed and\n            # removes it from the epoll automatically, causing future\n            # update_handler calls to fail.  Since we can't tell when\n            # this has happened, always use remove and re-add\n            # instead of update.\n            if fd in self._fds:\n                self.io_loop.remove_handler(fd)\n            self.io_loop.add_handler(fd, self._handle_events,\n                                     ioloop_event)\n            self._fds[fd] = ioloop_event\n\n    def _set_timeout(self, msecs):\n        \"\"\"Called by libcurl to schedule a timeout.\"\"\"\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n        self._timeout = self.io_loop.add_timeout(\n            self.io_loop.time() + msecs / 1000.0, self._handle_timeout)\n\n    def _handle_events(self, fd, events):\n        \"\"\"Called by IOLoop when there is activity on one of our\n        file descriptors.\n        \"\"\"\n        action = 0\n        if events & ioloop.IOLoop.READ:\n            action |= pycurl.CSELECT_IN\n        if events & ioloop.IOLoop.WRITE:\n            action |= pycurl.CSELECT_OUT\n        while True:\n            try:\n                ret, num_handles = self._socket_action(fd, action)\n            except pycurl.error as e:\n                ret = e.args[0]\n            if ret != pycurl.E_CALL_MULTI_PERFORM:\n                break\n        self._finish_pending_requests()\n\n    def _handle_timeout(self):\n        \"\"\"Called by IOLoop when the requested timeout has passed.\"\"\"\n        with stack_context.NullContext():\n            self._timeout = None\n            while True:\n                try:\n                    ret, num_handles = self._socket_action(\n                        pycurl.SOCKET_TIMEOUT, 0)\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n        # In theory, we shouldn't have to do this because curl will\n        # call _set_timeout whenever the timeout changes.  However,\n        # sometimes after _handle_timeout we will need to reschedule\n        # immediately even though nothing has changed from curl's\n        # perspective.  This is because when socket_action is\n        # called with SOCKET_TIMEOUT, libcurl decides internally which\n        # timeouts need to be processed by using a monotonic clock\n        # (where available) while tornado uses python's time.time()\n        # to decide when timeouts have occurred.  When those clocks\n        # disagree on elapsed time (as they will whenever there is an\n        # NTP adjustment), tornado might call _handle_timeout before\n        # libcurl is ready.  After each timeout, resync the scheduled\n        # timeout with libcurl's current state.\n        new_timeout = self._multi.timeout()\n        if new_timeout >= 0:\n            self._set_timeout(new_timeout)\n\n    def _handle_force_timeout(self):\n        \"\"\"Called by IOLoop periodically to ask libcurl to process any\n        events it may have forgotten about.\n        \"\"\"\n        with stack_context.NullContext():\n            while True:\n                try:\n                    ret, num_handles = self._multi.socket_all()\n                except pycurl.error as e:\n                    ret = e.args[0]\n                if ret != pycurl.E_CALL_MULTI_PERFORM:\n                    break\n            self._finish_pending_requests()\n\n    def _finish_pending_requests(self):\n        \"\"\"Process any requests that were completed by the last\n        call to multi.socket_action.\n        \"\"\"\n        while True:\n            num_q, ok_list, err_list = self._multi.info_read()\n            for curl in ok_list:\n                self._finish(curl)\n            for curl, errnum, errmsg in err_list:\n                self._finish(curl, errnum, errmsg)\n            if num_q == 0:\n                break\n        self._process_queue()\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while True:\n                started = 0\n                while self._free_list and self._requests:\n                    started += 1\n                    curl = self._free_list.pop()\n                    (request, callback) = self._requests.popleft()\n                    curl.info = {\n                        \"headers\": httputil.HTTPHeaders(),\n                        \"buffer\": BytesIO(),\n                        \"request\": request,\n                        \"callback\": callback,\n                        \"curl_start_time\": time.time(),\n                    }\n                    # Disable IPv6 to mitigate the effects of this bug\n                    # on curl versions <= 7.21.0\n                    # http://sourceforge.net/tracker/?func=detail&aid=3017819&group_id=976&atid=100976\n                    if pycurl.version_info()[2] <= 0x71500:  # 7.21.0\n                        curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n                    _curl_setup_request(curl, request, curl.info[\"buffer\"],\n                                        curl.info[\"headers\"])\n                    self._multi.add_handle(curl)\n\n                if not started:\n                    break\n\n    def _finish(self, curl, curl_error=None, curl_message=None):\n        info = curl.info\n        curl.info = None\n        self._multi.remove_handle(curl)\n        self._free_list.append(curl)\n        buffer = info[\"buffer\"]\n        if curl_error:\n            error = CurlError(curl_error, curl_message)\n            code = error.code\n            effective_url = None\n            buffer.close()\n            buffer = None\n        else:\n            error = None\n            code = curl.getinfo(pycurl.HTTP_CODE)\n            effective_url = curl.getinfo(pycurl.EFFECTIVE_URL)\n            buffer.seek(0)\n        # the various curl timings are documented at\n        # http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html\n        time_info = dict(\n            queue=info[\"curl_start_time\"] - info[\"request\"].start_time,\n            namelookup=curl.getinfo(pycurl.NAMELOOKUP_TIME),\n            connect=curl.getinfo(pycurl.CONNECT_TIME),\n            pretransfer=curl.getinfo(pycurl.PRETRANSFER_TIME),\n            starttransfer=curl.getinfo(pycurl.STARTTRANSFER_TIME),\n            total=curl.getinfo(pycurl.TOTAL_TIME),\n            redirect=curl.getinfo(pycurl.REDIRECT_TIME),\n        )\n        try:\n            info[\"callback\"](HTTPResponse(\n                request=info[\"request\"], code=code, headers=info[\"headers\"],\n                buffer=buffer, effective_url=effective_url, error=error,\n                request_time=time.time() - info[\"curl_start_time\"],\n                time_info=time_info))\n        except Exception:\n            self.handle_callback_exception(info[\"callback\"])\n\n    def handle_callback_exception(self, callback):\n        self.io_loop.handle_callback_exception(callback)\n\n\nclass CurlError(HTTPError):\n    def __init__(self, errno, message):\n        HTTPError.__init__(self, 599, message)\n        self.errno = errno\n\n\ndef _curl_create():\n    curl = pycurl.Curl()\n    if gen_log.isEnabledFor(logging.DEBUG):\n        curl.setopt(pycurl.VERBOSE, 1)\n        curl.setopt(pycurl.DEBUGFUNCTION, _curl_debug)\n    return curl\n\n\ndef _curl_setup_request(curl, request, buffer, headers):\n    curl.setopt(pycurl.URL, native_str(request.url))\n\n    # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n    # with servers that don't support it (which include, among others,\n    # Google's OpenID endpoint).  Additionally, this behavior has\n    # a bug in conjunction with the curl_multi_socket_action API\n    # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n    # which increases the delays.  It's more trouble than it's worth,\n    # so just turn off the feature (yes, setting Expect: to an empty\n    # value is the official way to disable this)\n    if \"Expect\" not in request.headers:\n        request.headers[\"Expect\"] = \"\"\n\n    # libcurl adds Pragma: no-cache by default; disable that too\n    if \"Pragma\" not in request.headers:\n        request.headers[\"Pragma\"] = \"\"\n\n    # Request headers may be either a regular dict or HTTPHeaders object\n    if isinstance(request.headers, httputil.HTTPHeaders):\n        curl.setopt(pycurl.HTTPHEADER,\n                    [native_str(\"%s: %s\" % i) for i in request.headers.get_all()])\n    else:\n        curl.setopt(pycurl.HTTPHEADER,\n                    [native_str(\"%s: %s\" % i) for i in request.headers.items()])\n\n    if request.header_callback:\n        curl.setopt(pycurl.HEADERFUNCTION, request.header_callback)\n    else:\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    lambda line: _curl_header_callback(headers, line))\n    if request.streaming_callback:\n        write_function = request.streaming_callback\n    else:\n        write_function = buffer.write\n    if bytes_type is str:  # py2\n        curl.setopt(pycurl.WRITEFUNCTION, write_function)\n    else:  # py3\n        # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n        # a fork/port.  That version has a bug in which it passes unicode\n        # strings instead of bytes to the WRITEFUNCTION.  This means that\n        # if you use a WRITEFUNCTION (which tornado always does), you cannot\n        # download arbitrary binary data.  This needs to be fixed in the\n        # ported pycurl package, but in the meantime this lambda will\n        # make it work for downloading (utf8) text.\n        curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n    curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n    curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n    curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n    curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n    if request.user_agent:\n        curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n    else:\n        curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n    if request.network_interface:\n        curl.setopt(pycurl.INTERFACE, request.network_interface)\n    if request.use_gzip:\n        curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n    else:\n        curl.setopt(pycurl.ENCODING, \"none\")\n    if request.proxy_host and request.proxy_port:\n        curl.setopt(pycurl.PROXY, request.proxy_host)\n        curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n        if request.proxy_username:\n            credentials = '%s:%s' % (request.proxy_username,\n                                     request.proxy_password)\n            curl.setopt(pycurl.PROXYUSERPWD, credentials)\n    else:\n        curl.setopt(pycurl.PROXY, '')\n        curl.unsetopt(pycurl.PROXYUSERPWD)\n    if request.validate_cert:\n        curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n        curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n    else:\n        curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n        curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n    if request.ca_certs is not None:\n        curl.setopt(pycurl.CAINFO, request.ca_certs)\n    else:\n        # There is no way to restore pycurl.CAINFO to its default value\n        # (Using unsetopt makes it reject all certificates).\n        # I don't see any way to read the default value from python so it\n        # can be restored later.  We'll have to just leave CAINFO untouched\n        # if no ca_certs file was specified, and require that if any\n        # request uses a custom ca_certs file, they all must.\n        pass\n\n    if request.allow_ipv6 is False:\n        # Curl behaves reasonably when DNS resolution gives an ipv6 address\n        # that we can't reach, so allow ipv6 unless the user asks to disable.\n        # (but see version check in _process_queue above)\n        curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n    else:\n        curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n    # Set the request method through curl's irritating interface which makes\n    # up names for almost every single method\n    curl_options = {\n        \"GET\": pycurl.HTTPGET,\n        \"POST\": pycurl.POST,\n        \"PUT\": pycurl.UPLOAD,\n        \"HEAD\": pycurl.NOBODY,\n    }\n    custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n    for o in curl_options.values():\n        curl.setopt(o, False)\n    if request.method in curl_options:\n        curl.unsetopt(pycurl.CUSTOMREQUEST)\n        curl.setopt(curl_options[request.method], True)\n    elif request.allow_nonstandard_methods or request.method in custom_methods:\n        curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n    else:\n        raise KeyError('unknown method ' + request.method)\n\n    # Handle curl's cryptic options for every individual HTTP method\n    if request.method in (\"POST\", \"PUT\"):\n        if request.body is None:\n            raise AssertionError(\n                'Body must not be empty for \"%s\" request'\n                % request.method)\n\n        request_buffer = BytesIO(utf8(request.body))\n        curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n        if request.method == \"POST\":\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            curl.setopt(pycurl.POSTFIELDSIZE, len(request.body))\n        else:\n            curl.setopt(pycurl.INFILESIZE, len(request.body))\n    elif request.method == \"GET\":\n        if request.body is not None:\n            raise AssertionError('Body must be empty for GET request')\n\n    if request.auth_username is not None:\n        userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n        if request.auth_mode is None or request.auth_mode == \"basic\":\n            curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n        elif request.auth_mode == \"digest\":\n            curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n        else:\n            raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n        curl.setopt(pycurl.USERPWD, native_str(userpwd))\n        gen_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                      request.auth_username)\n    else:\n        curl.unsetopt(pycurl.USERPWD)\n        gen_log.debug(\"%s %s\", request.method, request.url)\n\n    if request.client_cert is not None:\n        curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n    if request.client_key is not None:\n        curl.setopt(pycurl.SSLKEY, request.client_key)\n\n    if threading.activeCount() > 1:\n        # libcurl/pycurl is not thread-safe by default.  When multiple threads\n        # are used, signals should be disabled.  This has the side effect\n        # of disabling DNS timeouts in some environments (when libcurl is\n        # not linked against ares), so we don't do it when there is only one\n        # thread.  Applications that use many short-lived threads may need\n        # to set NOSIGNAL manually in a prepare_curl_callback since\n        # there may not be any other threads running at the time we call\n        # threading.activeCount.\n        curl.setopt(pycurl.NOSIGNAL, 1)\n    if request.prepare_curl_callback is not None:\n        request.prepare_curl_callback(curl)\n\n\ndef _curl_header_callback(headers, header_line):\n    # header_line as returned by curl includes the end-of-line characters.\n    header_line = header_line.strip()\n    if header_line.startswith(\"HTTP/\"):\n        headers.clear()\n        return\n    if not header_line:\n        return\n    headers.parse_line(header_line)\n\n\ndef _curl_debug(debug_type, debug_msg):\n    debug_types = ('I', '<', '>', '<', '>')\n    if debug_type == 0:\n        gen_log.debug('%s', debug_msg.strip())\n    elif debug_type in (1, 2):\n        for line in debug_msg.splitlines():\n            gen_log.debug('%s %s', debug_types[debug_type], line)\n    elif debug_type == 4:\n        gen_log.debug('%s %r', debug_types[debug_type], debug_msg)\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(CurlAsyncHTTPClient)\n    main()\n",
            "header": {
                "file": {
                    "path": "tornado/curl_httpclient.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "efa361c4",
                "new_commit_hash": "cb97710a",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/tornado/curl_httpclient.py"
            },
            "plus_file": {
                "path": "b/tornado/curl_httpclient.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 407,
                            "length": 6
                        },
                        "new_range": {
                            "start": 407,
                            "length": 11
                        },
                        "section": "def _curl_setup_request(curl, request, buffer, headers):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    # Handle curl's cryptic options for every individual HTTP method",
                                "type": "context"
                            },
                            {
                                "content": "    if request.method in (\"POST\", \"PUT\"):",
                                "type": "context"
                            },
                            {
                                "content": "        if request.body is None:",
                                "type": "added"
                            },
                            {
                                "content": "            raise AssertionError(",
                                "type": "added"
                            },
                            {
                                "content": "                'Body must not be empty for \"%s\" request'",
                                "type": "added"
                            },
                            {
                                "content": "                % request.method)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        request_buffer = BytesIO(utf8(request.body))",
                                "type": "context"
                            },
                            {
                                "content": "        curl.setopt(pycurl.READFUNCTION, request_buffer.read)",
                                "type": "context"
                            },
                            {
                                "content": "        if request.method == \"POST\":",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/curl_httpclient.py",
                            "type": "function",
                            "name": "_curl_setup_request",
                            "content": "def _curl_setup_request(curl, request, buffer, headers):\n    curl.setopt(pycurl.URL, native_str(request.url))\n\n    # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n    # with servers that don't support it (which include, among others,\n    # Google's OpenID endpoint).  Additionally, this behavior has\n    # a bug in conjunction with the curl_multi_socket_action API\n    # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n    # which increases the delays.  It's more trouble than it's worth,\n    # so just turn off the feature (yes, setting Expect: to an empty\n    # value is the official way to disable this)\n    if \"Expect\" not in request.headers:\n        request.headers[\"Expect\"] = \"\"\n\n    # libcurl adds Pragma: no-cache by default; disable that too\n    if \"Pragma\" not in request.headers:\n        request.headers[\"Pragma\"] = \"\"\n\n    # Request headers may be either a regular dict or HTTPHeaders object\n    if isinstance(request.headers, httputil.HTTPHeaders):\n        curl.setopt(pycurl.HTTPHEADER,\n                    [native_str(\"%s: %s\" % i) for i in request.headers.get_all()])\n    else:\n        curl.setopt(pycurl.HTTPHEADER,\n                    [native_str(\"%s: %s\" % i) for i in request.headers.items()])\n\n    if request.header_callback:\n        curl.setopt(pycurl.HEADERFUNCTION, request.header_callback)\n    else:\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    lambda line: _curl_header_callback(headers, line))\n    if request.streaming_callback:\n        write_function = request.streaming_callback\n    else:\n        write_function = buffer.write\n    if bytes_type is str:  # py2\n        curl.setopt(pycurl.WRITEFUNCTION, write_function)\n    else:  # py3\n        # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n        # a fork/port.  That version has a bug in which it passes unicode\n        # strings instead of bytes to the WRITEFUNCTION.  This means that\n        # if you use a WRITEFUNCTION (which tornado always does), you cannot\n        # download arbitrary binary data.  This needs to be fixed in the\n        # ported pycurl package, but in the meantime this lambda will\n        # make it work for downloading (utf8) text.\n        curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n    curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n    curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n    curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n    curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n    if request.user_agent:\n        curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n    else:\n        curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n    if request.network_interface:\n        curl.setopt(pycurl.INTERFACE, request.network_interface)\n    if request.use_gzip:\n        curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n    else:\n        curl.setopt(pycurl.ENCODING, \"none\")\n    if request.proxy_host and request.proxy_port:\n        curl.setopt(pycurl.PROXY, request.proxy_host)\n        curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n        if request.proxy_username:\n            credentials = '%s:%s' % (request.proxy_username,\n                                     request.proxy_password)\n            curl.setopt(pycurl.PROXYUSERPWD, credentials)\n    else:\n        curl.setopt(pycurl.PROXY, '')\n        curl.unsetopt(pycurl.PROXYUSERPWD)\n    if request.validate_cert:\n        curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n        curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n    else:\n        curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n        curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n    if request.ca_certs is not None:\n        curl.setopt(pycurl.CAINFO, request.ca_certs)\n    else:\n        # There is no way to restore pycurl.CAINFO to its default value\n        # (Using unsetopt makes it reject all certificates).\n        # I don't see any way to read the default value from python so it\n        # can be restored later.  We'll have to just leave CAINFO untouched\n        # if no ca_certs file was specified, and require that if any\n        # request uses a custom ca_certs file, they all must.\n        pass\n\n    if request.allow_ipv6 is False:\n        # Curl behaves reasonably when DNS resolution gives an ipv6 address\n        # that we can't reach, so allow ipv6 unless the user asks to disable.\n        # (but see version check in _process_queue above)\n        curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n    else:\n        curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n    # Set the request method through curl's irritating interface which makes\n    # up names for almost every single method\n    curl_options = {\n        \"GET\": pycurl.HTTPGET,\n        \"POST\": pycurl.POST,\n        \"PUT\": pycurl.UPLOAD,\n        \"HEAD\": pycurl.NOBODY,\n    }\n    custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n    for o in curl_options.values():\n        curl.setopt(o, False)\n    if request.method in curl_options:\n        curl.unsetopt(pycurl.CUSTOMREQUEST)\n        curl.setopt(curl_options[request.method], True)\n    elif request.allow_nonstandard_methods or request.method in custom_methods:\n        curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n    else:\n        raise KeyError('unknown method ' + request.method)\n\n    # Handle curl's cryptic options for every individual HTTP method\n    if request.method in (\"POST\", \"PUT\"):\n        if request.body is None:\n            raise AssertionError(\n                'Body must not be empty for \"%s\" request'\n                % request.method)\n\n        request_buffer = BytesIO(utf8(request.body))\n        curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n        if request.method == \"POST\":\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            curl.setopt(pycurl.POSTFIELDSIZE, len(request.body))\n        else:\n            curl.setopt(pycurl.INFILESIZE, len(request.body))\n    elif request.method == \"GET\":\n        if request.body is not None:\n            raise AssertionError('Body must be empty for GET request')\n\n    if request.auth_username is not None:\n        userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n        if request.auth_mode is None or request.auth_mode == \"basic\":\n            curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n        elif request.auth_mode == \"digest\":\n            curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n        else:\n            raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n        curl.setopt(pycurl.USERPWD, native_str(userpwd))\n        gen_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                      request.auth_username)\n    else:\n        curl.unsetopt(pycurl.USERPWD)\n        gen_log.debug(\"%s %s\", request.method, request.url)\n\n    if request.client_cert is not None:\n        curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n    if request.client_key is not None:\n        curl.setopt(pycurl.SSLKEY, request.client_key)\n\n    if threading.activeCount() > 1:\n        # libcurl/pycurl is not thread-safe by default.  When multiple threads\n        # are used, signals should be disabled.  This has the side effect\n        # of disabling DNS timeouts in some environments (when libcurl is\n        # not linked against ares), so we don't do it when there is only one\n        # thread.  Applications that use many short-lived threads may need\n        # to set NOSIGNAL manually in a prepare_curl_callback since\n        # there may not be any other threads running at the time we call\n        # threading.activeCount.\n        curl.setopt(pycurl.NOSIGNAL, 1)\n    if request.prepare_curl_callback is not None:\n        request.prepare_curl_callback(curl)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 294,
                            "end_lineno": 463,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 417,
                            "length": 6
                        },
                        "new_range": {
                            "start": 422,
                            "length": 9
                        },
                        "section": "def _curl_setup_request(curl, request, buffer, headers):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            curl.setopt(pycurl.POSTFIELDSIZE, len(request.body))",
                                "type": "context"
                            },
                            {
                                "content": "        else:",
                                "type": "context"
                            },
                            {
                                "content": "            curl.setopt(pycurl.INFILESIZE, len(request.body))",
                                "type": "context"
                            },
                            {
                                "content": "    elif request.method == \"GET\":",
                                "type": "added"
                            },
                            {
                                "content": "        if request.body is not None:",
                                "type": "added"
                            },
                            {
                                "content": "            raise AssertionError('Body must be empty for GET request')",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    if request.auth_username is not None:",
                                "type": "context"
                            },
                            {
                                "content": "        userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/curl_httpclient.py",
                            "type": "function",
                            "name": "_curl_setup_request",
                            "content": "def _curl_setup_request(curl, request, buffer, headers):\n    curl.setopt(pycurl.URL, native_str(request.url))\n\n    # libcurl's magic \"Expect: 100-continue\" behavior causes delays\n    # with servers that don't support it (which include, among others,\n    # Google's OpenID endpoint).  Additionally, this behavior has\n    # a bug in conjunction with the curl_multi_socket_action API\n    # (https://sourceforge.net/tracker/?func=detail&atid=100976&aid=3039744&group_id=976),\n    # which increases the delays.  It's more trouble than it's worth,\n    # so just turn off the feature (yes, setting Expect: to an empty\n    # value is the official way to disable this)\n    if \"Expect\" not in request.headers:\n        request.headers[\"Expect\"] = \"\"\n\n    # libcurl adds Pragma: no-cache by default; disable that too\n    if \"Pragma\" not in request.headers:\n        request.headers[\"Pragma\"] = \"\"\n\n    # Request headers may be either a regular dict or HTTPHeaders object\n    if isinstance(request.headers, httputil.HTTPHeaders):\n        curl.setopt(pycurl.HTTPHEADER,\n                    [native_str(\"%s: %s\" % i) for i in request.headers.get_all()])\n    else:\n        curl.setopt(pycurl.HTTPHEADER,\n                    [native_str(\"%s: %s\" % i) for i in request.headers.items()])\n\n    if request.header_callback:\n        curl.setopt(pycurl.HEADERFUNCTION, request.header_callback)\n    else:\n        curl.setopt(pycurl.HEADERFUNCTION,\n                    lambda line: _curl_header_callback(headers, line))\n    if request.streaming_callback:\n        write_function = request.streaming_callback\n    else:\n        write_function = buffer.write\n    if bytes_type is str:  # py2\n        curl.setopt(pycurl.WRITEFUNCTION, write_function)\n    else:  # py3\n        # Upstream pycurl doesn't support py3, but ubuntu 12.10 includes\n        # a fork/port.  That version has a bug in which it passes unicode\n        # strings instead of bytes to the WRITEFUNCTION.  This means that\n        # if you use a WRITEFUNCTION (which tornado always does), you cannot\n        # download arbitrary binary data.  This needs to be fixed in the\n        # ported pycurl package, but in the meantime this lambda will\n        # make it work for downloading (utf8) text.\n        curl.setopt(pycurl.WRITEFUNCTION, lambda s: write_function(utf8(s)))\n    curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects)\n    curl.setopt(pycurl.MAXREDIRS, request.max_redirects)\n    curl.setopt(pycurl.CONNECTTIMEOUT_MS, int(1000 * request.connect_timeout))\n    curl.setopt(pycurl.TIMEOUT_MS, int(1000 * request.request_timeout))\n    if request.user_agent:\n        curl.setopt(pycurl.USERAGENT, native_str(request.user_agent))\n    else:\n        curl.setopt(pycurl.USERAGENT, \"Mozilla/5.0 (compatible; pycurl)\")\n    if request.network_interface:\n        curl.setopt(pycurl.INTERFACE, request.network_interface)\n    if request.use_gzip:\n        curl.setopt(pycurl.ENCODING, \"gzip,deflate\")\n    else:\n        curl.setopt(pycurl.ENCODING, \"none\")\n    if request.proxy_host and request.proxy_port:\n        curl.setopt(pycurl.PROXY, request.proxy_host)\n        curl.setopt(pycurl.PROXYPORT, request.proxy_port)\n        if request.proxy_username:\n            credentials = '%s:%s' % (request.proxy_username,\n                                     request.proxy_password)\n            curl.setopt(pycurl.PROXYUSERPWD, credentials)\n    else:\n        curl.setopt(pycurl.PROXY, '')\n        curl.unsetopt(pycurl.PROXYUSERPWD)\n    if request.validate_cert:\n        curl.setopt(pycurl.SSL_VERIFYPEER, 1)\n        curl.setopt(pycurl.SSL_VERIFYHOST, 2)\n    else:\n        curl.setopt(pycurl.SSL_VERIFYPEER, 0)\n        curl.setopt(pycurl.SSL_VERIFYHOST, 0)\n    if request.ca_certs is not None:\n        curl.setopt(pycurl.CAINFO, request.ca_certs)\n    else:\n        # There is no way to restore pycurl.CAINFO to its default value\n        # (Using unsetopt makes it reject all certificates).\n        # I don't see any way to read the default value from python so it\n        # can be restored later.  We'll have to just leave CAINFO untouched\n        # if no ca_certs file was specified, and require that if any\n        # request uses a custom ca_certs file, they all must.\n        pass\n\n    if request.allow_ipv6 is False:\n        # Curl behaves reasonably when DNS resolution gives an ipv6 address\n        # that we can't reach, so allow ipv6 unless the user asks to disable.\n        # (but see version check in _process_queue above)\n        curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4)\n    else:\n        curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER)\n\n    # Set the request method through curl's irritating interface which makes\n    # up names for almost every single method\n    curl_options = {\n        \"GET\": pycurl.HTTPGET,\n        \"POST\": pycurl.POST,\n        \"PUT\": pycurl.UPLOAD,\n        \"HEAD\": pycurl.NOBODY,\n    }\n    custom_methods = set([\"DELETE\", \"OPTIONS\", \"PATCH\"])\n    for o in curl_options.values():\n        curl.setopt(o, False)\n    if request.method in curl_options:\n        curl.unsetopt(pycurl.CUSTOMREQUEST)\n        curl.setopt(curl_options[request.method], True)\n    elif request.allow_nonstandard_methods or request.method in custom_methods:\n        curl.setopt(pycurl.CUSTOMREQUEST, request.method)\n    else:\n        raise KeyError('unknown method ' + request.method)\n\n    # Handle curl's cryptic options for every individual HTTP method\n    if request.method in (\"POST\", \"PUT\"):\n        if request.body is None:\n            raise AssertionError(\n                'Body must not be empty for \"%s\" request'\n                % request.method)\n\n        request_buffer = BytesIO(utf8(request.body))\n        curl.setopt(pycurl.READFUNCTION, request_buffer.read)\n        if request.method == \"POST\":\n            def ioctl(cmd):\n                if cmd == curl.IOCMD_RESTARTREAD:\n                    request_buffer.seek(0)\n            curl.setopt(pycurl.IOCTLFUNCTION, ioctl)\n            curl.setopt(pycurl.POSTFIELDSIZE, len(request.body))\n        else:\n            curl.setopt(pycurl.INFILESIZE, len(request.body))\n    elif request.method == \"GET\":\n        if request.body is not None:\n            raise AssertionError('Body must be empty for GET request')\n\n    if request.auth_username is not None:\n        userpwd = \"%s:%s\" % (request.auth_username, request.auth_password or '')\n\n        if request.auth_mode is None or request.auth_mode == \"basic\":\n            curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC)\n        elif request.auth_mode == \"digest\":\n            curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_DIGEST)\n        else:\n            raise ValueError(\"Unsupported auth_mode %s\" % request.auth_mode)\n\n        curl.setopt(pycurl.USERPWD, native_str(userpwd))\n        gen_log.debug(\"%s %s (username: %r)\", request.method, request.url,\n                      request.auth_username)\n    else:\n        curl.unsetopt(pycurl.USERPWD)\n        gen_log.debug(\"%s %s\", request.method, request.url)\n\n    if request.client_cert is not None:\n        curl.setopt(pycurl.SSLCERT, request.client_cert)\n\n    if request.client_key is not None:\n        curl.setopt(pycurl.SSLKEY, request.client_key)\n\n    if threading.activeCount() > 1:\n        # libcurl/pycurl is not thread-safe by default.  When multiple threads\n        # are used, signals should be disabled.  This has the side effect\n        # of disabling DNS timeouts in some environments (when libcurl is\n        # not linked against ares), so we don't do it when there is only one\n        # thread.  Applications that use many short-lived threads may need\n        # to set NOSIGNAL manually in a prepare_curl_callback since\n        # there may not be any other threads running at the time we call\n        # threading.activeCount.\n        curl.setopt(pycurl.NOSIGNAL, 1)\n    if request.prepare_curl_callback is not None:\n        request.prepare_curl_callback(curl)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 294,
                            "end_lineno": 463,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "#!/usr/bin/env python\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nfrom tornado.escape import utf8, _unicode, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main, _RequestProxy\nfrom tornado.httputil import HTTPHeaders\nfrom tornado.iostream import IOStream, SSLIOStream\nfrom tornado.netutil import Resolver, OverrideResolver\nfrom tornado.log import gen_log\nfrom tornado import stack_context\nfrom tornado.util import GzipDecompressor\n\nimport base64\nimport collections\nimport copy\nimport functools\nimport os.path\nimport re\nimport socket\nimport ssl\nimport sys\n\ntry:\n    from io import BytesIO  # python 3\nexcept ImportError:\n    from cStringIO import StringIO as BytesIO  # python 2\n\ntry:\n    import urlparse  # py2\nexcept ImportError:\n    import urllib.parse as urlparse  # py3\n\n_DEFAULT_CA_CERTS = os.path.dirname(__file__) + '/ca-certificates.crt'\n\n\nclass SimpleAsyncHTTPClient(AsyncHTTPClient):\n    \"\"\"Non-blocking HTTP client with no external dependencies.\n\n    This class implements an HTTP 1.1 client on top of Tornado's IOStreams.\n    It does not currently implement all applicable parts of the HTTP\n    specification, but it does enough to work with major web service APIs.\n\n    Some features found in the curl-based AsyncHTTPClient are not yet\n    supported.  In particular, proxies are not supported, connections\n    are not reused, and callers cannot select the network interface to be\n    used.\n    \"\"\"\n    def initialize(self, io_loop, max_clients=10,\n                   hostname_mapping=None, max_buffer_size=104857600,\n                   resolver=None, defaults=None):\n        \"\"\"Creates a AsyncHTTPClient.\n\n        Only a single AsyncHTTPClient instance exists per IOLoop\n        in order to provide limitations on the number of pending connections.\n        force_instance=True may be used to suppress this behavior.\n\n        max_clients is the number of concurrent requests that can be\n        in progress.  Note that this arguments are only used when the\n        client is first created, and will be ignored when an existing\n        client is reused.\n\n        hostname_mapping is a dictionary mapping hostnames to IP addresses.\n        It can be used to make local DNS changes when modifying system-wide\n        settings like /etc/hosts is not possible or desirable (e.g. in\n        unittests).\n\n        max_buffer_size is the number of bytes that can be read by IOStream. It\n        defaults to 100mb.\n        \"\"\"\n        super(SimpleAsyncHTTPClient, self).initialize(io_loop,\n                                                      defaults=defaults)\n        self.max_clients = max_clients\n        self.queue = collections.deque()\n        self.active = {}\n        self.waiting = {}\n        self.max_buffer_size = max_buffer_size\n        if resolver:\n            self.resolver = resolver\n            self.own_resolver = False\n        else:\n            self.resolver = Resolver(io_loop=io_loop)\n            self.own_resolver = True\n        if hostname_mapping is not None:\n            self.resolver = OverrideResolver(resolver=self.resolver,\n                                             mapping=hostname_mapping)\n\n    def close(self):\n        super(SimpleAsyncHTTPClient, self).close()\n        if self.own_resolver:\n            self.resolver.close()\n\n    def fetch_impl(self, request, callback):\n        key = object()\n        self.queue.append((key, request, callback))\n        if not len(self.active) < self.max_clients:\n            timeout_handle = self.io_loop.add_timeout(\n                    self.io_loop.time() + min(request.connect_timeout,\n                                              request.request_timeout),\n                    functools.partial(self._on_timeout, key))\n        else:\n            timeout_handle = None\n        self.waiting[key] = (request, callback, timeout_handle)\n        self._process_queue()\n        if self.queue:\n            gen_log.debug(\"max_clients limit reached, request queued. \"\n                          \"%d active, %d queued requests.\" % (\n                              len(self.active), len(self.queue)))\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while self.queue and len(self.active) < self.max_clients:\n                key, request, callback = self.queue.popleft()\n                if key not in self.waiting:\n                    continue\n                self._remove_timeout(key)\n                self.active[key] = (request, callback)\n                release_callback = functools.partial(self._release_fetch, key)\n                self._handle_request(request, release_callback, callback)\n\n    def _handle_request(self, request, release_callback, final_callback):\n        _HTTPConnection(self.io_loop, self, request, release_callback,\n                        final_callback, self.max_buffer_size, self.resolver)\n\n    def _release_fetch(self, key):\n        del self.active[key]\n        self._process_queue()\n\n    def _remove_timeout(self, key):\n        if key in self.waiting:\n            request, callback, timeout_handle = self.waiting[key]\n            if timeout_handle is not None:\n                self.io_loop.remove_timeout(timeout_handle)\n            del self.waiting[key]\n\n    def _on_timeout(self, key):\n        request, callback, timeout_handle = self.waiting[key]\n        self.queue.remove((key, request, callback))\n        timeout_response = HTTPResponse(\n                request, 599, error=HTTPError(599, \"Timeout\"),\n                request_time=self.io_loop.time() - request.start_time)\n        self.io_loop.add_callback(callback, timeout_response)\n        del self.waiting[key]\n\n\nclass _HTTPConnection(object):\n    _SUPPORTED_METHODS = set([\"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"OPTIONS\"])\n\n    def __init__(self, io_loop, client, request, release_callback,\n                 final_callback, max_buffer_size, resolver):\n        self.start_time = io_loop.time()\n        self.io_loop = io_loop\n        self.client = client\n        self.request = request\n        self.release_callback = release_callback\n        self.final_callback = final_callback\n        self.max_buffer_size = max_buffer_size\n        self.resolver = resolver\n        self.code = None\n        self.headers = None\n        self.chunks = None\n        self._decompressor = None\n        # Timeout handle returned by IOLoop.add_timeout\n        self._timeout = None\n        with stack_context.ExceptionStackContext(self._handle_exception):\n            self.parsed = urlparse.urlsplit(_unicode(self.request.url))\n            if self.parsed.scheme not in (\"http\", \"https\"):\n                raise ValueError(\"Unsupported url scheme: %s\" %\n                                 self.request.url)\n            # urlsplit results have hostname and port results, but they\n            # didn't support ipv6 literals until python 2.7.\n            netloc = self.parsed.netloc\n            if \"@\" in netloc:\n                userpass, _, netloc = netloc.rpartition(\"@\")\n            match = re.match(r'^(.+):(\\d+)$', netloc)\n            if match:\n                host = match.group(1)\n                port = int(match.group(2))\n            else:\n                host = netloc\n                port = 443 if self.parsed.scheme == \"https\" else 80\n            if re.match(r'^\\[.*\\]$', host):\n                # raw ipv6 addresses in urls are enclosed in brackets\n                host = host[1:-1]\n            self.parsed_hostname = host  # save final host for _on_connect\n\n            if request.allow_ipv6:\n                af = socket.AF_UNSPEC\n            else:\n                # We only try the first IP we get from getaddrinfo,\n                # so restrict to ipv4 by default.\n                af = socket.AF_INET\n\n            timeout = min(self.request.connect_timeout, self.request.request_timeout)\n            if timeout:\n                self._timeout = self.io_loop.add_timeout(\n                    self.start_time + timeout,\n                    stack_context.wrap(self._on_timeout))\n            self.resolver.resolve(host, port, af, callback=self._on_resolve)\n\n    def _on_resolve(self, addrinfo):\n        if self.final_callback is None:\n            # final_callback is cleared if we've hit our timeout\n            return\n        self.stream = self._create_stream(addrinfo)\n        self.stream.set_close_callback(self._on_close)\n        # ipv6 addresses are broken (in self.parsed.hostname) until\n        # 2.7, here is correctly parsed value calculated in __init__\n        sockaddr = addrinfo[0][1]\n        self.stream.connect(sockaddr, self._on_connect,\n                            server_hostname=self.parsed_hostname)\n\n    def _create_stream(self, addrinfo):\n        af = addrinfo[0][0]\n        if self.parsed.scheme == \"https\":\n            ssl_options = {}\n            if self.request.validate_cert:\n                ssl_options[\"cert_reqs\"] = ssl.CERT_REQUIRED\n            if self.request.ca_certs is not None:\n                ssl_options[\"ca_certs\"] = self.request.ca_certs\n            else:\n                ssl_options[\"ca_certs\"] = _DEFAULT_CA_CERTS\n            if self.request.client_key is not None:\n                ssl_options[\"keyfile\"] = self.request.client_key\n            if self.request.client_cert is not None:\n                ssl_options[\"certfile\"] = self.request.client_cert\n\n            # SSL interoperability is tricky.  We want to disable\n            # SSLv2 for security reasons; it wasn't disabled by default\n            # until openssl 1.0.  The best way to do this is to use\n            # the SSL_OP_NO_SSLv2, but that wasn't exposed to python\n            # until 3.2.  Python 2.7 adds the ciphers argument, which\n            # can also be used to disable SSLv2.  As a last resort\n            # on python 2.6, we set ssl_version to TLSv1.  This is\n            # more narrow than we'd like since it also breaks\n            # compatibility with servers configured for SSLv3 only,\n            # but nearly all servers support both SSLv3 and TLSv1:\n            # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html\n            if sys.version_info >= (2, 7):\n                ssl_options[\"ciphers\"] = \"DEFAULT:!SSLv2\"\n            else:\n                # This is really only necessary for pre-1.0 versions\n                # of openssl, but python 2.6 doesn't expose version\n                # information.\n                ssl_options[\"ssl_version\"] = ssl.PROTOCOL_TLSv1\n\n            return SSLIOStream(socket.socket(af),\n                               io_loop=self.io_loop,\n                               ssl_options=ssl_options,\n                               max_buffer_size=self.max_buffer_size)\n        else:\n            return IOStream(socket.socket(af),\n                            io_loop=self.io_loop,\n                            max_buffer_size=self.max_buffer_size)\n\n    def _on_timeout(self):\n        self._timeout = None\n        if self.final_callback is not None:\n            raise HTTPError(599, \"Timeout\")\n\n    def _remove_timeout(self):\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n            self._timeout = None\n\n    def _on_connect(self):\n        self._remove_timeout()\n        if self.final_callback is None:\n            return\n        if self.request.request_timeout:\n            self._timeout = self.io_loop.add_timeout(\n                self.start_time + self.request.request_timeout,\n                stack_context.wrap(self._on_timeout))\n        if (self.request.method not in self._SUPPORTED_METHODS and\n                not self.request.allow_nonstandard_methods):\n            raise KeyError(\"unknown method %s\" % self.request.method)\n        for key in ('network_interface',\n                    'proxy_host', 'proxy_port',\n                    'proxy_username', 'proxy_password'):\n            if getattr(self.request, key, None):\n                raise NotImplementedError('%s not supported' % key)\n        if \"Connection\" not in self.request.headers:\n            self.request.headers[\"Connection\"] = \"close\"\n        if \"Host\" not in self.request.headers:\n            if '@' in self.parsed.netloc:\n                self.request.headers[\"Host\"] = self.parsed.netloc.rpartition('@')[-1]\n            else:\n                self.request.headers[\"Host\"] = self.parsed.netloc\n        username, password = None, None\n        if self.parsed.username is not None:\n            username, password = self.parsed.username, self.parsed.password\n        elif self.request.auth_username is not None:\n            username = self.request.auth_username\n            password = self.request.auth_password or ''\n        if username is not None:\n            if self.request.auth_mode not in (None, \"basic\"):\n                raise ValueError(\"unsupported auth_mode %s\",\n                                 self.request.auth_mode)\n            auth = utf8(username) + b\":\" + utf8(password)\n            self.request.headers[\"Authorization\"] = (b\"Basic \" +\n                                                     base64.b64encode(auth))\n        if self.request.user_agent:\n            self.request.headers[\"User-Agent\"] = self.request.user_agent\n        if not self.request.allow_nonstandard_methods:\n            if self.request.method in (\"POST\", \"PATCH\", \"PUT\"):\n                assert self.request.body is not None\n            else:\n                assert self.request.body is None\n        if self.request.body is not None:\n            self.request.headers[\"Content-Length\"] = str(len(\n                self.request.body))\n        if (self.request.method == \"POST\" and\n                \"Content-Type\" not in self.request.headers):\n            self.request.headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\"\n        if self.request.use_gzip:\n            self.request.headers[\"Accept-Encoding\"] = \"gzip\"\n        req_path = ((self.parsed.path or '/') +\n                   (('?' + self.parsed.query) if self.parsed.query else ''))\n        request_lines = [utf8(\"%s %s HTTP/1.1\" % (self.request.method,\n                                                  req_path))]\n        for k, v in self.request.headers.get_all():\n            line = utf8(k) + b\": \" + utf8(v)\n            if b'\\n' in line:\n                raise ValueError('Newline in header: ' + repr(line))\n            request_lines.append(line)\n        request_str = b\"\\r\\n\".join(request_lines) + b\"\\r\\n\\r\\n\"\n        if self.request.body is not None:\n            request_str += self.request.body\n        self.stream.set_nodelay(True)\n        self.stream.write(request_str)\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _release(self):\n        if self.release_callback is not None:\n            release_callback = self.release_callback\n            self.release_callback = None\n            release_callback()\n\n    def _run_callback(self, response):\n        self._release()\n        if self.final_callback is not None:\n            final_callback = self.final_callback\n            self.final_callback = None\n            self.io_loop.add_callback(final_callback, response)\n\n    def _handle_exception(self, typ, value, tb):\n        if self.final_callback:\n            self._remove_timeout()\n            self._run_callback(HTTPResponse(self.request, 599, error=value,\n                                            request_time=self.io_loop.time() - self.start_time,\n                                            ))\n\n            if hasattr(self, \"stream\"):\n                self.stream.close()\n            return True\n        else:\n            # If our callback has already been called, we are probably\n            # catching an exception that is not caused by us but rather\n            # some child of our callback. Rather than drop it on the floor,\n            # pass it along.\n            return False\n\n    def _on_close(self):\n        if self.final_callback is not None:\n            message = \"Connection closed\"\n            if self.stream.error:\n                message = str(self.stream.error)\n            raise HTTPError(599, message)\n\n    def _handle_1xx(self, code):\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _on_headers(self, data):\n        data = native_str(data.decode(\"latin1\"))\n        first_line, _, header_data = data.partition(\"\\n\")\n        match = re.match(\"HTTP/1.[01] ([0-9]+) ([^\\r]*)\", first_line)\n        assert match\n        code = int(match.group(1))\n        self.headers = HTTPHeaders.parse(header_data)\n        if 100 <= code < 200:\n            self._handle_1xx(code)\n            return\n        else:\n            self.code = code\n            self.reason = match.group(2)\n\n        if \"Content-Length\" in self.headers:\n            if \",\" in self.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*', self.headers[\"Content-Length\"])\n                if any(i != pieces[0] for i in pieces):\n                    raise ValueError(\"Multiple unequal Content-Lengths: %r\" %\n                                     self.headers[\"Content-Length\"])\n                self.headers[\"Content-Length\"] = pieces[0]\n            content_length = int(self.headers[\"Content-Length\"])\n        else:\n            content_length = None\n\n        if self.request.header_callback is not None:\n            # re-attach the newline we split on earlier\n            self.request.header_callback(first_line + _)\n            for k, v in self.headers.get_all():\n                self.request.header_callback(\"%s: %s\\r\\n\" % (k, v))\n            self.request.header_callback('\\r\\n')\n\n        if self.request.method == \"HEAD\" or self.code == 304:\n            # HEAD requests and 304 responses never have content, even\n            # though they may have content-length headers\n            self._on_body(b\"\")\n            return\n        if 100 <= self.code < 200 or self.code == 204:\n            # These response codes never have bodies\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3\n            if (\"Transfer-Encoding\" in self.headers or\n                    content_length not in (None, 0)):\n                raise ValueError(\"Response with code %d should not have body\" %\n                                 self.code)\n            self._on_body(b\"\")\n            return\n\n        if (self.request.use_gzip and\n                self.headers.get(\"Content-Encoding\") == \"gzip\"):\n            self._decompressor = GzipDecompressor()\n        if self.headers.get(\"Transfer-Encoding\") == \"chunked\":\n            self.chunks = []\n            self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)\n        elif content_length is not None:\n            self.stream.read_bytes(content_length, self._on_body)\n        else:\n            self.stream.read_until_close(self._on_body)\n\n    def _on_body(self, data):\n        self._remove_timeout()\n        original_request = getattr(self.request, \"original_request\",\n                                   self.request)\n        if (self.request.follow_redirects and\n            self.request.max_redirects > 0 and\n                self.code in (301, 302, 303, 307)):\n            assert isinstance(self.request, _RequestProxy)\n            new_request = copy.copy(self.request.request)\n            new_request.url = urlparse.urljoin(self.request.url,\n                                               self.headers[\"Location\"])\n            new_request.max_redirects = self.request.max_redirects - 1\n            del new_request.headers[\"Host\"]\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4\n            # Client SHOULD make a GET request after a 303.\n            # According to the spec, 302 should be followed by the same\n            # method as the original request, but in practice browsers\n            # treat 302 the same as 303, and many servers use 302 for\n            # compatibility with pre-HTTP/1.1 user agents which don't\n            # understand the 303 status.\n            if self.code in (302, 303):\n                new_request.method = \"GET\"\n                new_request.body = None\n                for h in [\"Content-Length\", \"Content-Type\",\n                          \"Content-Encoding\", \"Transfer-Encoding\"]:\n                    try:\n                        del self.request.headers[h]\n                    except KeyError:\n                        pass\n            new_request.original_request = original_request\n            final_callback = self.final_callback\n            self.final_callback = None\n            self._release()\n            self.client.fetch(new_request, final_callback)\n            self._on_end_request()\n            return\n        if self._decompressor:\n            data = (self._decompressor.decompress(data) +\n                    self._decompressor.flush())\n        if self.request.streaming_callback:\n            if self.chunks is None:\n                # if chunks is not None, we already called streaming_callback\n                # in _on_chunk_data\n                self.request.streaming_callback(data)\n            buffer = BytesIO()\n        else:\n            buffer = BytesIO(data)  # TODO: don't require one big string?\n        response = HTTPResponse(original_request,\n                                self.code, reason=self.reason,\n                                headers=self.headers,\n                                request_time=self.io_loop.time() - self.start_time,\n                                buffer=buffer,\n                                effective_url=self.request.url)\n        self._run_callback(response)\n        self._on_end_request()\n\n    def _on_end_request(self):\n        self.stream.close()\n\n    def _on_chunk_length(self, data):\n        # TODO: \"chunk extensions\" http://tools.ietf.org/html/rfc2616#section-3.6.1\n        length = int(data.strip(), 16)\n        if length == 0:\n            if self._decompressor is not None:\n                tail = self._decompressor.flush()\n                if tail:\n                    # I believe the tail will always be empty (i.e.\n                    # decompress will return all it can).  The purpose\n                    # of the flush call is to detect errors such\n                    # as truncated input.  But in case it ever returns\n                    # anything, treat it as an extra chunk\n                    if self.request.streaming_callback is not None:\n                        self.request.streaming_callback(tail)\n                    else:\n                        self.chunks.append(tail)\n                # all the data has been decompressed, so we don't need to\n                # decompress again in _on_body\n                self._decompressor = None\n            self._on_body(b''.join(self.chunks))\n        else:\n            self.stream.read_bytes(length + 2,  # chunk ends with \\r\\n\n                                   self._on_chunk_data)\n\n    def _on_chunk_data(self, data):\n        assert data[-2:] == b\"\\r\\n\"\n        chunk = data[:-2]\n        if self._decompressor:\n            chunk = self._decompressor.decompress(chunk)\n        if self.request.streaming_callback is not None:\n            self.request.streaming_callback(chunk)\n        else:\n            self.chunks.append(chunk)\n        self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(SimpleAsyncHTTPClient)\n    main()\n",
            "new_file_content": "#!/usr/bin/env python\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nfrom tornado.escape import utf8, _unicode, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main, _RequestProxy\nfrom tornado.httputil import HTTPHeaders\nfrom tornado.iostream import IOStream, SSLIOStream\nfrom tornado.netutil import Resolver, OverrideResolver\nfrom tornado.log import gen_log\nfrom tornado import stack_context\nfrom tornado.util import GzipDecompressor\n\nimport base64\nimport collections\nimport copy\nimport functools\nimport os.path\nimport re\nimport socket\nimport ssl\nimport sys\n\ntry:\n    from io import BytesIO  # python 3\nexcept ImportError:\n    from cStringIO import StringIO as BytesIO  # python 2\n\ntry:\n    import urlparse  # py2\nexcept ImportError:\n    import urllib.parse as urlparse  # py3\n\n_DEFAULT_CA_CERTS = os.path.dirname(__file__) + '/ca-certificates.crt'\n\n\nclass SimpleAsyncHTTPClient(AsyncHTTPClient):\n    \"\"\"Non-blocking HTTP client with no external dependencies.\n\n    This class implements an HTTP 1.1 client on top of Tornado's IOStreams.\n    It does not currently implement all applicable parts of the HTTP\n    specification, but it does enough to work with major web service APIs.\n\n    Some features found in the curl-based AsyncHTTPClient are not yet\n    supported.  In particular, proxies are not supported, connections\n    are not reused, and callers cannot select the network interface to be\n    used.\n    \"\"\"\n    def initialize(self, io_loop, max_clients=10,\n                   hostname_mapping=None, max_buffer_size=104857600,\n                   resolver=None, defaults=None):\n        \"\"\"Creates a AsyncHTTPClient.\n\n        Only a single AsyncHTTPClient instance exists per IOLoop\n        in order to provide limitations on the number of pending connections.\n        force_instance=True may be used to suppress this behavior.\n\n        max_clients is the number of concurrent requests that can be\n        in progress.  Note that this arguments are only used when the\n        client is first created, and will be ignored when an existing\n        client is reused.\n\n        hostname_mapping is a dictionary mapping hostnames to IP addresses.\n        It can be used to make local DNS changes when modifying system-wide\n        settings like /etc/hosts is not possible or desirable (e.g. in\n        unittests).\n\n        max_buffer_size is the number of bytes that can be read by IOStream. It\n        defaults to 100mb.\n        \"\"\"\n        super(SimpleAsyncHTTPClient, self).initialize(io_loop,\n                                                      defaults=defaults)\n        self.max_clients = max_clients\n        self.queue = collections.deque()\n        self.active = {}\n        self.waiting = {}\n        self.max_buffer_size = max_buffer_size\n        if resolver:\n            self.resolver = resolver\n            self.own_resolver = False\n        else:\n            self.resolver = Resolver(io_loop=io_loop)\n            self.own_resolver = True\n        if hostname_mapping is not None:\n            self.resolver = OverrideResolver(resolver=self.resolver,\n                                             mapping=hostname_mapping)\n\n    def close(self):\n        super(SimpleAsyncHTTPClient, self).close()\n        if self.own_resolver:\n            self.resolver.close()\n\n    def fetch_impl(self, request, callback):\n        key = object()\n        self.queue.append((key, request, callback))\n        if not len(self.active) < self.max_clients:\n            timeout_handle = self.io_loop.add_timeout(\n                    self.io_loop.time() + min(request.connect_timeout,\n                                              request.request_timeout),\n                    functools.partial(self._on_timeout, key))\n        else:\n            timeout_handle = None\n        self.waiting[key] = (request, callback, timeout_handle)\n        self._process_queue()\n        if self.queue:\n            gen_log.debug(\"max_clients limit reached, request queued. \"\n                          \"%d active, %d queued requests.\" % (\n                              len(self.active), len(self.queue)))\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while self.queue and len(self.active) < self.max_clients:\n                key, request, callback = self.queue.popleft()\n                if key not in self.waiting:\n                    continue\n                self._remove_timeout(key)\n                self.active[key] = (request, callback)\n                release_callback = functools.partial(self._release_fetch, key)\n                self._handle_request(request, release_callback, callback)\n\n    def _handle_request(self, request, release_callback, final_callback):\n        _HTTPConnection(self.io_loop, self, request, release_callback,\n                        final_callback, self.max_buffer_size, self.resolver)\n\n    def _release_fetch(self, key):\n        del self.active[key]\n        self._process_queue()\n\n    def _remove_timeout(self, key):\n        if key in self.waiting:\n            request, callback, timeout_handle = self.waiting[key]\n            if timeout_handle is not None:\n                self.io_loop.remove_timeout(timeout_handle)\n            del self.waiting[key]\n\n    def _on_timeout(self, key):\n        request, callback, timeout_handle = self.waiting[key]\n        self.queue.remove((key, request, callback))\n        timeout_response = HTTPResponse(\n                request, 599, error=HTTPError(599, \"Timeout\"),\n                request_time=self.io_loop.time() - request.start_time)\n        self.io_loop.add_callback(callback, timeout_response)\n        del self.waiting[key]\n\n\nclass _HTTPConnection(object):\n    _SUPPORTED_METHODS = set([\"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"OPTIONS\"])\n\n    def __init__(self, io_loop, client, request, release_callback,\n                 final_callback, max_buffer_size, resolver):\n        self.start_time = io_loop.time()\n        self.io_loop = io_loop\n        self.client = client\n        self.request = request\n        self.release_callback = release_callback\n        self.final_callback = final_callback\n        self.max_buffer_size = max_buffer_size\n        self.resolver = resolver\n        self.code = None\n        self.headers = None\n        self.chunks = None\n        self._decompressor = None\n        # Timeout handle returned by IOLoop.add_timeout\n        self._timeout = None\n        with stack_context.ExceptionStackContext(self._handle_exception):\n            self.parsed = urlparse.urlsplit(_unicode(self.request.url))\n            if self.parsed.scheme not in (\"http\", \"https\"):\n                raise ValueError(\"Unsupported url scheme: %s\" %\n                                 self.request.url)\n            # urlsplit results have hostname and port results, but they\n            # didn't support ipv6 literals until python 2.7.\n            netloc = self.parsed.netloc\n            if \"@\" in netloc:\n                userpass, _, netloc = netloc.rpartition(\"@\")\n            match = re.match(r'^(.+):(\\d+)$', netloc)\n            if match:\n                host = match.group(1)\n                port = int(match.group(2))\n            else:\n                host = netloc\n                port = 443 if self.parsed.scheme == \"https\" else 80\n            if re.match(r'^\\[.*\\]$', host):\n                # raw ipv6 addresses in urls are enclosed in brackets\n                host = host[1:-1]\n            self.parsed_hostname = host  # save final host for _on_connect\n\n            if request.allow_ipv6:\n                af = socket.AF_UNSPEC\n            else:\n                # We only try the first IP we get from getaddrinfo,\n                # so restrict to ipv4 by default.\n                af = socket.AF_INET\n\n            timeout = min(self.request.connect_timeout, self.request.request_timeout)\n            if timeout:\n                self._timeout = self.io_loop.add_timeout(\n                    self.start_time + timeout,\n                    stack_context.wrap(self._on_timeout))\n            self.resolver.resolve(host, port, af, callback=self._on_resolve)\n\n    def _on_resolve(self, addrinfo):\n        if self.final_callback is None:\n            # final_callback is cleared if we've hit our timeout\n            return\n        self.stream = self._create_stream(addrinfo)\n        self.stream.set_close_callback(self._on_close)\n        # ipv6 addresses are broken (in self.parsed.hostname) until\n        # 2.7, here is correctly parsed value calculated in __init__\n        sockaddr = addrinfo[0][1]\n        self.stream.connect(sockaddr, self._on_connect,\n                            server_hostname=self.parsed_hostname)\n\n    def _create_stream(self, addrinfo):\n        af = addrinfo[0][0]\n        if self.parsed.scheme == \"https\":\n            ssl_options = {}\n            if self.request.validate_cert:\n                ssl_options[\"cert_reqs\"] = ssl.CERT_REQUIRED\n            if self.request.ca_certs is not None:\n                ssl_options[\"ca_certs\"] = self.request.ca_certs\n            else:\n                ssl_options[\"ca_certs\"] = _DEFAULT_CA_CERTS\n            if self.request.client_key is not None:\n                ssl_options[\"keyfile\"] = self.request.client_key\n            if self.request.client_cert is not None:\n                ssl_options[\"certfile\"] = self.request.client_cert\n\n            # SSL interoperability is tricky.  We want to disable\n            # SSLv2 for security reasons; it wasn't disabled by default\n            # until openssl 1.0.  The best way to do this is to use\n            # the SSL_OP_NO_SSLv2, but that wasn't exposed to python\n            # until 3.2.  Python 2.7 adds the ciphers argument, which\n            # can also be used to disable SSLv2.  As a last resort\n            # on python 2.6, we set ssl_version to TLSv1.  This is\n            # more narrow than we'd like since it also breaks\n            # compatibility with servers configured for SSLv3 only,\n            # but nearly all servers support both SSLv3 and TLSv1:\n            # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html\n            if sys.version_info >= (2, 7):\n                ssl_options[\"ciphers\"] = \"DEFAULT:!SSLv2\"\n            else:\n                # This is really only necessary for pre-1.0 versions\n                # of openssl, but python 2.6 doesn't expose version\n                # information.\n                ssl_options[\"ssl_version\"] = ssl.PROTOCOL_TLSv1\n\n            return SSLIOStream(socket.socket(af),\n                               io_loop=self.io_loop,\n                               ssl_options=ssl_options,\n                               max_buffer_size=self.max_buffer_size)\n        else:\n            return IOStream(socket.socket(af),\n                            io_loop=self.io_loop,\n                            max_buffer_size=self.max_buffer_size)\n\n    def _on_timeout(self):\n        self._timeout = None\n        if self.final_callback is not None:\n            raise HTTPError(599, \"Timeout\")\n\n    def _remove_timeout(self):\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n            self._timeout = None\n\n    def _on_connect(self):\n        self._remove_timeout()\n        if self.final_callback is None:\n            return\n        if self.request.request_timeout:\n            self._timeout = self.io_loop.add_timeout(\n                self.start_time + self.request.request_timeout,\n                stack_context.wrap(self._on_timeout))\n        if (self.request.method not in self._SUPPORTED_METHODS and\n                not self.request.allow_nonstandard_methods):\n            raise KeyError(\"unknown method %s\" % self.request.method)\n        for key in ('network_interface',\n                    'proxy_host', 'proxy_port',\n                    'proxy_username', 'proxy_password'):\n            if getattr(self.request, key, None):\n                raise NotImplementedError('%s not supported' % key)\n        if \"Connection\" not in self.request.headers:\n            self.request.headers[\"Connection\"] = \"close\"\n        if \"Host\" not in self.request.headers:\n            if '@' in self.parsed.netloc:\n                self.request.headers[\"Host\"] = self.parsed.netloc.rpartition('@')[-1]\n            else:\n                self.request.headers[\"Host\"] = self.parsed.netloc\n        username, password = None, None\n        if self.parsed.username is not None:\n            username, password = self.parsed.username, self.parsed.password\n        elif self.request.auth_username is not None:\n            username = self.request.auth_username\n            password = self.request.auth_password or ''\n        if username is not None:\n            if self.request.auth_mode not in (None, \"basic\"):\n                raise ValueError(\"unsupported auth_mode %s\",\n                                 self.request.auth_mode)\n            auth = utf8(username) + b\":\" + utf8(password)\n            self.request.headers[\"Authorization\"] = (b\"Basic \" +\n                                                     base64.b64encode(auth))\n        if self.request.user_agent:\n            self.request.headers[\"User-Agent\"] = self.request.user_agent\n        if not self.request.allow_nonstandard_methods:\n            if self.request.method in (\"POST\", \"PATCH\", \"PUT\"):\n                if self.request.body is None:\n                    raise AssertionError(\n                        'Body must not be empty for \"%s\" request'\n                        % self.request.method)\n            else:\n                if self.request.body is not None:\n                    raise AssertionError(\n                        'Body must be empty for \"%s\" request'\n                        % self.request.method)\n        if self.request.body is not None:\n            self.request.headers[\"Content-Length\"] = str(len(\n                self.request.body))\n        if (self.request.method == \"POST\" and\n                \"Content-Type\" not in self.request.headers):\n            self.request.headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\"\n        if self.request.use_gzip:\n            self.request.headers[\"Accept-Encoding\"] = \"gzip\"\n        req_path = ((self.parsed.path or '/') +\n                   (('?' + self.parsed.query) if self.parsed.query else ''))\n        request_lines = [utf8(\"%s %s HTTP/1.1\" % (self.request.method,\n                                                  req_path))]\n        for k, v in self.request.headers.get_all():\n            line = utf8(k) + b\": \" + utf8(v)\n            if b'\\n' in line:\n                raise ValueError('Newline in header: ' + repr(line))\n            request_lines.append(line)\n        request_str = b\"\\r\\n\".join(request_lines) + b\"\\r\\n\\r\\n\"\n        if self.request.body is not None:\n            request_str += self.request.body\n        self.stream.set_nodelay(True)\n        self.stream.write(request_str)\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _release(self):\n        if self.release_callback is not None:\n            release_callback = self.release_callback\n            self.release_callback = None\n            release_callback()\n\n    def _run_callback(self, response):\n        self._release()\n        if self.final_callback is not None:\n            final_callback = self.final_callback\n            self.final_callback = None\n            self.io_loop.add_callback(final_callback, response)\n\n    def _handle_exception(self, typ, value, tb):\n        if self.final_callback:\n            self._remove_timeout()\n            self._run_callback(HTTPResponse(self.request, 599, error=value,\n                                            request_time=self.io_loop.time() - self.start_time,\n                                            ))\n\n            if hasattr(self, \"stream\"):\n                self.stream.close()\n            return True\n        else:\n            # If our callback has already been called, we are probably\n            # catching an exception that is not caused by us but rather\n            # some child of our callback. Rather than drop it on the floor,\n            # pass it along.\n            return False\n\n    def _on_close(self):\n        if self.final_callback is not None:\n            message = \"Connection closed\"\n            if self.stream.error:\n                message = str(self.stream.error)\n            raise HTTPError(599, message)\n\n    def _handle_1xx(self, code):\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _on_headers(self, data):\n        data = native_str(data.decode(\"latin1\"))\n        first_line, _, header_data = data.partition(\"\\n\")\n        match = re.match(\"HTTP/1.[01] ([0-9]+) ([^\\r]*)\", first_line)\n        assert match\n        code = int(match.group(1))\n        self.headers = HTTPHeaders.parse(header_data)\n        if 100 <= code < 200:\n            self._handle_1xx(code)\n            return\n        else:\n            self.code = code\n            self.reason = match.group(2)\n\n        if \"Content-Length\" in self.headers:\n            if \",\" in self.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*', self.headers[\"Content-Length\"])\n                if any(i != pieces[0] for i in pieces):\n                    raise ValueError(\"Multiple unequal Content-Lengths: %r\" %\n                                     self.headers[\"Content-Length\"])\n                self.headers[\"Content-Length\"] = pieces[0]\n            content_length = int(self.headers[\"Content-Length\"])\n        else:\n            content_length = None\n\n        if self.request.header_callback is not None:\n            # re-attach the newline we split on earlier\n            self.request.header_callback(first_line + _)\n            for k, v in self.headers.get_all():\n                self.request.header_callback(\"%s: %s\\r\\n\" % (k, v))\n            self.request.header_callback('\\r\\n')\n\n        if self.request.method == \"HEAD\" or self.code == 304:\n            # HEAD requests and 304 responses never have content, even\n            # though they may have content-length headers\n            self._on_body(b\"\")\n            return\n        if 100 <= self.code < 200 or self.code == 204:\n            # These response codes never have bodies\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3\n            if (\"Transfer-Encoding\" in self.headers or\n                    content_length not in (None, 0)):\n                raise ValueError(\"Response with code %d should not have body\" %\n                                 self.code)\n            self._on_body(b\"\")\n            return\n\n        if (self.request.use_gzip and\n                self.headers.get(\"Content-Encoding\") == \"gzip\"):\n            self._decompressor = GzipDecompressor()\n        if self.headers.get(\"Transfer-Encoding\") == \"chunked\":\n            self.chunks = []\n            self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)\n        elif content_length is not None:\n            self.stream.read_bytes(content_length, self._on_body)\n        else:\n            self.stream.read_until_close(self._on_body)\n\n    def _on_body(self, data):\n        self._remove_timeout()\n        original_request = getattr(self.request, \"original_request\",\n                                   self.request)\n        if (self.request.follow_redirects and\n            self.request.max_redirects > 0 and\n                self.code in (301, 302, 303, 307)):\n            assert isinstance(self.request, _RequestProxy)\n            new_request = copy.copy(self.request.request)\n            new_request.url = urlparse.urljoin(self.request.url,\n                                               self.headers[\"Location\"])\n            new_request.max_redirects = self.request.max_redirects - 1\n            del new_request.headers[\"Host\"]\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4\n            # Client SHOULD make a GET request after a 303.\n            # According to the spec, 302 should be followed by the same\n            # method as the original request, but in practice browsers\n            # treat 302 the same as 303, and many servers use 302 for\n            # compatibility with pre-HTTP/1.1 user agents which don't\n            # understand the 303 status.\n            if self.code in (302, 303):\n                new_request.method = \"GET\"\n                new_request.body = None\n                for h in [\"Content-Length\", \"Content-Type\",\n                          \"Content-Encoding\", \"Transfer-Encoding\"]:\n                    try:\n                        del self.request.headers[h]\n                    except KeyError:\n                        pass\n            new_request.original_request = original_request\n            final_callback = self.final_callback\n            self.final_callback = None\n            self._release()\n            self.client.fetch(new_request, final_callback)\n            self._on_end_request()\n            return\n        if self._decompressor:\n            data = (self._decompressor.decompress(data) +\n                    self._decompressor.flush())\n        if self.request.streaming_callback:\n            if self.chunks is None:\n                # if chunks is not None, we already called streaming_callback\n                # in _on_chunk_data\n                self.request.streaming_callback(data)\n            buffer = BytesIO()\n        else:\n            buffer = BytesIO(data)  # TODO: don't require one big string?\n        response = HTTPResponse(original_request,\n                                self.code, reason=self.reason,\n                                headers=self.headers,\n                                request_time=self.io_loop.time() - self.start_time,\n                                buffer=buffer,\n                                effective_url=self.request.url)\n        self._run_callback(response)\n        self._on_end_request()\n\n    def _on_end_request(self):\n        self.stream.close()\n\n    def _on_chunk_length(self, data):\n        # TODO: \"chunk extensions\" http://tools.ietf.org/html/rfc2616#section-3.6.1\n        length = int(data.strip(), 16)\n        if length == 0:\n            if self._decompressor is not None:\n                tail = self._decompressor.flush()\n                if tail:\n                    # I believe the tail will always be empty (i.e.\n                    # decompress will return all it can).  The purpose\n                    # of the flush call is to detect errors such\n                    # as truncated input.  But in case it ever returns\n                    # anything, treat it as an extra chunk\n                    if self.request.streaming_callback is not None:\n                        self.request.streaming_callback(tail)\n                    else:\n                        self.chunks.append(tail)\n                # all the data has been decompressed, so we don't need to\n                # decompress again in _on_body\n                self._decompressor = None\n            self._on_body(b''.join(self.chunks))\n        else:\n            self.stream.read_bytes(length + 2,  # chunk ends with \\r\\n\n                                   self._on_chunk_data)\n\n    def _on_chunk_data(self, data):\n        assert data[-2:] == b\"\\r\\n\"\n        chunk = data[:-2]\n        if self._decompressor:\n            chunk = self._decompressor.decompress(chunk)\n        if self.request.streaming_callback is not None:\n            self.request.streaming_callback(chunk)\n        else:\n            self.chunks.append(chunk)\n        self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(SimpleAsyncHTTPClient)\n    main()\n",
            "header": {
                "file": {
                    "path": "tornado/simple_httpclient.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "faf75cec",
                "new_commit_hash": "2558ada8",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/tornado/simple_httpclient.py"
            },
            "plus_file": {
                "path": "b/tornado/simple_httpclient.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 302,
                            "length": 9
                        },
                        "new_range": {
                            "start": 302,
                            "length": 15
                        },
                        "section": "class _HTTPConnection(object):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            self.request.headers[\"User-Agent\"] = self.request.user_agent",
                                "type": "context"
                            },
                            {
                                "content": "        if not self.request.allow_nonstandard_methods:",
                                "type": "context"
                            },
                            {
                                "content": "            if self.request.method in (\"POST\", \"PATCH\", \"PUT\"):",
                                "type": "context"
                            },
                            {
                                "content": "                assert self.request.body is not None",
                                "type": "deleted"
                            },
                            {
                                "content": "                if self.request.body is None:",
                                "type": "added"
                            },
                            {
                                "content": "                    raise AssertionError(",
                                "type": "added"
                            },
                            {
                                "content": "                        'Body must not be empty for \"%s\" request'",
                                "type": "added"
                            },
                            {
                                "content": "                        % self.request.method)",
                                "type": "added"
                            },
                            {
                                "content": "            else:",
                                "type": "context"
                            },
                            {
                                "content": "                assert self.request.body is None",
                                "type": "deleted"
                            },
                            {
                                "content": "                if self.request.body is not None:",
                                "type": "added"
                            },
                            {
                                "content": "                    raise AssertionError(",
                                "type": "added"
                            },
                            {
                                "content": "                        'Body must be empty for \"%s\" request'",
                                "type": "added"
                            },
                            {
                                "content": "                        % self.request.method)",
                                "type": "added"
                            },
                            {
                                "content": "        if self.request.body is not None:",
                                "type": "context"
                            },
                            {
                                "content": "            self.request.headers[\"Content-Length\"] = str(len(",
                                "type": "context"
                            },
                            {
                                "content": "                self.request.body))",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/simple_httpclient.py",
                            "type": "method",
                            "name": "_HTTPConnection._on_connect",
                            "content": "class _HTTPConnection(object):\n    _SUPPORTED_METHODS = set([\"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"OPTIONS\"])\n\n    def __init__(self, io_loop, client, request, release_callback,\n                 final_callback, max_buffer_size, resolver):\n        self.start_time = io_loop.time()\n        self.io_loop = io_loop\n        self.client = client\n        self.request = request\n        self.release_callback = release_callback\n        self.final_callback = final_callback\n        self.max_buffer_size = max_buffer_size\n        self.resolver = resolver\n        self.code = None\n        self.headers = None\n        self.chunks = None\n        self._decompressor = None\n        # Timeout handle returned by IOLoop.add_timeout\n        self._timeout = None\n        with stack_context.ExceptionStackContext(self._handle_exception):\n            self.parsed = urlparse.urlsplit(_unicode(self.request.url))\n            if self.parsed.scheme not in (\"http\", \"https\"):\n                raise ValueError(\"Unsupported url scheme: %s\" %\n                                 self.request.url)\n            # urlsplit results have hostname and port results, but they\n            # didn't support ipv6 literals until python 2.7.\n            netloc = self.parsed.netloc\n            if \"@\" in netloc:\n                userpass, _, netloc = netloc.rpartition(\"@\")\n            match = re.match(r'^(.+):(\\d+)$', netloc)\n            if match:\n                host = match.group(1)\n                port = int(match.group(2))\n            else:\n                host = netloc\n                port = 443 if self.parsed.scheme == \"https\" else 80\n            if re.match(r'^\\[.*\\]$', host):\n                # raw ipv6 addresses in urls are enclosed in brackets\n                host = host[1:-1]\n            self.parsed_hostname = host  # save final host for _on_connect\n\n            if request.allow_ipv6:\n                af = socket.AF_UNSPEC\n            else:\n                # We only try the first IP we get from getaddrinfo,\n                # so restrict to ipv4 by default.\n                af = socket.AF_INET\n\n            timeout = min(self.request.connect_timeout, self.request.request_timeout)\n            if timeout:\n                self._timeout = self.io_loop.add_timeout(\n                    self.start_time + timeout,\n                    stack_context.wrap(self._on_timeout))\n            self.resolver.resolve(host, port, af, callback=self._on_resolve)\n\n    def _on_resolve(self, addrinfo):\n        if self.final_callback is None:\n            # final_callback is cleared if we've hit our timeout\n            return\n        self.stream = self._create_stream(addrinfo)\n        self.stream.set_close_callback(self._on_close)\n        # ipv6 addresses are broken (in self.parsed.hostname) until\n        # 2.7, here is correctly parsed value calculated in __init__\n        sockaddr = addrinfo[0][1]\n        self.stream.connect(sockaddr, self._on_connect,\n                            server_hostname=self.parsed_hostname)\n\n    def _create_stream(self, addrinfo):\n        af = addrinfo[0][0]\n        if self.parsed.scheme == \"https\":\n            ssl_options = {}\n            if self.request.validate_cert:\n                ssl_options[\"cert_reqs\"] = ssl.CERT_REQUIRED\n            if self.request.ca_certs is not None:\n                ssl_options[\"ca_certs\"] = self.request.ca_certs\n            else:\n                ssl_options[\"ca_certs\"] = _DEFAULT_CA_CERTS\n            if self.request.client_key is not None:\n                ssl_options[\"keyfile\"] = self.request.client_key\n            if self.request.client_cert is not None:\n                ssl_options[\"certfile\"] = self.request.client_cert\n\n            # SSL interoperability is tricky.  We want to disable\n            # SSLv2 for security reasons; it wasn't disabled by default\n            # until openssl 1.0.  The best way to do this is to use\n            # the SSL_OP_NO_SSLv2, but that wasn't exposed to python\n            # until 3.2.  Python 2.7 adds the ciphers argument, which\n            # can also be used to disable SSLv2.  As a last resort\n            # on python 2.6, we set ssl_version to TLSv1.  This is\n            # more narrow than we'd like since it also breaks\n            # compatibility with servers configured for SSLv3 only,\n            # but nearly all servers support both SSLv3 and TLSv1:\n            # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html\n            if sys.version_info >= (2, 7):\n                ssl_options[\"ciphers\"] = \"DEFAULT:!SSLv2\"\n            else:\n                # This is really only necessary for pre-1.0 versions\n                # of openssl, but python 2.6 doesn't expose version\n                # information.\n                ssl_options[\"ssl_version\"] = ssl.PROTOCOL_TLSv1\n\n            return SSLIOStream(socket.socket(af),\n                               io_loop=self.io_loop,\n                               ssl_options=ssl_options,\n                               max_buffer_size=self.max_buffer_size)\n        else:\n            return IOStream(socket.socket(af),\n                            io_loop=self.io_loop,\n                            max_buffer_size=self.max_buffer_size)\n\n    def _on_timeout(self):\n        self._timeout = None\n        if self.final_callback is not None:\n            raise HTTPError(599, \"Timeout\")\n\n    def _remove_timeout(self):\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n            self._timeout = None\n\n    def _on_connect(self):\n        self._remove_timeout()\n        if self.final_callback is None:\n            return\n        if self.request.request_timeout:\n            self._timeout = self.io_loop.add_timeout(\n                self.start_time + self.request.request_timeout,\n                stack_context.wrap(self._on_timeout))\n        if (self.request.method not in self._SUPPORTED_METHODS and\n                not self.request.allow_nonstandard_methods):\n            raise KeyError(\"unknown method %s\" % self.request.method)\n        for key in ('network_interface',\n                    'proxy_host', 'proxy_port',\n                    'proxy_username', 'proxy_password'):\n            if getattr(self.request, key, None):\n                raise NotImplementedError('%s not supported' % key)\n        if \"Connection\" not in self.request.headers:\n            self.request.headers[\"Connection\"] = \"close\"\n        if \"Host\" not in self.request.headers:\n            if '@' in self.parsed.netloc:\n                self.request.headers[\"Host\"] = self.parsed.netloc.rpartition('@')[-1]\n            else:\n                self.request.headers[\"Host\"] = self.parsed.netloc\n        username, password = None, None\n        if self.parsed.username is not None:\n            username, password = self.parsed.username, self.parsed.password\n        elif self.request.auth_username is not None:\n            username = self.request.auth_username\n            password = self.request.auth_password or ''\n        if username is not None:\n            if self.request.auth_mode not in (None, \"basic\"):\n                raise ValueError(\"unsupported auth_mode %s\",\n                                 self.request.auth_mode)\n            auth = utf8(username) + b\":\" + utf8(password)\n            self.request.headers[\"Authorization\"] = (b\"Basic \" +\n                                                     base64.b64encode(auth))\n        if self.request.user_agent:\n            self.request.headers[\"User-Agent\"] = self.request.user_agent\n        if not self.request.allow_nonstandard_methods:\n            if self.request.method in (\"POST\", \"PATCH\", \"PUT\"):\n                assert self.request.body is not None\n            else:\n                assert self.request.body is None\n        if self.request.body is not None:\n            self.request.headers[\"Content-Length\"] = str(len(\n                self.request.body))\n        if (self.request.method == \"POST\" and\n                \"Content-Type\" not in self.request.headers):\n            self.request.headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\"\n        if self.request.use_gzip:\n            self.request.headers[\"Accept-Encoding\"] = \"gzip\"\n        req_path = ((self.parsed.path or '/') +\n                   (('?' + self.parsed.query) if self.parsed.query else ''))\n        request_lines = [utf8(\"%s %s HTTP/1.1\" % (self.request.method,\n                                                  req_path))]\n        for k, v in self.request.headers.get_all():\n            line = utf8(k) + b\": \" + utf8(v)\n            if b'\\n' in line:\n                raise ValueError('Newline in header: ' + repr(line))\n            request_lines.append(line)\n        request_str = b\"\\r\\n\".join(request_lines) + b\"\\r\\n\\r\\n\"\n        if self.request.body is not None:\n            request_str += self.request.body\n        self.stream.set_nodelay(True)\n        self.stream.write(request_str)\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _release(self):\n        if self.release_callback is not None:\n            release_callback = self.release_callback\n            self.release_callback = None\n            release_callback()\n\n    def _run_callback(self, response):\n        self._release()\n        if self.final_callback is not None:\n            final_callback = self.final_callback\n            self.final_callback = None\n            self.io_loop.add_callback(final_callback, response)\n\n    def _handle_exception(self, typ, value, tb):\n        if self.final_callback:\n            self._remove_timeout()\n            self._run_callback(HTTPResponse(self.request, 599, error=value,\n                                            request_time=self.io_loop.time() - self.start_time,\n                                            ))\n\n            if hasattr(self, \"stream\"):\n                self.stream.close()\n            return True\n        else:\n            # If our callback has already been called, we are probably\n            # catching an exception that is not caused by us but rather\n            # some child of our callback. Rather than drop it on the floor,\n            # pass it along.\n            return False\n\n    def _on_close(self):\n        if self.final_callback is not None:\n            message = \"Connection closed\"\n            if self.stream.error:\n                message = str(self.stream.error)\n            raise HTTPError(599, message)\n\n    def _handle_1xx(self, code):\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _on_headers(self, data):\n        data = native_str(data.decode(\"latin1\"))\n        first_line, _, header_data = data.partition(\"\\n\")\n        match = re.match(\"HTTP/1.[01] ([0-9]+) ([^\\r]*)\", first_line)\n        assert match\n        code = int(match.group(1))\n        self.headers = HTTPHeaders.parse(header_data)\n        if 100 <= code < 200:\n            self._handle_1xx(code)\n            return\n        else:\n            self.code = code\n            self.reason = match.group(2)\n\n        if \"Content-Length\" in self.headers:\n            if \",\" in self.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*', self.headers[\"Content-Length\"])\n                if any(i != pieces[0] for i in pieces):\n                    raise ValueError(\"Multiple unequal Content-Lengths: %r\" %\n                                     self.headers[\"Content-Length\"])\n                self.headers[\"Content-Length\"] = pieces[0]\n            content_length = int(self.headers[\"Content-Length\"])\n        else:\n            content_length = None\n\n        if self.request.header_callback is not None:\n            # re-attach the newline we split on earlier\n            self.request.header_callback(first_line + _)\n            for k, v in self.headers.get_all():\n                self.request.header_callback(\"%s: %s\\r\\n\" % (k, v))\n            self.request.header_callback('\\r\\n')\n\n        if self.request.method == \"HEAD\" or self.code == 304:\n            # HEAD requests and 304 responses never have content, even\n            # though they may have content-length headers\n            self._on_body(b\"\")\n            return\n        if 100 <= self.code < 200 or self.code == 204:\n            # These response codes never have bodies\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3\n            if (\"Transfer-Encoding\" in self.headers or\n                    content_length not in (None, 0)):\n                raise ValueError(\"Response with code %d should not have body\" %\n                                 self.code)\n            self._on_body(b\"\")\n            return\n\n        if (self.request.use_gzip and\n                self.headers.get(\"Content-Encoding\") == \"gzip\"):\n            self._decompressor = GzipDecompressor()\n        if self.headers.get(\"Transfer-Encoding\") == \"chunked\":\n            self.chunks = []\n            self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)\n        elif content_length is not None:\n            self.stream.read_bytes(content_length, self._on_body)\n        else:\n            self.stream.read_until_close(self._on_body)\n\n    def _on_body(self, data):\n        self._remove_timeout()\n        original_request = getattr(self.request, \"original_request\",\n                                   self.request)\n        if (self.request.follow_redirects and\n            self.request.max_redirects > 0 and\n                self.code in (301, 302, 303, 307)):\n            assert isinstance(self.request, _RequestProxy)\n            new_request = copy.copy(self.request.request)\n            new_request.url = urlparse.urljoin(self.request.url,\n                                               self.headers[\"Location\"])\n            new_request.max_redirects = self.request.max_redirects - 1\n            del new_request.headers[\"Host\"]\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4\n            # Client SHOULD make a GET request after a 303.\n            # According to the spec, 302 should be followed by the same\n            # method as the original request, but in practice browsers\n            # treat 302 the same as 303, and many servers use 302 for\n            # compatibility with pre-HTTP/1.1 user agents which don't\n            # understand the 303 status.\n            if self.code in (302, 303):\n                new_request.method = \"GET\"\n                new_request.body = None\n                for h in [\"Content-Length\", \"Content-Type\",\n                          \"Content-Encoding\", \"Transfer-Encoding\"]:\n                    try:\n                        del self.request.headers[h]\n                    except KeyError:\n                        pass\n            new_request.original_request = original_request\n            final_callback = self.final_callback\n            self.final_callback = None\n            self._release()\n            self.client.fetch(new_request, final_callback)\n            self._on_end_request()\n            return\n        if self._decompressor:\n            data = (self._decompressor.decompress(data) +\n                    self._decompressor.flush())\n        if self.request.streaming_callback:\n            if self.chunks is None:\n                # if chunks is not None, we already called streaming_callback\n                # in _on_chunk_data\n                self.request.streaming_callback(data)\n            buffer = BytesIO()\n        else:\n            buffer = BytesIO(data)  # TODO: don't require one big string?\n        response = HTTPResponse(original_request,\n                                self.code, reason=self.reason,\n                                headers=self.headers,\n                                request_time=self.io_loop.time() - self.start_time,\n                                buffer=buffer,\n                                effective_url=self.request.url)\n        self._run_callback(response)\n        self._on_end_request()\n\n    def _on_end_request(self):\n        self.stream.close()\n\n    def _on_chunk_length(self, data):\n        # TODO: \"chunk extensions\" http://tools.ietf.org/html/rfc2616#section-3.6.1\n        length = int(data.strip(), 16)\n        if length == 0:\n            if self._decompressor is not None:\n                tail = self._decompressor.flush()\n                if tail:\n                    # I believe the tail will always be empty (i.e.\n                    # decompress will return all it can).  The purpose\n                    # of the flush call is to detect errors such\n                    # as truncated input.  But in case it ever returns\n                    # anything, treat it as an extra chunk\n                    if self.request.streaming_callback is not None:\n                        self.request.streaming_callback(tail)\n                    else:\n                        self.chunks.append(tail)\n                # all the data has been decompressed, so we don't need to\n                # decompress again in _on_body\n                self._decompressor = None\n            self._on_body(b''.join(self.chunks))\n        else:\n            self.stream.read_bytes(length + 2,  # chunk ends with \\r\\n\n                                   self._on_chunk_data)\n\n    def _on_chunk_data(self, data):\n        assert data[-2:] == b\"\\r\\n\"\n        chunk = data[:-2]\n        if self._decompressor:\n            chunk = self._decompressor.decompress(chunk)\n        if self.request.streaming_callback is not None:\n            self.request.streaming_callback(chunk)\n        else:\n            self.chunks.append(chunk)\n        self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 265,
                            "end_lineno": 330,
                            "parent": {
                                "file_name": "tornado/simple_httpclient.py",
                                "type": "class",
                                "name": "_HTTPConnection",
                                "content": "class _HTTPConnection(object):\n    _SUPPORTED_METHODS = set([\"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"OPTIONS\"])\n\n    def __init__(self, io_loop, client, request, release_callback,\n                 final_callback, max_buffer_size, resolver):\n        self.start_time = io_loop.time()\n        self.io_loop = io_loop\n        self.client = client\n        self.request = request\n        self.release_callback = release_callback\n        self.final_callback = final_callback\n        self.max_buffer_size = max_buffer_size\n        self.resolver = resolver\n        self.code = None\n        self.headers = None\n        self.chunks = None\n        self._decompressor = None\n        # Timeout handle returned by IOLoop.add_timeout\n        self._timeout = None\n        with stack_context.ExceptionStackContext(self._handle_exception):\n            self.parsed = urlparse.urlsplit(_unicode(self.request.url))\n            if self.parsed.scheme not in (\"http\", \"https\"):\n                raise ValueError(\"Unsupported url scheme: %s\" %\n                                 self.request.url)\n            # urlsplit results have hostname and port results, but they\n            # didn't support ipv6 literals until python 2.7.\n            netloc = self.parsed.netloc\n            if \"@\" in netloc:\n                userpass, _, netloc = netloc.rpartition(\"@\")\n            match = re.match(r'^(.+):(\\d+)$', netloc)\n            if match:\n                host = match.group(1)\n                port = int(match.group(2))\n            else:\n                host = netloc\n                port = 443 if self.parsed.scheme == \"https\" else 80\n            if re.match(r'^\\[.*\\]$', host):\n                # raw ipv6 addresses in urls are enclosed in brackets\n                host = host[1:-1]\n            self.parsed_hostname = host  # save final host for _on_connect\n\n            if request.allow_ipv6:\n                af = socket.AF_UNSPEC\n            else:\n                # We only try the first IP we get from getaddrinfo,\n                # so restrict to ipv4 by default.\n                af = socket.AF_INET\n\n            timeout = min(self.request.connect_timeout, self.request.request_timeout)\n            if timeout:\n                self._timeout = self.io_loop.add_timeout(\n                    self.start_time + timeout,\n                    stack_context.wrap(self._on_timeout))\n            self.resolver.resolve(host, port, af, callback=self._on_resolve)\n\n    def _on_resolve(self, addrinfo):\n        if self.final_callback is None:\n            # final_callback is cleared if we've hit our timeout\n            return\n        self.stream = self._create_stream(addrinfo)\n        self.stream.set_close_callback(self._on_close)\n        # ipv6 addresses are broken (in self.parsed.hostname) until\n        # 2.7, here is correctly parsed value calculated in __init__\n        sockaddr = addrinfo[0][1]\n        self.stream.connect(sockaddr, self._on_connect,\n                            server_hostname=self.parsed_hostname)\n\n    def _create_stream(self, addrinfo):\n        af = addrinfo[0][0]\n        if self.parsed.scheme == \"https\":\n            ssl_options = {}\n            if self.request.validate_cert:\n                ssl_options[\"cert_reqs\"] = ssl.CERT_REQUIRED\n            if self.request.ca_certs is not None:\n                ssl_options[\"ca_certs\"] = self.request.ca_certs\n            else:\n                ssl_options[\"ca_certs\"] = _DEFAULT_CA_CERTS\n            if self.request.client_key is not None:\n                ssl_options[\"keyfile\"] = self.request.client_key\n            if self.request.client_cert is not None:\n                ssl_options[\"certfile\"] = self.request.client_cert\n\n            # SSL interoperability is tricky.  We want to disable\n            # SSLv2 for security reasons; it wasn't disabled by default\n            # until openssl 1.0.  The best way to do this is to use\n            # the SSL_OP_NO_SSLv2, but that wasn't exposed to python\n            # until 3.2.  Python 2.7 adds the ciphers argument, which\n            # can also be used to disable SSLv2.  As a last resort\n            # on python 2.6, we set ssl_version to TLSv1.  This is\n            # more narrow than we'd like since it also breaks\n            # compatibility with servers configured for SSLv3 only,\n            # but nearly all servers support both SSLv3 and TLSv1:\n            # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html\n            if sys.version_info >= (2, 7):\n                ssl_options[\"ciphers\"] = \"DEFAULT:!SSLv2\"\n            else:\n                # This is really only necessary for pre-1.0 versions\n                # of openssl, but python 2.6 doesn't expose version\n                # information.\n                ssl_options[\"ssl_version\"] = ssl.PROTOCOL_TLSv1\n\n            return SSLIOStream(socket.socket(af),\n                               io_loop=self.io_loop,\n                               ssl_options=ssl_options,\n                               max_buffer_size=self.max_buffer_size)\n        else:\n            return IOStream(socket.socket(af),\n                            io_loop=self.io_loop,\n                            max_buffer_size=self.max_buffer_size)\n\n    def _on_timeout(self):\n        self._timeout = None\n        if self.final_callback is not None:\n            raise HTTPError(599, \"Timeout\")\n\n    def _remove_timeout(self):\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n            self._timeout = None\n\n    def _on_connect(self):\n        self._remove_timeout()\n        if self.final_callback is None:\n            return\n        if self.request.request_timeout:\n            self._timeout = self.io_loop.add_timeout(\n                self.start_time + self.request.request_timeout,\n                stack_context.wrap(self._on_timeout))\n        if (self.request.method not in self._SUPPORTED_METHODS and\n                not self.request.allow_nonstandard_methods):\n            raise KeyError(\"unknown method %s\" % self.request.method)\n        for key in ('network_interface',\n                    'proxy_host', 'proxy_port',\n                    'proxy_username', 'proxy_password'):\n            if getattr(self.request, key, None):\n                raise NotImplementedError('%s not supported' % key)\n        if \"Connection\" not in self.request.headers:\n            self.request.headers[\"Connection\"] = \"close\"\n        if \"Host\" not in self.request.headers:\n            if '@' in self.parsed.netloc:\n                self.request.headers[\"Host\"] = self.parsed.netloc.rpartition('@')[-1]\n            else:\n                self.request.headers[\"Host\"] = self.parsed.netloc\n        username, password = None, None\n        if self.parsed.username is not None:\n            username, password = self.parsed.username, self.parsed.password\n        elif self.request.auth_username is not None:\n            username = self.request.auth_username\n            password = self.request.auth_password or ''\n        if username is not None:\n            if self.request.auth_mode not in (None, \"basic\"):\n                raise ValueError(\"unsupported auth_mode %s\",\n                                 self.request.auth_mode)\n            auth = utf8(username) + b\":\" + utf8(password)\n            self.request.headers[\"Authorization\"] = (b\"Basic \" +\n                                                     base64.b64encode(auth))\n        if self.request.user_agent:\n            self.request.headers[\"User-Agent\"] = self.request.user_agent\n        if not self.request.allow_nonstandard_methods:\n            if self.request.method in (\"POST\", \"PATCH\", \"PUT\"):\n                assert self.request.body is not None\n            else:\n                assert self.request.body is None\n        if self.request.body is not None:\n            self.request.headers[\"Content-Length\"] = str(len(\n                self.request.body))\n        if (self.request.method == \"POST\" and\n                \"Content-Type\" not in self.request.headers):\n            self.request.headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\"\n        if self.request.use_gzip:\n            self.request.headers[\"Accept-Encoding\"] = \"gzip\"\n        req_path = ((self.parsed.path or '/') +\n                   (('?' + self.parsed.query) if self.parsed.query else ''))\n        request_lines = [utf8(\"%s %s HTTP/1.1\" % (self.request.method,\n                                                  req_path))]\n        for k, v in self.request.headers.get_all():\n            line = utf8(k) + b\": \" + utf8(v)\n            if b'\\n' in line:\n                raise ValueError('Newline in header: ' + repr(line))\n            request_lines.append(line)\n        request_str = b\"\\r\\n\".join(request_lines) + b\"\\r\\n\\r\\n\"\n        if self.request.body is not None:\n            request_str += self.request.body\n        self.stream.set_nodelay(True)\n        self.stream.write(request_str)\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _release(self):\n        if self.release_callback is not None:\n            release_callback = self.release_callback\n            self.release_callback = None\n            release_callback()\n\n    def _run_callback(self, response):\n        self._release()\n        if self.final_callback is not None:\n            final_callback = self.final_callback\n            self.final_callback = None\n            self.io_loop.add_callback(final_callback, response)\n\n    def _handle_exception(self, typ, value, tb):\n        if self.final_callback:\n            self._remove_timeout()\n            self._run_callback(HTTPResponse(self.request, 599, error=value,\n                                            request_time=self.io_loop.time() - self.start_time,\n                                            ))\n\n            if hasattr(self, \"stream\"):\n                self.stream.close()\n            return True\n        else:\n            # If our callback has already been called, we are probably\n            # catching an exception that is not caused by us but rather\n            # some child of our callback. Rather than drop it on the floor,\n            # pass it along.\n            return False\n\n    def _on_close(self):\n        if self.final_callback is not None:\n            message = \"Connection closed\"\n            if self.stream.error:\n                message = str(self.stream.error)\n            raise HTTPError(599, message)\n\n    def _handle_1xx(self, code):\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _on_headers(self, data):\n        data = native_str(data.decode(\"latin1\"))\n        first_line, _, header_data = data.partition(\"\\n\")\n        match = re.match(\"HTTP/1.[01] ([0-9]+) ([^\\r]*)\", first_line)\n        assert match\n        code = int(match.group(1))\n        self.headers = HTTPHeaders.parse(header_data)\n        if 100 <= code < 200:\n            self._handle_1xx(code)\n            return\n        else:\n            self.code = code\n            self.reason = match.group(2)\n\n        if \"Content-Length\" in self.headers:\n            if \",\" in self.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*', self.headers[\"Content-Length\"])\n                if any(i != pieces[0] for i in pieces):\n                    raise ValueError(\"Multiple unequal Content-Lengths: %r\" %\n                                     self.headers[\"Content-Length\"])\n                self.headers[\"Content-Length\"] = pieces[0]\n            content_length = int(self.headers[\"Content-Length\"])\n        else:\n            content_length = None\n\n        if self.request.header_callback is not None:\n            # re-attach the newline we split on earlier\n            self.request.header_callback(first_line + _)\n            for k, v in self.headers.get_all():\n                self.request.header_callback(\"%s: %s\\r\\n\" % (k, v))\n            self.request.header_callback('\\r\\n')\n\n        if self.request.method == \"HEAD\" or self.code == 304:\n            # HEAD requests and 304 responses never have content, even\n            # though they may have content-length headers\n            self._on_body(b\"\")\n            return\n        if 100 <= self.code < 200 or self.code == 204:\n            # These response codes never have bodies\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3\n            if (\"Transfer-Encoding\" in self.headers or\n                    content_length not in (None, 0)):\n                raise ValueError(\"Response with code %d should not have body\" %\n                                 self.code)\n            self._on_body(b\"\")\n            return\n\n        if (self.request.use_gzip and\n                self.headers.get(\"Content-Encoding\") == \"gzip\"):\n            self._decompressor = GzipDecompressor()\n        if self.headers.get(\"Transfer-Encoding\") == \"chunked\":\n            self.chunks = []\n            self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)\n        elif content_length is not None:\n            self.stream.read_bytes(content_length, self._on_body)\n        else:\n            self.stream.read_until_close(self._on_body)\n\n    def _on_body(self, data):\n        self._remove_timeout()\n        original_request = getattr(self.request, \"original_request\",\n                                   self.request)\n        if (self.request.follow_redirects and\n            self.request.max_redirects > 0 and\n                self.code in (301, 302, 303, 307)):\n            assert isinstance(self.request, _RequestProxy)\n            new_request = copy.copy(self.request.request)\n            new_request.url = urlparse.urljoin(self.request.url,\n                                               self.headers[\"Location\"])\n            new_request.max_redirects = self.request.max_redirects - 1\n            del new_request.headers[\"Host\"]\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4\n            # Client SHOULD make a GET request after a 303.\n            # According to the spec, 302 should be followed by the same\n            # method as the original request, but in practice browsers\n            # treat 302 the same as 303, and many servers use 302 for\n            # compatibility with pre-HTTP/1.1 user agents which don't\n            # understand the 303 status.\n            if self.code in (302, 303):\n                new_request.method = \"GET\"\n                new_request.body = None\n                for h in [\"Content-Length\", \"Content-Type\",\n                          \"Content-Encoding\", \"Transfer-Encoding\"]:\n                    try:\n                        del self.request.headers[h]\n                    except KeyError:\n                        pass\n            new_request.original_request = original_request\n            final_callback = self.final_callback\n            self.final_callback = None\n            self._release()\n            self.client.fetch(new_request, final_callback)\n            self._on_end_request()\n            return\n        if self._decompressor:\n            data = (self._decompressor.decompress(data) +\n                    self._decompressor.flush())\n        if self.request.streaming_callback:\n            if self.chunks is None:\n                # if chunks is not None, we already called streaming_callback\n                # in _on_chunk_data\n                self.request.streaming_callback(data)\n            buffer = BytesIO()\n        else:\n            buffer = BytesIO(data)  # TODO: don't require one big string?\n        response = HTTPResponse(original_request,\n                                self.code, reason=self.reason,\n                                headers=self.headers,\n                                request_time=self.io_loop.time() - self.start_time,\n                                buffer=buffer,\n                                effective_url=self.request.url)\n        self._run_callback(response)\n        self._on_end_request()\n\n    def _on_end_request(self):\n        self.stream.close()\n\n    def _on_chunk_length(self, data):\n        # TODO: \"chunk extensions\" http://tools.ietf.org/html/rfc2616#section-3.6.1\n        length = int(data.strip(), 16)\n        if length == 0:\n            if self._decompressor is not None:\n                tail = self._decompressor.flush()\n                if tail:\n                    # I believe the tail will always be empty (i.e.\n                    # decompress will return all it can).  The purpose\n                    # of the flush call is to detect errors such\n                    # as truncated input.  But in case it ever returns\n                    # anything, treat it as an extra chunk\n                    if self.request.streaming_callback is not None:\n                        self.request.streaming_callback(tail)\n                    else:\n                        self.chunks.append(tail)\n                # all the data has been decompressed, so we don't need to\n                # decompress again in _on_body\n                self._decompressor = None\n            self._on_body(b''.join(self.chunks))\n        else:\n            self.stream.read_bytes(length + 2,  # chunk ends with \\r\\n\n                                   self._on_chunk_data)\n\n    def _on_chunk_data(self, data):\n        assert data[-2:] == b\"\\r\\n\"\n        chunk = data[:-2]\n        if self._decompressor:\n            chunk = self._decompressor.decompress(chunk)\n        if self.request.streaming_callback is not None:\n            self.request.streaming_callback(chunk)\n        else:\n            self.chunks.append(chunk)\n        self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 145,
                                "end_lineno": 525,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "tornado/simple_httpclient.py",
                            "type": "class",
                            "name": "_HTTPConnection",
                            "content": "class _HTTPConnection(object):\n    _SUPPORTED_METHODS = set([\"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"OPTIONS\"])\n\n    def __init__(self, io_loop, client, request, release_callback,\n                 final_callback, max_buffer_size, resolver):\n        self.start_time = io_loop.time()\n        self.io_loop = io_loop\n        self.client = client\n        self.request = request\n        self.release_callback = release_callback\n        self.final_callback = final_callback\n        self.max_buffer_size = max_buffer_size\n        self.resolver = resolver\n        self.code = None\n        self.headers = None\n        self.chunks = None\n        self._decompressor = None\n        # Timeout handle returned by IOLoop.add_timeout\n        self._timeout = None\n        with stack_context.ExceptionStackContext(self._handle_exception):\n            self.parsed = urlparse.urlsplit(_unicode(self.request.url))\n            if self.parsed.scheme not in (\"http\", \"https\"):\n                raise ValueError(\"Unsupported url scheme: %s\" %\n                                 self.request.url)\n            # urlsplit results have hostname and port results, but they\n            # didn't support ipv6 literals until python 2.7.\n            netloc = self.parsed.netloc\n            if \"@\" in netloc:\n                userpass, _, netloc = netloc.rpartition(\"@\")\n            match = re.match(r'^(.+):(\\d+)$', netloc)\n            if match:\n                host = match.group(1)\n                port = int(match.group(2))\n            else:\n                host = netloc\n                port = 443 if self.parsed.scheme == \"https\" else 80\n            if re.match(r'^\\[.*\\]$', host):\n                # raw ipv6 addresses in urls are enclosed in brackets\n                host = host[1:-1]\n            self.parsed_hostname = host  # save final host for _on_connect\n\n            if request.allow_ipv6:\n                af = socket.AF_UNSPEC\n            else:\n                # We only try the first IP we get from getaddrinfo,\n                # so restrict to ipv4 by default.\n                af = socket.AF_INET\n\n            timeout = min(self.request.connect_timeout, self.request.request_timeout)\n            if timeout:\n                self._timeout = self.io_loop.add_timeout(\n                    self.start_time + timeout,\n                    stack_context.wrap(self._on_timeout))\n            self.resolver.resolve(host, port, af, callback=self._on_resolve)\n\n    def _on_resolve(self, addrinfo):\n        if self.final_callback is None:\n            # final_callback is cleared if we've hit our timeout\n            return\n        self.stream = self._create_stream(addrinfo)\n        self.stream.set_close_callback(self._on_close)\n        # ipv6 addresses are broken (in self.parsed.hostname) until\n        # 2.7, here is correctly parsed value calculated in __init__\n        sockaddr = addrinfo[0][1]\n        self.stream.connect(sockaddr, self._on_connect,\n                            server_hostname=self.parsed_hostname)\n\n    def _create_stream(self, addrinfo):\n        af = addrinfo[0][0]\n        if self.parsed.scheme == \"https\":\n            ssl_options = {}\n            if self.request.validate_cert:\n                ssl_options[\"cert_reqs\"] = ssl.CERT_REQUIRED\n            if self.request.ca_certs is not None:\n                ssl_options[\"ca_certs\"] = self.request.ca_certs\n            else:\n                ssl_options[\"ca_certs\"] = _DEFAULT_CA_CERTS\n            if self.request.client_key is not None:\n                ssl_options[\"keyfile\"] = self.request.client_key\n            if self.request.client_cert is not None:\n                ssl_options[\"certfile\"] = self.request.client_cert\n\n            # SSL interoperability is tricky.  We want to disable\n            # SSLv2 for security reasons; it wasn't disabled by default\n            # until openssl 1.0.  The best way to do this is to use\n            # the SSL_OP_NO_SSLv2, but that wasn't exposed to python\n            # until 3.2.  Python 2.7 adds the ciphers argument, which\n            # can also be used to disable SSLv2.  As a last resort\n            # on python 2.6, we set ssl_version to TLSv1.  This is\n            # more narrow than we'd like since it also breaks\n            # compatibility with servers configured for SSLv3 only,\n            # but nearly all servers support both SSLv3 and TLSv1:\n            # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html\n            if sys.version_info >= (2, 7):\n                ssl_options[\"ciphers\"] = \"DEFAULT:!SSLv2\"\n            else:\n                # This is really only necessary for pre-1.0 versions\n                # of openssl, but python 2.6 doesn't expose version\n                # information.\n                ssl_options[\"ssl_version\"] = ssl.PROTOCOL_TLSv1\n\n            return SSLIOStream(socket.socket(af),\n                               io_loop=self.io_loop,\n                               ssl_options=ssl_options,\n                               max_buffer_size=self.max_buffer_size)\n        else:\n            return IOStream(socket.socket(af),\n                            io_loop=self.io_loop,\n                            max_buffer_size=self.max_buffer_size)\n\n    def _on_timeout(self):\n        self._timeout = None\n        if self.final_callback is not None:\n            raise HTTPError(599, \"Timeout\")\n\n    def _remove_timeout(self):\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n            self._timeout = None\n\n    def _on_connect(self):\n        self._remove_timeout()\n        if self.final_callback is None:\n            return\n        if self.request.request_timeout:\n            self._timeout = self.io_loop.add_timeout(\n                self.start_time + self.request.request_timeout,\n                stack_context.wrap(self._on_timeout))\n        if (self.request.method not in self._SUPPORTED_METHODS and\n                not self.request.allow_nonstandard_methods):\n            raise KeyError(\"unknown method %s\" % self.request.method)\n        for key in ('network_interface',\n                    'proxy_host', 'proxy_port',\n                    'proxy_username', 'proxy_password'):\n            if getattr(self.request, key, None):\n                raise NotImplementedError('%s not supported' % key)\n        if \"Connection\" not in self.request.headers:\n            self.request.headers[\"Connection\"] = \"close\"\n        if \"Host\" not in self.request.headers:\n            if '@' in self.parsed.netloc:\n                self.request.headers[\"Host\"] = self.parsed.netloc.rpartition('@')[-1]\n            else:\n                self.request.headers[\"Host\"] = self.parsed.netloc\n        username, password = None, None\n        if self.parsed.username is not None:\n            username, password = self.parsed.username, self.parsed.password\n        elif self.request.auth_username is not None:\n            username = self.request.auth_username\n            password = self.request.auth_password or ''\n        if username is not None:\n            if self.request.auth_mode not in (None, \"basic\"):\n                raise ValueError(\"unsupported auth_mode %s\",\n                                 self.request.auth_mode)\n            auth = utf8(username) + b\":\" + utf8(password)\n            self.request.headers[\"Authorization\"] = (b\"Basic \" +\n                                                     base64.b64encode(auth))\n        if self.request.user_agent:\n            self.request.headers[\"User-Agent\"] = self.request.user_agent\n        if not self.request.allow_nonstandard_methods:\n            if self.request.method in (\"POST\", \"PATCH\", \"PUT\"):\n                assert self.request.body is not None\n            else:\n                assert self.request.body is None\n        if self.request.body is not None:\n            self.request.headers[\"Content-Length\"] = str(len(\n                self.request.body))\n        if (self.request.method == \"POST\" and\n                \"Content-Type\" not in self.request.headers):\n            self.request.headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\"\n        if self.request.use_gzip:\n            self.request.headers[\"Accept-Encoding\"] = \"gzip\"\n        req_path = ((self.parsed.path or '/') +\n                   (('?' + self.parsed.query) if self.parsed.query else ''))\n        request_lines = [utf8(\"%s %s HTTP/1.1\" % (self.request.method,\n                                                  req_path))]\n        for k, v in self.request.headers.get_all():\n            line = utf8(k) + b\": \" + utf8(v)\n            if b'\\n' in line:\n                raise ValueError('Newline in header: ' + repr(line))\n            request_lines.append(line)\n        request_str = b\"\\r\\n\".join(request_lines) + b\"\\r\\n\\r\\n\"\n        if self.request.body is not None:\n            request_str += self.request.body\n        self.stream.set_nodelay(True)\n        self.stream.write(request_str)\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _release(self):\n        if self.release_callback is not None:\n            release_callback = self.release_callback\n            self.release_callback = None\n            release_callback()\n\n    def _run_callback(self, response):\n        self._release()\n        if self.final_callback is not None:\n            final_callback = self.final_callback\n            self.final_callback = None\n            self.io_loop.add_callback(final_callback, response)\n\n    def _handle_exception(self, typ, value, tb):\n        if self.final_callback:\n            self._remove_timeout()\n            self._run_callback(HTTPResponse(self.request, 599, error=value,\n                                            request_time=self.io_loop.time() - self.start_time,\n                                            ))\n\n            if hasattr(self, \"stream\"):\n                self.stream.close()\n            return True\n        else:\n            # If our callback has already been called, we are probably\n            # catching an exception that is not caused by us but rather\n            # some child of our callback. Rather than drop it on the floor,\n            # pass it along.\n            return False\n\n    def _on_close(self):\n        if self.final_callback is not None:\n            message = \"Connection closed\"\n            if self.stream.error:\n                message = str(self.stream.error)\n            raise HTTPError(599, message)\n\n    def _handle_1xx(self, code):\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _on_headers(self, data):\n        data = native_str(data.decode(\"latin1\"))\n        first_line, _, header_data = data.partition(\"\\n\")\n        match = re.match(\"HTTP/1.[01] ([0-9]+) ([^\\r]*)\", first_line)\n        assert match\n        code = int(match.group(1))\n        self.headers = HTTPHeaders.parse(header_data)\n        if 100 <= code < 200:\n            self._handle_1xx(code)\n            return\n        else:\n            self.code = code\n            self.reason = match.group(2)\n\n        if \"Content-Length\" in self.headers:\n            if \",\" in self.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*', self.headers[\"Content-Length\"])\n                if any(i != pieces[0] for i in pieces):\n                    raise ValueError(\"Multiple unequal Content-Lengths: %r\" %\n                                     self.headers[\"Content-Length\"])\n                self.headers[\"Content-Length\"] = pieces[0]\n            content_length = int(self.headers[\"Content-Length\"])\n        else:\n            content_length = None\n\n        if self.request.header_callback is not None:\n            # re-attach the newline we split on earlier\n            self.request.header_callback(first_line + _)\n            for k, v in self.headers.get_all():\n                self.request.header_callback(\"%s: %s\\r\\n\" % (k, v))\n            self.request.header_callback('\\r\\n')\n\n        if self.request.method == \"HEAD\" or self.code == 304:\n            # HEAD requests and 304 responses never have content, even\n            # though they may have content-length headers\n            self._on_body(b\"\")\n            return\n        if 100 <= self.code < 200 or self.code == 204:\n            # These response codes never have bodies\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3\n            if (\"Transfer-Encoding\" in self.headers or\n                    content_length not in (None, 0)):\n                raise ValueError(\"Response with code %d should not have body\" %\n                                 self.code)\n            self._on_body(b\"\")\n            return\n\n        if (self.request.use_gzip and\n                self.headers.get(\"Content-Encoding\") == \"gzip\"):\n            self._decompressor = GzipDecompressor()\n        if self.headers.get(\"Transfer-Encoding\") == \"chunked\":\n            self.chunks = []\n            self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)\n        elif content_length is not None:\n            self.stream.read_bytes(content_length, self._on_body)\n        else:\n            self.stream.read_until_close(self._on_body)\n\n    def _on_body(self, data):\n        self._remove_timeout()\n        original_request = getattr(self.request, \"original_request\",\n                                   self.request)\n        if (self.request.follow_redirects and\n            self.request.max_redirects > 0 and\n                self.code in (301, 302, 303, 307)):\n            assert isinstance(self.request, _RequestProxy)\n            new_request = copy.copy(self.request.request)\n            new_request.url = urlparse.urljoin(self.request.url,\n                                               self.headers[\"Location\"])\n            new_request.max_redirects = self.request.max_redirects - 1\n            del new_request.headers[\"Host\"]\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4\n            # Client SHOULD make a GET request after a 303.\n            # According to the spec, 302 should be followed by the same\n            # method as the original request, but in practice browsers\n            # treat 302 the same as 303, and many servers use 302 for\n            # compatibility with pre-HTTP/1.1 user agents which don't\n            # understand the 303 status.\n            if self.code in (302, 303):\n                new_request.method = \"GET\"\n                new_request.body = None\n                for h in [\"Content-Length\", \"Content-Type\",\n                          \"Content-Encoding\", \"Transfer-Encoding\"]:\n                    try:\n                        del self.request.headers[h]\n                    except KeyError:\n                        pass\n            new_request.original_request = original_request\n            final_callback = self.final_callback\n            self.final_callback = None\n            self._release()\n            self.client.fetch(new_request, final_callback)\n            self._on_end_request()\n            return\n        if self._decompressor:\n            data = (self._decompressor.decompress(data) +\n                    self._decompressor.flush())\n        if self.request.streaming_callback:\n            if self.chunks is None:\n                # if chunks is not None, we already called streaming_callback\n                # in _on_chunk_data\n                self.request.streaming_callback(data)\n            buffer = BytesIO()\n        else:\n            buffer = BytesIO(data)  # TODO: don't require one big string?\n        response = HTTPResponse(original_request,\n                                self.code, reason=self.reason,\n                                headers=self.headers,\n                                request_time=self.io_loop.time() - self.start_time,\n                                buffer=buffer,\n                                effective_url=self.request.url)\n        self._run_callback(response)\n        self._on_end_request()\n\n    def _on_end_request(self):\n        self.stream.close()\n\n    def _on_chunk_length(self, data):\n        # TODO: \"chunk extensions\" http://tools.ietf.org/html/rfc2616#section-3.6.1\n        length = int(data.strip(), 16)\n        if length == 0:\n            if self._decompressor is not None:\n                tail = self._decompressor.flush()\n                if tail:\n                    # I believe the tail will always be empty (i.e.\n                    # decompress will return all it can).  The purpose\n                    # of the flush call is to detect errors such\n                    # as truncated input.  But in case it ever returns\n                    # anything, treat it as an extra chunk\n                    if self.request.streaming_callback is not None:\n                        self.request.streaming_callback(tail)\n                    else:\n                        self.chunks.append(tail)\n                # all the data has been decompressed, so we don't need to\n                # decompress again in _on_body\n                self._decompressor = None\n            self._on_body(b''.join(self.chunks))\n        else:\n            self.stream.read_bytes(length + 2,  # chunk ends with \\r\\n\n                                   self._on_chunk_data)\n\n    def _on_chunk_data(self, data):\n        assert data[-2:] == b\"\\r\\n\"\n        chunk = data[:-2]\n        if self._decompressor:\n            chunk = self._decompressor.decompress(chunk)\n        if self.request.streaming_callback is not None:\n            self.request.streaming_callback(chunk)\n        else:\n            self.chunks.append(chunk)\n        self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 145,
                            "end_lineno": 525,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "#!/usr/bin/env python\n\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nimport base64\nimport binascii\nfrom contextlib import closing\nimport functools\nimport sys\nimport threading\n\nfrom tornado.escape import utf8\nfrom tornado.httpclient import HTTPRequest, HTTPResponse, _RequestProxy, HTTPError, HTTPClient\nfrom tornado.httpserver import HTTPServer\nfrom tornado.ioloop import IOLoop\nfrom tornado.iostream import IOStream\nfrom tornado.log import gen_log\nfrom tornado import netutil\nfrom tornado.stack_context import ExceptionStackContext, NullContext\nfrom tornado.testing import AsyncHTTPTestCase, bind_unused_port, gen_test, ExpectLog\nfrom tornado.test.util import unittest\nfrom tornado.util import u, bytes_type\nfrom tornado.web import Application, RequestHandler, url\n\ntry:\n    from io import BytesIO  # python 3\nexcept ImportError:\n    from cStringIO import StringIO as BytesIO\n\n\nclass HelloWorldHandler(RequestHandler):\n    def get(self):\n        name = self.get_argument(\"name\", \"world\")\n        self.set_header(\"Content-Type\", \"text/plain\")\n        self.finish(\"Hello %s!\" % name)\n\n\nclass PostHandler(RequestHandler):\n    def post(self):\n        self.finish(\"Post arg1: %s, arg2: %s\" % (\n            self.get_argument(\"arg1\"), self.get_argument(\"arg2\")))\n\n\nclass ChunkHandler(RequestHandler):\n    def get(self):\n        self.write(\"asdf\")\n        self.flush()\n        self.write(\"qwer\")\n\n\nclass AuthHandler(RequestHandler):\n    def get(self):\n        self.finish(self.request.headers[\"Authorization\"])\n\n\nclass CountdownHandler(RequestHandler):\n    def get(self, count):\n        count = int(count)\n        if count > 0:\n            self.redirect(self.reverse_url(\"countdown\", count - 1))\n        else:\n            self.write(\"Zero\")\n\n\nclass EchoPostHandler(RequestHandler):\n    def post(self):\n        self.write(self.request.body)\n\n\nclass UserAgentHandler(RequestHandler):\n    def get(self):\n        self.write(self.request.headers.get('User-Agent', 'User agent not set'))\n\n\nclass ContentLength304Handler(RequestHandler):\n    def get(self):\n        self.set_status(304)\n        self.set_header('Content-Length', 42)\n\n    def _clear_headers_for_304(self):\n        # Tornado strips content-length from 304 responses, but here we\n        # want to simulate servers that include the headers anyway.\n        pass\n\n\nclass AllMethodsHandler(RequestHandler):\n    SUPPORTED_METHODS = RequestHandler.SUPPORTED_METHODS + ('OTHER',)\n\n    def method(self):\n        self.write(self.request.method)\n\n    get = post = put = delete = options = patch = other = method\n\n# These tests end up getting run redundantly: once here with the default\n# HTTPClient implementation, and then again in each implementation's own\n# test suite.\n\n\nclass HTTPClientCommonTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([\n            url(\"/hello\", HelloWorldHandler),\n            url(\"/post\", PostHandler),\n            url(\"/chunk\", ChunkHandler),\n            url(\"/auth\", AuthHandler),\n            url(\"/countdown/([0-9]+)\", CountdownHandler, name=\"countdown\"),\n            url(\"/echopost\", EchoPostHandler),\n            url(\"/user_agent\", UserAgentHandler),\n            url(\"/304_with_content_length\", ContentLength304Handler),\n            url(\"/all_methods\", AllMethodsHandler),\n        ], gzip=True)\n\n    def test_hello_world(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.headers[\"Content-Type\"], \"text/plain\")\n        self.assertEqual(response.body, b\"Hello world!\")\n        self.assertEqual(int(response.request_time), 0)\n\n        response = self.fetch(\"/hello?name=Ben\")\n        self.assertEqual(response.body, b\"Hello Ben!\")\n\n    def test_streaming_callback(self):\n        # streaming_callback is also tested in test_chunked\n        chunks = []\n        response = self.fetch(\"/hello\",\n                              streaming_callback=chunks.append)\n        # with streaming_callback, data goes to the callback and not response.body\n        self.assertEqual(chunks, [b\"Hello world!\"])\n        self.assertFalse(response.body)\n\n    def test_post(self):\n        response = self.fetch(\"/post\", method=\"POST\",\n                              body=\"arg1=foo&arg2=bar\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.body, b\"Post arg1: foo, arg2: bar\")\n\n    def test_chunked(self):\n        response = self.fetch(\"/chunk\")\n        self.assertEqual(response.body, b\"asdfqwer\")\n\n        chunks = []\n        response = self.fetch(\"/chunk\",\n                              streaming_callback=chunks.append)\n        self.assertEqual(chunks, [b\"asdf\", b\"qwer\"])\n        self.assertFalse(response.body)\n\n    def test_chunked_close(self):\n        # test case in which chunks spread read-callback processing\n        # over several ioloop iterations, but the connection is already closed.\n        sock, port = bind_unused_port()\n        with closing(sock):\n            def write_response(stream, request_data):\n                stream.write(b\"\"\"\\\nHTTP/1.1 200 OK\nTransfer-Encoding: chunked\n\n1\n1\n1\n2\n0\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"), callback=stream.close)\n\n            def accept_callback(conn, address):\n                # fake an HTTP server using chunked encoding where the final chunks\n                # and connection close all happen at once\n                stream = IOStream(conn, io_loop=self.io_loop)\n                stream.read_until(b\"\\r\\n\\r\\n\",\n                                  functools.partial(write_response, stream))\n            netutil.add_accept_handler(sock, accept_callback, self.io_loop)\n            self.http_client.fetch(\"http://127.0.0.1:%d/\" % port, self.stop)\n            resp = self.wait()\n            resp.rethrow()\n            self.assertEqual(resp.body, b\"12\")\n            self.io_loop.remove_handler(sock.fileno())\n\n    def test_streaming_stack_context(self):\n        chunks = []\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def streaming_cb(chunk):\n            chunks.append(chunk)\n            if chunk == b'qwer':\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', streaming_callback=streaming_cb)\n\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_basic_auth(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_basic_auth_explicit_mode(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\",\n                                    auth_mode=\"basic\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_unsupported_auth_mode(self):\n        # curl and simple clients handle errors a bit differently; the\n        # important thing is that they don't fall back to basic auth\n        # on an unknown mode.\n        with ExpectLog(gen_log, \"uncaught exception\", required=False):\n            with self.assertRaises((ValueError, HTTPError)):\n                response = self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                      auth_password=\"open sesame\",\n                                      auth_mode=\"asdf\")\n                response.rethrow()\n\n    def test_follow_redirect(self):\n        response = self.fetch(\"/countdown/2\", follow_redirects=False)\n        self.assertEqual(302, response.code)\n        self.assertTrue(response.headers[\"Location\"].endswith(\"/countdown/1\"))\n\n        response = self.fetch(\"/countdown/2\")\n        self.assertEqual(200, response.code)\n        self.assertTrue(response.effective_url.endswith(\"/countdown/0\"))\n        self.assertEqual(b\"Zero\", response.body)\n\n    def test_credentials_in_url(self):\n        url = self.get_url(\"/auth\").replace(\"http://\", \"http://me:secret@\")\n        self.http_client.fetch(url, self.stop)\n        response = self.wait()\n        self.assertEqual(b\"Basic \" + base64.b64encode(b\"me:secret\"),\n                         response.body)\n\n    def test_body_encoding(self):\n        unicode_body = u(\"\\xe9\")\n        byte_body = binascii.a2b_hex(b\"e9\")\n\n        # unicode string in body gets converted to utf8\n        response = self.fetch(\"/echopost\", method=\"POST\", body=unicode_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"2\")\n        self.assertEqual(response.body, utf8(unicode_body))\n\n        # byte strings pass through directly\n        response = self.fetch(\"/echopost\", method=\"POST\",\n                              body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n        # Mixing unicode in headers and byte string bodies shouldn't\n        # break anything\n        response = self.fetch(\"/echopost\", method=\"POST\", body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"},\n                              user_agent=u(\"foo\"))\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n    def test_types(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(type(response.body), bytes_type)\n        self.assertEqual(type(response.headers[\"Content-Type\"]), str)\n        self.assertEqual(type(response.code), int)\n        self.assertEqual(type(response.effective_url), str)\n\n    def test_header_callback(self):\n        first_line = []\n        headers = {}\n        chunks = []\n\n        def header_callback(header_line):\n            if header_line.startswith('HTTP/'):\n                first_line.append(header_line)\n            elif header_line != '\\r\\n':\n                k, v = header_line.split(':', 1)\n                headers[k] = v.strip()\n\n        def streaming_callback(chunk):\n            # All header callbacks are run before any streaming callbacks,\n            # so the header data is available to process the data as it\n            # comes in.\n            self.assertEqual(headers['Content-Type'], 'text/html; charset=UTF-8')\n            chunks.append(chunk)\n\n        self.fetch('/chunk', header_callback=header_callback,\n                   streaming_callback=streaming_callback)\n        self.assertEqual(len(first_line), 1)\n        self.assertRegexpMatches(first_line[0], 'HTTP/1.[01] 200 OK\\r\\n')\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n\n    def test_header_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def header_callback(header_line):\n            if header_line.startswith('Content-Type:'):\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', header_callback=header_callback)\n        self.assertEqual(len(exc_info), 1)\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_configure_defaults(self):\n        defaults = dict(user_agent='TestDefaultUserAgent')\n        # Construct a new instance of the configured client class\n        client = self.http_client.__class__(self.io_loop, force_instance=True,\n                                            defaults=defaults)\n        client.fetch(self.get_url('/user_agent'), callback=self.stop)\n        response = self.wait()\n        self.assertEqual(response.body, b'TestDefaultUserAgent')\n        client.close()\n\n    def test_304_with_content_length(self):\n        # According to the spec 304 responses SHOULD NOT include\n        # Content-Length or other entity headers, but some servers do it\n        # anyway.\n        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5\n        response = self.fetch('/304_with_content_length')\n        self.assertEqual(response.code, 304)\n        self.assertEqual(response.headers['Content-Length'], '42')\n\n    def test_final_callback_stack_context(self):\n        # The final callback should be run outside of the httpclient's\n        # stack_context.  We want to ensure that there is not stack_context\n        # between the user's callback and the IOLoop, so monkey-patch\n        # IOLoop.handle_callback_exception and disable the test harness's\n        # context with a NullContext.\n        # Note that this does not apply to secondary callbacks (header\n        # and streaming_callback), as errors there must be seen as errors\n        # by the http client so it can clean up the connection.\n        exc_info = []\n\n        def handle_callback_exception(callback):\n            exc_info.append(sys.exc_info())\n            self.stop()\n        self.io_loop.handle_callback_exception = handle_callback_exception\n        with NullContext():\n            self.http_client.fetch(self.get_url('/hello'),\n                                   lambda response: 1 / 0)\n        self.wait()\n        self.assertEqual(exc_info[0][0], ZeroDivisionError)\n\n    @gen_test\n    def test_future_interface(self):\n        response = yield self.http_client.fetch(self.get_url('/hello'))\n        self.assertEqual(response.body, b'Hello world!')\n\n    @gen_test\n    def test_future_http_error(self):\n        try:\n            yield self.http_client.fetch(self.get_url('/notfound'))\n        except HTTPError as e:\n            self.assertEqual(e.code, 404)\n            self.assertEqual(e.response.code, 404)\n\n    @gen_test\n    def test_reuse_request_from_response(self):\n        # The response.request attribute should be an HTTPRequest, not\n        # a _RequestProxy.\n        # This test uses self.http_client.fetch because self.fetch calls\n        # self.get_url on the input unconditionally.\n        url = self.get_url('/hello')\n        response = yield self.http_client.fetch(url)\n        self.assertEqual(response.request.url, url)\n        self.assertTrue(isinstance(response.request, HTTPRequest))\n        response2 = yield self.http_client.fetch(response.request)\n        self.assertEqual(response2.body, b'Hello world!')\n\n    def test_all_methods(self):\n        for method in ['GET', 'DELETE', 'OPTIONS']:\n            response = self.fetch('/all_methods', method=method)\n            self.assertEqual(response.body, utf8(method))\n        for method in ['POST', 'PUT', 'PATCH']:\n            response = self.fetch('/all_methods', method=method, body=b'')\n            self.assertEqual(response.body, utf8(method))\n        response = self.fetch('/all_methods', method='HEAD')\n        self.assertEqual(response.body, b'')\n        response = self.fetch('/all_methods', method='OTHER',\n                              allow_nonstandard_methods=True)\n        self.assertEqual(response.body, b'OTHER')\n\n\nclass RequestProxyTest(unittest.TestCase):\n    def test_request_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/',\n                                          user_agent='foo'),\n                              dict())\n        self.assertEqual(proxy.user_agent, 'foo')\n\n    def test_default_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'),\n                              dict(network_interface='foo'))\n        self.assertEqual(proxy.network_interface, 'foo')\n\n    def test_both_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/',\n                                          proxy_host='foo'),\n                              dict(proxy_host='bar'))\n        self.assertEqual(proxy.proxy_host, 'foo')\n\n    def test_neither_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'),\n                              dict())\n        self.assertIs(proxy.auth_username, None)\n\n    def test_bad_attribute(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'),\n                              dict())\n        with self.assertRaises(AttributeError):\n            proxy.foo\n\n    def test_defaults_none(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'), None)\n        self.assertIs(proxy.auth_username, None)\n\n\nclass HTTPResponseTestCase(unittest.TestCase):\n    def test_str(self):\n        response = HTTPResponse(HTTPRequest('http://example.com'),\n                                200, headers={}, buffer=BytesIO())\n        s = str(response)\n        self.assertTrue(s.startswith('HTTPResponse('))\n        self.assertIn('code=200', s)\n\n\nclass SyncHTTPClientTest(unittest.TestCase):\n    def setUp(self):\n        if IOLoop.configured_class().__name__ in ('TwistedIOLoop',\n                                                  'AsyncIOMainLoop'):\n            # TwistedIOLoop only supports the global reactor, so we can't have\n            # separate IOLoops for client and server threads.\n            # AsyncIOMainLoop doesn't work with the default policy\n            # (although it could with some tweaks to this test and a\n            # policy that created loops for non-main threads).\n            raise unittest.SkipTest(\n                'Sync HTTPClient not compatible with TwistedIOLoop or '\n                'AsyncIOMainLoop')\n        self.server_ioloop = IOLoop()\n\n        sock, self.port = bind_unused_port()\n        app = Application([('/', HelloWorldHandler)])\n        self.server = HTTPServer(app, io_loop=self.server_ioloop)\n        self.server.add_socket(sock)\n\n        self.server_thread = threading.Thread(target=self.server_ioloop.start)\n        self.server_thread.start()\n\n        self.http_client = HTTPClient()\n\n    def tearDown(self):\n        def stop_server():\n            self.server.stop()\n            self.server_ioloop.stop()\n        self.server_ioloop.add_callback(stop_server)\n        self.server_thread.join()\n        self.http_client.close()\n        self.server_ioloop.close(all_fds=True)\n\n    def get_url(self, path):\n        return 'http://localhost:%d%s' % (self.port, path)\n\n    def test_sync_client(self):\n        response = self.http_client.fetch(self.get_url('/'))\n        self.assertEqual(b'Hello world!', response.body)\n\n    def test_sync_client_error(self):\n        # Synchronous HTTPClient raises errors directly; no need for\n        # response.rethrow()\n        with self.assertRaises(HTTPError) as assertion:\n            self.http_client.fetch(self.get_url('/notfound'))\n        self.assertEqual(assertion.exception.code, 404)\n",
            "new_file_content": "#!/usr/bin/env python\n\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nimport base64\nimport binascii\nfrom contextlib import closing\nimport functools\nimport sys\nimport threading\n\nfrom tornado.escape import utf8\nfrom tornado.httpclient import HTTPRequest, HTTPResponse, _RequestProxy, HTTPError, HTTPClient\nfrom tornado.httpserver import HTTPServer\nfrom tornado.ioloop import IOLoop\nfrom tornado.iostream import IOStream\nfrom tornado.log import gen_log\nfrom tornado import netutil\nfrom tornado.stack_context import ExceptionStackContext, NullContext\nfrom tornado.testing import AsyncHTTPTestCase, bind_unused_port, gen_test, ExpectLog\nfrom tornado.test.util import unittest\nfrom tornado.util import u, bytes_type\nfrom tornado.web import Application, RequestHandler, url\n\ntry:\n    from io import BytesIO  # python 3\nexcept ImportError:\n    from cStringIO import StringIO as BytesIO\n\n\nclass HelloWorldHandler(RequestHandler):\n    def get(self):\n        name = self.get_argument(\"name\", \"world\")\n        self.set_header(\"Content-Type\", \"text/plain\")\n        self.finish(\"Hello %s!\" % name)\n\n\nclass PostHandler(RequestHandler):\n    def post(self):\n        self.finish(\"Post arg1: %s, arg2: %s\" % (\n            self.get_argument(\"arg1\"), self.get_argument(\"arg2\")))\n\n\nclass ChunkHandler(RequestHandler):\n    def get(self):\n        self.write(\"asdf\")\n        self.flush()\n        self.write(\"qwer\")\n\n\nclass AuthHandler(RequestHandler):\n    def get(self):\n        self.finish(self.request.headers[\"Authorization\"])\n\n\nclass CountdownHandler(RequestHandler):\n    def get(self, count):\n        count = int(count)\n        if count > 0:\n            self.redirect(self.reverse_url(\"countdown\", count - 1))\n        else:\n            self.write(\"Zero\")\n\n\nclass EchoPostHandler(RequestHandler):\n    def post(self):\n        self.write(self.request.body)\n\n\nclass UserAgentHandler(RequestHandler):\n    def get(self):\n        self.write(self.request.headers.get('User-Agent', 'User agent not set'))\n\n\nclass ContentLength304Handler(RequestHandler):\n    def get(self):\n        self.set_status(304)\n        self.set_header('Content-Length', 42)\n\n    def _clear_headers_for_304(self):\n        # Tornado strips content-length from 304 responses, but here we\n        # want to simulate servers that include the headers anyway.\n        pass\n\n\nclass AllMethodsHandler(RequestHandler):\n    SUPPORTED_METHODS = RequestHandler.SUPPORTED_METHODS + ('OTHER',)\n\n    def method(self):\n        self.write(self.request.method)\n\n    get = post = put = delete = options = patch = other = method\n\n# These tests end up getting run redundantly: once here with the default\n# HTTPClient implementation, and then again in each implementation's own\n# test suite.\n\n\nclass HTTPClientCommonTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([\n            url(\"/hello\", HelloWorldHandler),\n            url(\"/post\", PostHandler),\n            url(\"/chunk\", ChunkHandler),\n            url(\"/auth\", AuthHandler),\n            url(\"/countdown/([0-9]+)\", CountdownHandler, name=\"countdown\"),\n            url(\"/echopost\", EchoPostHandler),\n            url(\"/user_agent\", UserAgentHandler),\n            url(\"/304_with_content_length\", ContentLength304Handler),\n            url(\"/all_methods\", AllMethodsHandler),\n        ], gzip=True)\n\n    def test_hello_world(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.headers[\"Content-Type\"], \"text/plain\")\n        self.assertEqual(response.body, b\"Hello world!\")\n        self.assertEqual(int(response.request_time), 0)\n\n        response = self.fetch(\"/hello?name=Ben\")\n        self.assertEqual(response.body, b\"Hello Ben!\")\n\n    def test_streaming_callback(self):\n        # streaming_callback is also tested in test_chunked\n        chunks = []\n        response = self.fetch(\"/hello\",\n                              streaming_callback=chunks.append)\n        # with streaming_callback, data goes to the callback and not response.body\n        self.assertEqual(chunks, [b\"Hello world!\"])\n        self.assertFalse(response.body)\n\n    def test_post(self):\n        response = self.fetch(\"/post\", method=\"POST\",\n                              body=\"arg1=foo&arg2=bar\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.body, b\"Post arg1: foo, arg2: bar\")\n\n    def test_chunked(self):\n        response = self.fetch(\"/chunk\")\n        self.assertEqual(response.body, b\"asdfqwer\")\n\n        chunks = []\n        response = self.fetch(\"/chunk\",\n                              streaming_callback=chunks.append)\n        self.assertEqual(chunks, [b\"asdf\", b\"qwer\"])\n        self.assertFalse(response.body)\n\n    def test_chunked_close(self):\n        # test case in which chunks spread read-callback processing\n        # over several ioloop iterations, but the connection is already closed.\n        sock, port = bind_unused_port()\n        with closing(sock):\n            def write_response(stream, request_data):\n                stream.write(b\"\"\"\\\nHTTP/1.1 200 OK\nTransfer-Encoding: chunked\n\n1\n1\n1\n2\n0\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"), callback=stream.close)\n\n            def accept_callback(conn, address):\n                # fake an HTTP server using chunked encoding where the final chunks\n                # and connection close all happen at once\n                stream = IOStream(conn, io_loop=self.io_loop)\n                stream.read_until(b\"\\r\\n\\r\\n\",\n                                  functools.partial(write_response, stream))\n            netutil.add_accept_handler(sock, accept_callback, self.io_loop)\n            self.http_client.fetch(\"http://127.0.0.1:%d/\" % port, self.stop)\n            resp = self.wait()\n            resp.rethrow()\n            self.assertEqual(resp.body, b\"12\")\n            self.io_loop.remove_handler(sock.fileno())\n\n    def test_streaming_stack_context(self):\n        chunks = []\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def streaming_cb(chunk):\n            chunks.append(chunk)\n            if chunk == b'qwer':\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', streaming_callback=streaming_cb)\n\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_basic_auth(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_basic_auth_explicit_mode(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\",\n                                    auth_mode=\"basic\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_unsupported_auth_mode(self):\n        # curl and simple clients handle errors a bit differently; the\n        # important thing is that they don't fall back to basic auth\n        # on an unknown mode.\n        with ExpectLog(gen_log, \"uncaught exception\", required=False):\n            with self.assertRaises((ValueError, HTTPError)):\n                response = self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                      auth_password=\"open sesame\",\n                                      auth_mode=\"asdf\")\n                response.rethrow()\n\n    def test_follow_redirect(self):\n        response = self.fetch(\"/countdown/2\", follow_redirects=False)\n        self.assertEqual(302, response.code)\n        self.assertTrue(response.headers[\"Location\"].endswith(\"/countdown/1\"))\n\n        response = self.fetch(\"/countdown/2\")\n        self.assertEqual(200, response.code)\n        self.assertTrue(response.effective_url.endswith(\"/countdown/0\"))\n        self.assertEqual(b\"Zero\", response.body)\n\n    def test_credentials_in_url(self):\n        url = self.get_url(\"/auth\").replace(\"http://\", \"http://me:secret@\")\n        self.http_client.fetch(url, self.stop)\n        response = self.wait()\n        self.assertEqual(b\"Basic \" + base64.b64encode(b\"me:secret\"),\n                         response.body)\n\n    def test_body_encoding(self):\n        unicode_body = u(\"\\xe9\")\n        byte_body = binascii.a2b_hex(b\"e9\")\n\n        # unicode string in body gets converted to utf8\n        response = self.fetch(\"/echopost\", method=\"POST\", body=unicode_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"2\")\n        self.assertEqual(response.body, utf8(unicode_body))\n\n        # byte strings pass through directly\n        response = self.fetch(\"/echopost\", method=\"POST\",\n                              body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n        # Mixing unicode in headers and byte string bodies shouldn't\n        # break anything\n        response = self.fetch(\"/echopost\", method=\"POST\", body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"},\n                              user_agent=u(\"foo\"))\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n    def test_types(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(type(response.body), bytes_type)\n        self.assertEqual(type(response.headers[\"Content-Type\"]), str)\n        self.assertEqual(type(response.code), int)\n        self.assertEqual(type(response.effective_url), str)\n\n    def test_header_callback(self):\n        first_line = []\n        headers = {}\n        chunks = []\n\n        def header_callback(header_line):\n            if header_line.startswith('HTTP/'):\n                first_line.append(header_line)\n            elif header_line != '\\r\\n':\n                k, v = header_line.split(':', 1)\n                headers[k] = v.strip()\n\n        def streaming_callback(chunk):\n            # All header callbacks are run before any streaming callbacks,\n            # so the header data is available to process the data as it\n            # comes in.\n            self.assertEqual(headers['Content-Type'], 'text/html; charset=UTF-8')\n            chunks.append(chunk)\n\n        self.fetch('/chunk', header_callback=header_callback,\n                   streaming_callback=streaming_callback)\n        self.assertEqual(len(first_line), 1)\n        self.assertRegexpMatches(first_line[0], 'HTTP/1.[01] 200 OK\\r\\n')\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n\n    def test_header_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def header_callback(header_line):\n            if header_line.startswith('Content-Type:'):\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', header_callback=header_callback)\n        self.assertEqual(len(exc_info), 1)\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_configure_defaults(self):\n        defaults = dict(user_agent='TestDefaultUserAgent')\n        # Construct a new instance of the configured client class\n        client = self.http_client.__class__(self.io_loop, force_instance=True,\n                                            defaults=defaults)\n        client.fetch(self.get_url('/user_agent'), callback=self.stop)\n        response = self.wait()\n        self.assertEqual(response.body, b'TestDefaultUserAgent')\n        client.close()\n\n    def test_304_with_content_length(self):\n        # According to the spec 304 responses SHOULD NOT include\n        # Content-Length or other entity headers, but some servers do it\n        # anyway.\n        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5\n        response = self.fetch('/304_with_content_length')\n        self.assertEqual(response.code, 304)\n        self.assertEqual(response.headers['Content-Length'], '42')\n\n    def test_final_callback_stack_context(self):\n        # The final callback should be run outside of the httpclient's\n        # stack_context.  We want to ensure that there is not stack_context\n        # between the user's callback and the IOLoop, so monkey-patch\n        # IOLoop.handle_callback_exception and disable the test harness's\n        # context with a NullContext.\n        # Note that this does not apply to secondary callbacks (header\n        # and streaming_callback), as errors there must be seen as errors\n        # by the http client so it can clean up the connection.\n        exc_info = []\n\n        def handle_callback_exception(callback):\n            exc_info.append(sys.exc_info())\n            self.stop()\n        self.io_loop.handle_callback_exception = handle_callback_exception\n        with NullContext():\n            self.http_client.fetch(self.get_url('/hello'),\n                                   lambda response: 1 / 0)\n        self.wait()\n        self.assertEqual(exc_info[0][0], ZeroDivisionError)\n\n    @gen_test\n    def test_future_interface(self):\n        response = yield self.http_client.fetch(self.get_url('/hello'))\n        self.assertEqual(response.body, b'Hello world!')\n\n    @gen_test\n    def test_future_http_error(self):\n        try:\n            yield self.http_client.fetch(self.get_url('/notfound'))\n        except HTTPError as e:\n            self.assertEqual(e.code, 404)\n            self.assertEqual(e.response.code, 404)\n\n    @gen_test\n    def test_reuse_request_from_response(self):\n        # The response.request attribute should be an HTTPRequest, not\n        # a _RequestProxy.\n        # This test uses self.http_client.fetch because self.fetch calls\n        # self.get_url on the input unconditionally.\n        url = self.get_url('/hello')\n        response = yield self.http_client.fetch(url)\n        self.assertEqual(response.request.url, url)\n        self.assertTrue(isinstance(response.request, HTTPRequest))\n        response2 = yield self.http_client.fetch(response.request)\n        self.assertEqual(response2.body, b'Hello world!')\n\n    def test_all_methods(self):\n        for method in ['GET', 'DELETE', 'OPTIONS']:\n            response = self.fetch('/all_methods', method=method)\n            self.assertEqual(response.body, utf8(method))\n        for method in ['POST', 'PUT', 'PATCH']:\n            response = self.fetch('/all_methods', method=method, body=b'')\n            self.assertEqual(response.body, utf8(method))\n        response = self.fetch('/all_methods', method='HEAD')\n        self.assertEqual(response.body, b'')\n        response = self.fetch('/all_methods', method='OTHER',\n                              allow_nonstandard_methods=True)\n        self.assertEqual(response.body, b'OTHER')\n\n    @gen_test\n    def test_body(self):\n        hello_url = self.get_url('/hello')\n        with self.assertRaises(AssertionError) as context:\n            yield self.http_client.fetch(hello_url, body='data')\n\n        self.assertTrue('must be empty' in str(context.exception))\n\n        with self.assertRaises(AssertionError) as context:\n            yield self.http_client.fetch(hello_url, method='POST')\n\n        self.assertTrue('must not be empty' in str(context.exception))\n\n\nclass RequestProxyTest(unittest.TestCase):\n    def test_request_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/',\n                                          user_agent='foo'),\n                              dict())\n        self.assertEqual(proxy.user_agent, 'foo')\n\n    def test_default_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'),\n                              dict(network_interface='foo'))\n        self.assertEqual(proxy.network_interface, 'foo')\n\n    def test_both_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/',\n                                          proxy_host='foo'),\n                              dict(proxy_host='bar'))\n        self.assertEqual(proxy.proxy_host, 'foo')\n\n    def test_neither_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'),\n                              dict())\n        self.assertIs(proxy.auth_username, None)\n\n    def test_bad_attribute(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'),\n                              dict())\n        with self.assertRaises(AttributeError):\n            proxy.foo\n\n    def test_defaults_none(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'), None)\n        self.assertIs(proxy.auth_username, None)\n\n\nclass HTTPResponseTestCase(unittest.TestCase):\n    def test_str(self):\n        response = HTTPResponse(HTTPRequest('http://example.com'),\n                                200, headers={}, buffer=BytesIO())\n        s = str(response)\n        self.assertTrue(s.startswith('HTTPResponse('))\n        self.assertIn('code=200', s)\n\n\nclass SyncHTTPClientTest(unittest.TestCase):\n    def setUp(self):\n        if IOLoop.configured_class().__name__ in ('TwistedIOLoop',\n                                                  'AsyncIOMainLoop'):\n            # TwistedIOLoop only supports the global reactor, so we can't have\n            # separate IOLoops for client and server threads.\n            # AsyncIOMainLoop doesn't work with the default policy\n            # (although it could with some tweaks to this test and a\n            # policy that created loops for non-main threads).\n            raise unittest.SkipTest(\n                'Sync HTTPClient not compatible with TwistedIOLoop or '\n                'AsyncIOMainLoop')\n        self.server_ioloop = IOLoop()\n\n        sock, self.port = bind_unused_port()\n        app = Application([('/', HelloWorldHandler)])\n        self.server = HTTPServer(app, io_loop=self.server_ioloop)\n        self.server.add_socket(sock)\n\n        self.server_thread = threading.Thread(target=self.server_ioloop.start)\n        self.server_thread.start()\n\n        self.http_client = HTTPClient()\n\n    def tearDown(self):\n        def stop_server():\n            self.server.stop()\n            self.server_ioloop.stop()\n        self.server_ioloop.add_callback(stop_server)\n        self.server_thread.join()\n        self.http_client.close()\n        self.server_ioloop.close(all_fds=True)\n\n    def get_url(self, path):\n        return 'http://localhost:%d%s' % (self.port, path)\n\n    def test_sync_client(self):\n        response = self.http_client.fetch(self.get_url('/'))\n        self.assertEqual(b'Hello world!', response.body)\n\n    def test_sync_client_error(self):\n        # Synchronous HTTPClient raises errors directly; no need for\n        # response.rethrow()\n        with self.assertRaises(HTTPError) as assertion:\n            self.http_client.fetch(self.get_url('/notfound'))\n        self.assertEqual(assertion.exception.code, 404)\n",
            "header": {
                "file": {
                    "path": "tornado/test/httpclient_test.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "15bb8e44",
                "new_commit_hash": "b051e02e",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/tornado/test/httpclient_test.py"
            },
            "plus_file": {
                "path": "b/tornado/test/httpclient_test.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 387,
                            "length": 6
                        },
                        "new_range": {
                            "start": 387,
                            "length": 19
                        },
                        "section": "Transfer-Encoding: chunked"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "                              allow_nonstandard_methods=True)",
                                "type": "context"
                            },
                            {
                                "content": "        self.assertEqual(response.body, b'OTHER')",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    @gen_test",
                                "type": "added"
                            },
                            {
                                "content": "    def test_body(self):",
                                "type": "added"
                            },
                            {
                                "content": "        hello_url = self.get_url('/hello')",
                                "type": "added"
                            },
                            {
                                "content": "        with self.assertRaises(AssertionError) as context:",
                                "type": "added"
                            },
                            {
                                "content": "            yield self.http_client.fetch(hello_url, body='data')",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertTrue('must be empty' in str(context.exception))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        with self.assertRaises(AssertionError) as context:",
                                "type": "added"
                            },
                            {
                                "content": "            yield self.http_client.fetch(hello_url, method='POST')",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        self.assertTrue('must not be empty' in str(context.exception))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "class RequestProxyTest(unittest.TestCase):",
                                "type": "context"
                            },
                            {
                                "content": "    def test_request_set(self):",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "tornado/test/httpclient_test.py",
                            "type": "class",
                            "name": "HTTPClientCommonTestCase",
                            "content": "class HTTPClientCommonTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([\n            url(\"/hello\", HelloWorldHandler),\n            url(\"/post\", PostHandler),\n            url(\"/chunk\", ChunkHandler),\n            url(\"/auth\", AuthHandler),\n            url(\"/countdown/([0-9]+)\", CountdownHandler, name=\"countdown\"),\n            url(\"/echopost\", EchoPostHandler),\n            url(\"/user_agent\", UserAgentHandler),\n            url(\"/304_with_content_length\", ContentLength304Handler),\n            url(\"/all_methods\", AllMethodsHandler),\n        ], gzip=True)\n\n    def test_hello_world(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.headers[\"Content-Type\"], \"text/plain\")\n        self.assertEqual(response.body, b\"Hello world!\")\n        self.assertEqual(int(response.request_time), 0)\n\n        response = self.fetch(\"/hello?name=Ben\")\n        self.assertEqual(response.body, b\"Hello Ben!\")\n\n    def test_streaming_callback(self):\n        # streaming_callback is also tested in test_chunked\n        chunks = []\n        response = self.fetch(\"/hello\",\n                              streaming_callback=chunks.append)\n        # with streaming_callback, data goes to the callback and not response.body\n        self.assertEqual(chunks, [b\"Hello world!\"])\n        self.assertFalse(response.body)\n\n    def test_post(self):\n        response = self.fetch(\"/post\", method=\"POST\",\n                              body=\"arg1=foo&arg2=bar\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.body, b\"Post arg1: foo, arg2: bar\")\n\n    def test_chunked(self):\n        response = self.fetch(\"/chunk\")\n        self.assertEqual(response.body, b\"asdfqwer\")\n\n        chunks = []\n        response = self.fetch(\"/chunk\",\n                              streaming_callback=chunks.append)\n        self.assertEqual(chunks, [b\"asdf\", b\"qwer\"])\n        self.assertFalse(response.body)\n\n    def test_chunked_close(self):\n        # test case in which chunks spread read-callback processing\n        # over several ioloop iterations, but the connection is already closed.\n        sock, port = bind_unused_port()\n        with closing(sock):\n            def write_response(stream, request_data):\n                stream.write(b\"\"\"\\\nHTTP/1.1 200 OK\nTransfer-Encoding: chunked\n\n1\n1\n1\n2\n0\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"), callback=stream.close)\n\n            def accept_callback(conn, address):\n                # fake an HTTP server using chunked encoding where the final chunks\n                # and connection close all happen at once\n                stream = IOStream(conn, io_loop=self.io_loop)\n                stream.read_until(b\"\\r\\n\\r\\n\",\n                                  functools.partial(write_response, stream))\n            netutil.add_accept_handler(sock, accept_callback, self.io_loop)\n            self.http_client.fetch(\"http://127.0.0.1:%d/\" % port, self.stop)\n            resp = self.wait()\n            resp.rethrow()\n            self.assertEqual(resp.body, b\"12\")\n            self.io_loop.remove_handler(sock.fileno())\n\n    def test_streaming_stack_context(self):\n        chunks = []\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def streaming_cb(chunk):\n            chunks.append(chunk)\n            if chunk == b'qwer':\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', streaming_callback=streaming_cb)\n\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_basic_auth(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_basic_auth_explicit_mode(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\",\n                                    auth_mode=\"basic\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_unsupported_auth_mode(self):\n        # curl and simple clients handle errors a bit differently; the\n        # important thing is that they don't fall back to basic auth\n        # on an unknown mode.\n        with ExpectLog(gen_log, \"uncaught exception\", required=False):\n            with self.assertRaises((ValueError, HTTPError)):\n                response = self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                      auth_password=\"open sesame\",\n                                      auth_mode=\"asdf\")\n                response.rethrow()\n\n    def test_follow_redirect(self):\n        response = self.fetch(\"/countdown/2\", follow_redirects=False)\n        self.assertEqual(302, response.code)\n        self.assertTrue(response.headers[\"Location\"].endswith(\"/countdown/1\"))\n\n        response = self.fetch(\"/countdown/2\")\n        self.assertEqual(200, response.code)\n        self.assertTrue(response.effective_url.endswith(\"/countdown/0\"))\n        self.assertEqual(b\"Zero\", response.body)\n\n    def test_credentials_in_url(self):\n        url = self.get_url(\"/auth\").replace(\"http://\", \"http://me:secret@\")\n        self.http_client.fetch(url, self.stop)\n        response = self.wait()\n        self.assertEqual(b\"Basic \" + base64.b64encode(b\"me:secret\"),\n                         response.body)\n\n    def test_body_encoding(self):\n        unicode_body = u(\"\\xe9\")\n        byte_body = binascii.a2b_hex(b\"e9\")\n\n        # unicode string in body gets converted to utf8\n        response = self.fetch(\"/echopost\", method=\"POST\", body=unicode_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"2\")\n        self.assertEqual(response.body, utf8(unicode_body))\n\n        # byte strings pass through directly\n        response = self.fetch(\"/echopost\", method=\"POST\",\n                              body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n        # Mixing unicode in headers and byte string bodies shouldn't\n        # break anything\n        response = self.fetch(\"/echopost\", method=\"POST\", body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"},\n                              user_agent=u(\"foo\"))\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n    def test_types(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(type(response.body), bytes_type)\n        self.assertEqual(type(response.headers[\"Content-Type\"]), str)\n        self.assertEqual(type(response.code), int)\n        self.assertEqual(type(response.effective_url), str)\n\n    def test_header_callback(self):\n        first_line = []\n        headers = {}\n        chunks = []\n\n        def header_callback(header_line):\n            if header_line.startswith('HTTP/'):\n                first_line.append(header_line)\n            elif header_line != '\\r\\n':\n                k, v = header_line.split(':', 1)\n                headers[k] = v.strip()\n\n        def streaming_callback(chunk):\n            # All header callbacks are run before any streaming callbacks,\n            # so the header data is available to process the data as it\n            # comes in.\n            self.assertEqual(headers['Content-Type'], 'text/html; charset=UTF-8')\n            chunks.append(chunk)\n\n        self.fetch('/chunk', header_callback=header_callback,\n                   streaming_callback=streaming_callback)\n        self.assertEqual(len(first_line), 1)\n        self.assertRegexpMatches(first_line[0], 'HTTP/1.[01] 200 OK\\r\\n')\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n\n    def test_header_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def header_callback(header_line):\n            if header_line.startswith('Content-Type:'):\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', header_callback=header_callback)\n        self.assertEqual(len(exc_info), 1)\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_configure_defaults(self):\n        defaults = dict(user_agent='TestDefaultUserAgent')\n        # Construct a new instance of the configured client class\n        client = self.http_client.__class__(self.io_loop, force_instance=True,\n                                            defaults=defaults)\n        client.fetch(self.get_url('/user_agent'), callback=self.stop)\n        response = self.wait()\n        self.assertEqual(response.body, b'TestDefaultUserAgent')\n        client.close()\n\n    def test_304_with_content_length(self):\n        # According to the spec 304 responses SHOULD NOT include\n        # Content-Length or other entity headers, but some servers do it\n        # anyway.\n        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5\n        response = self.fetch('/304_with_content_length')\n        self.assertEqual(response.code, 304)\n        self.assertEqual(response.headers['Content-Length'], '42')\n\n    def test_final_callback_stack_context(self):\n        # The final callback should be run outside of the httpclient's\n        # stack_context.  We want to ensure that there is not stack_context\n        # between the user's callback and the IOLoop, so monkey-patch\n        # IOLoop.handle_callback_exception and disable the test harness's\n        # context with a NullContext.\n        # Note that this does not apply to secondary callbacks (header\n        # and streaming_callback), as errors there must be seen as errors\n        # by the http client so it can clean up the connection.\n        exc_info = []\n\n        def handle_callback_exception(callback):\n            exc_info.append(sys.exc_info())\n            self.stop()\n        self.io_loop.handle_callback_exception = handle_callback_exception\n        with NullContext():\n            self.http_client.fetch(self.get_url('/hello'),\n                                   lambda response: 1 / 0)\n        self.wait()\n        self.assertEqual(exc_info[0][0], ZeroDivisionError)\n\n    @gen_test\n    def test_future_interface(self):\n        response = yield self.http_client.fetch(self.get_url('/hello'))\n        self.assertEqual(response.body, b'Hello world!')\n\n    @gen_test\n    def test_future_http_error(self):\n        try:\n            yield self.http_client.fetch(self.get_url('/notfound'))\n        except HTTPError as e:\n            self.assertEqual(e.code, 404)\n            self.assertEqual(e.response.code, 404)\n\n    @gen_test\n    def test_reuse_request_from_response(self):\n        # The response.request attribute should be an HTTPRequest, not\n        # a _RequestProxy.\n        # This test uses self.http_client.fetch because self.fetch calls\n        # self.get_url on the input unconditionally.\n        url = self.get_url('/hello')\n        response = yield self.http_client.fetch(url)\n        self.assertEqual(response.request.url, url)\n        self.assertTrue(isinstance(response.request, HTTPRequest))\n        response2 = yield self.http_client.fetch(response.request)\n        self.assertEqual(response2.body, b'Hello world!')\n\n    def test_all_methods(self):\n        for method in ['GET', 'DELETE', 'OPTIONS']:\n            response = self.fetch('/all_methods', method=method)\n            self.assertEqual(response.body, utf8(method))\n        for method in ['POST', 'PUT', 'PATCH']:\n            response = self.fetch('/all_methods', method=method, body=b'')\n            self.assertEqual(response.body, utf8(method))\n        response = self.fetch('/all_methods', method='HEAD')\n        self.assertEqual(response.body, b'')\n        response = self.fetch('/all_methods', method='OTHER',\n                              allow_nonstandard_methods=True)\n        self.assertEqual(response.body, b'OTHER')\n\n    @gen_test\n    def test_body(self):\n        hello_url = self.get_url('/hello')\n        with self.assertRaises(AssertionError) as context:\n            yield self.http_client.fetch(hello_url, body='data')\n\n        self.assertTrue('must be empty' in str(context.exception))\n\n        with self.assertRaises(AssertionError) as context:\n            yield self.http_client.fetch(hello_url, method='POST')\n\n        self.assertTrue('must not be empty' in str(context.exception))",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 99,
                            "end_lineno": 401,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "tornado/test/httpclient_test.py",
                            "type": "method",
                            "name": "HTTPClientCommonTestCase.test_body",
                            "content": "class HTTPClientCommonTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([\n            url(\"/hello\", HelloWorldHandler),\n            url(\"/post\", PostHandler),\n            url(\"/chunk\", ChunkHandler),\n            url(\"/auth\", AuthHandler),\n            url(\"/countdown/([0-9]+)\", CountdownHandler, name=\"countdown\"),\n            url(\"/echopost\", EchoPostHandler),\n            url(\"/user_agent\", UserAgentHandler),\n            url(\"/304_with_content_length\", ContentLength304Handler),\n            url(\"/all_methods\", AllMethodsHandler),\n        ], gzip=True)\n\n    def test_hello_world(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.headers[\"Content-Type\"], \"text/plain\")\n        self.assertEqual(response.body, b\"Hello world!\")\n        self.assertEqual(int(response.request_time), 0)\n\n        response = self.fetch(\"/hello?name=Ben\")\n        self.assertEqual(response.body, b\"Hello Ben!\")\n\n    def test_streaming_callback(self):\n        # streaming_callback is also tested in test_chunked\n        chunks = []\n        response = self.fetch(\"/hello\",\n                              streaming_callback=chunks.append)\n        # with streaming_callback, data goes to the callback and not response.body\n        self.assertEqual(chunks, [b\"Hello world!\"])\n        self.assertFalse(response.body)\n\n    def test_post(self):\n        response = self.fetch(\"/post\", method=\"POST\",\n                              body=\"arg1=foo&arg2=bar\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.body, b\"Post arg1: foo, arg2: bar\")\n\n    def test_chunked(self):\n        response = self.fetch(\"/chunk\")\n        self.assertEqual(response.body, b\"asdfqwer\")\n\n        chunks = []\n        response = self.fetch(\"/chunk\",\n                              streaming_callback=chunks.append)\n        self.assertEqual(chunks, [b\"asdf\", b\"qwer\"])\n        self.assertFalse(response.body)\n\n    def test_chunked_close(self):\n        # test case in which chunks spread read-callback processing\n        # over several ioloop iterations, but the connection is already closed.\n        sock, port = bind_unused_port()\n        with closing(sock):\n            def write_response(stream, request_data):\n                stream.write(b\"\"\"\\\nHTTP/1.1 200 OK\nTransfer-Encoding: chunked\n\n1\n1\n1\n2\n0\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"), callback=stream.close)\n\n            def accept_callback(conn, address):\n                # fake an HTTP server using chunked encoding where the final chunks\n                # and connection close all happen at once\n                stream = IOStream(conn, io_loop=self.io_loop)\n                stream.read_until(b\"\\r\\n\\r\\n\",\n                                  functools.partial(write_response, stream))\n            netutil.add_accept_handler(sock, accept_callback, self.io_loop)\n            self.http_client.fetch(\"http://127.0.0.1:%d/\" % port, self.stop)\n            resp = self.wait()\n            resp.rethrow()\n            self.assertEqual(resp.body, b\"12\")\n            self.io_loop.remove_handler(sock.fileno())\n\n    def test_streaming_stack_context(self):\n        chunks = []\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def streaming_cb(chunk):\n            chunks.append(chunk)\n            if chunk == b'qwer':\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', streaming_callback=streaming_cb)\n\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_basic_auth(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_basic_auth_explicit_mode(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\",\n                                    auth_mode=\"basic\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_unsupported_auth_mode(self):\n        # curl and simple clients handle errors a bit differently; the\n        # important thing is that they don't fall back to basic auth\n        # on an unknown mode.\n        with ExpectLog(gen_log, \"uncaught exception\", required=False):\n            with self.assertRaises((ValueError, HTTPError)):\n                response = self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                      auth_password=\"open sesame\",\n                                      auth_mode=\"asdf\")\n                response.rethrow()\n\n    def test_follow_redirect(self):\n        response = self.fetch(\"/countdown/2\", follow_redirects=False)\n        self.assertEqual(302, response.code)\n        self.assertTrue(response.headers[\"Location\"].endswith(\"/countdown/1\"))\n\n        response = self.fetch(\"/countdown/2\")\n        self.assertEqual(200, response.code)\n        self.assertTrue(response.effective_url.endswith(\"/countdown/0\"))\n        self.assertEqual(b\"Zero\", response.body)\n\n    def test_credentials_in_url(self):\n        url = self.get_url(\"/auth\").replace(\"http://\", \"http://me:secret@\")\n        self.http_client.fetch(url, self.stop)\n        response = self.wait()\n        self.assertEqual(b\"Basic \" + base64.b64encode(b\"me:secret\"),\n                         response.body)\n\n    def test_body_encoding(self):\n        unicode_body = u(\"\\xe9\")\n        byte_body = binascii.a2b_hex(b\"e9\")\n\n        # unicode string in body gets converted to utf8\n        response = self.fetch(\"/echopost\", method=\"POST\", body=unicode_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"2\")\n        self.assertEqual(response.body, utf8(unicode_body))\n\n        # byte strings pass through directly\n        response = self.fetch(\"/echopost\", method=\"POST\",\n                              body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n        # Mixing unicode in headers and byte string bodies shouldn't\n        # break anything\n        response = self.fetch(\"/echopost\", method=\"POST\", body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"},\n                              user_agent=u(\"foo\"))\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n    def test_types(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(type(response.body), bytes_type)\n        self.assertEqual(type(response.headers[\"Content-Type\"]), str)\n        self.assertEqual(type(response.code), int)\n        self.assertEqual(type(response.effective_url), str)\n\n    def test_header_callback(self):\n        first_line = []\n        headers = {}\n        chunks = []\n\n        def header_callback(header_line):\n            if header_line.startswith('HTTP/'):\n                first_line.append(header_line)\n            elif header_line != '\\r\\n':\n                k, v = header_line.split(':', 1)\n                headers[k] = v.strip()\n\n        def streaming_callback(chunk):\n            # All header callbacks are run before any streaming callbacks,\n            # so the header data is available to process the data as it\n            # comes in.\n            self.assertEqual(headers['Content-Type'], 'text/html; charset=UTF-8')\n            chunks.append(chunk)\n\n        self.fetch('/chunk', header_callback=header_callback,\n                   streaming_callback=streaming_callback)\n        self.assertEqual(len(first_line), 1)\n        self.assertRegexpMatches(first_line[0], 'HTTP/1.[01] 200 OK\\r\\n')\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n\n    def test_header_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def header_callback(header_line):\n            if header_line.startswith('Content-Type:'):\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', header_callback=header_callback)\n        self.assertEqual(len(exc_info), 1)\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_configure_defaults(self):\n        defaults = dict(user_agent='TestDefaultUserAgent')\n        # Construct a new instance of the configured client class\n        client = self.http_client.__class__(self.io_loop, force_instance=True,\n                                            defaults=defaults)\n        client.fetch(self.get_url('/user_agent'), callback=self.stop)\n        response = self.wait()\n        self.assertEqual(response.body, b'TestDefaultUserAgent')\n        client.close()\n\n    def test_304_with_content_length(self):\n        # According to the spec 304 responses SHOULD NOT include\n        # Content-Length or other entity headers, but some servers do it\n        # anyway.\n        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5\n        response = self.fetch('/304_with_content_length')\n        self.assertEqual(response.code, 304)\n        self.assertEqual(response.headers['Content-Length'], '42')\n\n    def test_final_callback_stack_context(self):\n        # The final callback should be run outside of the httpclient's\n        # stack_context.  We want to ensure that there is not stack_context\n        # between the user's callback and the IOLoop, so monkey-patch\n        # IOLoop.handle_callback_exception and disable the test harness's\n        # context with a NullContext.\n        # Note that this does not apply to secondary callbacks (header\n        # and streaming_callback), as errors there must be seen as errors\n        # by the http client so it can clean up the connection.\n        exc_info = []\n\n        def handle_callback_exception(callback):\n            exc_info.append(sys.exc_info())\n            self.stop()\n        self.io_loop.handle_callback_exception = handle_callback_exception\n        with NullContext():\n            self.http_client.fetch(self.get_url('/hello'),\n                                   lambda response: 1 / 0)\n        self.wait()\n        self.assertEqual(exc_info[0][0], ZeroDivisionError)\n\n    @gen_test\n    def test_future_interface(self):\n        response = yield self.http_client.fetch(self.get_url('/hello'))\n        self.assertEqual(response.body, b'Hello world!')\n\n    @gen_test\n    def test_future_http_error(self):\n        try:\n            yield self.http_client.fetch(self.get_url('/notfound'))\n        except HTTPError as e:\n            self.assertEqual(e.code, 404)\n            self.assertEqual(e.response.code, 404)\n\n    @gen_test\n    def test_reuse_request_from_response(self):\n        # The response.request attribute should be an HTTPRequest, not\n        # a _RequestProxy.\n        # This test uses self.http_client.fetch because self.fetch calls\n        # self.get_url on the input unconditionally.\n        url = self.get_url('/hello')\n        response = yield self.http_client.fetch(url)\n        self.assertEqual(response.request.url, url)\n        self.assertTrue(isinstance(response.request, HTTPRequest))\n        response2 = yield self.http_client.fetch(response.request)\n        self.assertEqual(response2.body, b'Hello world!')\n\n    def test_all_methods(self):\n        for method in ['GET', 'DELETE', 'OPTIONS']:\n            response = self.fetch('/all_methods', method=method)\n            self.assertEqual(response.body, utf8(method))\n        for method in ['POST', 'PUT', 'PATCH']:\n            response = self.fetch('/all_methods', method=method, body=b'')\n            self.assertEqual(response.body, utf8(method))\n        response = self.fetch('/all_methods', method='HEAD')\n        self.assertEqual(response.body, b'')\n        response = self.fetch('/all_methods', method='OTHER',\n                              allow_nonstandard_methods=True)\n        self.assertEqual(response.body, b'OTHER')\n\n    @gen_test\n    def test_body(self):\n        hello_url = self.get_url('/hello')\n        with self.assertRaises(AssertionError) as context:\n            yield self.http_client.fetch(hello_url, body='data')\n\n        self.assertTrue('must be empty' in str(context.exception))\n\n        with self.assertRaises(AssertionError) as context:\n            yield self.http_client.fetch(hello_url, method='POST')\n\n        self.assertTrue('must not be empty' in str(context.exception))",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 391,
                            "end_lineno": 401,
                            "parent": {
                                "file_name": "tornado/test/httpclient_test.py",
                                "type": "class",
                                "name": "HTTPClientCommonTestCase",
                                "content": "class HTTPClientCommonTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([\n            url(\"/hello\", HelloWorldHandler),\n            url(\"/post\", PostHandler),\n            url(\"/chunk\", ChunkHandler),\n            url(\"/auth\", AuthHandler),\n            url(\"/countdown/([0-9]+)\", CountdownHandler, name=\"countdown\"),\n            url(\"/echopost\", EchoPostHandler),\n            url(\"/user_agent\", UserAgentHandler),\n            url(\"/304_with_content_length\", ContentLength304Handler),\n            url(\"/all_methods\", AllMethodsHandler),\n        ], gzip=True)\n\n    def test_hello_world(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.headers[\"Content-Type\"], \"text/plain\")\n        self.assertEqual(response.body, b\"Hello world!\")\n        self.assertEqual(int(response.request_time), 0)\n\n        response = self.fetch(\"/hello?name=Ben\")\n        self.assertEqual(response.body, b\"Hello Ben!\")\n\n    def test_streaming_callback(self):\n        # streaming_callback is also tested in test_chunked\n        chunks = []\n        response = self.fetch(\"/hello\",\n                              streaming_callback=chunks.append)\n        # with streaming_callback, data goes to the callback and not response.body\n        self.assertEqual(chunks, [b\"Hello world!\"])\n        self.assertFalse(response.body)\n\n    def test_post(self):\n        response = self.fetch(\"/post\", method=\"POST\",\n                              body=\"arg1=foo&arg2=bar\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.body, b\"Post arg1: foo, arg2: bar\")\n\n    def test_chunked(self):\n        response = self.fetch(\"/chunk\")\n        self.assertEqual(response.body, b\"asdfqwer\")\n\n        chunks = []\n        response = self.fetch(\"/chunk\",\n                              streaming_callback=chunks.append)\n        self.assertEqual(chunks, [b\"asdf\", b\"qwer\"])\n        self.assertFalse(response.body)\n\n    def test_chunked_close(self):\n        # test case in which chunks spread read-callback processing\n        # over several ioloop iterations, but the connection is already closed.\n        sock, port = bind_unused_port()\n        with closing(sock):\n            def write_response(stream, request_data):\n                stream.write(b\"\"\"\\\nHTTP/1.1 200 OK\nTransfer-Encoding: chunked\n\n1\n1\n1\n2\n0\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"), callback=stream.close)\n\n            def accept_callback(conn, address):\n                # fake an HTTP server using chunked encoding where the final chunks\n                # and connection close all happen at once\n                stream = IOStream(conn, io_loop=self.io_loop)\n                stream.read_until(b\"\\r\\n\\r\\n\",\n                                  functools.partial(write_response, stream))\n            netutil.add_accept_handler(sock, accept_callback, self.io_loop)\n            self.http_client.fetch(\"http://127.0.0.1:%d/\" % port, self.stop)\n            resp = self.wait()\n            resp.rethrow()\n            self.assertEqual(resp.body, b\"12\")\n            self.io_loop.remove_handler(sock.fileno())\n\n    def test_streaming_stack_context(self):\n        chunks = []\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def streaming_cb(chunk):\n            chunks.append(chunk)\n            if chunk == b'qwer':\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', streaming_callback=streaming_cb)\n\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_basic_auth(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_basic_auth_explicit_mode(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\",\n                                    auth_mode=\"basic\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_unsupported_auth_mode(self):\n        # curl and simple clients handle errors a bit differently; the\n        # important thing is that they don't fall back to basic auth\n        # on an unknown mode.\n        with ExpectLog(gen_log, \"uncaught exception\", required=False):\n            with self.assertRaises((ValueError, HTTPError)):\n                response = self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                      auth_password=\"open sesame\",\n                                      auth_mode=\"asdf\")\n                response.rethrow()\n\n    def test_follow_redirect(self):\n        response = self.fetch(\"/countdown/2\", follow_redirects=False)\n        self.assertEqual(302, response.code)\n        self.assertTrue(response.headers[\"Location\"].endswith(\"/countdown/1\"))\n\n        response = self.fetch(\"/countdown/2\")\n        self.assertEqual(200, response.code)\n        self.assertTrue(response.effective_url.endswith(\"/countdown/0\"))\n        self.assertEqual(b\"Zero\", response.body)\n\n    def test_credentials_in_url(self):\n        url = self.get_url(\"/auth\").replace(\"http://\", \"http://me:secret@\")\n        self.http_client.fetch(url, self.stop)\n        response = self.wait()\n        self.assertEqual(b\"Basic \" + base64.b64encode(b\"me:secret\"),\n                         response.body)\n\n    def test_body_encoding(self):\n        unicode_body = u(\"\\xe9\")\n        byte_body = binascii.a2b_hex(b\"e9\")\n\n        # unicode string in body gets converted to utf8\n        response = self.fetch(\"/echopost\", method=\"POST\", body=unicode_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"2\")\n        self.assertEqual(response.body, utf8(unicode_body))\n\n        # byte strings pass through directly\n        response = self.fetch(\"/echopost\", method=\"POST\",\n                              body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n        # Mixing unicode in headers and byte string bodies shouldn't\n        # break anything\n        response = self.fetch(\"/echopost\", method=\"POST\", body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"},\n                              user_agent=u(\"foo\"))\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n    def test_types(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(type(response.body), bytes_type)\n        self.assertEqual(type(response.headers[\"Content-Type\"]), str)\n        self.assertEqual(type(response.code), int)\n        self.assertEqual(type(response.effective_url), str)\n\n    def test_header_callback(self):\n        first_line = []\n        headers = {}\n        chunks = []\n\n        def header_callback(header_line):\n            if header_line.startswith('HTTP/'):\n                first_line.append(header_line)\n            elif header_line != '\\r\\n':\n                k, v = header_line.split(':', 1)\n                headers[k] = v.strip()\n\n        def streaming_callback(chunk):\n            # All header callbacks are run before any streaming callbacks,\n            # so the header data is available to process the data as it\n            # comes in.\n            self.assertEqual(headers['Content-Type'], 'text/html; charset=UTF-8')\n            chunks.append(chunk)\n\n        self.fetch('/chunk', header_callback=header_callback,\n                   streaming_callback=streaming_callback)\n        self.assertEqual(len(first_line), 1)\n        self.assertRegexpMatches(first_line[0], 'HTTP/1.[01] 200 OK\\r\\n')\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n\n    def test_header_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def header_callback(header_line):\n            if header_line.startswith('Content-Type:'):\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', header_callback=header_callback)\n        self.assertEqual(len(exc_info), 1)\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_configure_defaults(self):\n        defaults = dict(user_agent='TestDefaultUserAgent')\n        # Construct a new instance of the configured client class\n        client = self.http_client.__class__(self.io_loop, force_instance=True,\n                                            defaults=defaults)\n        client.fetch(self.get_url('/user_agent'), callback=self.stop)\n        response = self.wait()\n        self.assertEqual(response.body, b'TestDefaultUserAgent')\n        client.close()\n\n    def test_304_with_content_length(self):\n        # According to the spec 304 responses SHOULD NOT include\n        # Content-Length or other entity headers, but some servers do it\n        # anyway.\n        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5\n        response = self.fetch('/304_with_content_length')\n        self.assertEqual(response.code, 304)\n        self.assertEqual(response.headers['Content-Length'], '42')\n\n    def test_final_callback_stack_context(self):\n        # The final callback should be run outside of the httpclient's\n        # stack_context.  We want to ensure that there is not stack_context\n        # between the user's callback and the IOLoop, so monkey-patch\n        # IOLoop.handle_callback_exception and disable the test harness's\n        # context with a NullContext.\n        # Note that this does not apply to secondary callbacks (header\n        # and streaming_callback), as errors there must be seen as errors\n        # by the http client so it can clean up the connection.\n        exc_info = []\n\n        def handle_callback_exception(callback):\n            exc_info.append(sys.exc_info())\n            self.stop()\n        self.io_loop.handle_callback_exception = handle_callback_exception\n        with NullContext():\n            self.http_client.fetch(self.get_url('/hello'),\n                                   lambda response: 1 / 0)\n        self.wait()\n        self.assertEqual(exc_info[0][0], ZeroDivisionError)\n\n    @gen_test\n    def test_future_interface(self):\n        response = yield self.http_client.fetch(self.get_url('/hello'))\n        self.assertEqual(response.body, b'Hello world!')\n\n    @gen_test\n    def test_future_http_error(self):\n        try:\n            yield self.http_client.fetch(self.get_url('/notfound'))\n        except HTTPError as e:\n            self.assertEqual(e.code, 404)\n            self.assertEqual(e.response.code, 404)\n\n    @gen_test\n    def test_reuse_request_from_response(self):\n        # The response.request attribute should be an HTTPRequest, not\n        # a _RequestProxy.\n        # This test uses self.http_client.fetch because self.fetch calls\n        # self.get_url on the input unconditionally.\n        url = self.get_url('/hello')\n        response = yield self.http_client.fetch(url)\n        self.assertEqual(response.request.url, url)\n        self.assertTrue(isinstance(response.request, HTTPRequest))\n        response2 = yield self.http_client.fetch(response.request)\n        self.assertEqual(response2.body, b'Hello world!')\n\n    def test_all_methods(self):\n        for method in ['GET', 'DELETE', 'OPTIONS']:\n            response = self.fetch('/all_methods', method=method)\n            self.assertEqual(response.body, utf8(method))\n        for method in ['POST', 'PUT', 'PATCH']:\n            response = self.fetch('/all_methods', method=method, body=b'')\n            self.assertEqual(response.body, utf8(method))\n        response = self.fetch('/all_methods', method='HEAD')\n        self.assertEqual(response.body, b'')\n        response = self.fetch('/all_methods', method='OTHER',\n                              allow_nonstandard_methods=True)\n        self.assertEqual(response.body, b'OTHER')\n\n    @gen_test\n    def test_body(self):\n        hello_url = self.get_url('/hello')\n        with self.assertRaises(AssertionError) as context:\n            yield self.http_client.fetch(hello_url, body='data')\n\n        self.assertTrue('must be empty' in str(context.exception))\n\n        with self.assertRaises(AssertionError) as context:\n            yield self.http_client.fetch(hello_url, method='POST')\n\n        self.assertTrue('must not be empty' in str(context.exception))",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 99,
                                "end_lineno": 401,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "4f276caf08ee3b9b7270f295d5cce84c91c9122b^",
    "new_commit_hash": "4f276caf08ee3b9b7270f295d5cce84c91c9122b",
    "commit_message": "Merge pull request #928 from ajdavis/better-assertion-in-httpclient\n\nBetter AsyncHTTPClient error for GET with body or POST without one. Fixes #927.",
    "commit_date": "2013-11-04T12:23:26-08:00",
    "metadata": {}
}
{
    "repo_name": "tornado",
    "new_commit_hash": "4f276caf08ee3b9b7270f295d5cce84c91c9122b",
    "test_file_codes": [
        "#!/usr/bin/env python\n\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nimport base64\nimport binascii\nfrom contextlib import closing\nimport functools\nimport sys\nimport threading\n\nfrom tornado.escape import utf8\nfrom tornado.httpclient import HTTPRequest, HTTPResponse, _RequestProxy, HTTPError, HTTPClient\nfrom tornado.httpserver import HTTPServer\nfrom tornado.ioloop import IOLoop\nfrom tornado.iostream import IOStream\nfrom tornado.log import gen_log\nfrom tornado import netutil\nfrom tornado.stack_context import ExceptionStackContext, NullContext\nfrom tornado.testing import AsyncHTTPTestCase, bind_unused_port, gen_test, ExpectLog\nfrom tornado.test.util import unittest\nfrom tornado.util import u, bytes_type\nfrom tornado.web import Application, RequestHandler, url\n\ntry:\n    from io import BytesIO  # python 3\nexcept ImportError:\n    from cStringIO import StringIO as BytesIO\n\n\nclass HelloWorldHandler(RequestHandler):\n    def get(self):\n        name = self.get_argument(\"name\", \"world\")\n        self.set_header(\"Content-Type\", \"text/plain\")\n        self.finish(\"Hello %s!\" % name)\n\n\nclass PostHandler(RequestHandler):\n    def post(self):\n        self.finish(\"Post arg1: %s, arg2: %s\" % (\n            self.get_argument(\"arg1\"), self.get_argument(\"arg2\")))\n\n\nclass ChunkHandler(RequestHandler):\n    def get(self):\n        self.write(\"asdf\")\n        self.flush()\n        self.write(\"qwer\")\n\n\nclass AuthHandler(RequestHandler):\n    def get(self):\n        self.finish(self.request.headers[\"Authorization\"])\n\n\nclass CountdownHandler(RequestHandler):\n    def get(self, count):\n        count = int(count)\n        if count > 0:\n            self.redirect(self.reverse_url(\"countdown\", count - 1))\n        else:\n            self.write(\"Zero\")\n\n\nclass EchoPostHandler(RequestHandler):\n    def post(self):\n        self.write(self.request.body)\n\n\nclass UserAgentHandler(RequestHandler):\n    def get(self):\n        self.write(self.request.headers.get('User-Agent', 'User agent not set'))\n\n\nclass ContentLength304Handler(RequestHandler):\n    def get(self):\n        self.set_status(304)\n        self.set_header('Content-Length', 42)\n\n    def _clear_headers_for_304(self):\n        # Tornado strips content-length from 304 responses, but here we\n        # want to simulate servers that include the headers anyway.\n        pass\n\n\nclass AllMethodsHandler(RequestHandler):\n    SUPPORTED_METHODS = RequestHandler.SUPPORTED_METHODS + ('OTHER',)\n\n    def method(self):\n        self.write(self.request.method)\n\n    get = post = put = delete = options = patch = other = method\n\n# These tests end up getting run redundantly: once here with the default\n# HTTPClient implementation, and then again in each implementation's own\n# test suite.\n\n\nclass HTTPClientCommonTestCase(AsyncHTTPTestCase):\n    def get_app(self):\n        return Application([\n            url(\"/hello\", HelloWorldHandler),\n            url(\"/post\", PostHandler),\n            url(\"/chunk\", ChunkHandler),\n            url(\"/auth\", AuthHandler),\n            url(\"/countdown/([0-9]+)\", CountdownHandler, name=\"countdown\"),\n            url(\"/echopost\", EchoPostHandler),\n            url(\"/user_agent\", UserAgentHandler),\n            url(\"/304_with_content_length\", ContentLength304Handler),\n            url(\"/all_methods\", AllMethodsHandler),\n        ], gzip=True)\n\n    def test_hello_world(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.headers[\"Content-Type\"], \"text/plain\")\n        self.assertEqual(response.body, b\"Hello world!\")\n        self.assertEqual(int(response.request_time), 0)\n\n        response = self.fetch(\"/hello?name=Ben\")\n        self.assertEqual(response.body, b\"Hello Ben!\")\n\n    def test_streaming_callback(self):\n        # streaming_callback is also tested in test_chunked\n        chunks = []\n        response = self.fetch(\"/hello\",\n                              streaming_callback=chunks.append)\n        # with streaming_callback, data goes to the callback and not response.body\n        self.assertEqual(chunks, [b\"Hello world!\"])\n        self.assertFalse(response.body)\n\n    def test_post(self):\n        response = self.fetch(\"/post\", method=\"POST\",\n                              body=\"arg1=foo&arg2=bar\")\n        self.assertEqual(response.code, 200)\n        self.assertEqual(response.body, b\"Post arg1: foo, arg2: bar\")\n\n    def test_chunked(self):\n        response = self.fetch(\"/chunk\")\n        self.assertEqual(response.body, b\"asdfqwer\")\n\n        chunks = []\n        response = self.fetch(\"/chunk\",\n                              streaming_callback=chunks.append)\n        self.assertEqual(chunks, [b\"asdf\", b\"qwer\"])\n        self.assertFalse(response.body)\n\n    def test_chunked_close(self):\n        # test case in which chunks spread read-callback processing\n        # over several ioloop iterations, but the connection is already closed.\n        sock, port = bind_unused_port()\n        with closing(sock):\n            def write_response(stream, request_data):\n                stream.write(b\"\"\"\\\nHTTP/1.1 200 OK\nTransfer-Encoding: chunked\n\n1\n1\n1\n2\n0\n\n\"\"\".replace(b\"\\n\", b\"\\r\\n\"), callback=stream.close)\n\n            def accept_callback(conn, address):\n                # fake an HTTP server using chunked encoding where the final chunks\n                # and connection close all happen at once\n                stream = IOStream(conn, io_loop=self.io_loop)\n                stream.read_until(b\"\\r\\n\\r\\n\",\n                                  functools.partial(write_response, stream))\n            netutil.add_accept_handler(sock, accept_callback, self.io_loop)\n            self.http_client.fetch(\"http://127.0.0.1:%d/\" % port, self.stop)\n            resp = self.wait()\n            resp.rethrow()\n            self.assertEqual(resp.body, b\"12\")\n            self.io_loop.remove_handler(sock.fileno())\n\n    def test_streaming_stack_context(self):\n        chunks = []\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def streaming_cb(chunk):\n            chunks.append(chunk)\n            if chunk == b'qwer':\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', streaming_callback=streaming_cb)\n\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n        self.assertEqual(1, len(exc_info))\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_basic_auth(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_basic_auth_explicit_mode(self):\n        self.assertEqual(self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                    auth_password=\"open sesame\",\n                                    auth_mode=\"basic\").body,\n                         b\"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\")\n\n    def test_unsupported_auth_mode(self):\n        # curl and simple clients handle errors a bit differently; the\n        # important thing is that they don't fall back to basic auth\n        # on an unknown mode.\n        with ExpectLog(gen_log, \"uncaught exception\", required=False):\n            with self.assertRaises((ValueError, HTTPError)):\n                response = self.fetch(\"/auth\", auth_username=\"Aladdin\",\n                                      auth_password=\"open sesame\",\n                                      auth_mode=\"asdf\")\n                response.rethrow()\n\n    def test_follow_redirect(self):\n        response = self.fetch(\"/countdown/2\", follow_redirects=False)\n        self.assertEqual(302, response.code)\n        self.assertTrue(response.headers[\"Location\"].endswith(\"/countdown/1\"))\n\n        response = self.fetch(\"/countdown/2\")\n        self.assertEqual(200, response.code)\n        self.assertTrue(response.effective_url.endswith(\"/countdown/0\"))\n        self.assertEqual(b\"Zero\", response.body)\n\n    def test_credentials_in_url(self):\n        url = self.get_url(\"/auth\").replace(\"http://\", \"http://me:secret@\")\n        self.http_client.fetch(url, self.stop)\n        response = self.wait()\n        self.assertEqual(b\"Basic \" + base64.b64encode(b\"me:secret\"),\n                         response.body)\n\n    def test_body_encoding(self):\n        unicode_body = u(\"\\xe9\")\n        byte_body = binascii.a2b_hex(b\"e9\")\n\n        # unicode string in body gets converted to utf8\n        response = self.fetch(\"/echopost\", method=\"POST\", body=unicode_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"2\")\n        self.assertEqual(response.body, utf8(unicode_body))\n\n        # byte strings pass through directly\n        response = self.fetch(\"/echopost\", method=\"POST\",\n                              body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"})\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n        # Mixing unicode in headers and byte string bodies shouldn't\n        # break anything\n        response = self.fetch(\"/echopost\", method=\"POST\", body=byte_body,\n                              headers={\"Content-Type\": \"application/blah\"},\n                              user_agent=u(\"foo\"))\n        self.assertEqual(response.headers[\"Content-Length\"], \"1\")\n        self.assertEqual(response.body, byte_body)\n\n    def test_types(self):\n        response = self.fetch(\"/hello\")\n        self.assertEqual(type(response.body), bytes_type)\n        self.assertEqual(type(response.headers[\"Content-Type\"]), str)\n        self.assertEqual(type(response.code), int)\n        self.assertEqual(type(response.effective_url), str)\n\n    def test_header_callback(self):\n        first_line = []\n        headers = {}\n        chunks = []\n\n        def header_callback(header_line):\n            if header_line.startswith('HTTP/'):\n                first_line.append(header_line)\n            elif header_line != '\\r\\n':\n                k, v = header_line.split(':', 1)\n                headers[k] = v.strip()\n\n        def streaming_callback(chunk):\n            # All header callbacks are run before any streaming callbacks,\n            # so the header data is available to process the data as it\n            # comes in.\n            self.assertEqual(headers['Content-Type'], 'text/html; charset=UTF-8')\n            chunks.append(chunk)\n\n        self.fetch('/chunk', header_callback=header_callback,\n                   streaming_callback=streaming_callback)\n        self.assertEqual(len(first_line), 1)\n        self.assertRegexpMatches(first_line[0], 'HTTP/1.[01] 200 OK\\r\\n')\n        self.assertEqual(chunks, [b'asdf', b'qwer'])\n\n    def test_header_callback_stack_context(self):\n        exc_info = []\n\n        def error_handler(typ, value, tb):\n            exc_info.append((typ, value, tb))\n            return True\n\n        def header_callback(header_line):\n            if header_line.startswith('Content-Type:'):\n                1 / 0\n\n        with ExceptionStackContext(error_handler):\n            self.fetch('/chunk', header_callback=header_callback)\n        self.assertEqual(len(exc_info), 1)\n        self.assertIs(exc_info[0][0], ZeroDivisionError)\n\n    def test_configure_defaults(self):\n        defaults = dict(user_agent='TestDefaultUserAgent')\n        # Construct a new instance of the configured client class\n        client = self.http_client.__class__(self.io_loop, force_instance=True,\n                                            defaults=defaults)\n        client.fetch(self.get_url('/user_agent'), callback=self.stop)\n        response = self.wait()\n        self.assertEqual(response.body, b'TestDefaultUserAgent')\n        client.close()\n\n    def test_304_with_content_length(self):\n        # According to the spec 304 responses SHOULD NOT include\n        # Content-Length or other entity headers, but some servers do it\n        # anyway.\n        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5\n        response = self.fetch('/304_with_content_length')\n        self.assertEqual(response.code, 304)\n        self.assertEqual(response.headers['Content-Length'], '42')\n\n    def test_final_callback_stack_context(self):\n        # The final callback should be run outside of the httpclient's\n        # stack_context.  We want to ensure that there is not stack_context\n        # between the user's callback and the IOLoop, so monkey-patch\n        # IOLoop.handle_callback_exception and disable the test harness's\n        # context with a NullContext.\n        # Note that this does not apply to secondary callbacks (header\n        # and streaming_callback), as errors there must be seen as errors\n        # by the http client so it can clean up the connection.\n        exc_info = []\n\n        def handle_callback_exception(callback):\n            exc_info.append(sys.exc_info())\n            self.stop()\n        self.io_loop.handle_callback_exception = handle_callback_exception\n        with NullContext():\n            self.http_client.fetch(self.get_url('/hello'),\n                                   lambda response: 1 / 0)\n        self.wait()\n        self.assertEqual(exc_info[0][0], ZeroDivisionError)\n\n    @gen_test\n    def test_future_interface(self):\n        response = yield self.http_client.fetch(self.get_url('/hello'))\n        self.assertEqual(response.body, b'Hello world!')\n\n    @gen_test\n    def test_future_http_error(self):\n        try:\n            yield self.http_client.fetch(self.get_url('/notfound'))\n        except HTTPError as e:\n            self.assertEqual(e.code, 404)\n            self.assertEqual(e.response.code, 404)\n\n    @gen_test\n    def test_reuse_request_from_response(self):\n        # The response.request attribute should be an HTTPRequest, not\n        # a _RequestProxy.\n        # This test uses self.http_client.fetch because self.fetch calls\n        # self.get_url on the input unconditionally.\n        url = self.get_url('/hello')\n        response = yield self.http_client.fetch(url)\n        self.assertEqual(response.request.url, url)\n        self.assertTrue(isinstance(response.request, HTTPRequest))\n        response2 = yield self.http_client.fetch(response.request)\n        self.assertEqual(response2.body, b'Hello world!')\n\n    def test_all_methods(self):\n        for method in ['GET', 'DELETE', 'OPTIONS']:\n            response = self.fetch('/all_methods', method=method)\n            self.assertEqual(response.body, utf8(method))\n        for method in ['POST', 'PUT', 'PATCH']:\n            response = self.fetch('/all_methods', method=method, body=b'')\n            self.assertEqual(response.body, utf8(method))\n        response = self.fetch('/all_methods', method='HEAD')\n        self.assertEqual(response.body, b'')\n        response = self.fetch('/all_methods', method='OTHER',\n                              allow_nonstandard_methods=True)\n        self.assertEqual(response.body, b'OTHER')\n\n    @gen_test\n    def test_body(self):\n        hello_url = self.get_url('/hello')\n        with self.assertRaises(AssertionError) as context:\n            yield self.http_client.fetch(hello_url, body='data')\n\n        self.assertTrue('must be empty' in str(context.exception))\n\n        with self.assertRaises(AssertionError) as context:\n            yield self.http_client.fetch(hello_url, method='POST')\n\n        self.assertTrue('must not be empty' in str(context.exception))\n\n\nclass RequestProxyTest(unittest.TestCase):\n    def test_request_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/',\n                                          user_agent='foo'),\n                              dict())\n        self.assertEqual(proxy.user_agent, 'foo')\n\n    def test_default_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'),\n                              dict(network_interface='foo'))\n        self.assertEqual(proxy.network_interface, 'foo')\n\n    def test_both_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/',\n                                          proxy_host='foo'),\n                              dict(proxy_host='bar'))\n        self.assertEqual(proxy.proxy_host, 'foo')\n\n    def test_neither_set(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'),\n                              dict())\n        self.assertIs(proxy.auth_username, None)\n\n    def test_bad_attribute(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'),\n                              dict())\n        with self.assertRaises(AttributeError):\n            proxy.foo\n\n    def test_defaults_none(self):\n        proxy = _RequestProxy(HTTPRequest('http://example.com/'), None)\n        self.assertIs(proxy.auth_username, None)\n\n\nclass HTTPResponseTestCase(unittest.TestCase):\n    def test_str(self):\n        response = HTTPResponse(HTTPRequest('http://example.com'),\n                                200, headers={}, buffer=BytesIO())\n        s = str(response)\n        self.assertTrue(s.startswith('HTTPResponse('))\n        self.assertIn('code=200', s)\n\n\nclass SyncHTTPClientTest(unittest.TestCase):\n    def setUp(self):\n        if IOLoop.configured_class().__name__ in ('TwistedIOLoop',\n                                                  'AsyncIOMainLoop'):\n            # TwistedIOLoop only supports the global reactor, so we can't have\n            # separate IOLoops for client and server threads.\n            # AsyncIOMainLoop doesn't work with the default policy\n            # (although it could with some tweaks to this test and a\n            # policy that created loops for non-main threads).\n            raise unittest.SkipTest(\n                'Sync HTTPClient not compatible with TwistedIOLoop or '\n                'AsyncIOMainLoop')\n        self.server_ioloop = IOLoop()\n\n        sock, self.port = bind_unused_port()\n        app = Application([('/', HelloWorldHandler)])\n        self.server = HTTPServer(app, io_loop=self.server_ioloop)\n        self.server.add_socket(sock)\n\n        self.server_thread = threading.Thread(target=self.server_ioloop.start)\n        self.server_thread.start()\n\n        self.http_client = HTTPClient()\n\n    def tearDown(self):\n        def stop_server():\n            self.server.stop()\n            self.server_ioloop.stop()\n        self.server_ioloop.add_callback(stop_server)\n        self.server_thread.join()\n        self.http_client.close()\n        self.server_ioloop.close(all_fds=True)\n\n    def get_url(self, path):\n        return 'http://localhost:%d%s' % (self.port, path)\n\n    def test_sync_client(self):\n        response = self.http_client.fetch(self.get_url('/'))\n        self.assertEqual(b'Hello world!', response.body)\n\n    def test_sync_client_error(self):\n        # Synchronous HTTPClient raises errors directly; no need for\n        # response.rethrow()\n        with self.assertRaises(HTTPError) as assertion:\n            self.http_client.fetch(self.get_url('/notfound'))\n        self.assertEqual(assertion.exception.code, 404)\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 653ms\nPrepared 1 package in 625ms\nInstalled 1 package in 0.64ms\n + tornado==3.2.dev2 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_4f276caf08ee3b9b7270f295d5cce84c91c9122b)\nResolved 29 packages in 12ms\nInstalled 29 packages in 46ms\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": 0,
    "new_commit_res_stdout": "============================= test session starts =============================\nplatform linux -- Python 3.9.21\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_4f276caf08ee3b9b7270f295d5cce84c91c9122b\n................................\ncollected 32 items\n\n==================== short test summary info ====================\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_304_with_content_length\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_all_methods\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_basic_auth\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_basic_auth_explicit_mode\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_body\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_body_encoding\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_chunked\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_chunked_close\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_configure_defaults\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_credentials_in_url\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_final_callback_stack_context\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_follow_redirect\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_future_http_error\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_future_interface\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_header_callback\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_header_callback_stack_context\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_hello_world\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_post\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_reuse_request_from_response\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_streaming_callback\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_streaming_stack_context\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_types\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_unsupported_auth_mode\nPASSED r2e_tests.test_1::HTTPResponseTestCase::test_str\nPASSED r2e_tests.test_1::RequestProxyTest::test_bad_attribute\nPASSED r2e_tests.test_1::RequestProxyTest::test_both_set\nPASSED r2e_tests.test_1::RequestProxyTest::test_default_set\nPASSED r2e_tests.test_1::RequestProxyTest::test_defaults_none\nPASSED r2e_tests.test_1::RequestProxyTest::test_neither_set\nPASSED r2e_tests.test_1::RequestProxyTest::test_request_set\nPASSED r2e_tests.test_1::SyncHTTPClientTest::test_sync_client\nPASSED r2e_tests.test_1::SyncHTTPClientTest::test_sync_client_error\n=================== 32 passed in 0.05s ===================\n",
    "new_commit_res_stderr": "WARNING:tornado.access:405 HEAD /all_methods (127.0.0.1) 0.28ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.22ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.56ms\n",
    "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_4f276caf08ee3b9b7270f295d5cce84c91c9122b\n................................\ncollected 32 items\n\n=================================== FAILURES ===================================\n__________ HTTPClientCommonTestCase.test_body __________\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_4f276caf08ee3b9b7270f295d5cce84c91c9122b/tornado/testing.py\", line 426, in wrapper\n    return self.io_loop.run_sync(\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_4f276caf08ee3b9b7270f295d5cce84c91c9122b/tornado/ioloop.py\", line 373, in run_sync\n    return future_cell[0].result()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_4f276caf08ee3b9b7270f295d5cce84c91c9122b/tornado/concurrent.py\", line 129, in result\n    raise_exc_info(self.__exc_info)\n  File \"<string>\", line 3, in raise_exc_info\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_4f276caf08ee3b9b7270f295d5cce84c91c9122b/tornado/stack_context.py\", line 302, in wrapped\n    ret = fn(*args, **kwargs)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_4f276caf08ee3b9b7270f295d5cce84c91c9122b/tornado/gen.py\", line 565, in inner\n    self.set_result(key, result)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_4f276caf08ee3b9b7270f295d5cce84c91c9122b/tornado/gen.py\", line 491, in set_result\n    self.run()\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_4f276caf08ee3b9b7270f295d5cce84c91c9122b/tornado/gen.py\", line 520, in run\n    yielded = self.gen.throw(*exc_info)\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/tornado_4f276caf08ee3b9b7270f295d5cce84c91c9122b/r2e_tests/test_1.py\", line 396, in test_body\n    self.assertTrue('must be empty' in str(context.exception))\nAssertionError: False is not true\n\n==================== short test summary info ====================\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_304_with_content_length\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_all_methods\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_basic_auth\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_basic_auth_explicit_mode\nFAILED r2e_tests.test_1::HTTPClientCommonTestCase::test_body\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_body_encoding\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_chunked\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_chunked_close\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_configure_defaults\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_credentials_in_url\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_final_callback_stack_context\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_follow_redirect\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_future_http_error\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_future_interface\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_header_callback\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_header_callback_stack_context\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_hello_world\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_post\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_reuse_request_from_response\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_streaming_callback\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_streaming_stack_context\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_types\nPASSED r2e_tests.test_1::HTTPClientCommonTestCase::test_unsupported_auth_mode\nPASSED r2e_tests.test_1::HTTPResponseTestCase::test_str\nPASSED r2e_tests.test_1::RequestProxyTest::test_bad_attribute\nPASSED r2e_tests.test_1::RequestProxyTest::test_both_set\nPASSED r2e_tests.test_1::RequestProxyTest::test_default_set\nPASSED r2e_tests.test_1::RequestProxyTest::test_defaults_none\nPASSED r2e_tests.test_1::RequestProxyTest::test_neither_set\nPASSED r2e_tests.test_1::RequestProxyTest::test_request_set\nPASSED r2e_tests.test_1::SyncHTTPClientTest::test_sync_client\nPASSED r2e_tests.test_1::SyncHTTPClientTest::test_sync_client_error\n=================== 1 failed, 31 passed in 0.05s ===================\n",
    "old_commit_res_stderr": "WARNING:tornado.access:405 HEAD /all_methods (127.0.0.1) 0.31ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.23ms\nWARNING:tornado.access:404 GET /notfound (127.0.0.1) 0.24ms\n"
}
tornado/curl_httpclient.py tornado/simple_httpclient.py tornado/test/httpclient_test.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]

**Title:** AsyncHTTPClient Raises Unclear Errors for GET Requests with Body and POST Requests without Body

**Description:**

When using `AsyncHTTPClient` to make HTTP requests, providing a body with a GET request or omitting the body in a POST request results in generic `AssertionError` exceptions. This lack of specific error messages makes it difficult to understand the root cause of the failure.

**Example Code:**

```python
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
import asyncio

async def make_requests():
    client = AsyncHTTPClient()
    
    # GET request with a body
    get_request = HTTPRequest(url="http://example.com/api", method="GET", body="data")
    await client.fetch(get_request)
    
    # POST request without a body
    post_request = HTTPRequest(url="http://example.com/api", method="POST")
    await client.fetch(post_request)

# To run the async function
asyncio.run(make_requests())
```

**Expected Behavior:**

- **GET Request with Body:** The client should raise an `AssertionError` with the message `"Body must be empty for GET request."`
- **POST Request without Body:** The client should raise an `AssertionError` with the message `"Body must not be empty for POST request."`

**Actual Behavior:**

- **GET Request with Body:** An `AssertionError` is raised without a clear message, making it unclear that the issue is the presence of a body in a GET request.
- **POST Request without Body:** An `AssertionError` is raised without a specific message, making it difficult to identify that the problem is the missing body in the POST request.

[/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/simple_httpclient.py
#!/usr/bin/env python
from __future__ import absolute_import, division, print_function, with_statement

from tornado.escape import utf8, _unicode, native_str
from tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main, _RequestProxy
from tornado.httputil import HTTPHeaders
from tornado.iostream import IOStream, SSLIOStream
from tornado.netutil import Resolver, OverrideResolver
from tornado.log import gen_log
from tornado import stack_context
from tornado.util import GzipDecompressor

import base64
import collections
import copy
import functools
import os.path
import re
import socket
import ssl
import sys

try:
    from io import BytesIO  # python 3
except ImportError:
    from cStringIO import StringIO as BytesIO  # python 2

try:
    import urlparse  # py2
except ImportError:
    import urllib.parse as urlparse  # py3

_DEFAULT_CA_CERTS = os.path.dirname(__file__) + '/ca-certificates.crt'


class SimpleAsyncHTTPClient(AsyncHTTPClient):
    """Non-blocking HTTP client with no external dependencies.

    This class implements an HTTP 1.1 client on top of Tornado's IOStreams.
    It does not currently implement all applicable parts of the HTTP
    specification, but it does enough to work with major web service APIs.

    Some features found in the curl-based AsyncHTTPClient are not yet
    supported.  In particular, proxies are not supported, connections
    are not reused, and callers cannot select the network interface to be
    used.
    """
    def initialize(self, io_loop, max_clients=10,
                   hostname_mapping=None, max_buffer_size=104857600,
                   resolver=None, defaults=None):
        """Creates a AsyncHTTPClient.

        Only a single AsyncHTTPClient instance exists per IOLoop
        in order to provide limitations on the number of pending connections.
        force_instance=True may be used to suppress this behavior.

        max_clients is the number of concurrent requests that can be
        in progress.  Note that this arguments are only used when the
        client is first created, and will be ignored when an existing
        client is reused.

        hostname_mapping is a dictionary mapping hostnames to IP addresses.
        It can be used to make local DNS changes when modifying system-wide
        settings like /etc/hosts is not possible or desirable (e.g. in
        unittests).

        max_buffer_size is the number of bytes that can be read by IOStream. It
        defaults to 100mb.
        """
        super(SimpleAsyncHTTPClient, self).initialize(io_loop,
                                                      defaults=defaults)
        self.max_clients = max_clients
        self.queue = collections.deque()
        self.active = {}
        self.waiting = {}
        self.max_buffer_size = max_buffer_size
        if resolver:
            self.resolver = resolver
            self.own_resolver = False
        else:
            self.resolver = Resolver(io_loop=io_loop)
            self.own_resolver = True
        if hostname_mapping is not None:
            self.resolver = OverrideResolver(resolver=self.resolver,
                                             mapping=hostname_mapping)

    def close(self):
        super(SimpleAsyncHTTPClient, self).close()
        if self.own_resolver:
            self.resolver.close()

    def fetch_impl(self, request, callback):
        key = object()
        self.queue.append((key, request, callback))
        if not len(self.active) < self.max_clients:
            timeout_handle = self.io_loop.add_timeout(
                    self.io_loop.time() + min(request.connect_timeout,
                                              request.request_timeout),
                    functools.partial(self._on_timeout, key))
        else:
            timeout_handle = None
        self.waiting[key] = (request, callback, timeout_handle)
        self._process_queue()
        if self.queue:
            gen_log.debug("max_clients limit reached, request queued. "
                          "%d active, %d queued requests." % (
                              len(self.active), len(self.queue)))

    def _process_queue(self):
        with stack_context.NullContext():
            while self.queue and len(self.active) < self.max_clients:
                key, request, callback = self.queue.popleft()
                if key not in self.waiting:
                    continue
                self._remove_timeout(key)
                self.active[key] = (request, callback)
                release_callback = functools.partial(self._release_fetch, key)
                self._handle_request(request, release_callback, callback)

    def _handle_request(self, request, release_callback, final_callback):
        _HTTPConnection(self.io_loop, self, request, release_callback,
                        final_callback, self.max_buffer_size, self.resolver)

    def _release_fetch(self, key):
        del self.active[key]
        self._process_queue()

    def _remove_timeout(self, key):
        if key in self.waiting:
            request, callback, timeout_handle = self.waiting[key]
            if timeout_handle is not None:
                self.io_loop.remove_timeout(timeout_handle)
            del self.waiting[key]

    def _on_timeout(self, key):
        request, callback, timeout_handle = self.waiting[key]
        self.queue.remove((key, request, callback))
        timeout_response = HTTPResponse(
                request, 599, error=HTTPError(599, "Timeout"),
                request_time=self.io_loop.time() - request.start_time)
        self.io_loop.add_callback(callback, timeout_response)
        del self.waiting[key]


class _HTTPConnection(object):
    _SUPPORTED_METHODS = set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])

    def __init__(self, io_loop, client, request, release_callback,
                 final_callback, max_buffer_size, resolver):
        self.start_time = io_loop.time()
        self.io_loop = io_loop
        self.client = client
        self.request = request
        self.release_callback = release_callback
        self.final_callback = final_callback
        self.max_buffer_size = max_buffer_size
        self.resolver = resolver
        self.code = None
        self.headers = None
        self.chunks = None
        self._decompressor = None
        # Timeout handle returned by IOLoop.add_timeout
        self._timeout = None
        with stack_context.ExceptionStackContext(self._handle_exception):
            self.parsed = urlparse.urlsplit(_unicode(self.request.url))
            if self.parsed.scheme not in ("http", "https"):
                raise ValueError("Unsupported url scheme: %s" %
                                 self.request.url)
            # urlsplit results have hostname and port results, but they
            # didn't support ipv6 literals until python 2.7.
            netloc = self.parsed.netloc
            if "@" in netloc:
                userpass, _, netloc = netloc.rpartition("@")
            match = re.match(r'^(.+):(\d+)$', netloc)
            if match:
                host = match.group(1)
                port = int(match.group(2))
            else:
                host = netloc
                port = 443 if self.parsed.scheme == "https" else 80
            if re.match(r'^\[.*\]$', host):
                # raw ipv6 addresses in urls are enclosed in brackets
                host = host[1:-1]
            self.parsed_hostname = host  # save final host for _on_connect

            if request.allow_ipv6:
                af = socket.AF_UNSPEC
            else:
                # We only try the first IP we get from getaddrinfo,
                # so restrict to ipv4 by default.
                af = socket.AF_INET

            timeout = min(self.request.connect_timeout, self.request.request_timeout)
            if timeout:
                self._timeout = self.io_loop.add_timeout(
                    self.start_time + timeout,
                    stack_context.wrap(self._on_timeout))
            self.resolver.resolve(host, port, af, callback=self._on_resolve)

    def _on_resolve(self, addrinfo):
        if self.final_callback is None:
            # final_callback is cleared if we've hit our timeout
            return
        self.stream = self._create_stream(addrinfo)
        self.stream.set_close_callback(self._on_close)
        # ipv6 addresses are broken (in self.parsed.hostname) until
        # 2.7, here is correctly parsed value calculated in __init__
        sockaddr = addrinfo[0][1]
        self.stream.connect(sockaddr, self._on_connect,
                            server_hostname=self.parsed_hostname)

    def _create_stream(self, addrinfo):
        af = addrinfo[0][0]
        if self.parsed.scheme == "https":
            ssl_options = {}
            if self.request.validate_cert:
                ssl_options["cert_reqs"] = ssl.CERT_REQUIRED
            if self.request.ca_certs is not None:
                ssl_options["ca_certs"] = self.request.ca_certs
            else:
                ssl_options["ca_certs"] = _DEFAULT_CA_CERTS
            if self.request.client_key is not None:
                ssl_options["keyfile"] = self.request.client_key
            if self.request.client_cert is not None:
                ssl_options["certfile"] = self.request.client_cert

            # SSL interoperability is tricky.  We want to disable
            # SSLv2 for security reasons; it wasn't disabled by default
            # until openssl 1.0.  The best way to do this is to use
            # the SSL_OP_NO_SSLv2, but that wasn't exposed to python
            # until 3.2.  Python 2.7 adds the ciphers argument, which
            # can also be used to disable SSLv2.  As a last resort
            # on python 2.6, we set ssl_version to TLSv1.  This is
            # more narrow than we'd like since it also breaks
            # compatibility with servers configured for SSLv3 only,
            # but nearly all servers support both SSLv3 and TLSv1:
            # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html
            if sys.version_info >= (2, 7):
                ssl_options["ciphers"] = "DEFAULT:!SSLv2"
            else:
                # This is really only necessary for pre-1.0 versions
                # of openssl, but python 2.6 doesn't expose version
                # information.
                ssl_options["ssl_version"] = ssl.PROTOCOL_TLSv1

            return SSLIOStream(socket.socket(af),
                               io_loop=self.io_loop,
                               ssl_options=ssl_options,
                               max_buffer_size=self.max_buffer_size)
        else:
            return IOStream(socket.socket(af),
                            io_loop=self.io_loop,
                            max_buffer_size=self.max_buffer_size)

    def _on_timeout(self):
        self._timeout = None
        if self.final_callback is not None:
            raise HTTPError(599, "Timeout")

    def _remove_timeout(self):
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
            self._timeout = None

    def _on_connect(self):
        self._remove_timeout()
        if self.final_callback is None:
            return
        if self.request.request_timeout:
            self._timeout = self.io_loop.add_timeout(
                self.start_time + self.request.request_timeout,
                stack_context.wrap(self._on_timeout))
        if (self.request.method not in self._SUPPORTED_METHODS and
                not self.request.allow_nonstandard_methods):
            raise KeyError("unknown method %s" % self.request.method)
        for key in ('network_interface',
                    'proxy_host', 'proxy_port',
                    'proxy_username', 'proxy_password'):
            if getattr(self.request, key, None):
                raise NotImplementedError('%s not supported' % key)
        if "Connection" not in self.request.headers:
            self.request.headers["Connection"] = "close"
        if "Host" not in self.request.headers:
            if '@' in self.parsed.netloc:
                self.request.headers["Host"] = self.parsed.netloc.rpartition('@')[-1]
            else:
                self.request.headers["Host"] = self.parsed.netloc
        username, password = None, None
        if self.parsed.username is not None:
            username, password = self.parsed.username, self.parsed.password
        elif self.request.auth_username is not None:
            username = self.request.auth_username
            password = self.request.auth_password or ''
        if username is not None:
            if self.request.auth_mode not in (None, "basic"):
                raise ValueError("unsupported auth_mode %s",
                                 self.request.auth_mode)
            auth = utf8(username) + b":" + utf8(password)
            self.request.headers["Authorization"] = (b"Basic " +
                                                     base64.b64encode(auth))
        if self.request.user_agent:
            self.request.headers["User-Agent"] = self.request.user_agent
        if not self.request.allow_nonstandard_methods:
            if self.request.method in ("POST", "PATCH", "PUT"):
                assert self.request.body is not None
            else:
                assert self.request.body is None
        if self.request.body is not None:
            self.request.headers["Content-Length"] = str(len(
                self.request.body))
        if (self.request.method == "POST" and
                "Content-Type" not in self.request.headers):
            self.request.headers["Content-Type"] = "application/x-www-form-urlencoded"
        if self.request.use_gzip:
            self.request.headers["Accept-Encoding"] = "gzip"
        req_path = ((self.parsed.path or '/') +
                   (('?' + self.parsed.query) if self.parsed.query else ''))
        request_lines = [utf8("%s %s HTTP/1.1" % (self.request.method,
                                                  req_path))]
        for k, v in self.request.headers.get_all():
            line = utf8(k) + b": " + utf8(v)
            if b'\n' in line:
                raise ValueError('Newline in header: ' + repr(line))
            request_lines.append(line)
        request_str = b"\r\n".join(request_lines) + b"\r\n\r\n"
        if self.request.body is not None:
            request_str += self.request.body
        self.stream.set_nodelay(True)
        self.stream.write(request_str)
        self.stream.read_until_regex(b"\r?\n\r?\n", self._on_headers)

    def _release(self):
        if self.release_callback is not None:
            release_callback = self.release_callback
            self.release_callback = None
            release_callback()

    def _run_callback(self, response):
        self._release()
        if self.final_callback is not None:
            final_callback = self.final_callback
            self.final_callback = None
            self.io_loop.add_callback(final_callback, response)

    def _handle_exception(self, typ, value, tb):
        if self.final_callback:
            self._remove_timeout()
            self._run_callback(HTTPResponse(self.request, 599, error=value,
                                            request_time=self.io_loop.time() - self.start_time,
                                            ))

            if hasattr(self, "stream"):
                self.stream.close()
            return True
        else:
            # If our callback has already been called, we are probably
            # catching an exception that is not caused by us but rather
            # some child of our callback. Rather than drop it on the floor,
            # pass it along.
            return False

    def _on_close(self):
        if self.final_callback is not None:
            message = "Connection closed"
            if self.stream.error:
                message = str(self.stream.error)
            raise HTTPError(599, message)

    def _handle_1xx(self, code):
        self.stream.read_until_regex(b"\r?\n\r?\n", self._on_headers)

    def _on_headers(self, data):
        data = native_str(data.decode("latin1"))
        first_line, _, header_data = data.partition("\n")
        match = re.match("HTTP/1.[01] ([0-9]+) ([^\r]*)", first_line)
        assert match
        code = int(match.group(1))
        self.headers = HTTPHeaders.parse(header_data)
        if 100 <= code < 200:
            self._handle_1xx(code)
            return
        else:
            self.code = code
            self.reason = match.group(2)

        if "Content-Length" in self.headers:
            if "," in self.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*', self.headers["Content-Length"])
                if any(i != pieces[0] for i in pieces):
                    raise ValueError("Multiple unequal Content-Lengths: %r" %
                                     self.headers["Content-Length"])
                self.headers["Content-Length"] = pieces[0]
            content_length = int(self.headers["Content-Length"])
        else:
            content_length = None

        if self.request.header_callback is not None:
            # re-attach the newline we split on earlier
            self.request.header_callback(first_line + _)
            for k, v in self.headers.get_all():
                self.request.header_callback("%s: %s\r\n" % (k, v))
            self.request.header_callback('\r\n')

        if self.request.method == "HEAD" or self.code == 304:
            # HEAD requests and 304 responses never have content, even
            # though they may have content-length headers
            self._on_body(b"")
            return
        if 100 <= self.code < 200 or self.code == 204:
            # These response codes never have bodies
            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
            if ("Transfer-Encoding" in self.headers or
                    content_length not in (None, 0)):
                raise ValueError("Response with code %d should not have body" %
                                 self.code)
            self._on_body(b"")
            return

        if (self.request.use_gzip and
                self.headers.get("Content-Encoding") == "gzip"):
            self._decompressor = GzipDecompressor()
        if self.headers.get("Transfer-Encoding") == "chunked":
            self.chunks = []
            self.stream.read_until(b"\r\n", self._on_chunk_length)
        elif content_length is not None:
            self.stream.read_bytes(content_length, self._on_body)
        else:
            self.stream.read_until_close(self._on_body)

    def _on_body(self, data):
        self._remove_timeout()
        original_request = getattr(self.request, "original_request",
                                   self.request)
        if (self.request.follow_redirects and
            self.request.max_redirects > 0 and
                self.code in (301, 302, 303, 307)):
            assert isinstance(self.request, _RequestProxy)
            new_request = copy.copy(self.request.request)
            new_request.url = urlparse.urljoin(self.request.url,
                                               self.headers["Location"])
            new_request.max_redirects = self.request.max_redirects - 1
            del new_request.headers["Host"]
            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
            # Client SHOULD make a GET request after a 303.
            # According to the spec, 302 should be followed by the same
            # method as the original request, but in practice browsers
            # treat 302 the same as 303, and many servers use 302 for
            # compatibility with pre-HTTP/1.1 user agents which don't
            # understand the 303 status.
            if self.code in (302, 303):
                new_request.method = "GET"
                new_request.body = None
                for h in ["Content-Length", "Content-Type",
                          "Content-Encoding", "Transfer-Encoding"]:
                    try:
                        del self.request.headers[h]
                    except KeyError:
                        pass
            new_request.original_request = original_request
            final_callback = self.final_callback
            self.final_callback = None
            self._release()
            self.client.fetch(new_request, final_callback)
            self._on_end_request()
            return
        if self._decompressor:
            data = (self._decompressor.decompress(data) +
                    self._decompressor.flush())
        if self.request.streaming_callback:
            if self.chunks is None:
                # if chunks is not None, we already called streaming_callback
                # in _on_chunk_data
                self.request.streaming_callback(data)
            buffer = BytesIO()
        else:
            buffer = BytesIO(data)  # TODO: don't require one big string?
        response = HTTPResponse(original_request,
                                self.code, reason=self.reason,
                                headers=self.headers,
                                request_time=self.io_loop.time() - self.start_time,
                                buffer=buffer,
                                effective_url=self.request.url)
        self._run_callback(response)
        self._on_end_request()

    def _on_end_request(self):
        self.stream.close()

    def _on_chunk_length(self, data):
        # TODO: "chunk extensions" http://tools.ietf.org/html/rfc2616#section-3.6.1
        length = int(data.strip(), 16)
        if length == 0:
            if self._decompressor is not None:
                tail = self._decompressor.flush()
                if tail:
                    # I believe the tail will always be empty (i.e.
                    # decompress will return all it can).  The purpose
                    # of the flush call is to detect errors such
                    # as truncated input.  But in case it ever returns
                    # anything, treat it as an extra chunk
                    if self.request.streaming_callback is not None:
                        self.request.streaming_callback(tail)
                    else:
                        self.chunks.append(tail)
                # all the data has been decompressed, so we don't need to
                # decompress again in _on_body
                self._decompressor = None
            self._on_body(b''.join(self.chunks))
        else:
            self.stream.read_bytes(length + 2,  # chunk ends with \r\n
                                   self._on_chunk_data)

    def _on_chunk_data(self, data):
        assert data[-2:] == b"\r\n"
        chunk = data[:-2]
        if self._decompressor:
            chunk = self._decompressor.decompress(chunk)
        if self.request.streaming_callback is not None:
            self.request.streaming_callback(chunk)
        else:
            self.chunks.append(chunk)
        self.stream.read_until(b"\r\n", self._on_chunk_length)


if __name__ == "__main__":
    AsyncHTTPClient.configure(SimpleAsyncHTTPClient)
    main()

```
--- 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:** AsyncHTTPClient Raises Unclear Errors for GET Requests with Body and POST Requests without Body

**Description:**

When using `AsyncHTTPClient` to make HTTP requests, providing a body with a GET request or omitting the body in a POST request results in generic `AssertionError` exceptions. This lack of specific error messages makes it difficult to understand the root cause of the failure.

**Example Code:**

```python
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
import asyncio

async def make_requests():
    client = AsyncHTTPClient()
    
    # GET request with a body
    get_request = HTTPRequest(url="http://example.com/api", method="GET", body="data")
    await client.fetch(get_request)
    
    # POST request without a body
    post_request = HTTPRequest(url="http://example.com/api", method="POST")
    await client.fetch(post_request)

# To run the async function
asyncio.run(make_requests())
```

**Expected Behavior:**

- **GET Request with Body:** The client should raise an `AssertionError` with the message `"Body must be empty for GET request."`
- **POST Request without Body:** The client should raise an `AssertionError` with the message `"Body must not be empty for POST request."`

**Actual Behavior:**

- **GET Request with Body:** An `AssertionError` is raised without a clear message, making it unclear that the issue is the presence of a body in a GET request.
- **POST Request without Body:** An `AssertionError` is raised without a specific message, making it difficult to identify that the problem is the missing body in the POST request.

[/ISSUE]
{
    "HTTPClientCommonTestCase.test_304_with_content_length": "PASSED",
    "HTTPClientCommonTestCase.test_all_methods": "PASSED",
    "HTTPClientCommonTestCase.test_basic_auth": "PASSED",
    "HTTPClientCommonTestCase.test_basic_auth_explicit_mode": "PASSED",
    "HTTPClientCommonTestCase.test_body": "PASSED",
    "HTTPClientCommonTestCase.test_body_encoding": "PASSED",
    "HTTPClientCommonTestCase.test_chunked": "PASSED",
    "HTTPClientCommonTestCase.test_chunked_close": "PASSED",
    "HTTPClientCommonTestCase.test_configure_defaults": "PASSED",
    "HTTPClientCommonTestCase.test_credentials_in_url": "PASSED",
    "HTTPClientCommonTestCase.test_final_callback_stack_context": "PASSED",
    "HTTPClientCommonTestCase.test_follow_redirect": "PASSED",
    "HTTPClientCommonTestCase.test_future_http_error": "PASSED",
    "HTTPClientCommonTestCase.test_future_interface": "PASSED",
    "HTTPClientCommonTestCase.test_header_callback": "PASSED",
    "HTTPClientCommonTestCase.test_header_callback_stack_context": "PASSED",
    "HTTPClientCommonTestCase.test_hello_world": "PASSED",
    "HTTPClientCommonTestCase.test_post": "PASSED",
    "HTTPClientCommonTestCase.test_reuse_request_from_response": "PASSED",
    "HTTPClientCommonTestCase.test_streaming_callback": "PASSED",
    "HTTPClientCommonTestCase.test_streaming_stack_context": "PASSED",
    "HTTPClientCommonTestCase.test_types": "PASSED",
    "HTTPClientCommonTestCase.test_unsupported_auth_mode": "PASSED",
    "HTTPResponseTestCase.test_str": "PASSED",
    "RequestProxyTest.test_bad_attribute": "PASSED",
    "RequestProxyTest.test_both_set": "PASSED",
    "RequestProxyTest.test_default_set": "PASSED",
    "RequestProxyTest.test_defaults_none": "PASSED",
    "RequestProxyTest.test_neither_set": "PASSED",
    "RequestProxyTest.test_request_set": "PASSED",
    "SyncHTTPClientTest.test_sync_client": "PASSED",
    "SyncHTTPClientTest.test_sync_client_error": "PASSED"
}
diff --git a/tornado/curl_httpclient.py b/tornado/curl_httpclient.py
index efa361c4..cb97710a 100644
--- a/tornado/curl_httpclient.py
+++ b/tornado/curl_httpclient.py
@@ -407,6 +407,11 @@ def _curl_setup_request(curl, request, buffer, headers):
 
     # Handle curl's cryptic options for every individual HTTP method
     if request.method in ("POST", "PUT"):
+        if request.body is None:
+            raise AssertionError(
+                'Body must not be empty for "%s" request'
+                % request.method)
+
         request_buffer = BytesIO(utf8(request.body))
         curl.setopt(pycurl.READFUNCTION, request_buffer.read)
         if request.method == "POST":
@@ -417,6 +422,9 @@ def _curl_setup_request(curl, request, buffer, headers):
             curl.setopt(pycurl.POSTFIELDSIZE, len(request.body))
         else:
             curl.setopt(pycurl.INFILESIZE, len(request.body))
+    elif request.method == "GET":
+        if request.body is not None:
+            raise AssertionError('Body must be empty for GET request')
 
     if request.auth_username is not None:
         userpwd = "%s:%s" % (request.auth_username, request.auth_password or '')
diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py
index faf75cec..2558ada8 100644
--- a/tornado/simple_httpclient.py
+++ b/tornado/simple_httpclient.py
@@ -302,9 +302,15 @@ class _HTTPConnection(object):
             self.request.headers["User-Agent"] = self.request.user_agent
         if not self.request.allow_nonstandard_methods:
             if self.request.method in ("POST", "PATCH", "PUT"):
-                assert self.request.body is not None
+                if self.request.body is None:
+                    raise AssertionError(
+                        'Body must not be empty for "%s" request'
+                        % self.request.method)
             else:
-                assert self.request.body is None
+                if self.request.body is not None:
+                    raise AssertionError(
+                        'Body must be empty for "%s" request'
+                        % self.request.method)
         if self.request.body is not None:
             self.request.headers["Content-Length"] = str(len(
                 self.request.body))

{"tornado/simple_httpclient.py": "#!/usr/bin/env python\nfrom __future__ import absolute_import, division, print_function, with_statement\n\nfrom tornado.escape import utf8, _unicode, native_str\nfrom tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main, _RequestProxy\nfrom tornado.httputil import HTTPHeaders\nfrom tornado.iostream import IOStream, SSLIOStream\nfrom tornado.netutil import Resolver, OverrideResolver\nfrom tornado.log import gen_log\nfrom tornado import stack_context\nfrom tornado.util import GzipDecompressor\n\nimport base64\nimport collections\nimport copy\nimport functools\nimport os.path\nimport re\nimport socket\nimport ssl\nimport sys\n\ntry:\n    from io import BytesIO  # python 3\nexcept ImportError:\n    from cStringIO import StringIO as BytesIO  # python 2\n\ntry:\n    import urlparse  # py2\nexcept ImportError:\n    import urllib.parse as urlparse  # py3\n\n_DEFAULT_CA_CERTS = os.path.dirname(__file__) + '/ca-certificates.crt'\n\n\nclass SimpleAsyncHTTPClient(AsyncHTTPClient):\n    \"\"\"Non-blocking HTTP client with no external dependencies.\n\n    This class implements an HTTP 1.1 client on top of Tornado's IOStreams.\n    It does not currently implement all applicable parts of the HTTP\n    specification, but it does enough to work with major web service APIs.\n\n    Some features found in the curl-based AsyncHTTPClient are not yet\n    supported.  In particular, proxies are not supported, connections\n    are not reused, and callers cannot select the network interface to be\n    used.\n    \"\"\"\n    def initialize(self, io_loop, max_clients=10,\n                   hostname_mapping=None, max_buffer_size=104857600,\n                   resolver=None, defaults=None):\n        \"\"\"Creates a AsyncHTTPClient.\n\n        Only a single AsyncHTTPClient instance exists per IOLoop\n        in order to provide limitations on the number of pending connections.\n        force_instance=True may be used to suppress this behavior.\n\n        max_clients is the number of concurrent requests that can be\n        in progress.  Note that this arguments are only used when the\n        client is first created, and will be ignored when an existing\n        client is reused.\n\n        hostname_mapping is a dictionary mapping hostnames to IP addresses.\n        It can be used to make local DNS changes when modifying system-wide\n        settings like /etc/hosts is not possible or desirable (e.g. in\n        unittests).\n\n        max_buffer_size is the number of bytes that can be read by IOStream. It\n        defaults to 100mb.\n        \"\"\"\n        super(SimpleAsyncHTTPClient, self).initialize(io_loop,\n                                                      defaults=defaults)\n        self.max_clients = max_clients\n        self.queue = collections.deque()\n        self.active = {}\n        self.waiting = {}\n        self.max_buffer_size = max_buffer_size\n        if resolver:\n            self.resolver = resolver\n            self.own_resolver = False\n        else:\n            self.resolver = Resolver(io_loop=io_loop)\n            self.own_resolver = True\n        if hostname_mapping is not None:\n            self.resolver = OverrideResolver(resolver=self.resolver,\n                                             mapping=hostname_mapping)\n\n    def close(self):\n        super(SimpleAsyncHTTPClient, self).close()\n        if self.own_resolver:\n            self.resolver.close()\n\n    def fetch_impl(self, request, callback):\n        key = object()\n        self.queue.append((key, request, callback))\n        if not len(self.active) < self.max_clients:\n            timeout_handle = self.io_loop.add_timeout(\n                    self.io_loop.time() + min(request.connect_timeout,\n                                              request.request_timeout),\n                    functools.partial(self._on_timeout, key))\n        else:\n            timeout_handle = None\n        self.waiting[key] = (request, callback, timeout_handle)\n        self._process_queue()\n        if self.queue:\n            gen_log.debug(\"max_clients limit reached, request queued. \"\n                          \"%d active, %d queued requests.\" % (\n                              len(self.active), len(self.queue)))\n\n    def _process_queue(self):\n        with stack_context.NullContext():\n            while self.queue and len(self.active) < self.max_clients:\n                key, request, callback = self.queue.popleft()\n                if key not in self.waiting:\n                    continue\n                self._remove_timeout(key)\n                self.active[key] = (request, callback)\n                release_callback = functools.partial(self._release_fetch, key)\n                self._handle_request(request, release_callback, callback)\n\n    def _handle_request(self, request, release_callback, final_callback):\n        _HTTPConnection(self.io_loop, self, request, release_callback,\n                        final_callback, self.max_buffer_size, self.resolver)\n\n    def _release_fetch(self, key):\n        del self.active[key]\n        self._process_queue()\n\n    def _remove_timeout(self, key):\n        if key in self.waiting:\n            request, callback, timeout_handle = self.waiting[key]\n            if timeout_handle is not None:\n                self.io_loop.remove_timeout(timeout_handle)\n            del self.waiting[key]\n\n    def _on_timeout(self, key):\n        request, callback, timeout_handle = self.waiting[key]\n        self.queue.remove((key, request, callback))\n        timeout_response = HTTPResponse(\n                request, 599, error=HTTPError(599, \"Timeout\"),\n                request_time=self.io_loop.time() - request.start_time)\n        self.io_loop.add_callback(callback, timeout_response)\n        del self.waiting[key]\n\n\nclass _HTTPConnection(object):\n    _SUPPORTED_METHODS = set([\"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"PATCH\", \"OPTIONS\"])\n\n    def __init__(self, io_loop, client, request, release_callback,\n                 final_callback, max_buffer_size, resolver):\n        self.start_time = io_loop.time()\n        self.io_loop = io_loop\n        self.client = client\n        self.request = request\n        self.release_callback = release_callback\n        self.final_callback = final_callback\n        self.max_buffer_size = max_buffer_size\n        self.resolver = resolver\n        self.code = None\n        self.headers = None\n        self.chunks = None\n        self._decompressor = None\n        # Timeout handle returned by IOLoop.add_timeout\n        self._timeout = None\n        with stack_context.ExceptionStackContext(self._handle_exception):\n            self.parsed = urlparse.urlsplit(_unicode(self.request.url))\n            if self.parsed.scheme not in (\"http\", \"https\"):\n                raise ValueError(\"Unsupported url scheme: %s\" %\n                                 self.request.url)\n            # urlsplit results have hostname and port results, but they\n            # didn't support ipv6 literals until python 2.7.\n            netloc = self.parsed.netloc\n            if \"@\" in netloc:\n                userpass, _, netloc = netloc.rpartition(\"@\")\n            match = re.match(r'^(.+):(\\d+)$', netloc)\n            if match:\n                host = match.group(1)\n                port = int(match.group(2))\n            else:\n                host = netloc\n                port = 443 if self.parsed.scheme == \"https\" else 80\n            if re.match(r'^\\[.*\\]$', host):\n                # raw ipv6 addresses in urls are enclosed in brackets\n                host = host[1:-1]\n            self.parsed_hostname = host  # save final host for _on_connect\n\n            if request.allow_ipv6:\n                af = socket.AF_UNSPEC\n            else:\n                # We only try the first IP we get from getaddrinfo,\n                # so restrict to ipv4 by default.\n                af = socket.AF_INET\n\n            timeout = min(self.request.connect_timeout, self.request.request_timeout)\n            if timeout:\n                self._timeout = self.io_loop.add_timeout(\n                    self.start_time + timeout,\n                    stack_context.wrap(self._on_timeout))\n            self.resolver.resolve(host, port, af, callback=self._on_resolve)\n\n    def _on_resolve(self, addrinfo):\n        if self.final_callback is None:\n            # final_callback is cleared if we've hit our timeout\n            return\n        self.stream = self._create_stream(addrinfo)\n        self.stream.set_close_callback(self._on_close)\n        # ipv6 addresses are broken (in self.parsed.hostname) until\n        # 2.7, here is correctly parsed value calculated in __init__\n        sockaddr = addrinfo[0][1]\n        self.stream.connect(sockaddr, self._on_connect,\n                            server_hostname=self.parsed_hostname)\n\n    def _create_stream(self, addrinfo):\n        af = addrinfo[0][0]\n        if self.parsed.scheme == \"https\":\n            ssl_options = {}\n            if self.request.validate_cert:\n                ssl_options[\"cert_reqs\"] = ssl.CERT_REQUIRED\n            if self.request.ca_certs is not None:\n                ssl_options[\"ca_certs\"] = self.request.ca_certs\n            else:\n                ssl_options[\"ca_certs\"] = _DEFAULT_CA_CERTS\n            if self.request.client_key is not None:\n                ssl_options[\"keyfile\"] = self.request.client_key\n            if self.request.client_cert is not None:\n                ssl_options[\"certfile\"] = self.request.client_cert\n\n            # SSL interoperability is tricky.  We want to disable\n            # SSLv2 for security reasons; it wasn't disabled by default\n            # until openssl 1.0.  The best way to do this is to use\n            # the SSL_OP_NO_SSLv2, but that wasn't exposed to python\n            # until 3.2.  Python 2.7 adds the ciphers argument, which\n            # can also be used to disable SSLv2.  As a last resort\n            # on python 2.6, we set ssl_version to TLSv1.  This is\n            # more narrow than we'd like since it also breaks\n            # compatibility with servers configured for SSLv3 only,\n            # but nearly all servers support both SSLv3 and TLSv1:\n            # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html\n            if sys.version_info >= (2, 7):\n                ssl_options[\"ciphers\"] = \"DEFAULT:!SSLv2\"\n            else:\n                # This is really only necessary for pre-1.0 versions\n                # of openssl, but python 2.6 doesn't expose version\n                # information.\n                ssl_options[\"ssl_version\"] = ssl.PROTOCOL_TLSv1\n\n            return SSLIOStream(socket.socket(af),\n                               io_loop=self.io_loop,\n                               ssl_options=ssl_options,\n                               max_buffer_size=self.max_buffer_size)\n        else:\n            return IOStream(socket.socket(af),\n                            io_loop=self.io_loop,\n                            max_buffer_size=self.max_buffer_size)\n\n    def _on_timeout(self):\n        self._timeout = None\n        if self.final_callback is not None:\n            raise HTTPError(599, \"Timeout\")\n\n    def _remove_timeout(self):\n        if self._timeout is not None:\n            self.io_loop.remove_timeout(self._timeout)\n            self._timeout = None\n\n    def _on_connect(self):\n        self._remove_timeout()\n        if self.final_callback is None:\n            return\n        if self.request.request_timeout:\n            self._timeout = self.io_loop.add_timeout(\n                self.start_time + self.request.request_timeout,\n                stack_context.wrap(self._on_timeout))\n        if (self.request.method not in self._SUPPORTED_METHODS and\n                not self.request.allow_nonstandard_methods):\n            raise KeyError(\"unknown method %s\" % self.request.method)\n        for key in ('network_interface',\n                    'proxy_host', 'proxy_port',\n                    'proxy_username', 'proxy_password'):\n            if getattr(self.request, key, None):\n                raise NotImplementedError('%s not supported' % key)\n        if \"Connection\" not in self.request.headers:\n            self.request.headers[\"Connection\"] = \"close\"\n        if \"Host\" not in self.request.headers:\n            if '@' in self.parsed.netloc:\n                self.request.headers[\"Host\"] = self.parsed.netloc.rpartition('@')[-1]\n            else:\n                self.request.headers[\"Host\"] = self.parsed.netloc\n        username, password = None, None\n        if self.parsed.username is not None:\n            username, password = self.parsed.username, self.parsed.password\n        elif self.request.auth_username is not None:\n            username = self.request.auth_username\n            password = self.request.auth_password or ''\n        if username is not None:\n            if self.request.auth_mode not in (None, \"basic\"):\n                raise ValueError(\"unsupported auth_mode %s\",\n                                 self.request.auth_mode)\n            auth = utf8(username) + b\":\" + utf8(password)\n            self.request.headers[\"Authorization\"] = (b\"Basic \" +\n                                                     base64.b64encode(auth))\n        if self.request.user_agent:\n            self.request.headers[\"User-Agent\"] = self.request.user_agent\n        if not self.request.allow_nonstandard_methods:\n            if self.request.method in (\"POST\", \"PATCH\", \"PUT\"):\n                assert self.request.body is not None\n            else:\n                assert self.request.body is None\n        if self.request.body is not None:\n            self.request.headers[\"Content-Length\"] = str(len(\n                self.request.body))\n        if (self.request.method == \"POST\" and\n                \"Content-Type\" not in self.request.headers):\n            self.request.headers[\"Content-Type\"] = \"application/x-www-form-urlencoded\"\n        if self.request.use_gzip:\n            self.request.headers[\"Accept-Encoding\"] = \"gzip\"\n        req_path = ((self.parsed.path or '/') +\n                   (('?' + self.parsed.query) if self.parsed.query else ''))\n        request_lines = [utf8(\"%s %s HTTP/1.1\" % (self.request.method,\n                                                  req_path))]\n        for k, v in self.request.headers.get_all():\n            line = utf8(k) + b\": \" + utf8(v)\n            if b'\\n' in line:\n                raise ValueError('Newline in header: ' + repr(line))\n            request_lines.append(line)\n        request_str = b\"\\r\\n\".join(request_lines) + b\"\\r\\n\\r\\n\"\n        if self.request.body is not None:\n            request_str += self.request.body\n        self.stream.set_nodelay(True)\n        self.stream.write(request_str)\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _release(self):\n        if self.release_callback is not None:\n            release_callback = self.release_callback\n            self.release_callback = None\n            release_callback()\n\n    def _run_callback(self, response):\n        self._release()\n        if self.final_callback is not None:\n            final_callback = self.final_callback\n            self.final_callback = None\n            self.io_loop.add_callback(final_callback, response)\n\n    def _handle_exception(self, typ, value, tb):\n        if self.final_callback:\n            self._remove_timeout()\n            self._run_callback(HTTPResponse(self.request, 599, error=value,\n                                            request_time=self.io_loop.time() - self.start_time,\n                                            ))\n\n            if hasattr(self, \"stream\"):\n                self.stream.close()\n            return True\n        else:\n            # If our callback has already been called, we are probably\n            # catching an exception that is not caused by us but rather\n            # some child of our callback. Rather than drop it on the floor,\n            # pass it along.\n            return False\n\n    def _on_close(self):\n        if self.final_callback is not None:\n            message = \"Connection closed\"\n            if self.stream.error:\n                message = str(self.stream.error)\n            raise HTTPError(599, message)\n\n    def _handle_1xx(self, code):\n        self.stream.read_until_regex(b\"\\r?\\n\\r?\\n\", self._on_headers)\n\n    def _on_headers(self, data):\n        data = native_str(data.decode(\"latin1\"))\n        first_line, _, header_data = data.partition(\"\\n\")\n        match = re.match(\"HTTP/1.[01] ([0-9]+) ([^\\r]*)\", first_line)\n        assert match\n        code = int(match.group(1))\n        self.headers = HTTPHeaders.parse(header_data)\n        if 100 <= code < 200:\n            self._handle_1xx(code)\n            return\n        else:\n            self.code = code\n            self.reason = match.group(2)\n\n        if \"Content-Length\" in self.headers:\n            if \",\" in self.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*', self.headers[\"Content-Length\"])\n                if any(i != pieces[0] for i in pieces):\n                    raise ValueError(\"Multiple unequal Content-Lengths: %r\" %\n                                     self.headers[\"Content-Length\"])\n                self.headers[\"Content-Length\"] = pieces[0]\n            content_length = int(self.headers[\"Content-Length\"])\n        else:\n            content_length = None\n\n        if self.request.header_callback is not None:\n            # re-attach the newline we split on earlier\n            self.request.header_callback(first_line + _)\n            for k, v in self.headers.get_all():\n                self.request.header_callback(\"%s: %s\\r\\n\" % (k, v))\n            self.request.header_callback('\\r\\n')\n\n        if self.request.method == \"HEAD\" or self.code == 304:\n            # HEAD requests and 304 responses never have content, even\n            # though they may have content-length headers\n            self._on_body(b\"\")\n            return\n        if 100 <= self.code < 200 or self.code == 204:\n            # These response codes never have bodies\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3\n            if (\"Transfer-Encoding\" in self.headers or\n                    content_length not in (None, 0)):\n                raise ValueError(\"Response with code %d should not have body\" %\n                                 self.code)\n            self._on_body(b\"\")\n            return\n\n        if (self.request.use_gzip and\n                self.headers.get(\"Content-Encoding\") == \"gzip\"):\n            self._decompressor = GzipDecompressor()\n        if self.headers.get(\"Transfer-Encoding\") == \"chunked\":\n            self.chunks = []\n            self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)\n        elif content_length is not None:\n            self.stream.read_bytes(content_length, self._on_body)\n        else:\n            self.stream.read_until_close(self._on_body)\n\n    def _on_body(self, data):\n        self._remove_timeout()\n        original_request = getattr(self.request, \"original_request\",\n                                   self.request)\n        if (self.request.follow_redirects and\n            self.request.max_redirects > 0 and\n                self.code in (301, 302, 303, 307)):\n            assert isinstance(self.request, _RequestProxy)\n            new_request = copy.copy(self.request.request)\n            new_request.url = urlparse.urljoin(self.request.url,\n                                               self.headers[\"Location\"])\n            new_request.max_redirects = self.request.max_redirects - 1\n            del new_request.headers[\"Host\"]\n            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4\n            # Client SHOULD make a GET request after a 303.\n            # According to the spec, 302 should be followed by the same\n            # method as the original request, but in practice browsers\n            # treat 302 the same as 303, and many servers use 302 for\n            # compatibility with pre-HTTP/1.1 user agents which don't\n            # understand the 303 status.\n            if self.code in (302, 303):\n                new_request.method = \"GET\"\n                new_request.body = None\n                for h in [\"Content-Length\", \"Content-Type\",\n                          \"Content-Encoding\", \"Transfer-Encoding\"]:\n                    try:\n                        del self.request.headers[h]\n                    except KeyError:\n                        pass\n            new_request.original_request = original_request\n            final_callback = self.final_callback\n            self.final_callback = None\n            self._release()\n            self.client.fetch(new_request, final_callback)\n            self._on_end_request()\n            return\n        if self._decompressor:\n            data = (self._decompressor.decompress(data) +\n                    self._decompressor.flush())\n        if self.request.streaming_callback:\n            if self.chunks is None:\n                # if chunks is not None, we already called streaming_callback\n                # in _on_chunk_data\n                self.request.streaming_callback(data)\n            buffer = BytesIO()\n        else:\n            buffer = BytesIO(data)  # TODO: don't require one big string?\n        response = HTTPResponse(original_request,\n                                self.code, reason=self.reason,\n                                headers=self.headers,\n                                request_time=self.io_loop.time() - self.start_time,\n                                buffer=buffer,\n                                effective_url=self.request.url)\n        self._run_callback(response)\n        self._on_end_request()\n\n    def _on_end_request(self):\n        self.stream.close()\n\n    def _on_chunk_length(self, data):\n        # TODO: \"chunk extensions\" http://tools.ietf.org/html/rfc2616#section-3.6.1\n        length = int(data.strip(), 16)\n        if length == 0:\n            if self._decompressor is not None:\n                tail = self._decompressor.flush()\n                if tail:\n                    # I believe the tail will always be empty (i.e.\n                    # decompress will return all it can).  The purpose\n                    # of the flush call is to detect errors such\n                    # as truncated input.  But in case it ever returns\n                    # anything, treat it as an extra chunk\n                    if self.request.streaming_callback is not None:\n                        self.request.streaming_callback(tail)\n                    else:\n                        self.chunks.append(tail)\n                # all the data has been decompressed, so we don't need to\n                # decompress again in _on_body\n                self._decompressor = None\n            self._on_body(b''.join(self.chunks))\n        else:\n            self.stream.read_bytes(length + 2,  # chunk ends with \\r\\n\n                                   self._on_chunk_data)\n\n    def _on_chunk_data(self, data):\n        assert data[-2:] == b\"\\r\\n\"\n        chunk = data[:-2]\n        if self._decompressor:\n            chunk = self._decompressor.decompress(chunk)\n        if self.request.streaming_callback is not None:\n            self.request.streaming_callback(chunk)\n        else:\n            self.chunks.append(chunk)\n        self.stream.read_until(b\"\\r\\n\", self._on_chunk_length)\n\n\nif __name__ == \"__main__\":\n    AsyncHTTPClient.configure(SimpleAsyncHTTPClient)\n    main()\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:** AsyncHTTPClient Raises Unclear Errors for GET Requests with Body and POST Requests without Body

**Description:**

When using `AsyncHTTPClient` to make HTTP requests, providing a body with a GET request or omitting the body in a POST request results in generic `AssertionError` exceptions. This lack of specific error messages makes it difficult to understand the root cause of the failure.

**Example Code:**

```python
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
import asyncio

async def make_requests():
    client = AsyncHTTPClient()
    
    # GET request with a body
    get_request = HTTPRequest(url="http://example.com/api", method="GET", body="data")
    await client.fetch(get_request)
    
    # POST request without a body
    post_request = HTTPRequest(url="http://example.com/api", method="POST")
    await client.fetch(post_request)

# To run the async function
asyncio.run(make_requests())
```

**Expected Behavior:**

- **GET Request with Body:** The client should raise an `AssertionError` with the message `"Body must be empty for GET request."`
- **POST Request without Body:** The client should raise an `AssertionError` with the message `"Body must not be empty for POST request."`

**Actual Behavior:**

- **GET Request with Body:** An `AssertionError` is raised without a clear message, making it unclear that the issue is the presence of a body in a GET request.
- **POST Request without Body:** An `AssertionError` is raised without a specific message, making it difficult to identify that the problem is the missing body in the POST request.

[/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/simple_httpclient.py
#!/usr/bin/env python
from __future__ import absolute_import, division, print_function, with_statement

from tornado.escape import utf8, _unicode, native_str
from tornado.httpclient import HTTPResponse, HTTPError, AsyncHTTPClient, main, _RequestProxy
from tornado.httputil import HTTPHeaders
from tornado.iostream import IOStream, SSLIOStream
from tornado.netutil import Resolver, OverrideResolver
from tornado.log import gen_log
from tornado import stack_context
from tornado.util import GzipDecompressor

import base64
import collections
import copy
import functools
import os.path
import re
import socket
import ssl
import sys

try:
    from io import BytesIO  # python 3
except ImportError:
    from cStringIO import StringIO as BytesIO  # python 2

try:
    import urlparse  # py2
except ImportError:
    import urllib.parse as urlparse  # py3

_DEFAULT_CA_CERTS = os.path.dirname(__file__) + '/ca-certificates.crt'


class SimpleAsyncHTTPClient(AsyncHTTPClient):
    """Non-blocking HTTP client with no external dependencies.

    This class implements an HTTP 1.1 client on top of Tornado's IOStreams.
    It does not currently implement all applicable parts of the HTTP
    specification, but it does enough to work with major web service APIs.

    Some features found in the curl-based AsyncHTTPClient are not yet
    supported.  In particular, proxies are not supported, connections
    are not reused, and callers cannot select the network interface to be
    used.
    """
    def initialize(self, io_loop, max_clients=10,
                   hostname_mapping=None, max_buffer_size=104857600,
                   resolver=None, defaults=None):
        """Creates a AsyncHTTPClient.

        Only a single AsyncHTTPClient instance exists per IOLoop
        in order to provide limitations on the number of pending connections.
        force_instance=True may be used to suppress this behavior.

        max_clients is the number of concurrent requests that can be
        in progress.  Note that this arguments are only used when the
        client is first created, and will be ignored when an existing
        client is reused.

        hostname_mapping is a dictionary mapping hostnames to IP addresses.
        It can be used to make local DNS changes when modifying system-wide
        settings like /etc/hosts is not possible or desirable (e.g. in
        unittests).

        max_buffer_size is the number of bytes that can be read by IOStream. It
        defaults to 100mb.
        """
        super(SimpleAsyncHTTPClient, self).initialize(io_loop,
                                                      defaults=defaults)
        self.max_clients = max_clients
        self.queue = collections.deque()
        self.active = {}
        self.waiting = {}
        self.max_buffer_size = max_buffer_size
        if resolver:
            self.resolver = resolver
            self.own_resolver = False
        else:
            self.resolver = Resolver(io_loop=io_loop)
            self.own_resolver = True
        if hostname_mapping is not None:
            self.resolver = OverrideResolver(resolver=self.resolver,
                                             mapping=hostname_mapping)

    def close(self):
        super(SimpleAsyncHTTPClient, self).close()
        if self.own_resolver:
            self.resolver.close()

    def fetch_impl(self, request, callback):
        key = object()
        self.queue.append((key, request, callback))
        if not len(self.active) < self.max_clients:
            timeout_handle = self.io_loop.add_timeout(
                    self.io_loop.time() + min(request.connect_timeout,
                                              request.request_timeout),
                    functools.partial(self._on_timeout, key))
        else:
            timeout_handle = None
        self.waiting[key] = (request, callback, timeout_handle)
        self._process_queue()
        if self.queue:
            gen_log.debug("max_clients limit reached, request queued. "
                          "%d active, %d queued requests." % (
                              len(self.active), len(self.queue)))

    def _process_queue(self):
        with stack_context.NullContext():
            while self.queue and len(self.active) < self.max_clients:
                key, request, callback = self.queue.popleft()
                if key not in self.waiting:
                    continue
                self._remove_timeout(key)
                self.active[key] = (request, callback)
                release_callback = functools.partial(self._release_fetch, key)
                self._handle_request(request, release_callback, callback)

    def _handle_request(self, request, release_callback, final_callback):
        _HTTPConnection(self.io_loop, self, request, release_callback,
                        final_callback, self.max_buffer_size, self.resolver)

    def _release_fetch(self, key):
        del self.active[key]
        self._process_queue()

    def _remove_timeout(self, key):
        if key in self.waiting:
            request, callback, timeout_handle = self.waiting[key]
            if timeout_handle is not None:
                self.io_loop.remove_timeout(timeout_handle)
            del self.waiting[key]

    def _on_timeout(self, key):
        request, callback, timeout_handle = self.waiting[key]
        self.queue.remove((key, request, callback))
        timeout_response = HTTPResponse(
                request, 599, error=HTTPError(599, "Timeout"),
                request_time=self.io_loop.time() - request.start_time)
        self.io_loop.add_callback(callback, timeout_response)
        del self.waiting[key]


class _HTTPConnection(object):
    _SUPPORTED_METHODS = set(["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"])

    def __init__(self, io_loop, client, request, release_callback,
                 final_callback, max_buffer_size, resolver):
        self.start_time = io_loop.time()
        self.io_loop = io_loop
        self.client = client
        self.request = request
        self.release_callback = release_callback
        self.final_callback = final_callback
        self.max_buffer_size = max_buffer_size
        self.resolver = resolver
        self.code = None
        self.headers = None
        self.chunks = None
        self._decompressor = None
        # Timeout handle returned by IOLoop.add_timeout
        self._timeout = None
        with stack_context.ExceptionStackContext(self._handle_exception):
            self.parsed = urlparse.urlsplit(_unicode(self.request.url))
            if self.parsed.scheme not in ("http", "https"):
                raise ValueError("Unsupported url scheme: %s" %
                                 self.request.url)
            # urlsplit results have hostname and port results, but they
            # didn't support ipv6 literals until python 2.7.
            netloc = self.parsed.netloc
            if "@" in netloc:
                userpass, _, netloc = netloc.rpartition("@")
            match = re.match(r'^(.+):(\d+)$', netloc)
            if match:
                host = match.group(1)
                port = int(match.group(2))
            else:
                host = netloc
                port = 443 if self.parsed.scheme == "https" else 80
            if re.match(r'^\[.*\]$', host):
                # raw ipv6 addresses in urls are enclosed in brackets
                host = host[1:-1]
            self.parsed_hostname = host  # save final host for _on_connect

            if request.allow_ipv6:
                af = socket.AF_UNSPEC
            else:
                # We only try the first IP we get from getaddrinfo,
                # so restrict to ipv4 by default.
                af = socket.AF_INET

            timeout = min(self.request.connect_timeout, self.request.request_timeout)
            if timeout:
                self._timeout = self.io_loop.add_timeout(
                    self.start_time + timeout,
                    stack_context.wrap(self._on_timeout))
            self.resolver.resolve(host, port, af, callback=self._on_resolve)

    def _on_resolve(self, addrinfo):
        if self.final_callback is None:
            # final_callback is cleared if we've hit our timeout
            return
        self.stream = self._create_stream(addrinfo)
        self.stream.set_close_callback(self._on_close)
        # ipv6 addresses are broken (in self.parsed.hostname) until
        # 2.7, here is correctly parsed value calculated in __init__
        sockaddr = addrinfo[0][1]
        self.stream.connect(sockaddr, self._on_connect,
                            server_hostname=self.parsed_hostname)

    def _create_stream(self, addrinfo):
        af = addrinfo[0][0]
        if self.parsed.scheme == "https":
            ssl_options = {}
            if self.request.validate_cert:
                ssl_options["cert_reqs"] = ssl.CERT_REQUIRED
            if self.request.ca_certs is not None:
                ssl_options["ca_certs"] = self.request.ca_certs
            else:
                ssl_options["ca_certs"] = _DEFAULT_CA_CERTS
            if self.request.client_key is not None:
                ssl_options["keyfile"] = self.request.client_key
            if self.request.client_cert is not None:
                ssl_options["certfile"] = self.request.client_cert

            # SSL interoperability is tricky.  We want to disable
            # SSLv2 for security reasons; it wasn't disabled by default
            # until openssl 1.0.  The best way to do this is to use
            # the SSL_OP_NO_SSLv2, but that wasn't exposed to python
            # until 3.2.  Python 2.7 adds the ciphers argument, which
            # can also be used to disable SSLv2.  As a last resort
            # on python 2.6, we set ssl_version to TLSv1.  This is
            # more narrow than we'd like since it also breaks
            # compatibility with servers configured for SSLv3 only,
            # but nearly all servers support both SSLv3 and TLSv1:
            # http://blog.ivanristic.com/2011/09/ssl-survey-protocol-support.html
            if sys.version_info >= (2, 7):
                ssl_options["ciphers"] = "DEFAULT:!SSLv2"
            else:
                # This is really only necessary for pre-1.0 versions
                # of openssl, but python 2.6 doesn't expose version
                # information.
                ssl_options["ssl_version"] = ssl.PROTOCOL_TLSv1

            return SSLIOStream(socket.socket(af),
                               io_loop=self.io_loop,
                               ssl_options=ssl_options,
                               max_buffer_size=self.max_buffer_size)
        else:
            return IOStream(socket.socket(af),
                            io_loop=self.io_loop,
                            max_buffer_size=self.max_buffer_size)

    def _on_timeout(self):
        self._timeout = None
        if self.final_callback is not None:
            raise HTTPError(599, "Timeout")

    def _remove_timeout(self):
        if self._timeout is not None:
            self.io_loop.remove_timeout(self._timeout)
            self._timeout = None

    def _on_connect(self):
        self._remove_timeout()
        if self.final_callback is None:
            return
        if self.request.request_timeout:
            self._timeout = self.io_loop.add_timeout(
                self.start_time + self.request.request_timeout,
                stack_context.wrap(self._on_timeout))
        if (self.request.method not in self._SUPPORTED_METHODS and
                not self.request.allow_nonstandard_methods):
            raise KeyError("unknown method %s" % self.request.method)
        for key in ('network_interface',
                    'proxy_host', 'proxy_port',
                    'proxy_username', 'proxy_password'):
            if getattr(self.request, key, None):
                raise NotImplementedError('%s not supported' % key)
        if "Connection" not in self.request.headers:
            self.request.headers["Connection"] = "close"
        if "Host" not in self.request.headers:
            if '@' in self.parsed.netloc:
                self.request.headers["Host"] = self.parsed.netloc.rpartition('@')[-1]
            else:
                self.request.headers["Host"] = self.parsed.netloc
        username, password = None, None
        if self.parsed.username is not None:
            username, password = self.parsed.username, self.parsed.password
        elif self.request.auth_username is not None:
            username = self.request.auth_username
            password = self.request.auth_password or ''
        if username is not None:
            if self.request.auth_mode not in (None, "basic"):
                raise ValueError("unsupported auth_mode %s",
                                 self.request.auth_mode)
            auth = utf8(username) + b":" + utf8(password)
            self.request.headers["Authorization"] = (b"Basic " +
                                                     base64.b64encode(auth))
        if self.request.user_agent:
            self.request.headers["User-Agent"] = self.request.user_agent
        if not self.request.allow_nonstandard_methods:
            if self.request.method in ("POST", "PATCH", "PUT"):
                assert self.request.body is not None
            else:
                assert self.request.body is None
        if self.request.body is not None:
            self.request.headers["Content-Length"] = str(len(
                self.request.body))
        if (self.request.method == "POST" and
                "Content-Type" not in self.request.headers):
            self.request.headers["Content-Type"] = "application/x-www-form-urlencoded"
        if self.request.use_gzip:
            self.request.headers["Accept-Encoding"] = "gzip"
        req_path = ((self.parsed.path or '/') +
                   (('?' + self.parsed.query) if self.parsed.query else ''))
        request_lines = [utf8("%s %s HTTP/1.1" % (self.request.method,
                                                  req_path))]
        for k, v in self.request.headers.get_all():
            line = utf8(k) + b": " + utf8(v)
            if b'\n' in line:
                raise ValueError('Newline in header: ' + repr(line))
            request_lines.append(line)
        request_str = b"\r\n".join(request_lines) + b"\r\n\r\n"
        if self.request.body is not None:
            request_str += self.request.body
        self.stream.set_nodelay(True)
        self.stream.write(request_str)
        self.stream.read_until_regex(b"\r?\n\r?\n", self._on_headers)

    def _release(self):
        if self.release_callback is not None:
            release_callback = self.release_callback
            self.release_callback = None
            release_callback()

    def _run_callback(self, response):
        self._release()
        if self.final_callback is not None:
            final_callback = self.final_callback
            self.final_callback = None
            self.io_loop.add_callback(final_callback, response)

    def _handle_exception(self, typ, value, tb):
        if self.final_callback:
            self._remove_timeout()
            self._run_callback(HTTPResponse(self.request, 599, error=value,
                                            request_time=self.io_loop.time() - self.start_time,
                                            ))

            if hasattr(self, "stream"):
                self.stream.close()
            return True
        else:
            # If our callback has already been called, we are probably
            # catching an exception that is not caused by us but rather
            # some child of our callback. Rather than drop it on the floor,
            # pass it along.
            return False

    def _on_close(self):
        if self.final_callback is not None:
            message = "Connection closed"
            if self.stream.error:
                message = str(self.stream.error)
            raise HTTPError(599, message)

    def _handle_1xx(self, code):
        self.stream.read_until_regex(b"\r?\n\r?\n", self._on_headers)

    def _on_headers(self, data):
        data = native_str(data.decode("latin1"))
        first_line, _, header_data = data.partition("\n")
        match = re.match("HTTP/1.[01] ([0-9]+) ([^\r]*)", first_line)
        assert match
        code = int(match.group(1))
        self.headers = HTTPHeaders.parse(header_data)
        if 100 <= code < 200:
            self._handle_1xx(code)
            return
        else:
            self.code = code
            self.reason = match.group(2)

        if "Content-Length" in self.headers:
            if "," in self.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*', self.headers["Content-Length"])
                if any(i != pieces[0] for i in pieces):
                    raise ValueError("Multiple unequal Content-Lengths: %r" %
                                     self.headers["Content-Length"])
                self.headers["Content-Length"] = pieces[0]
            content_length = int(self.headers["Content-Length"])
        else:
            content_length = None

        if self.request.header_callback is not None:
            # re-attach the newline we split on earlier
            self.request.header_callback(first_line + _)
            for k, v in self.headers.get_all():
                self.request.header_callback("%s: %s\r\n" % (k, v))
            self.request.header_callback('\r\n')

        if self.request.method == "HEAD" or self.code == 304:
            # HEAD requests and 304 responses never have content, even
            # though they may have content-length headers
            self._on_body(b"")
            return
        if 100 <= self.code < 200 or self.code == 204:
            # These response codes never have bodies
            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
            if ("Transfer-Encoding" in self.headers or
                    content_length not in (None, 0)):
                raise ValueError("Response with code %d should not have body" %
                                 self.code)
            self._on_body(b"")
            return

        if (self.request.use_gzip and
                self.headers.get("Content-Encoding") == "gzip"):
            self._decompressor = GzipDecompressor()
        if self.headers.get("Transfer-Encoding") == "chunked":
            self.chunks = []
            self.stream.read_until(b"\r\n", self._on_chunk_length)
        elif content_length is not None:
            self.stream.read_bytes(content_length, self._on_body)
        else:
            self.stream.read_until_close(self._on_body)

    def _on_body(self, data):
        self._remove_timeout()
        original_request = getattr(self.request, "original_request",
                                   self.request)
        if (self.request.follow_redirects and
            self.request.max_redirects > 0 and
                self.code in (301, 302, 303, 307)):
            assert isinstance(self.request, _RequestProxy)
            new_request = copy.copy(self.request.request)
            new_request.url = urlparse.urljoin(self.request.url,
                                               self.headers["Location"])
            new_request.max_redirects = self.request.max_redirects - 1
            del new_request.headers["Host"]
            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
            # Client SHOULD make a GET request after a 303.
            # According to the spec, 302 should be followed by the same
            # method as the original request, but in practice browsers
            # treat 302 the same as 303, and many servers use 302 for
            # compatibility with pre-HTTP/1.1 user agents which don't
            # understand the 303 status.
            if self.code in (302, 303):
                new_request.method = "GET"
                new_request.body = None
                for h in ["Content-Length", "Content-Type",
                          "Content-Encoding", "Transfer-Encoding"]:
                    try:
                        del self.request.headers[h]
                    except KeyError:
                        pass
            new_request.original_request = original_request
            final_callback = self.final_callback
            self.final_callback = None
            self._release()
            self.client.fetch(new_request, final_callback)
            self._on_end_request()
            return
        if self._decompressor:
            data = (self._decompressor.decompress(data) +
                    self._decompressor.flush())
        if self.request.streaming_callback:
            if self.chunks is None:
                # if chunks is not None, we already called streaming_callback
                # in _on_chunk_data
                self.request.streaming_callback(data)
            buffer = BytesIO()
        else:
            buffer = BytesIO(data)  # TODO: don't require one big string?
        response = HTTPResponse(original_request,
                                self.code, reason=self.reason,
                                headers=self.headers,
                                request_time=self.io_loop.time() - self.start_time,
                                buffer=buffer,
                                effective_url=self.request.url)
        self._run_callback(response)
        self._on_end_request()

    def _on_end_request(self):
        self.stream.close()

    def _on_chunk_length(self, data):
        # TODO: "chunk extensions" http://tools.ietf.org/html/rfc2616#section-3.6.1
        length = int(data.strip(), 16)
        if length == 0:
            if self._decompressor is not None:
                tail = self._decompressor.flush()
                if tail:
                    # I believe the tail will always be empty (i.e.
                    # decompress will return all it can).  The purpose
                    # of the flush call is to detect errors such
                    # as truncated input.  But in case it ever returns
                    # anything, treat it as an extra chunk
                    if self.request.streaming_callback is not None:
                        self.request.streaming_callback(tail)
                    else:
                        self.chunks.append(tail)
                # all the data has been decompressed, so we don't need to
                # decompress again in _on_body
                self._decompressor = None
            self._on_body(b''.join(self.chunks))
        else:
            self.stream.read_bytes(length + 2,  # chunk ends with \r\n
                                   self._on_chunk_data)

    def _on_chunk_data(self, data):
        assert data[-2:] == b"\r\n"
        chunk = data[:-2]
        if self._decompressor:
            chunk = self._decompressor.decompress(chunk)
        if self.request.streaming_callback is not None:
            self.request.streaming_callback(chunk)
        else:
            self.chunks.append(chunk)
        self.stream.read_until(b"\r\n", self._on_chunk_length)


if __name__ == "__main__":
    AsyncHTTPClient.configure(SimpleAsyncHTTPClient)
    main()

```
--- 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