Как проверить, загрузился ли файл с CDN: браузер, curl и PHP

Проверяем CDN-ссылку на трех уровнях: глазами браузера, независимым запросом curl и серверной проверкой на PHP. С примерами команд, кода и критериями успеха.

Проверить CDN-ссылку можно, но сначала нужно определить, что считать успехом. Ответ 200 OK подтверждает получение HTTP-ответа, однако не доказывает, что браузер применил CSS, исполнил JavaScript или показал изображение. PHP на сервере тоже не видит, завершилась ли загрузка у пользователя: он может лишь сделать собственный запрос к тому же адресу.

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

Что именно проверять

У проверки есть несколько уровней:

  • сеть: DNS разрешается, TLS-соединение устанавливается, ответ приходит вовремя;
  • HTTP: получен ожидаемый код, обычно 200 или 206;
  • содержимое: изображение не заменено HTML-ошибкой, а CSS не отдан как text/html;
  • целостность: размер, сигнатура или контрольная сумма совпадают с ожидаемыми;
  • результат в браузере: ресурс загрузился и был использован страницей.

Для критичного файла одного статуса недостаточно. CDN или промежуточный прокси может вернуть страницу ошибки с кодом 200, устаревший объект из кэша либо неверный MIME-тип.

Проверка в браузере

Откройте инструменты разработчика, вкладку Network, включите Disable cache и перезагрузите страницу. Найдите ресурс по имени или CDN-домену и проверьте:

  1. итоговый URL и цепочку редиректов;
  2. статус ответа;
  3. Content-Type;
  4. объём переданных данных;
  5. время ожидания и загрузки;
  6. сообщения в Console о CORS, mixed content, MIME type или Content Security Policy.

Столбец Size помогает отличить сетевую передачу от memory cache и disk cache. При нестабильном сбое повторите загрузку в приватном окне и из другой сети: так проще отделить локальный кэш, расширения и маршрут провайдера от проблемы CDN.

Для изображения можно проверить события элемента:

<img id="hero" src="https://cdn.example.com/hero.webp" alt="">
<script>
const image = document.getElementById('hero');
image.addEventListener('load', () => console.log('CDN image loaded'));
image.addEventListener('error', () => console.error('CDN image failed'));
</script>

Если обработчики подключаются после возможной загрузки, дополнительно проверьте image.complete и image.naturalWidth > 0. Для CSS и JavaScript используйте события load и error соответствующих элементов.

Запрос fetch() к другому домену может быть заблокирован CORS, хотя обычный <img> отображается. Поэтому fetch() не является универсальной заменой проверке ресурса. И наоборот, успешный fetch() не доказывает, что файл применён страницей.

curl: независимый запрос

Начните с заголовков:

curl -sS -I -L \
  --connect-timeout 5 \
  --max-time 15 \
  --max-redirs 5 \
  https://cdn.example.com/assets/app.css

-L следует за редиректами, --connect-timeout ограничивает установку соединения, --max-time — весь запрос. Проверьте итоговый статус, Content-Type, Content-Length, ETag, Last-Modified и параметры кэша. Не делайте вывод по одному X-Cache: названия и значения диагностических заголовков зависят от поставщика.

Метод HEAD не всегда обрабатывается так же, как GET. Если получен 403, 405 или результат отличается от браузера, загрузите файл:

curl -sS -L \
  --fail-with-body \
  --connect-timeout 5 \
  --max-time 30 \
  -o /tmp/cdn-app.css \
  -w 'code=%{http_code} type=%{content_type} bytes=%{size_download} time=%{time_total}\n' \
  https://cdn.example.com/assets/app.css

Затем проверьте формат и при необходимости хеш:

file /tmp/cdn-app.css
sha256sum /tmp/cdn-app.css

Контрольная сумма подходит для версионированного неизменяемого файла. Для динамически собираемого объекта лучше проверять формат, минимальный размер или ожидаемый маркер.

Если curl успешен, а браузер нет, сравните URL, cookie, Origin, Referer, IPv4/IPv6 и сеть. Причина может находиться в CORS, CSP, авторизации, географических правилах или различиях маршрута.

PHP: только для заранее заданного URL

Серверную проверку безопаснее строить вокруг заранее известного адреса, а не принимать URL от посетителя. Ниже URL задан константой, разрешены только HTTPS-запросы, проверка TLS включена, а переходы по редиректам запрещены:

const CDN_PROBE_URL = 'https://cdn.example.com/assets/app.css';

$ch = curl_init(CDN_PROBE_URL);
curl_setopt_array($ch, [
    CURLOPT_NOBODY => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => false,
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT => 15,
    CURLOPT_SSL_VERIFYPEER => true,
    CURLOPT_SSL_VERIFYHOST => 2,
    CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
    CURLOPT_USERAGENT => 'CDN-check/1.0',
]);

curl_exec($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
curl_close($ch);

$code = isset($info['http_code']) ? (int) $info['http_code'] : 0;
$ok = $error === '' && $code >= 200 && $code < 300;

Этот пример нельзя превращать в публичный «проверить любой URL» без отдельной модели безопасности. Произвольный адрес создаёт риск SSRF: сервер могут заставить обратиться к локальным, облачным служебным или иным закрытым ресурсам. Если набор файлов меняется, храните разрешённые URL в конфигурации приложения и не принимайте адрес назначения из параметров запроса. Не включайте CURLOPT_FOLLOWLOCATION для такого теста: редирект способен увести запрос с разрешённого CDN-адреса на другое назначение.

Проверка подтверждает только то, что сервер с PHP выполнил запрос и получил определённый ответ. Она не подтверждает загрузку файла у пользователя. Если HEAD не поддерживается, для фиксированного URL можно отдельно реализовать ограниченный GET, не передавая полученное тело посетителю и контролируя максимальный объём на уровне приложения и инфраструктуры.

Почему результаты расходятся

PHP работает из дата-центра сервера, пользователь приходит через другого провайдера, DNS-резолвер и CDN-узел. Проверки расходятся, если один маршрут использует IPv6, CDN применяет географические правила, браузер отправляет особые заголовки, объект взят из локального кэша либо CORS/CSP блокирует использование успешного ответа.

Поэтому серверный тест нельзя подписывать как «файл загрузился у пользователя». Корректная формулировка: «URL доступен из точки проверки; получены такой-то код, тип и время ответа».

Рабочий алгоритм

  1. Воспроизведите проблему в Network и Console без локального кэша.
  2. Зафиксируйте точный URL, время, код, тип и редиректы.
  3. Повторите запрос через curl с лимитами времени.
  4. Если HEAD сомнителен, выполните GET и проверьте тело.
  5. Для фиксированного URL запустите PHP-проверку с сервера приложения.
  6. Сравните DNS, IPv4/IPv6, заголовки и маршруты.
  7. Для постоянного контроля задайте ожидаемый код, тип, размер или маркер, а не «любой ответ».

Разовый тест отвечает на вопрос «работает ли сейчас из этой точки». Если CDN-файл критичен, добавьте в Web-Puls отдельную проверку его публичного URL или страницы, которая от него зависит: ручная диагностика превратится в регулярное наблюдение с историей сбоев.

Источники

Проверьте свой сайт прямо сейчас

Введите адрес сайта: Web-Puls покажет HTTP-код, время ответа и базовую диагностику. Для постоянного контроля можно подключить мониторинг.

Хотите видеть такую историю по своему сайту?

Добавьте сайт в Web-Puls: мы будем проверять доступность, SSL и содержимое страницы.