/// <summary> /// Send request /// </summary> /// <param name="httpRequest"></param> /// <param name="httpMethod"></param> /// <param name="ct"></param> /// <returns></returns> private async Task <IHttpResponse> SendAsync(IHttpRequest httpRequest, HttpMethod httpMethod, CancellationToken ct) { if (!(httpRequest is HttpRequest wrapper)) { throw new InvalidOperationException("Bad request"); } using (var client = _factory.CreateClient(httpRequest.ResourceId ?? HttpHandlerFactory.DefaultResourceId)) { if (httpRequest.Options.Timeout.HasValue) { client.Timeout = httpRequest.Options.Timeout.Value; } var sw = Stopwatch.StartNew(); _logger.Verbose("Sending {method} request to {uri}...", httpMethod, httpRequest.Uri); // We will use this local function for Exception formatting HttpRequestException generateHttpRequestException(Exception e) { var errorMessage = e.Message; if (e.InnerException != null) { errorMessage += " - " + e.InnerException.Message; } _logger.Warning("{method} to {uri} failed (after {elapsed}) : {message}!", httpMethod, httpRequest.Uri, sw.Elapsed, errorMessage); _logger.Verbose(e, "{method} to {uri} failed (after {elapsed}) : {message}!", httpMethod, httpRequest.Uri, sw.Elapsed, errorMessage); return(new HttpRequestException(errorMessage, e)); } using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(ct)) { try { wrapper.Request.Method = httpMethod; using (var response = await client.SendAsync(wrapper.Request, linkedCts.Token)) { var result = new HttpResponse { ResourceId = httpRequest.ResourceId, StatusCode = response.StatusCode, Headers = response.Headers, ContentHeaders = response.Content.Headers, Content = await response.Content.ReadAsByteArrayAsync() }; if (result.IsError()) { _logger.Warning("{method} to {uri} returned {code} (took {elapsed}).", httpMethod, httpRequest.Uri, response.StatusCode, sw.Elapsed, result.GetContentAsString(Encoding.UTF8)); } else { _logger.Verbose("{method} to {uri} returned {code} (took {elapsed}).", httpMethod, httpRequest.Uri, response.StatusCode, sw.Elapsed); } return(result); } } catch (HttpRequestException e) { var requestEx = generateHttpRequestException(e); throw requestEx; } catch (OperationCanceledException e) { if (ct.IsCancellationRequested) { // Cancel was called. We will call ct.ThrowIfCancellationRequested() because the // token that is passed to the exception is the linked token. This way, // information about usage of linked tokens will not be leaked to the caller. ct.ThrowIfCancellationRequested(); } // Operation timed out. var requestEx = generateHttpRequestException(e); throw requestEx; } } } }