protected virtual async Task <byte[]> DownloadAsync(string url, CancellationToken token, HttpClient client, Action <DownloadProgress> progressAction, TaskParameter parameters) { using (var cancelHeadersToken = new CancellationTokenSource()) { cancelHeadersToken.CancelAfter(TimeSpan.FromSeconds(Configuration.HttpHeadersTimeout)); using (var linkedHeadersToken = CancellationTokenSource.CreateLinkedTokenSource(token, cancelHeadersToken.Token)) { try { using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, linkedHeadersToken.Token).ConfigureAwait(false)) { if (!response.IsSuccessStatusCode) { throw new HttpRequestException(response.StatusCode.ToString()); } if (response.Content == null) { throw new HttpRequestException("No Content"); } var mediaType = response.Content.Headers?.ContentType?.MediaType; if (!string.IsNullOrWhiteSpace(mediaType) && !mediaType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) { if (InvalidContentTypes.Any(v => mediaType.StartsWith(v, StringComparison.OrdinalIgnoreCase))) { throw new HttpRequestException($"Invalid response content type ({mediaType})"); } } ModifyParametersAfterResponse(response, parameters); using (var cancelReadTimeoutToken = new CancellationTokenSource()) { var readTimeoutToken = cancelReadTimeoutToken.Token; cancelReadTimeoutToken.CancelAfter(TimeSpan.FromSeconds(Configuration.HttpReadTimeout)); int total = (int)(response.Content.Headers.ContentLength ?? -1); var canReportProgress = progressAction != null; try { using (var outputStream = new MemoryStream()) using (var sourceStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { int totalRead = 0; var buffer = new byte[Configuration.HttpReadBufferSize]; int read = 0; while ((read = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { token.ThrowIfCancellationRequested(); readTimeoutToken.ThrowIfCancellationRequested(); outputStream.Write(buffer, 0, read); totalRead += read; if (canReportProgress) { progressAction(new DownloadProgress() { Total = total, Current = totalRead }); } } return(outputStream.ToArray()); } } catch (OperationCanceledException) { if (cancelReadTimeoutToken.IsCancellationRequested) { throw new Exception("HttpReadTimeout"); } else { throw; } } } } } catch (OperationCanceledException) { if (cancelHeadersToken.IsCancellationRequested) { throw new Exception("HttpHeadersTimeout"); } else { throw; } } } } }
protected virtual async Task <byte[]> DownloadAsync(string url, CancellationToken token, HttpClient client, TaskParameter parameters, DownloadInformation downloadInformation) { if (!parameters.Preload) { await Task.Delay(25).ConfigureAwait(false); token.ThrowIfCancellationRequested(); } var progressAction = parameters.OnDownloadProgress; using (var httpHeadersTimeoutTokenSource = new CancellationTokenSource()) using (var headersTimeoutTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, httpHeadersTimeoutTokenSource.Token)) { httpHeadersTimeoutTokenSource.CancelAfter(TimeSpan.FromSeconds(Configuration.HttpHeadersTimeout)); try { var headerTimeoutToken = headersTimeoutTokenSource.Token; using (var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, headerTimeoutToken).ConfigureAwait(false)) { headerTimeoutToken.ThrowIfCancellationRequested(); if (!response.IsSuccessStatusCode) { if (response.Content == null) { throw new DownloadHttpStatusCodeException(response.StatusCode); } using (response.Content) { var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); throw new DownloadHttpStatusCodeException(response.StatusCode, content); } } if (response.Content == null) { throw new DownloadException("No content"); } var mediaType = response.Content.Headers?.ContentType?.MediaType; if (!string.IsNullOrWhiteSpace(mediaType) && !mediaType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) { if (InvalidContentTypes.Any(v => mediaType.StartsWith(v, StringComparison.OrdinalIgnoreCase))) { throw new DownloadException($"Invalid response content type ({mediaType})"); } } if (!parameters.CacheDuration.HasValue && Configuration.TryToReadDiskCacheDurationFromHttpHeaders && response.Headers?.CacheControl?.MaxAge != null && response.Headers.CacheControl.MaxAge > TimeSpan.Zero) { downloadInformation.CacheValidity = response.Headers.CacheControl.MaxAge.Value; } ModifyParametersAfterResponse(response, parameters); using (var httpReadTimeoutTokenSource = new CancellationTokenSource()) using (var readTimeoutTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, httpReadTimeoutTokenSource.Token)) { var readTimeoutToken = readTimeoutTokenSource.Token; var httpReadTimeoutToken = httpReadTimeoutTokenSource.Token; var total = (int)(response.Content.Headers.ContentLength ?? -1); var canReportProgress = progressAction != null; httpReadTimeoutTokenSource.CancelAfter(TimeSpan.FromSeconds(Configuration.HttpReadTimeout)); readTimeoutToken.ThrowIfCancellationRequested(); try { using (var outputStream = new MemoryStream()) using (var sourceStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) { httpReadTimeoutToken.Register(() => sourceStream.TryDispose()); var totalRead = 0; var buffer = new byte[Configuration.HttpReadBufferSize]; var read = 0; while ((read = await sourceStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) { readTimeoutToken.ThrowIfCancellationRequested(); outputStream.Write(buffer, 0, read); totalRead += read; if (canReportProgress) { progressAction(new DownloadProgress(totalRead, total)); } } if (outputStream.Length == 0) { throw new DownloadException("Zero length stream"); } if (outputStream.Length < 32) { throw new DownloadException("Invalid stream"); } return(outputStream.ToArray()); } } catch (Exception ex) when(ex is OperationCanceledException || ex is ObjectDisposedException) { if (httpReadTimeoutTokenSource.IsCancellationRequested) { throw new DownloadReadTimeoutException(); } throw; } } } } catch (OperationCanceledException) { if (httpHeadersTimeoutTokenSource.IsCancellationRequested) { throw new DownloadHeadersTimeoutException(); } else { throw; } } } }