← Все статьи

SiteGuard12 мин чтения

Файл скачивается не полностью: как проверить HTTP Range и Content-Length

Если файл скачивается не полностью с сайта, сначала установите, какие байты должны были прийти и какие действительно сохранились. Открывающаяся страница загрузки и ответ 200 ещё не подтверждают передачу всего файла. Ошибка может находиться в исходном объекте, обработчике выдачи, промежуточном прокси или продолжении ранее прерванной загрузки. Проверка HTTP Range и Content-Length помогает разделить эти случаи, но окончательный результат подтверждают версия объекта и целостность полученной копии.

1. Зафиксируйте конкретный файл и условия обрыва

Начните с одного воспроизводимого случая. Запишите время, адрес загрузки без секретных параметров, имя и версию файла, ожидаемый размер в байтах, фактический размер локальной копии и сообщение клиента. Уточните, прерывается ли первоначальная передача или только докачка. Для разработчика это разные ветки расследования: исправный полный GET ещё не означает, что сервер правильно обслуживает диапазоны.

Ожидаемый размер возьмите из хранилища или каталога опубликованных файлов, а не из подписи «примерно 10 МБ». Проверьте, завершилось ли формирование исходного архива. Если приложение начало отдавать временный файл до окончания экспорта, клиент может добросовестно получить весь доступный на тот момент объект, который уже неполон по содержанию.

Сохраните неудачную копию отдельно. Повторная загрузка поверх неё уничтожит полезное свидетельство. Отметьте, повторяется ли остановка на одном смещении, через одинаковое время или при смене сети. Эти наблюдения выбирают следующий эксперимент, но сами по себе не доказывают причину: одинаковый размер может объясняться и серверным ограничением, и повреждённым объектом.

Владелец сайта может подготовить этот набор без изменения конфигурации: приложить обезличенный снимок ошибки, точное время и сведения о файле. Разработчику передайте отдельный вопрос: «Какой объект выдавался этому запросу и на каком участке перестали поступать байты?» Он конкретнее предположения «не хватает памяти» и помогает запросить нужные журналы у хостинга.

2. Разделите оригинал, тело ответа и полученную копию

В расследовании нужны три величины. Первая — размер выбранного представления файла целиком. Вторая — размер тела конкретного HTTP-ответа. Третья — число байтов этого тела, действительно принятых клиентом. Для полного несжатого ответа они ожидаемо совпадают, а для частичного ответа второй размер меньше первого. При декодировании содержимого появляется ещё размер локального результата после преобразования.

Что сравниваемОткуда берёмЧто выясняем
Полный объект нужной версииХранилище, манифест публикацииЧто собирались предоставить пользователю
Тело конкретного ответаЗаголовки и параметры запросаВесь файл это или выбранный диапазон
Полученные байты телаОтчёт клиента и диагностический файлЗавершилась ли именно эта передача

Согласуйте единицы до сравнения: 1 КиБ равен 1024 байтам, а округлённая подпись интерфейса не подходит для проверки границ. Не сопоставляйте счётчик сетевого трафика, включающий заголовки, с размером файла. В отчёте подпишите каждое число: «байты тела без HTTP-заголовков», «полный объект» или «декодированная копия». Это предотвращает ложные выводы о недостающих данных.

3. Прочитайте Content-Length без лишних предположений

Content-Length задаёт длину тела в байтах. Для ответа на GET это не число символов и не размер страницы вместе с заголовками. Если сервер объявил 10000 байт, а клиент получил только 6000, необходимо выяснить, где закончилась передача. Однако сначала убедитесь, что сравниваете одно представление и исключили автоматическое декодирование.

Отсутствие этого заголовка допустимо. HTTP/1.1 поддерживает передачу с Transfer-Encoding: chunked, а в HTTP/2 границы тела определяются средствами протокола без обязательного Content-Length. Поэтому добавлять заголовок вручную только ради исчезновения предупреждения нельзя. Объявленная длина должна соответствовать фактически выдаваемому телу.

RFC 9112 описывает незавершённые сообщения HTTP/1.1: при заданной длине недобор байтов означает неполный ответ; при chunked важен корректный завершающий блок. Если клиент сообщает ошибку завершения, сохраните её вместе с заголовками. Признак «соединение закрыто» без понимания способа определения длины недостаточен.

