Когда пользователь щелкает обычную HTML-ссылку, например:

<a href="
Войти в полноэкранный режим

Выйти из полноэкранного режима

Браузеры обычно пытаются открыть файл напрямую (встроенным) вместо открытия диалогового окна загрузки файла.

В современных браузерах вы можете использовать HTML5 download атрибут для запроса этого поведения:

<a href=" download>
Войти в полноэкранный режим

Выйти из полноэкранного режима

Вы также можете указать предлагаемое имя файла, которое пользователь увидит в диалоговом окне загрузки:

<a href=" download="myfile.ext">
Войти в полноэкранный режим

Выйти из полноэкранного режима

Если вы укажете только имя файла (без расширения), расширение обычно будет выводиться из исходного имени файла (в этом примере в большинстве браузеров по-прежнему предлагается «myfile.ext»):

<a href=" download="myfile">
Войти в полноэкранный режим

Выйти из полноэкранного режима


Ограничения и обходные пути

Настоящее время, download атрибут поддерживается во всех основных браузерах, но есть важное ограничение: этот атрибут работает только для URL-адресов одного происхождения (протокол, порт и хост должны совпадать), а blob: а также data: схемы.

Кроме download атрибут, вы мало что можете сделать, чтобы вызвать такое поведение со стороны клиента, но вы можете сделать это со стороны сервера.

В том числе Content-Disposition заголовок в ответе HTTP будет иметь аналогичный эффект download атрибут (но без этого ограничения):

Content-Disposition: attachment; filename=file.ext
Войти в полноэкранный режим

Выйти из полноэкранного режима

Вам также необходимо отправить Content-type заголовок и фактическое содержимое файла.

В PHP это будет выглядеть примерно так:

header('Content-Disposition: attachment; filename="downloaded.pdf"');
header('Content-type: application/pdf');
readfile('test.pdf');
Войти в полноэкранный режим

Выйти из полноэкранного режима

Но чтобы избежать снижения производительности, я рекомендую делать это на уровне веб-сервера, а не на уровне приложения.


нгинкс

За nginx это может быть так же просто, как:

add_header Content-Disposition 'attachment; filename="file.ext"';
Войти в полноэкранный режим

Выйти из полноэкранного режима

Примечание: nginx обычно отправляет правильный Content-type заголовок автоматически, но при необходимости вы можете переопределить это:

add_header Content-Type 'application/pdf'
Войти в полноэкранный режим

Выйти из полноэкранного режима

Для более общего случая вы можете отправить общий application/octet-stream заголовок и Content-Disposition без имени файла:

location /myloc {
        if ($request_filename ~* ^.*?\.(pdf|zip|docx)$) {
            add_header Content-Disposition attachment;
            add_header Content-Type application/octet-stream;
        }
    }
Войти в полноэкранный режим

Выйти из полноэкранного режима


Комбинация атрибута загрузки и заголовка Content-Disposition

Если присутствуют и атрибут загрузки, и заголовок Content-Disposition, и оба определяют имя файла, имя файла, определенное в заголовке, будет иметь приоритет.

В случае Content-Disposition: inline заголовок (который указывает браузеру отображать содержимое inline — как часть страницы) атрибут загрузки будет иметь приоритет (для URL-адресов того же происхождения).


Примечание. Это снимок страницы из BetterWays.dev wiki, вы можете найти последнюю (лучше отформатированную) версию здесь: betterways.dev/forceing-a-browser-to-download-a-file.