using System.Collections.Generic;
using System.Threading.Tasks;
using System.Net.Http.Headers;
static void Main(string[] args)
Task task = Task.Run(async () => {
using (HttpClient client = new HttpClient())
string uri = "http://i.imgur.com/z4d4kWk.jpg"; string fileName = "cat.jpg";
await DownloadFromSingleUri(uri);
await DownloadFromMultipleUri(uri, uri, uri, uri, uri);
async Task DownloadFromSingleUri(string _uri)
HeaderInfo headerInfo = await GetHeader(_uri);
if (!headerInfo.AcceptRanges || headerInfo.ContentLength == null)
HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
using (FileStream fs = new FileStream(fileName, FileMode.CreateNew))
using (Stream streamFromServer = await response.Content.ReadAsStreamAsync())
await streamFromServer.CopyToAsync(fs);
await Download(await GetHeader(_uri), _uri);
async Task DownloadFromMultipleUri(params string[] _uri)
if (_uri.Length == 0 || _uri.Length == 1)
throw new ArgumentException();
Task<HeaderInfo>[] tasks = new Task<HeaderInfo>[_uri.Length];
for (int i=0;i<_uri.Length;i++)
tasks[i] = GetHeader(_uri[i]);
await Task.WhenAll(tasks);
HeaderInfo headerInfo = tasks.First().Result;
if (headerInfo.ContentLength == null)
throw new Exception("server did not supply content length");
List<HeaderInfo> headerInfos = new List<HeaderInfo> { headerInfo };
for (int i = 1; i < _uri.Length; i++)
headerInfos.Add(tasks[i].Result);
HeaderInfo invalidHeaderInfo = headerInfos.Find(x => !x.AcceptRanges || x.ContentLength != headerInfo.ContentLength);
if (invalidHeaderInfo != null)
throw new Exception($"{invalidHeaderInfo.Uri} is not valid input for segmented download accept range={invalidHeaderInfo.AcceptRanges} content length={invalidHeaderInfo.ContentLength} from base content length={headerInfo.ContentLength}");
await Download(headerInfo, _uri);
async Task Download(HeaderInfo headerInfo, params string[] _uri)
int numberOfSegments = _uri.Length > 1 ? _uri.Length : 3;
int effectiveNumberOfSegments = headerInfo.ContentLength >= numberOfSegments ? numberOfSegments : (int)headerInfo.ContentLength;
long bytesPerSegment = headerInfo.ContentLength.Value / effectiveNumberOfSegments;
List<SegmentInfo> segmentInfos = new List<SegmentInfo>();
for (int i = 0; i < effectiveNumberOfSegments;)
segmentInfos.Add(new SegmentInfo
From = i * bytesPerSegment,
To = ++i * bytesPerSegment - 1,
long remainder = headerInfo.ContentLength.Value % effectiveNumberOfSegments;
segmentInfos.Last().To += remainder;
Task[] tasks = new Task[effectiveNumberOfSegments];
segmentInfos.ForEach(x => tasks[i++] = DownloadInternal(x));
await Task.WhenAll(tasks);
async Task DownloadInternal(SegmentInfo segmentInfo)
using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
RangeItemHeaderValue range = new RangeItemHeaderValue(segmentInfo.From, segmentInfo.To);
HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Get, segmentInfo.Uri);
message.Headers.Range = new RangeHeaderValue();
message.Headers.Range.Ranges.Add(range);
long offset = range.From ?? 0;
fs.Seek(offset, SeekOrigin.Begin);
HttpResponseMessage response = await client.SendAsync(message, HttpCompletionOption.ResponseHeadersRead);
using (Stream streamFromServer = await response.Content.ReadAsStreamAsync())
byte[] buffer = new byte[1024];
while ((read = await streamFromServer.ReadAsync(buffer, 0, buffer.Length)) > 0)
await fs.WriteAsync(buffer, 0, read);
segmentInfo.Next += read;
async Task<HeaderInfo> GetHeader(string _uri)
HttpResponseMessage response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, _uri));
AcceptRanges = response.Headers.AcceptRanges.Contains("bytes"),
ContentLength = response.Content.Headers.ContentLength,
while (!task.IsCompleted) ;
public string Uri { get; set; }
public bool AcceptRanges { get; set; }
public long? ContentLength { get; set; }
public int SegmentNo { get; set; }
public string Uri { get; set; }
public long From { get => from; set => Next = from = value; }
public long To { get; set; }
public long Next { get; set; }