Запрос HEAD полезен как предварительный осмотр метаданных, но не передаёт тело. Его успешное выполнение не проверяет чтение файла до конца. Для расследования понадобится небольшой GET, а для приёмки исправления — контролируемая загрузка целого тестового объекта.

4. Выполните ограниченный диагностический GET

Начните с небольшого диапазона, сохраняя ответ отдельно от пользовательского файла. Ниже учебная команда для публичного тестового URL; адрес нужно заменить своим. Запускайте её в отдельной рабочей папке, где файлов range.headers и range.bin ещё нет. Она не предназначена для обхода авторизации.

curl --location --max-redirs 3 \
  --connect-timeout 5 --max-time 15 \
  --max-filesize 65536 \
  --range 0-1023 \
  --header 'Accept-Encoding: identity' \
  --dump-header range.headers \
  --output range.bin \
  --write-out 'HTTP %{http_code}; bytes %{size_download}\n' \
  'https://downloads.example/file.bin'

Запрошены 1024 байта, порог размера установлен в 65536 байт, то есть 64 КиБ, подключение ограничено пятью секундами, вся попытка — пятнадцатью. Эти значения выбраны для учебной диагностики, а не как нормы для загрузки больших файлов. Ограничение размера защищает от неожиданного полного ответа при проигнорированном Range.

Согласно руководству curl, остановка по --max-filesize при заранее неизвестной длине работает начиная с версии 8.4.0. Проверьте версию через curl --version. В примере нет автоматической распаковки --compressed. Остановка по установленному вами пределу — результат диагностического ограничения, а не самостоятельное доказательство неисправности сервера.

Посмотрите последний ответ после перенаправлений, сохранённые заголовки, размер range.bin и сообщение curl. Без специальной обработки HTTP-ошибок успешный выход команды не заменяет проверку статуса. Получение первого килобайта также не доказывает, что большой файл удастся передать полностью.

5. Проверьте арифметику ответа 206

В механизме Range позиции байтов начинаются с нуля, обе границы включены. Поэтому bytes=0-1023 означает 1024 байта: 1023 − 0 + 1. Учебный объект ниже имеет полный размер 10000 байт, не сжат и не меняется между проверками. Ожидаемый фрагмент заголовков:

HTTP/1.1 206 Partial Content
Content-Range: bytes 0-1023/10000
Content-Length: 1024
ETag: "artifact-v7"

Здесь 10000 — длина полного представления, 0–1023 — выбранные позиции, 1024 — длина тела частичного ответа. Нельзя объявлять загрузку неполной только потому, что Content-Length меньше полного размера: для такого запроса это правильно. Рассматриваем один диапазон, без multipart-ответа с несколькими частями.

Если клиент принял 900 байт, недостаёт 124 байт именно запрошенного фрагмента: 1024 − 900. Если принял все 1024, эта небольшая передача закончилась успешно. Но мы пока ничего не проверили за пределами диапазона. Следующим отдельным экспериментом можно запросить небольшой участок рядом с местом прежнего обрыва, сохранив те же ограничения объёма и времени.

Заголовок Accept-Ranges: bytes — полезная подсказка, но не обещание будущего ответа 206. Его отсутствие тоже не запрещает клиенту попробовать Range. Это прямо уточняет RFC 9110. Проверяйте фактическое поведение конкретного маршрута.

6. Правильно истолкуйте 200 и 416

Сервер может проигнорировать Range и ответить 200 с полным представлением. Это отличается от поддержки докачки. Если клиент собирался дописать хвост, он должен распознать такой ответ и начать отдельное получение целого файла либо остановиться. Простое добавление тела 200 к старому префиксу создаёт неверную копию.

Ответ 416 означает, что запрошенный диапазон нельзя удовлетворить. В нём Content-Range может сообщать текущий полный размер в форме bytes */10000. Для нашего учебного файла запрос bytes=10000- начинается уже за последним байтом: последний допустимый индекс равен 9999.

НаблюдениеПрактическое действие
206 с ожидаемыми границамиСверить версию, длину полученного фрагмента и его положение
200 вместо ожидаемого 206Не дописывать тело; выяснить, проигнорирован ли Range или изменился объект
416 и bytes */10000Сопоставить смещение, текущую длину и идентичность локальной копии

416 не доказывает, что файл уже скачан. Локальная копия может иметь подходящий размер, но содержать другую версию или испорченные байты. Она также могла оказаться длиннее нового объекта. Сохраняйте её до проверки целостности, а не помечайте автоматически завершённой.

