Make an asynchronous HTTP request.

        Args:
            method (str): HTTP method to use for the request.
            url (str): URL to be requested.
            params (dict): (optional) Query parameters for the request.
                Defaults to ``None``.
            headers (dict): (optional) HTTP headers to send with the
                request. Headers pass through to the request will
                include :attr:`DEFAULT_REQUEST_HEADERS`.
            data (obj): (optional) A dictionary, bytes, or file-like
                object to send in the body of the request.
            json (obj): (optional) Any json compatible python
                object.
                NOTE: json and body parameters cannot be used at the same time.
            token_refresh_attempts (int): (optional) Number of attempts a token
                refresh should be performed.
        Returns:
            (str) HTTP response body.
        Raises:
            :exc:`.GCPHTTPError`: if any exception occurred,
                specifically a :exc:`.GCPHTTPResponseError`, if the
                exception is associated with a response status code.

async def request(self, method, url, params=None, headers=None,
                      data=None, json=None, token_refresh_attempts=2,
                      **kwargs):
        """Make an asynchronous HTTP request.

        Args:
            method (str): HTTP method to use for the request.
            url (str): URL to be requested.
            params (dict): (optional) Query parameters for the request.
                Defaults to ``None``.
            headers (dict): (optional) HTTP headers to send with the
                request. Headers pass through to the request will
                include :attr:`DEFAULT_REQUEST_HEADERS`.
            data (obj): (optional) A dictionary, bytes, or file-like
                object to send in the body of the request.
            json (obj): (optional) Any json compatible python
                object.
                NOTE: json and body parameters cannot be used at the same time.
            token_refresh_attempts (int): (optional) Number of attempts a token
                refresh should be performed.
        Returns:
            (str) HTTP response body.
        Raises:
            :exc:`.GCPHTTPError`: if any exception occurred,
                specifically a :exc:`.GCPHTTPResponseError`, if the
                exception is associated with a response status code.

        """
        if all([data, json]):
            msg = ('"data" and "json" request parameters can not be used '
                   'at the same time')
            logging.warn(msg)
            raise exceptions.GCPHTTPError(msg)

        req_headers = headers or {}
        req_headers.update(_utils.DEFAULT_REQUEST_HEADERS)
        req_kwargs = {
                'params': params,
                'headers': req_headers,
            }

        if data:
            req_kwargs['data'] = data
        if json:
            req_kwargs['json'] = json

        if token_refresh_attempts:
            if not await self.valid_token_set():
                await self._auth_client.refresh_token()
                token_refresh_attempts -= 1

        req_headers.update(
            {'Authorization': f'Bearer {self._auth_client.token}'}
        )

        request_id = kwargs.get('request_id', uuid.uuid4())
        logging.debug(_utils.REQ_LOG_FMT.format(
            request_id=request_id,
            method=method.upper(),
            url=url,
            kwargs=req_kwargs))
        try:
            async with self._session.request(method, url, **req_kwargs) as resp:
                log_kw = {
                    'request_id': request_id,
                    'method': method.upper(),
                    'url': resp.url,
                    'status': resp.status,
                    'reason': resp.reason
                }
                logging.debug(_utils.RESP_LOG_FMT.format(**log_kw))

                if resp.status in REFRESH_STATUS_CODES:
                    logging.warning(
                        f'[{request_id}] HTTP Status Code {resp.status}'
                        f' returned requesting {resp.url}: {resp.reason}')
                    if token_refresh_attempts:
                        logging.info(
                            f'[{request_id}] Attempting request to {resp.url} '
                            'again.')
                        return await self.request(
                            method, url,
                            token_refresh_attempts=token_refresh_attempts,
                            request_id=request_id,
                            **req_kwargs)

                    logging.warning(
                        f'[{request_id}] Max attempts refreshing auth token '
                        f'exhausted while requesting {resp.url}')

                resp.raise_for_status()

                return await resp.text()
        except aiohttp.ClientResponseError as e:
            # bad HTTP status; avoid leaky abstractions and wrap HTTP errors
            # with our own
            msg = f'[{request_id}] HTTP error response from {resp.url}: {e}'
            logging.error(msg, exc_info=e)
            raise exceptions.GCPHTTPResponseError(msg, resp.status)
        except exceptions.GCPHTTPResponseError as e:
            # from recursive call
            raise e
        except Exception as e:
            msg = f'[{request_id}] Request call failed: {e}'
            logging.error(msg, exc_info=e)
            raise exceptions.GCPHTTPError(msg)