public TransportException CreateClientException <TResponse>(
            TResponse response, IApiCallDetails callDetails, RequestData data, List <PipelineException> pipelineExceptions
            )
            where TResponse : class, ITransportResponse, new()
        {
            if (callDetails?.Success ?? false)
            {
                return(null);
            }

            var pipelineFailure = data.OnFailurePipelineFailure;
            var innerException  = callDetails?.OriginalException;

            if (pipelineExceptions.HasAny(out var exs))
            {
                pipelineFailure = exs.Last().FailureReason;
                innerException  = exs.AsAggregateOrFirst();
            }

            var statusCode = callDetails?.HttpStatusCode != null?callDetails.HttpStatusCode.Value.ToString() : "unknown";

            var resource = callDetails == null
                                ? "unknown resource"
                                : $"Status code {statusCode} from: {callDetails.HttpMethod} {callDetails.Uri.PathAndQuery}";

            var exceptionMessage = innerException?.Message ?? "Request failed to execute";

            if (IsTakingTooLong)
            {
                pipelineFailure = PipelineFailure.MaxTimeoutReached;
                Audit(MaxTimeoutReached);
                exceptionMessage = "Maximum timeout reached while retrying request";
            }
            else if (Retried >= MaxRetries && MaxRetries > 0)
            {
                pipelineFailure = PipelineFailure.MaxRetriesReached;
                Audit(MaxRetriesReached);
                exceptionMessage = "Maximum number of retries reached";

                var now         = _dateTimeProvider.Now();
                var activeNodes = _connectionPool.Nodes.Count(n => n.IsAlive || n.DeadUntil <= now);
                if (Retried >= activeNodes)
                {
                    Audit(FailedOverAllNodes);
                    exceptionMessage += ", failed over to all the known alive nodes before failing";
                }
            }

            exceptionMessage += $". Call: {resource}";
            if (response != null && _productRegistration.TryGetServerErrorReason(response, out var reason))
            {
                exceptionMessage += $". ServerError: {reason}";
            }

            var clientException = new TransportException(pipelineFailure, exceptionMessage, innerException)
            {
                Request    = data,
                Response   = callDetails,
                AuditTrail = AuditTrail
            };

            return(clientException);
        }
Example #2
0
 private static void DefaultRequestDataCreated(RequestData response)
 {
 }
        /// <summary>
        /// Registers an APM async task cancellation on the threadpool
        /// </summary>
        /// <returns>An unregister action that can be used to remove the waithandle prematurely</returns>
        private static Action RegisterApmTaskTimeout(IAsyncResult result, WebRequest request, RequestData requestData)
        {
            var waitHandle           = result.AsyncWaitHandle;
            var registeredWaitHandle =
                ThreadPool.RegisterWaitForSingleObject(waitHandle, TimeoutCallback, request, requestData.RequestTimeout, true);

            return(() => registeredWaitHandle?.Unregister(waitHandle));
        }
        public void BadResponse <TResponse>(ref TResponse response, IApiCallDetails callDetails, RequestData data,
                                            TransportException exception
                                            )
            where TResponse : class, ITransportResponse, new()
        {
            if (response == null)
            {
                //make sure we copy over the error body in case we disabled direct streaming.
                var s = callDetails?.ResponseBodyInBytes == null ? Stream.Null : _memoryStreamFactory.Create(callDetails.ResponseBodyInBytes);
                var m = callDetails?.ResponseMimeType ?? RequestData.MimeType;
                response = ResponseBuilder.ToResponse <TResponse>(data, exception, callDetails?.HttpStatusCode, null, s, m);
            }

            response.ApiCall.AuditTrail = AuditTrail;
        }
        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);
        }
        // TODO - make private in 8.0 and only expose SetAuthenticationIfNeeded
        protected virtual bool SetApiKeyAuthenticationIfNeeded(HttpWebRequest request, RequestData requestData)
        {
            // ApiKey auth credentials take the following precedence (highest -> lowest):
            // 1 - Specified on the request (highest precedence)
            // 2 - Specified at the global IConnectionSettings level

            string apiKey = null;

            if (requestData.ApiKeyAuthenticationCredentials != null)
            {
                apiKey = requestData.ApiKeyAuthenticationCredentials.Base64EncodedApiKey.CreateString();
            }

            if (string.IsNullOrWhiteSpace(apiKey))
            {
                return(false);
            }

            request.Headers["Authorization"] = $"ApiKey {apiKey}";
            return(true);
        }
        // TODO - make private in 8.0 and only expose SetAuthenticationIfNeeded
        protected virtual void SetBasicAuthenticationIfNeeded(HttpWebRequest request, RequestData requestData)
        {
            // Basic auth credentials take the following precedence (highest -> lowest):
            // 1 - Specified on the request (highest precedence)
            // 2 - Specified at the global IConnectionSettings level
            // 3 - Specified with the URI (lowest precedence)

            string userInfo = null;

            if (!string.IsNullOrEmpty(requestData.Uri.UserInfo))
            {
                userInfo = Uri.UnescapeDataString(requestData.Uri.UserInfo);
            }
            else if (requestData.BasicAuthorizationCredentials != null)
            {
                userInfo =
                    $"{requestData.BasicAuthorizationCredentials.Username}:{requestData.BasicAuthorizationCredentials.Password.CreateString()}";
            }

            if (string.IsNullOrWhiteSpace(userInfo))
            {
                return;
            }

            request.Headers["Authorization"] = $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes(userInfo))}";
        }
        protected virtual void SetServerCertificateValidationCallBackIfNeeded(HttpWebRequest request, RequestData requestData)
        {
            var callback = requestData?.ConnectionSettings?.ServerCertificateValidationCallback;

#if !__MonoCS__
            //Only assign if one is defined on connection settings and a subclass has not already set one
            if (callback != null && request.ServerCertificateValidationCallback == null)
            {
                request.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(callback);
            }
#else
            if (callback != null)
            {
                throw new Exception("Mono misses ServerCertificateValidationCallback on HttpWebRequest");
            }
                        #endif
        }
        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);
        }