7. Привяжите докачку к версии объекта

Продолжать безопасно только ту же последовательность байтов. Представим учебный случай: сохранены первые 6000 байт версии artifact-v7, то есть позиции 0–5999. Следующий запрос должен начинаться с позиции 6000. Для проверки неизменности используйте сильный ETag, полученный вместе с исходной частью:

Range: bytes=6000-
If-Range: "artifact-v7"

If-Range связывает диапазон с условием: при совпадении валидатора сервер может отдать требуемую часть, при несовпадении Range игнорируется и возможен полный ответ 200. Слабый ETag с префиксом W/ для If-Range не подходит. ETag при этом служит валидатором представления и не обязан быть контрольной суммой файла.

Для неизменного учебного объекта ожидаются Content-Range: bytes 6000-9999/10000 и тело длиной 4000 байт. Проверяем: 9999 − 6000 + 1 = 4000; 6000 + 4000 = 10000. Перед присоединением подтвердите начало диапазона и полное получение хвоста. При смене версии получайте новую копию отдельно.

Дата Last-Modified не является безусловной заменой ETag. Нормативные правила If-Range допускают дату при отсутствии ETag и выполнении условий сильного валидатора. Без такой уверенности безопаснее перезапустить загрузку. Опция curl -C - определяет место продолжения по локальному файлу; сама по себе она не доказывает неизменность объекта и не гарантирует успешную докачку.

Имя download.zip не фиксирует содержимое. Если по этому пути публикуют новые сборки, сохраните вместе с частичной копией её валидатор и идентификатор публикации. Для экспортов по запросу выясните, повторно ли используется готовый объект или при каждом обращении формируется новый. Во втором случае одинаковые параметры экспорта ещё не доказывают побайтовое совпадение двух результатов.

8. Исключите путаницу из-за сжатия

При Content-Encoding, например gzip, диапазоны относятся к закодированным байтам, что закреплено в правилах байтовых диапазонов. Content-Length также нельзя безоговорочно сравнивать с размером уже распакованного локального результата. Для проверки важно знать, что получил транспорт и что затем сохранил инструмент.

В учебной диагностике запрошен Accept-Encoding: identity, чтобы упростить сравнение. Всё равно прочитайте фактический Content-Encoding ответа. Если сервер прислал сжатую версию, не рассчитывайте смещение продолжения по размеру распакованного файла. Префикс и хвост должны принадлежать одной и той же последовательности байтов.

Попросите команду записать в карточке проверки выбранное кодирование и поведение клиента. Фраза «хэш не совпал» бесполезна, если одна сторона хэшировала архив как опубликованный файл, а другая — дополнительно сжатое HTTP-представление. Сначала договоритесь о точной форме данных, затем ищите повреждение. Для стабильного скачивания полезно выделить неизменяемые версии файлов и проверить их выдачу через реальный публичный маршрут.

9. Найдите участок между хранилищем и клиентом

Нарисуйте короткую цепочку конкретной загрузки: хранилище, приложение выдачи, обратный прокси, CDN, клиент. На каждом доступном участке сопоставьте время, идентификатор запроса, версию объекта, статус и переданные байты. Уточните значение счётчика в журнале: он может учитывать другой набор данных, чем клиентский отчёт.

Если разрешённый контрольный маршрут к источнику работает, а публичный путь обрывается, проверяйте преобразования и ограничения промежуточных узлов. Сравнение имеет смысл только для одинакового объекта и условий доступа. Другой Host, несжатый ответ или новый экспорт легко создадут ложное ощущение, что виноват прокси. Не открывайте закрытое хранилище ради такого эксперимента.

Запись 200 в журнале приложения означает недостаточно: заголовки могли уйти до сбоя чтения тела. Ищите событие завершения передачи, ошибку чтения источника, прерывание соединения и соответствующую запись следующего узла. Меняйте одну настройку за эксперимент, сохраняя исходный пример. Иначе успешная попытка после нескольких правок не покажет, что именно устранило дефект.

Составьте пару сопоставимых наблюдений: тот же файл через публичный адрес и через разрешённый технический маршрут. Повторите каждое отдельно, записав время и параметры. Если дефект возникает непостоянно, одного успешного запроса недостаточно для исключения участка. В карточке сохраняйте и удачные, и неудачные попытки: различие между ними иногда полезнее большого журнала без привязки к конкретной загрузке.

