async def http_fetch(self, url,
                         method='GET',
                         request_headers=None,
                         body=None,
                         content_type=CONTENT_TYPE_MSGPACK,
                         follow_redirects=False,
                         max_redirects=MAX_REDIRECTS,
                         connect_timeout=DEFAULT_CONNECT_TIMEOUT,
                         request_timeout=DEFAULT_REQUEST_TIMEOUT,
                         max_http_attempts=MAX_HTTP_RETRIES,
                         auth_username=None,
                         auth_password=None,
                         user_agent=None,
                         validate_cert=True,
                         allow_nonstandard_methods=False,
                         dont_retry=None):
        """Perform a HTTP request

        Will retry up to ``self.MAX_HTTP_RETRIES`` times.

        :param str url: The URL for the request
        :param str method: The HTTP request method, defaults to ``GET``
        :param dict request_headers: Headers to include in the HTTP request
        :param mixed body: The HTTP request body to send with the request
        :param content_type: The mime type to use for requests & responses.
            Defaults to ``application/msgpack``
        :type content_type: :py:class:`~ietfparse.datastructures.ContentType`
            or str
        :param bool follow_redirects: Follow HTTP redirects when received
        :param int max_redirects: Maximum number of redirects to follow,
            default is 5
        :param float connect_timeout: Timeout for initial connection in
            seconds, default 20 seconds
        :param float request_timeout:  Timeout for entire request in seconds,
            default 20 seconds
        :param int max_http_attempts: Maximum number of times to retry
            a request, default is 3 attempts
        :param str auth_username: Username for HTTP authentication
        :param str auth_password: Password for HTTP authentication
        :param str user_agent: The str used for the ``User-Agent`` header,
            default used if unspecified.
        :param bool validate_cert: For HTTPS requests, validate the server's
            certificate? Default is True
        :param bool allow_nonstandard_methods: Allow methods that don't adhere
            to the HTTP spec.
        :param set dont_retry: A list of status codes that will not be retried
            if an error is returned. Default: set({})
        :rtype: HTTPResponse

        """
        response = HTTPResponse()

        request_headers = self._http_req_apply_default_headers(
            request_headers, content_type, body)

        if body:
            body = self._http_req_body_serialize(
                body, request_headers['Content-Type'])

        if not dont_retry:
            dont_retry = set({})

        client = httpclient.AsyncHTTPClient()

        # Workaround for Tornado defect.
        if hasattr(client, 'max_clients') and os.getenv('HTTP_MAX_CLIENTS'):
            client.max_clients = int(os.getenv('HTTP_MAX_CLIENTS'))

        for attempt in range(0, max_http_attempts):
            LOGGER.debug('%s %s (Attempt %i of %i) %r',
                         method, url, attempt + 1, max_http_attempts,
                         request_headers)
            if attempt > 0:
                request_headers['X-Retry-Attempt'] = str(attempt + 1)
            try:
                resp = await client.fetch(
                    url,
                    method=method,
                    headers=request_headers,
                    body=body,
                    auth_username=auth_username,
                    auth_password=auth_password,
                    connect_timeout=connect_timeout,
                    request_timeout=request_timeout,
                    user_agent=user_agent or self._http_req_user_agent(),
                    follow_redirects=follow_redirects,
                    max_redirects=max_redirects,
                    raise_error=False,
                    validate_cert=validate_cert,
                    allow_nonstandard_methods=allow_nonstandard_methods)
            except (ConnectionError,
                    CurlError,
                    OSError,
                    socket.gaierror) as error:
                response.append_exception(error)
                LOGGER.warning(
                    'HTTP Request Error for %s to %s attempt %i of %i: %s',
                    method, url, attempt + 1, max_http_attempts, error)
                continue

            # Keep track of each response
            response.append_response(resp)

            # If the response is ok, finish and exit
            if response.ok:
                response.finish()
                return response
            elif resp.code in dont_retry:
                break
            elif resp.code in {423, 429}:
                await self._http_resp_rate_limited(resp)
            elif resp.code < 500:
                LOGGER.debug('HTTP Response Error for %s to %s'
                             'attempt %i of %i (%s): %s',
                             method, url, resp.code, attempt + 1,
                             max_http_attempts, response.body)
                response.finish()
                return response

            LOGGER.warning(
                'HTTP Error for %s to %s, attempt %i of %i (%s): %s',
                method, url, attempt + 1, max_http_attempts, resp.code,
                response.body)

        LOGGER.warning('HTTP %s to %s failed after %i attempts', method, url,
                       max_http_attempts)
        response.finish()
        return response
Perform a HTTP request

        Will retry up to ``self.MAX_HTTP_RETRIES`` times.

        :param str url: The URL for the request
        :param str method: The HTTP request method, defaults to ``GET``
        :param dict request_headers: Headers to include in the HTTP request
        :param mixed body: The HTTP request body to send with the request
        :param content_type: The mime type to use for requests & responses.
            Defaults to ``application/msgpack``
        :type content_type: :py:class:`~ietfparse.datastructures.ContentType`
            or str
        :param bool follow_redirects: Follow HTTP redirects when received
        :param int max_redirects: Maximum number of redirects to follow,
            default is 5
        :param float connect_timeout: Timeout for initial connection in
            seconds, default 20 seconds
        :param float request_timeout:  Timeout for entire request in seconds,
            default 20 seconds
        :param int max_http_attempts: Maximum number of times to retry
            a request, default is 3 attempts
        :param str auth_username: Username for HTTP authentication
        :param str auth_password: Password for HTTP authentication
        :param str user_agent: The str used for the ``User-Agent`` header,
            default used if unspecified.
        :param bool validate_cert: For HTTPS requests, validate the server's
            certificate? Default is True
        :param bool allow_nonstandard_methods: Allow methods that don't adhere
            to the HTTP spec.
        :param set dont_retry: A list of status codes that will not be retried
            if an error is returned. Default: set({})
        :rtype: HTTPResponse