static Task<HttpResponseMessage> HandleTransientErrors(Task<HttpResponseMessage> request, object state) { var executor = (RetryStrategyExecutor)state; if (!request.IsFaulted) { var response = request.Result; if (executor.CanContinue && Misc.IsTransientError(response)) { Log.To.Sync.V(Tag, "Retrying after transient error..."); return executor.Retry(); } if (!response.IsSuccessStatusCode) { Log.To.Sync.V(Tag, "Non transient error received ({0}), throwing HttpResponseException", response.StatusCode); var exception = new HttpResponseException(response.StatusCode); if(response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.ProxyAuthenticationRequired) { var responseChallenge = response.Headers.WwwAuthenticate; foreach(var header in responseChallenge) { var challenge = AuthUtils.ParseAuthHeader(header); if(challenge != null) { exception.Data["AuthChallenge"] = challenge; } } } throw exception; } // If it's not faulted, there's nothing here to do. return request; } string statusCode; if (!Misc.IsTransientNetworkError(request.Exception, out statusCode) || !executor.CanContinue) { if (!executor.CanContinue) { Log.To.Sync.V(Tag, "Out of retries for error, throwing", request.Exception); } else { Log.To.Sync.V(Tag, "Non transient error received (status), throwing", request.Exception); } // If it's not transient, pass the exception along // for any other handlers to respond to. throw request.Exception; } // Retry again. Log.To.Sync.V(Tag, "Retrying after transient error..."); return executor.Retry(); }
public void TranslateHttpResponseException_HiddenInAggregateException_UnpackTheMostSevere() { // Arrange HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri("http://localhost"), Method = HttpMethod.Get }; var ex500 = new HttpResponseException(HttpStatusCode.InternalServerError); var ex503 = new HttpResponseException(HttpStatusCode.ServiceUnavailable); var ex401 = new HttpResponseException(HttpStatusCode.Unauthorized); var ex503IsWithinMe = new Exception("I have 503 inside me", ex503); var aggregate = new AggregateException(ex500, ex503IsWithinMe, ex401); HttpError httpError = new HttpError(aggregate, includeErrorDetail: true); HttpResponseMessage errorResponse = request.CreateResponse(HttpStatusCode.ServiceUnavailable); errorResponse.Content = new ObjectContent<HttpError>(httpError, new JsonMediaTypeFormatter()); TraceRecord traceRecord = new TraceRecord(request, "System.Web.Http.Request", TraceLevel.Error) { Exception = new HttpResponseException(errorResponse) }; // Act new SystemDiagnosticsTraceWriter().TranslateHttpResponseException(traceRecord); // Assert Assert.Equal(TraceLevel.Error, traceRecord.Level); Assert.Equal(HttpStatusCode.ServiceUnavailable, traceRecord.Status); }
public void Trace_Traces_Warning_EventType_When_Translates_HttpResponseException_Error() { // Arrange SystemDiagnosticsTraceWriter writer = CreateTraceWriter(); writer.IsVerbose = true; HttpRequestMessage request = new HttpRequestMessage { RequestUri = new Uri("http://localhost"), Method = HttpMethod.Get }; HttpResponseMessage response = request.CreateErrorResponse(HttpStatusCode.BadRequest, "bad request"); HttpResponseException responseException = new HttpResponseException(response); // Act writer.Error(request, "TestCategory", responseException); // Assert Assert.Equal(TraceEventType.Warning, ((TestTraceListener)writer.TraceSource.Listeners[0]).TraceEventType); }
internal void SendAsyncMultipartRequest(HttpMethod method, string relativePath, MultipartContent multiPartEntity, RemoteRequestCompletionBlock completionHandler) { Uri url = null; try { url = _baseUrl.Append(relativePath); } catch(UriFormatException) { Log.To.Sync.E(Tag, "Invalid path received for request: {0}, throwing...", new SecureLogString(relativePath, LogMessageSensitivity.PotentiallyInsecure)); throw new ArgumentException("Invalid path", "relativePath"); } var message = new HttpRequestMessage(method, url); message.Content = multiPartEntity; message.Headers.Add("Accept", "*/*"); var client = default(CouchbaseLiteHttpClient); if(!_client.AcquireFor(TimeSpan.FromSeconds(1), out client)) { Log.To.Sync.I(Tag, "Client is disposed, aborting request to {0}", new SecureLogString(relativePath, LogMessageSensitivity.PotentiallyInsecure)); return; } var _lastError = default(Exception); client.Authenticator = Authenticator; var t = client.SendAsync(message, _cancellationTokenSource.Token).ContinueWith(response => { multiPartEntity.Dispose(); if(response.Status != TaskStatus.RanToCompletion) { _lastError = response.Exception; Log.To.Sync.W(Tag, "SendAsyncRequest did not run to completion, returning null..."); return Task.FromResult((Stream)null); } if((int)response.Result.StatusCode > 300) { _lastError = new HttpResponseException(response.Result.StatusCode); Log.To.Sync.W(Tag, "Server returned HTTP Error, returning null..."); return Task.FromResult((Stream)null); } return response.Result.Content.ReadAsStreamAsync(); }, _cancellationTokenSource.Token).ContinueWith(response => { try { var hasEmptyResult = response.Result == null || response.Result.Result == null || response.Result.Result.Length == 0; if(response.Status != TaskStatus.RanToCompletion) { Log.To.Sync.W(Tag, "SendAsyncRequest phase two did not run to completion, continuing..."); } else if(hasEmptyResult) { Log.To.Sync.W(Tag, "Server returned an empty response, continuing..."); } if(completionHandler != null) { object fullBody = null; if(!hasEmptyResult) { var mapper = Manager.GetObjectMapper(); fullBody = mapper.ReadValue<Object>(response.Result.Result); } completionHandler(fullBody, response.Exception ?? _lastError); } } finally { Task dummy; _requests.TryRemove(message, out dummy); } }, _cancellationTokenSource.Token); _requests.TryAdd(message, t); }
internal void SendAsyncMultipartDownloaderRequest(HttpMethod method, string relativePath, object body, Database db, RemoteRequestCompletionBlock onCompletion) { try { var url = _baseUrl.Append(relativePath); var message = new HttpRequestMessage(method, url); message.Headers.Add("Accept", "*/*"); AddRequestHeaders(message); var client = default(CouchbaseLiteHttpClient); if(!_client.AcquireFor(TimeSpan.FromSeconds(1), out client)) { Log.To.Sync.I(Tag, "Client is disposed, aborting request to {0}", new SecureLogString(relativePath, LogMessageSensitivity.PotentiallyInsecure)); return; } client.Authenticator = Authenticator; var request = client.SendAsync(message, _cancellationTokenSource.Token).ContinueWith(new Action<Task<HttpResponseMessage>>(responseMessage => { object fullBody = null; Exception error = null; try { if(responseMessage.IsFaulted) { error = responseMessage.Exception.InnerException; if(onCompletion != null) { onCompletion(null, error); } return; } var response = responseMessage.Result; // add in cookies to global store //CouchbaseLiteHttpClientFactory.Instance.AddCoIokies(clientFactory.HttpHandler.CookieContainer.GetCookies(url)); var status = response.StatusCode; if((Int32)status.GetStatusCode() >= 300) { Log.To.Sync.W(Tag, "Got error {0}", status.GetStatusCode()); Log.To.Sync.W(Tag, "Request was for: " + message); Log.To.Sync.W(Tag, "Status reason: " + response.ReasonPhrase); Log.To.Sync.W(Tag, "Passing error onto callback..."); error = new HttpResponseException(status); if(onCompletion != null) { onCompletion(null, error); } } else { var entity = response.Content; var contentTypeHeader = response.Content.Headers.ContentType; Stream inputStream = null; if(contentTypeHeader != null && contentTypeHeader.ToString().Contains("multipart/related")) { try { var reader = new MultipartDocumentReader(db); var contentType = contentTypeHeader.ToString(); reader.SetContentType(contentType); var inputStreamTask = entity.ReadAsStreamAsync(); //inputStreamTask.Wait(90000, CancellationTokenSource.Token); inputStream = inputStreamTask.Result; const int bufLen = 1024; var buffer = new byte[bufLen]; int numBytesRead = 0; while((numBytesRead = inputStream.Read(buffer, 0, buffer.Length)) > 0) { if(numBytesRead != bufLen) { var bufferToAppend = new Couchbase.Lite.Util.ArraySegment<Byte>(buffer, 0, numBytesRead); reader.AppendData(bufferToAppend); } else { reader.AppendData(buffer); } } reader.Finish(); fullBody = reader.GetDocumentProperties(); if(onCompletion != null) { onCompletion(fullBody, error); } } catch(Exception ex) { Log.To.Sync.W(Tag, "SendAsyncMultipartDownloaderRequest got an exception, aborting...", ex); } finally { try { inputStream.Close(); } catch(Exception) { } } } else { if(entity != null) { try { var readTask = entity.ReadAsStreamAsync(); //readTask.Wait(); // TODO: This should be scaled based on content length. inputStream = readTask.Result; fullBody = Manager.GetObjectMapper().ReadValue<Object>(inputStream); if(onCompletion != null) onCompletion(fullBody, error); } catch(Exception ex) { Log.To.Sync.W(Tag, "SendAsyncMultipartDownloaderRequest got an exception, aborting...", ex); } finally { try { inputStream.Close(); } catch(Exception) { } } } } } } catch(Exception e) { Log.To.Sync.W(Tag, "Got exception during SendAsyncMultipartDownload, aborting..."); error = e; } finally { Task dummy; _requests.TryRemove(message, out dummy); responseMessage.Result.Dispose(); } }), _workExecutor.Scheduler); _requests.TryAdd(message, request); } catch(UriFormatException e) { Log.To.Sync.W(Tag, "Malformed URL for async request, aborting...", e); } }
internal HttpRequestMessage SendAsyncRequest(HttpMethod method, Uri url, object body, RemoteRequestCompletionBlock completionHandler, bool ignoreCancel) { var message = new HttpRequestMessage(method, url); var mapper = Manager.GetObjectMapper(); message.Headers.Add("Accept", new[] { "multipart/related", "application/json" }); var bytes = default(byte[]); if(body != null) { bytes = mapper.WriteValueAsBytes(body).ToArray(); var byteContent = new ByteArrayContent(bytes); message.Content = byteContent; message.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); } var token = ignoreCancel ? CancellationToken.None : _remoteRequestCancellationSource.Token; Log.To.Sync.V(Tag, "{0} - Sending {1} request to: {2}", _id, method, new SecureLogUri(url)); var client = default(CouchbaseLiteHttpClient); if(!_client.AcquireFor(TimeSpan.FromSeconds(1), out client)) { Log.To.Sync.I(Tag, "Client is disposed, aborting request to {0}", new SecureLogUri(url)); return null; } client.Authenticator = Authenticator; var t = client.SendAsync(message, token).ContinueWith(response => { try { HttpResponseMessage result = null; Exception error = null; if(!response.IsFaulted && !response.IsCanceled) { result = response.Result; UpdateServerType(result); } else if(response.IsFaulted) { Log.To.Sync.W(Tag, String.Format("Http Message failed to send, or got error response, " + "passing to callback... {0}, ", new SecureLogUri(message.RequestUri)), response.Exception); if(bytes != null) { try { Log.To.Sync.W(Tag, "\tFailed content: {0}", new SecureLogString(bytes, LogMessageSensitivity.PotentiallyInsecure)); } catch(ObjectDisposedException) { } } } if(completionHandler != null) { object fullBody = null; try { if(response.Status != TaskStatus.RanToCompletion) { Log.To.Sync.V(Tag, "SendAsyncRequest did not run to completion."); } if(response.IsCanceled) { error = new WebException("SendAsyncRequest was cancelled", System.Net.WebExceptionStatus.RequestCanceled); } else { error = Misc.Flatten(response.Exception).FirstOrDefault(); } if(error == null) { if(!result.IsSuccessStatusCode) { result = response.Result; error = new HttpResponseException(result.StatusCode); } } if(error == null) { var content = result.Content; if(content != null) { fullBody = mapper.ReadValue<object>(content.ReadAsStreamAsync().Result); } error = null; } } catch(Exception e) { error = e; Log.To.Sync.W(Tag, "SendAsyncRequest got an exception while processing response, " + "passing it on to the callback.", e); } completionHandler(fullBody, error); } if(result != null) { result.Dispose(); } } finally { Task dummy; _requests.TryRemove(message, out dummy); message.Dispose(); } }, token); _requests.AddOrUpdate(message, k => t, (k, v) => t); return message; }
public void TraceBeginEndAsync_Does_Not_Trace_HttpResponseException_When_Tracing_Only_Higher_Level() { // Arrange TestTraceWriter traceWriter = new TestTraceWriter(); traceWriter.TraceSelector = (rqst, category, level) => level >= TraceLevel.Error; HttpRequestMessage request = new HttpRequestMessage(); bool invoked = false; Exception exception = new HttpResponseException(Net.HttpStatusCode.NotFound); // Act Exception thrown = Assert.Throws<HttpResponseException>( () => traceWriter.TraceBeginEndAsync(request, "", TraceLevel.Info, "", "", beginTrace: (tr) => { invoked = true; }, execute: () => TaskHelpers.FromError(exception), endTrace: (tr) => { }, errorTrace: (tr) => { }).Wait()); // Assert Assert.False(invoked); Assert.Empty(traceWriter.Traces); Assert.Same(thrown, exception); }
public void TraceBeginEndAsync_Traces_And_Throws_HttpResponseException() { // Arrange TestTraceWriter traceWriter = new TestTraceWriter(); HttpRequestMessage request = new HttpRequestMessage(); HttpResponseException exception = new HttpResponseException(Net.HttpStatusCode.InternalServerError); List<TraceRecord> expectedTraces = new List<TraceRecord> { new TraceRecord(request, "testCategory", TraceLevel.Info) { Kind = TraceKind.Begin, Operator = "tester", Operation = "testOp", Message = "beginMessage" }, new TraceRecord(request, "testCategory", TraceLevel.Error) { Kind = TraceKind.End, Operator = "tester", Operation = "testOp", Exception = exception, Message = "errorMessage", Status = Net.HttpStatusCode.InternalServerError }, }; // Act Exception thrown = Assert.Throws<HttpResponseException>( () => traceWriter.TraceBeginEndAsync(request, "testCategory", TraceLevel.Info, "tester", "testOp", beginTrace: (tr) => { tr.Message = "beginMessage"; }, execute: () => { throw exception; }, endTrace: (tr) => { tr.Message = "won't Happen"; }, errorTrace: (tr) => { tr.Message = "errorMessage"; }).Wait()); // Assert Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); Assert.Same(thrown, exception); }
private AggregateException CreateAggregateException(HttpRequestMessage request) { HttpError httpError = new HttpError(new ModelStateDictionary() { { "key", new ModelState() { Errors = { new ModelError("error") } } }, { "username", new ModelState() { Errors = { new ModelError("invalid") } } }, }, true); HttpResponseException hre = new HttpResponseException(request.CreateErrorResponse(Net.HttpStatusCode.BadRequest, new HttpError("Error Message from HRE."))); Exception nestedHre = new Exception("Level 1", new Exception("Level 2", new HttpResponseException(request.CreateErrorResponse(Net.HttpStatusCode.NotFound, httpError)))); List<Exception> exceptions = new List<Exception>(); exceptions.Add(hre); exceptions.Add(nestedHre); return new AggregateException(exceptions); }
public void SendAsync_HandlesHttpResponseExceptions_FromCustomRoutes() { // Arrange HttpResponseException routingError = new HttpResponseException(new HttpResponseMessage()); HttpRequestMessage request = new HttpRequestMessage(); Mock<IHttpRoute> customRoute = new Mock<IHttpRoute>(); customRoute.Setup(r => r.GetRouteData("/", request)).Throws(routingError); HttpConfiguration config = new HttpConfiguration(); config.Routes.Add("default", customRoute.Object); HttpRoutingDispatcher dispatcher = new HttpRoutingDispatcher(config); HttpMessageInvoker invoker = new HttpMessageInvoker(dispatcher); // Act var result = invoker.SendAsync(request, CancellationToken.None).Result; // Assert Assert.Same(routingError.Response, result); }
/// <summary> /// Gets the <see cref="TraceLevel"/> per the <see cref="HttpStatusCode"/>. /// </summary> /// <param name="httpResponseException">The exception for which the trace level has to be found.</param> /// <returns>The mapped trace level.</returns> public static TraceLevel? GetMappedTraceLevel(HttpResponseException httpResponseException) { Contract.Assert(httpResponseException != null); HttpResponseMessage response = httpResponseException.Response; Contract.Assert(response != null); TraceLevel? level = null; // Client level errors are downgraded to TraceLevel.Warn if ((int)response.StatusCode < (int)HttpStatusCode.InternalServerError) { level = TraceLevel.Warn; } // Non errors are downgraded to TraceLevel.Info if ((int)response.StatusCode < (int)HttpStatusCode.BadRequest) { level = TraceLevel.Info; } return level; }