10. Разделите таймаут, истечение ссылки и смену сессии

Если скачивание обрывается, сервер не обязательно прекращает его по одному общему таймеру. В конфигурации могут отдельно ограничиваться подключение, пауза без данных и общая длительность запроса. Зафиксируйте, продолжались ли поступления байтов перед обрывом. Увеличение допустимой паузы не исправит ограничение общей длительности, а увеличение всех таймеров скроет причину и дольше удержит ресурсы.

Для подписанной ссылки проверьте срок действия именно нового запроса докачки. Не предполагайте, что каждый провайдер обязательно оборвёт уже начатую передачу ровно в момент истечения подписи. Поведение уточняют по используемому хранилищу и наблюдаемому запросу. Обновлённая ссылка также должна вести на ту же версию, если продолжается старый файл.

Отдельно проверьте перенаправления и авторизацию. Клиент без браузерной сессии может получить HTML-страницу входа с ответом 200 вместо архива. Сопоставьте конечный адрес, Content-Type и небольшой сохранённый фрагмент. Не передавайте cookies, токены и полные подписанные URL в публичные отчёты. Зафиксируйте факт смены ресурса, а доступ разработчику предоставьте штатным способом.

Учебный пример: одна попытка длилась 30 секунд, другая 45, но обе завершились после десятисекундной паузы в поступлении данных. Это основание проверить таймаут ожидания, а не назначать общий лимит в 30 секунд. Подтверждение ищут в конфигурации ответственного узла и его журнале. Если момент обрыва меняется вместе с искусственно заданным клиентским пределом, сначала исключите сам диагностический инструмент.

11. Подтвердите целостность одинаковых данных

Совпадение длины — необходимая проверка в выбранном сценарии, но не доказательство равенства содержимого. Два файла одного размера могут различаться внутри. Сравните контрольную сумму полученной копии с доверенной суммой опубликованной версии, используя один алгоритм, например SHA-256, и одинаковое представление данных. Значение из случайного сообщения пользователя не заменяет эталон владельца файла.

Если хэш совпал, а архив не открывается, проверьте исходный опубликованный объект и совместимость программы чтения. Если хэш различается, сохраните обе версии и результаты передачи: размер, валидатор, кодирование, границы полученных частей. Это позволит отличить повреждение при публикации от ошибки сборки после докачки.

Для автоматизации заранее определите, когда временный файл становится готовым. Практичный порядок: получить данные в отдельный файл, проверить завершение, длину и контрольную сумму, затем разрешить дальнейшее использование. После неуспешной проверки копию не следует выдавать как готовый документ только потому, что у неё правильное расширение или ожидаемое имя.

12. Закройте дефект проверяемыми результатами

Приёмка должна повторять исходный пользовательский сценарий и проверять исправленный механизм отдельно. Маленький Range-запрос экономит время при диагностике; полноценное скачивание тестового объекта подтверждает передачу до конца. Для проверки большого файла заранее согласуйте объём и длительность, чтобы диагностический запуск не стал неожиданной нагрузкой.

  • Полная загрузка нужной версии завершается, длина и контрольная сумма совпадают с эталоном.
  • Искусственно прерванная загрузка продолжается с правильного смещения либо корректно запускается заново.
  • Смена объекта между попытками не приводит к склейке разных версий.
  • Ответы 200 и 416 обрабатываются явно, ошибочный файл не объявляется готовым.
  • Публичный маршрут и защищённый пользовательский сценарий проверены; непроверенные условия перечислены.

Запишите причину, изменённый компонент и свидетельство исправления. Если сервер сознательно не поддерживает докачку, это допустимое ограничение продукта, которое нужно учесть в клиентском поведении. Не обещайте докачку файлов только на основании одного заголовка.

Назначьте владельца результата: кто проверяет опубликованный объект, кто отвечает за выдачу через прокси и кто подтверждает пользовательскую загрузку. Приложите к задаче компактную таблицу попыток с версиями, размерами и итогами проверок. Тогда при повторении проблемы команда сравнит новые свидетельства с уже исправленным случаем, вместо того чтобы начинать расследование по одной жалобе заново.

Подключите SiteGuard для внешнего наблюдения за HTTP-доступностью важных адресов сайта. Полную передачу файла, безопасную докачку и совпадение хэшей проверяйте отдельными тестами: доступность страницы загрузки не подтверждает эти результаты.