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); }
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); }