Устройство медиа-типа
MIME-тип — правильнее называемый медиа-типом — это метка, которую сервер прикрепляет к ответу, чтобы клиент знал, как обработать следующие байты. Он состоит из двух частей, разделённых слэшем: верхнеуровневого типа, такого как text, image, audio, video, font, model или application, и подтипа, называющего конкретный формат. image/png и application/json — оба полные медиа-типы; одного image недостаточно.
Подтипы имеют свои соглашения. Ведущий x- когда-то отмечал экспериментальный тип, как в application/x-tar, и хотя практика устарела, сохранившиеся имена слишком широко распространены, чтобы их менять. Завершающий +suffix говорит, что формат построен на более общем синтаксисе: image/svg+xml — это XML, application/ld+json — это JSON, и парсер может откатиться к базовому синтаксису, когда не распознаёт конкретный формат.
Некоторые типы принимают параметры после точки с запятой. Самый важный — charset, как в text/html; charset=utf-8. Его отсутствие в текстовом ответе провоцирует браузер угадывать кодировку, что — классический источник mojibake, поэтому явное указание UTF-8 всегда стоит лишних символов.
Почему заголовок важнее расширения
Браузер решает, что делать с ответом, на основе заголовка Content-Type, а не имени файла в URL. Отдавая PNG как text/plain, вы увидите стену двоичного мусора; отдавая JSON как text/html, можно заставить fetch-клиента отвергнуть его. Правильный заголовок — это то, что открывает загрузку в нужном приложении и отрисовывает встроенный ресурс вместо скачивания.
Несоответствие имеет и последствия для безопасности. Старые браузеры практиковали MIME-сниффинг, осматривая первые байты ответа и переопределяя заголовок, который считали неверным. Злоумышленник, способный загрузить файл, похожий на HTML, мог заставить выполнить его в origin сайта, даже когда сервер пометил его как изображение. Заголовок X-Content-Type-Options: nosniff отключает это поведение и теперь является стандартной практикой на любой конечной точке, отдающей загруженное пользователями содержимое.
Для скачиваний сочетайте медиа-тип с Content-Disposition: attachment; filename="report.pdf". Медиа-тип описывает, что собой представляют байты, а disposition описывает, что браузер должен с ними сделать — они отвечают на разные вопросы.
Где на самом деле живёт сопоставление
Единой авторитетной таблицы «расширение — тип» не существует, и это удивляет людей. IANA регистрирует сами медиа-типы, но сопоставление от расширения файла к типу — это локальное соглашение, которое ведёт каждый сервер. Apache читает mime.types, nginx имеет свой файл mime.types, а фреймворки поставляются со своими таблицами поиска. Поэтому один и тот же файл .wasm может быть отдан правильно одним сервером и как application/octet-stream другим, который не обновлялся.
application/octet-stream — это общий запасной вариант со значением «произвольные двоичные данные». Это честный ответ, когда формат действительно неизвестен, но он отменяет встроенную отрисовку, поэтому стоит добавить явное сопоставление для любого формата, который ваш сайт регулярно отдаёт. Более новые форматы, такие как AVIF, JXL и WebAssembly, чаще всего отсутствуют в старой конфигурации сервера.