public virtual TResponse Request <TResponse>(RequestData requestData)
            where TResponse : class, ITransportResponse, new()
        {
            int?statusCode = null;
            IEnumerable <string> warnings = null;
            Stream    responseStream      = null;
            Exception ex       = null;
            string    mimeType = null;
            ReadOnlyDictionary <TcpState, int> tcpStats = null;
            ReadOnlyDictionary <string, ThreadPoolStatistics> threadPoolStats = null;

            try
            {
                var request = CreateHttpWebRequest(requestData);
                var data    = requestData.PostData;

                if (data != null)
                {
                    using (var stream = request.GetRequestStream())
                    {
                        if (requestData.HttpCompression)
                        {
                            using (var zipStream = new GZipStream(stream, CompressionMode.Compress))
                                data.Write(zipStream, requestData.ConnectionSettings);
                        }
                        else
                        {
                            data.Write(stream, requestData.ConnectionSettings);
                        }
                    }
                }
                requestData.MadeItToResponse = true;

                if (requestData.TcpStats)
                {
                    tcpStats = TcpStats.GetStates();
                }

                if (requestData.ThreadPoolStats)
                {
                    threadPoolStats = ThreadPoolStats.GetStats();
                }

                //http://msdn.microsoft.com/en-us/library/system.net.httpwebresponse.getresponsestream.aspx
                //Either the stream or the response object needs to be closed but not both although it won't
                //throw any errors if both are closed atleast one of them has to be Closed.
                //Since we expose the stream we let closing the stream determining when to close the connection
                var httpWebResponse = (HttpWebResponse)request.GetResponse();
                HandleResponse(httpWebResponse, out statusCode, out responseStream, out mimeType);

                //response.Headers.HasKeys() can return false even if response.Headers.AllKeys has values.
                if (httpWebResponse.SupportsHeaders && httpWebResponse.Headers.Count > 0 && httpWebResponse.Headers.AllKeys.Contains("Warning"))
                {
                    warnings = httpWebResponse.Headers.GetValues("Warning");
                }
            }
            catch (WebException e)
            {
                ex = e;
                if (e.Response is HttpWebResponse httpWebResponse)
                {
                    HandleResponse(httpWebResponse, out statusCode, out responseStream, out mimeType);
                }
            }

            responseStream ??= Stream.Null;
            var response = ResponseBuilder.ToResponse <TResponse>(requestData, ex, statusCode, warnings, responseStream, mimeType);

            // set TCP and threadpool stats on the response here so that in the event the request fails after the point of
            // gathering stats, they are still exposed on the call details. Ideally these would be set inside ResponseBuilder.ToResponse,
            // but doing so would be a breaking change in 7.x
            response.ApiCall.TcpStats        = tcpStats;
            response.ApiCall.ThreadPoolStats = threadPoolStats;
            return(response);
        }
        public virtual async Task <TResponse> RequestAsync <TResponse>(RequestData requestData,
                                                                       CancellationToken cancellationToken
                                                                       )
            where TResponse : class, ITransportResponse, new()
        {
            Action unregisterWaitHandle   = null;
            int?   statusCode             = null;
            IEnumerable <string> warnings = null;
            Stream    responseStream      = null;
            Exception ex       = null;
            string    mimeType = null;
            ReadOnlyDictionary <TcpState, int> tcpStats = null;
            ReadOnlyDictionary <string, ThreadPoolStatistics> threadPoolStats = null;

            try
            {
                var data    = requestData.PostData;
                var request = CreateHttpWebRequest(requestData);
                using (cancellationToken.Register(() => request.Abort()))
                {
                    if (data != null)
                    {
                        var apmGetRequestStreamTask =
                            Task.Factory.FromAsync(request.BeginGetRequestStream, r => request.EndGetRequestStream(r), null);
                        unregisterWaitHandle = RegisterApmTaskTimeout(apmGetRequestStreamTask, request, requestData);

                        using (var stream = await apmGetRequestStreamTask.ConfigureAwait(false))
                        {
                            if (requestData.HttpCompression)
                            {
                                using (var zipStream = new GZipStream(stream, CompressionMode.Compress))
                                    await data.WriteAsync(zipStream, requestData.ConnectionSettings, cancellationToken).ConfigureAwait(false);
                            }
                            else
                            {
                                await data.WriteAsync(stream, requestData.ConnectionSettings, cancellationToken).ConfigureAwait(false);
                            }
                        }
                        unregisterWaitHandle?.Invoke();
                    }
                    requestData.MadeItToResponse = true;
                    //http://msdn.microsoft.com/en-us/library/system.net.httpwebresponse.getresponsestream.aspx
                    //Either the stream or the response object needs to be closed but not both although it won't
                    //throw any errors if both are closed atleast one of them has to be Closed.
                    //Since we expose the stream we let closing the stream determining when to close the connection

                    var apmGetResponseTask = Task.Factory.FromAsync(request.BeginGetResponse, r => request.EndGetResponse(r), null);
                    unregisterWaitHandle = RegisterApmTaskTimeout(apmGetResponseTask, request, requestData);

                    if (requestData.TcpStats)
                    {
                        tcpStats = TcpStats.GetStates();
                    }

                    if (requestData.ThreadPoolStats)
                    {
                        threadPoolStats = ThreadPoolStats.GetStats();
                    }

                    var httpWebResponse = (HttpWebResponse)await apmGetResponseTask.ConfigureAwait(false);

                    HandleResponse(httpWebResponse, out statusCode, out responseStream, out mimeType);
                    if (httpWebResponse.SupportsHeaders && httpWebResponse.Headers.HasKeys() && httpWebResponse.Headers.AllKeys.Contains("Warning"))
                    {
                        warnings = httpWebResponse.Headers.GetValues("Warning");
                    }
                }
            }
            catch (WebException e)
            {
                ex = e;
                if (e.Response is HttpWebResponse httpWebResponse)
                {
                    HandleResponse(httpWebResponse, out statusCode, out responseStream, out mimeType);
                }
            }
            finally
            {
                unregisterWaitHandle?.Invoke();
            }
            responseStream ??= Stream.Null;
            var response = await ResponseBuilder.ToResponseAsync <TResponse>
                               (requestData, ex, statusCode, warnings, responseStream, mimeType, cancellationToken)
                           .ConfigureAwait(false);

            // set TCP and threadpool stats on the response here so that in the event the request fails after the point of
            // gathering stats, they are still exposed on the call details. Ideally these would be set inside ResponseBuilder.ToResponse,
            // but doing so would be a breaking change in 7.x
            response.ApiCall.TcpStats        = tcpStats;
            response.ApiCall.ThreadPoolStats = threadPoolStats;
            return(response);
        }