При работе с внешними ресурсами, такими как файлы, доступные по HTTP-ссылкам, часто возникает необходимость проверить, был ли файл уже скачан, прежде чем запускать процесс его повторной загрузки. Это позволяет существенно экономить сетевой трафик, время и ресурсы как клиента (вашей системы 1С), так и сервера, который предоставляет файлы. Рассмотрим, как эффективно реализовать такую проверку в 1С, опираясь на стандартные возможности платформы и протокола HTTP.
Изначально, многие разработчики сталкиваются с проблемой: как узнать о содержимом файла по ссылке, не скачивая его целиком? Часто первое, что приходит на ум, это вычисление хеш-суммы. Если локальная копия файла имеет ту же хеш-сумму, что и файл на сервере, значит, они идентичны. Однако, тут кроется подвох, который часто вызывает затруднения.
В ходе обсуждения на форуме один из пользователей предложил следующую функцию для вычисления хеш-суммы:
Функция MD5ХешФайл(тСтрока) Экспорт
Хеш = Новый ХешированиеДанных(ХешФункция.MD5);
Хеш.ДобавитьФайл(тСтрока);
Возврат Строка(Хеш.ХешСумма);
КонецФункции
На первый взгляд может показаться, что метод Хеш.ДобавитьФайл(тСтрока) примет URL и вычислит хеш от содержимого файла, находящегося по этому URL. Однако это не так. Метод ДобавитьФайл() объекта ХешированиеДанных ожидает путь к локальному файлу на диске. Если вы передадите ему URL-адрес (строку, например, "http://example.com/file.zip"), он попытается обработать эту строку как путь к локальному файлу. В лучшем случае это приведет к ошибке, если файла с таким именем не существует. В худшем — он может вычислить хеш от *самой строки URL*, что абсолютно бесполезно для проверки содержимого удаленного файла.
Таким образом, попытка получить хеш-сумму файла по ссылке без его предварительной загрузки стандартными средствами 1С напрямую невозможна. Нам приходится искать другие подходы.
Для эффективной проверки и предотвращения повторной скачки файла мы можем использовать несколько методов, которые можно применять как по отдельности, так и в комбинации.
Один из самых простых и быстрых способов получить некоторую информацию о файле на сервере без его полной загрузки — это выполнить HTTP-запрос типа HEAD. В отличие от GET-запроса, который загружает всё тело ответа, HEAD-запрос запрашивает только заголовки ответа. Среди этих заголовков обычно присутствует Content-Length, который указывает размер файла в байтах.
В 1С это можно реализовать следующим образом:
Функция РазмерФайлаHTTP(СоединениеHTTP, Знач ПутьНаСервере, Знач Заголовки = Неопределено)
ЗапросHTTP = Новый HTTPЗапрос(ПутьНаСервере, Заголовки);
Попытка
// Метод ПолучитьЗаголовки выполняет HEAD-запрос
ПолученныеЗаголовки = СоединениеHTTP.ПолучитьЗаголовки(ЗапросHTTP);
Исключение
Возврат 0; // В случае ошибки возвращаем 0
КонецПопытки;
// Получаем значение заголовка Content-Length, приводя его к нижнему регистру для надежности
РазмерСтрокой = ПолученныеЗаголовки.Заголовки["content-length"];
ТипЧисло = Новый ОписаниеТипов("Число");
РазмерФайла = ТипЧисло.ПривестиЗначение(РазмерСтрокой);
Возврат РазмерФайла;
КонецФункции
Как это использовать:
РазмерФайлаHTTP для того же URL.Преимущества: Быстро, экономит трафик, легко реализуется. Недостатки: Не 100% надежно. Два разных файла могут иметь одинаковый размер, хотя вероятность этого невелика для большинства практических сценариев.
Один из наиболее распространенных и надежных подходов, особенно для систем обновлений, заключается в использовании небольшого файла с метаданными (манифеста) на сервере. Этот файл не содержит самого обновляемого контента, а лишь информацию о нем.
Что может содержать файл метаданных:
Формат такого файла может быть любым: простой текстовый файл, JSON, XML.
Пример JSON-файла метаданных (update_info.json):
{
"version": "2.1.0",
"last_modified": "2023-10-27T10:30:00Z",
"md5_hash": "a1b2c3d4e5f67890a1b2c3d4e5f67890",
"sha256_hash": "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"file_size": 10240000,
"download_url": "http://example.com/new_app_version.zip"
}
Как это использовать в 1С:
ЧтениеJSON или ЧтениеXML).Этот подход очень гибкий и широко используется, например, самой платформой 1С для обновления конфигураций (файлы .cfu и .mft) или внешними утилитами обновления.
Преимущества: Высокая надежность, гибкость, возможность передачи дополнительной информации (описание, применимость). Недостатки: Требует дополнительной работы на стороне сервера (генерация файла метаданных) и на стороне клиента (парсинг и сравнение).
Протокол HTTP предоставляет встроенные механизмы для условных запросов, которые позволяют клиенту спрашивать сервер, изменился ли ресурс с момента последнего скачивания. Если ресурс не изменился, сервер возвращает статус 304 Not Modified без тела ответа, что позволяет избежать повторной загрузки.
Эти механизмы особенно эффективны, поскольку они являются частью стандарта HTTP и поддерживаются большинством веб-серверов. В 1С их можно использовать через объект HTTPЗапрос и свойство Заголовки.
Механизм основан на дате последнего изменения файла:
Last-Modified: При первой успешной загрузке файла сервер в ответных заголовках отправляет Last-Modified, указывая дату и время последнего изменения файла на сервере.If-Modified-Since: При следующем запросе того же файла 1С формирует HTTPЗапрос и добавляет в него заголовок If-Modified-Since, передавая в нем сохраненную дату.304 Not Modified без тела ответа. В противном случае, если файл изменился, сервер вернет статус 200 OK и полное тело файла.Пример в 1С:
// Предполагаем, что СоединениеHTTP уже создано
// и у нас есть путь к файлу на сервере (ПутьНаСервере)
// и сохраненная дата последнего изменения (ДатаПоследнегоИзмененияФайла)
// Шаг 1: Получение заголовка Last-Modified при первой загрузке
// (После успешной загрузки файла, например, в Ссылка.Загрузить(HTTPЗапрос, ИмяЛокальногоФайла))
// ПолученныеЗаголовки = СоединениеHTTP.ПолучитьЗаголовки(HTTPЗапрос);
// Если ПолученныеЗаголовки.Заголовки.Свойство("last-modified") Тогда
// ДатаПоследнегоИзмененияФайла = ПолученныеЗаголовки.Заголовки["last-modified"];
// КонецЕсли;
// Шаг 2: Создание запроса с If-Modified-Since для проверки
ЗапросHTTP = Новый HTTPЗапрос(ПутьНаСервере);
Если ЗначениеЗаполнено(ДатаПоследнегоИзмененияФайла) Тогда
// Форматируем дату в нужный HTTP-формат (RFC 1123)
// 1С не имеет встроенной функции для этого, можно использовать свою
// или попробовать передать Объект.Дата в заголовок, но лучше явно
// Для примера: Thu, 01 Dec 1994 16:00:00 GMT
ЗапросHTTP.Заголовки.Вставить("If-Modified-Since", ФорматироватьДатуДляHTTP(ДатаПоследнегоИзмененияФайла));
КонецЕсли;
Попытка
ОтветHTTP = СоединениеHTTP.ОтправитьДляПолученияДвоичныхДанных(ЗапросHTTP);
Если ОтветHTTP.КодСостояния = 304 Тогда
Сообщить("Файл не изменялся с " + ДатаПоследнегоИзмененияФайла + ". Используем локальную копию.");
// Файл не изменился, дальнейшие действия не требуются
Возврат Истина;
ИначеЕсли ОтветHTTP.КодСостояния = 200 Тогда
Сообщить("Файл изменился или это первая загрузка. Загружаем новую версию.");
// Сохраняем новую версию и новую дату Last-Modified
// ... (код для сохранения файла) ...
// ПолученныеЗаголовки = ОтветHTTP.Заголовки;
// Если ПолученныеЗаголовки.Свойство("last-modified") Тогда
// ДатаПоследнегоИзмененияФайла = ПолученныеЗаголовки["last-modified"];
// КонецЕсли;
Возврат Ложь;
Иначе
Сообщить("Ошибка HTTP: " + ОтветHTTP.КодСостояния + " - " + ОтветHTTP.Статус);
Возврат Ложь;
КонецЕсли;
Исключение
Сообщить("Ошибка при выполнении HTTP-запроса: " + ОписаниеОшибки());
Возврат Ложь;
КонецПопытки;
// Пример функции форматирования даты (может потребовать доработки для точного соответствия RFC 1123)
// В реальных условиях лучше использовать готовые библиотеки или компоненты,
// если требуется строгое соответствие формату.
// Для простоты, 1С может иногда сама преобразовать дату, но надежнее - явно.
Функция ФорматироватьДатуДляHTTP(Дата)
Возврат Формат(Дата, "ДФ=yyyy-MM-dd'T'HH:mm:ss'Z'"); // Пример: "2023-10-27T10:30:00Z"
// Реальный HTTP-формат сложнее: "Thu, 01 Dec 1994 16:00:00 GMT"
// Для корректной работы сервер должен понимать этот формат.
// Часто 1С HTTP-сервисы могут быть менее строги к форматам дат.
КонецФункции
Примечание: Форматирование даты для HTTP-заголовков (RFC 1123) может быть нетривиальной задачей в 1С. Убедитесь, что формат даты, который вы отправляете в If-Modified-Since, корректно интерпретируется вашим веб-сервером.
ETag (entity tag) — это более надежный идентификатор конкретной версии ресурса. Это своего рода "отпечаток пальца" файла. Если файл на сервере изменяется даже на один бит, его ETag должен измениться. Этот механизм более точен, чем Last-Modified, так как дата изменения может оставаться прежней при быстрых изменениях контента, или же изменения могут быть неважными (например, изменение прав доступа к файлу), но при этом вы не хотите перекачивать файл. Веб-сервер сам генерирует ETag.
ETag: При первой загрузке файла сервер в ответных заголовках отправляет ETag.ETag: Ваша система 1С сохраняет этот ETag вместе с локальной копией файла.If-None-Match: При следующем запросе того же файла 1С формирует HTTPЗапрос и добавляет в него заголовок If-None-Match, передавая в нем сохраненный ETag.ETag, полученный от клиента, совпадает с текущим ETag ресурса на сервере, сервер вернет HTTP-статус 304 Not Modified без тела ответа. Если ETag не совпадает, сервер вернет статус 200 OK и полное тело файла.Преимущества ETag:
ETag на основе хеш-суммы содержимого, даты изменения или любого другого алгоритма, который считает нужным.Если сервер поддерживает как If-Modified-Since, так и If-None-Match, то If-None-Match имеет приоритет.
Пример в 1С:
// Предполагаем, что СоединениеHTTP уже создано
// и у нас есть путь к файлу на сервере (ПутьНаСервере)
// и сохраненный ETag (СохраненныйETag)
// Шаг 1: Получение заголовка ETag при первой загрузке
// (После успешной загрузки файла)
// ПолученныеЗаголовки = ОтветHTTP.Заголовки;
// Если ПолученныеЗаголовки.Свойство("etag") Тогда
// СохраненныйETag = ПолученныеЗаголовки["etag"];
// КонецЕсли;
// Шаг 2: Создание запроса с If-None-Match для проверки
ЗапросHTTP = Новый HTTPЗапрос(ПутьНаСервере);
Если ЗначениеЗаполнено(СохраненныйETag) Тогда
ЗапросHTTP.Заголовки.Вставить("If-None-Match", СохраненныйETag);
КонецЕсли;
Попытка
ОтветHTTP = СоединениеHTTP.ОтправитьДляПолученияДвоичныхДанных(ЗапросHTTP);
Если ОтветHTTP.КодСостояния = 304 Тогда
Сообщить("Файл не изменялся. Используем локальную копию.");
Возврат Истина;
ИначеЕсли ОтветHTTP.КодСостояния = 200 Тогда
Сообщить("Файл изменился или это первая загрузка. Загружаем новую версию.");
// Сохраняем новую версию и новый ETag
// ... (код для сохранения файла) ...
// Если ОтветHTTP.Заголовки.Свойство("etag") Тогда
// СохраненныйETag = ОтветHTTP.Заголовки["etag"];
// КонецЕсли;
Возврат Ложь;
Иначе
Сообщить("Ошибка HTTP: " + ОтветHTTP.КодСостояния + " - " + ОтветHTTP.Статус);
Возврат Ложь;
КонецЕсли;
Исключение
Сообщить("Ошибка при выполнении HTTP-запроса: " + ОписаниеОшибки());
Возврат Ложь;
КонецПопытки;
Важный момент: Как было отмечено в дискуссии, HTTP-сервисы 1С поддерживают стандартные HTTP-заголовки, и это открывает широкие возможности для реализации сложной логики взаимодействия с веб-серверами.
Если формат файла известен и версия или ключевая информация, по которой можно определить изменение, хранится в фиксированном месте (например, в заголовке файла), мы можем запросить только эту часть файла.
Заголовок Range позволяет клиенту запросить только часть ресурса (например, первые 1024 байта). Сервер, поддерживающий этот заголовок, ответит статусом 206 Partial Content и телом, содержащим запрошенный диапазон байтов.
Как это использовать:
HTTPЗапрос и добавляем заголовок Range, например: ЗапросHTTP.Заголовки.Вставить("Range", "bytes=0-1023"); для получения первых 1 КБ.Преимущества: Экономия трафика при наличии специфического формата файла. Недостатки: Требует глубокого знания формата файла, не универсально.
ETag/If-None-Match.Last-Modified, ETag, размер, хеш-суммы из манифестов) вместе с локальной копией файла. Для этого подойдут регистры сведений, обычные файлы на диске или хранилище дополнительной информации об объектах.HEAD-запросы, If-Modified-Since, If-None-Match и Range.
Функция ВычислитьХешЛокальногоФайла(ПутьКФайлу, МетодХеширования = "MD5") Экспорт
// Проверяем существование файла
Если Не ФайлСуществует(ПутьКФайлу) Тогда
Возврат Неопределено;
КонецЕсли;
ХешФункцияДляВычисления;
Если МетодХеширования = "MD5" Тогда
ХешФункцияДляВычисления = ХешФункция.MD5;
ИначеЕсли МетодХеширования = "SHA1" Тогда
ХешФункцияДляВычисления = ХешФункция.SHA1;
ИначеЕсли МетодХеширования = "SHA256" Тогда
ХешФункцияДляВычисления = ХешФункция.SHA256;
Иначе
// Можно добавить другие методы или выбросить исключение
Возврат Неопределено;
КонецЕсли;
Хеш = Новый ХешированиеДанных(ХешФункцияДляВычисления);
Хеш.ДобавитьФайл(ПутьКФайлу);
Возврат Строка(Хеш.ХешСумма);
КонецФункции
Применение этих подходов позволит вам создавать более эффективные и отказоустойчивые системы загрузки и обновления файлов в 1С, минимизируя нежелательные повторные скачки и оптимизируя использование сетевых ресурсов.