Скачать файл частично

Возникла тут одна проблемка. Нужно скачать файл частично. От какого-то до какого-то байта.
Это делается при помощи заголовка Range:

Знаю - не все серверы поддерживают такую возможность. Но сейчас не об этом.
Проблема в том, что если задавать диапазон от и до, типа AddRange(100L, 10000L); - работает. А что если надо скачать от 100 и до конца? В момент задания диапазона я же ещё не знаю размер файла, а -1 оно не принимает :thinking:

:thinking:
Видимо, вот так:

        static void Main(string[] args)
        {
            // Create a New 'HttpWebRequest' object .
            HttpWebRequest myHttpWebRequest1 = (HttpWebRequest)WebRequest.Create("http://www.contoso.com");
            myHttpWebRequest1.AddRange(1000);
            Console.WriteLine("Call AddRange(1000)");
            Console.Write("Resulting Headers: ");
            Console.WriteLine(myHttpWebRequest1.Headers.ToString());

            // Create a New 'HttpWebRequest' object .
            HttpWebRequest myHttpWebRequest2 = (HttpWebRequest)WebRequest.Create("http://www.contoso.com");
            myHttpWebRequest2.AddRange(-1000);
            Console.WriteLine("Call AddRange(-1000)");
            Console.Write("Resulting Headers: ");
            Console.WriteLine(myHttpWebRequest2.Headers.ToString());

            Console.ReadLine();
        }
Call AddRange(1000)
Resulting Headers: Range: bytes=1000-


Call AddRange(-1000)
Resulting Headers: Range: bytes=-1000

Только, почему-то, подставляется префикс bytes=. А в браузерах его нет :thinking:

HEAD запрос сделать, чтоб узнать.

HTTP range requests - HTTP | MDN

Ок, работает. Но всплыла ещё одна проблема - многопоточность. А точнее - вычисление начала и конца каждого чанка, если указан рэндж.


        private IEnumerable<Tuple<long, long>> Split(long contentLength, int chunkCount)
        {
            if (chunkCount <= 1 || contentLength <= MEGABYTE)
            {
                long byteTo = RangeTo >= 0L ? RangeTo : contentLength + RangeFrom - 1;
                yield return new Tuple<long, long>(RangeFrom, byteTo);
                yield break;
            }

            long chunkSize = contentLength / chunkCount;
            for (int i = 0; i < chunkCount; i++)
            {
                long startPos = chunkSize * i;
                bool lastChunk = i == chunkCount - 1;
                long endPos = lastChunk ? (contentLength - 1) : (startPos + chunkSize - 1);
                yield return new Tuple<long, long>(startPos, endPos);
            }
        }
//contentLength - это уже с учётом рэнджа

Как сюда рэндж добавить?

Упростил алгоритм:

        private IEnumerable<Tuple<long, long>> Split(long contentLength, int chunkCount)
        {
            long contentLengthRanged = RangeTo >= 0L ? RangeTo - RangeFrom : contentLength - RangeFrom;
            if (chunkCount <= 1 || contentLengthRanged <= MEGABYTE)
            {
                long byteTo = RangeTo >= 0L ? RangeTo : contentLengthRanged + RangeFrom - 1;
                yield return new Tuple<long, long>(RangeFrom, byteTo);
                yield break;
            }

            long chunkSize = contentLengthRanged / chunkCount;
            long startPos = RangeFrom;
            for (int i = 0; i < chunkCount; ++i)
            {
                bool lastChunk = i == chunkCount - 1;
                long endPos;
                if (lastChunk)
                {
                    endPos = RangeTo >= 0 ? RangeTo : contentLength - 1;
                }
                else
                {
                    endPos = startPos + chunkSize;
                }
                System.Diagnostics.Debug.WriteLine($"{startPos}-{endPos}"); 
                yield return new Tuple<long, long>(startPos, endPos);
                if (!lastChunk)
                {
                    startPos += chunkSize + 1;
                }
            }
        }
//contentLength - полный размер

Судя по выводу, на чанки делится правильно :thinking: И качается, вроде, правильно. Хз как это проверить :man_shrugging: