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