company_banner

Как обойти ограничение браузера и прикрепить сразу два файла и более: мультидобавление файлов

    Привет, Хабр!

    Давайте решим нетривиальную задачу. Представьте, что вам нужно скачать данные через интерфейс элементарным способом, например, кликнуть по кнопке «Скачать файлы».

    Возьмём по умолчанию Chrome v.88. Задача звучит так:

    • Сгенерировать файлы на стороне клиента.
    • Скачать все сгенерированные файлы одним кликом.

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


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

    Вот небольшой пример скачивания с предварительным сжатием из документации:

    var zip = new JSZip();
    zip.file("Hello.txt", "Hello World\n");
    var img = zip.folder("images");
    img.file("smile.gif", imgData, {base64: true}); zip.generateAsync({type:"blob"}).then(function(content) {
    // see FileSaver.js
    saveAs(content, "example.zip"); });

    «А где тут нетривиальность?» — спросите вы. И будете правы. А если речь идёт об одновременном скачивании с сайта двух, трёх или десяти файлов? Например: есть лист в селекте, в котором можно выбрать определённое количество файлов для скачивания. Введём дополнительное условие: у пользователя нет установленного архиватора, поэтому вариант со сжатием в архив отбрасываем. Как решить такую задачу?

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

    1. Заходим в настройки сайтов: chrome://settings/content.
    2. Переходим в доступ дополнительных прав (Additional permissions).
    3. Выбираем Automatic downloads.
    4. Добавляем необходимый сайт в категорию Allow.




    Отлично, теперь ваш браузер стал менее безопасным позволяет скачивать на конкретном сайте сразу несколько файлов.

    Подход #1 — FileReader


    Первый подход рассмотрим на примере генерации файлов с помощью FileReader и API для чтения base64. Отмечу сразу, что у FileReader довольно обширное API, поэтому выбирайте то, что больше нравится: text, arrayBuffer или binaryString.

    (function () {
      const button = document.getElementById("download_with_reader");
      const content = ["content-1", "content-2", "content-3"];
        const createLink = () => {
        let link = document.createElement('a');
        link.download = 'hello.txt';
        return link;
      }
        const generateBlob = () => {
        for (const [index, value] of content.entries()) {
          const blob = new Blob([value], { type: "text/plain" });
          download(blob, index);
        }
      }
      const download = (blob, index) => {
        const link = createLink();
        let reader = new FileReader();
        reader.readAsDataURL(blob);
        reader.onload = function () {
          link.href = reader.result;
          link.download = `content-${index+1}.txt`;
          link.click();
        }
      }
        button.addEventListener("click", generateBlob);
    }) ();
    

    [код на Gitlab]

    Подход #2 — createObjectURL


    А ещё можно использовать createObjectURL — он позволяет хранить File-объекты или Blob-объекты.

    (function () {
      const button = document.getElementById("download_with_url_object");
      const content = ["content-1", "content-2", "content-3"];
      
      const createLink = () => {
        let link = document.createElement('a');
        link.download = 'hello.txt';
        return link;
      }
      const generateBlob = () => {
        for (const [index, value] of content.entries()) {
          const blob = new Blob([value], { type: "text/plain" });
          download(blob, index);
        }
      }
      const download = (blob, index) => {
        const link = createLink();
        link.href = URL.createObjectURL(blob);
        link.download = `content-${index+1}.txt`;
        link.click();
        URL.revokeObjectURL(link.href);
      }
       button.addEventListener("click", generateBlob);
    }) ();
    

    [код на Gitlab]

    Подход #3 — Скачивание по URL


    Два варианта выше генерируют файлы на стороне клиента. Конечно, так будет не всегда, время от времени мы получаем файлы со стороны бэкенда по прямым ссылкам. Это можно реализовать с помощью скачивания по URL. Chrome требует, чтобы была задержка, поэтому особенностью этого метода и станет реализация искусственной задержки.

    (function () {
    (function () {
      const button = document.getElementById("download_with_request");
      const urls = ["images/image-1.jpg", "images/image-2.jpg", "images/image-3.jpg"];
      
      const delay = () => new Promise(resolve => setTimeout(resolve, 1000));
      
      const downloadWithRequest = async () => {
        for await (const [index, url] of urls.entries()) {
          await delay();
          const link = document.createElement("a");
          link.href = url;
          link.download = `image-${index+1}`;
          link.click();
        }
      }
      
      button.addEventListener("click", downloadWithRequest);
    }) ();
    
    

    [код на Gitlab]

    Итого


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

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    Какой подход кажется вам наиболее логичным?

    • 13,6%С FileReader проще всего3
    • 4,6%CreateObjectURL лучше1
    • 72,7%Правильнее скачивать по URL, когда есть возможность16
    • 9,1%У меня получилось решить задачку своим способом, напишу в комментарии2
    Яндекс.Практикум
    Помогаем людям расти

    Комментарии 15

      +1

      Надо посмотреть, как это реализовано у Микрософт, когда скачиваешь с сайта обновлений несколько файлов сразу — они в результате скачиваются без всяких там дополнительных запросов (но проверял в файрфоксе, а не в хроме, может, ФФ либеральнее). Я бы сделал через создание iframe или XHR раз в Х секунд, пока каждый не запустился, в каждом вызывается один запрос на скачивание одного файла, результат — браузер получает команду от страницы "скачать раз файл, подождать, скачать два файл" и не упирается рогом. (PS: я не разработчик на Javascript, поэтому могу немного напутать в терминах)

        +1
        Да, кстати, гляну, как у Микрософт, интересно.
        Неплохая идея с iframe надо бы попробовать.
        С точки зрения концепции одного потока js идея с «XHR раз в Х» схожа с реализацией в «Подход #3», думаю можно и так )
        +4
        Введём дополнительное условие: у пользователя нет установленного архиватора, поэтому вариант со сжатием в архив отбрасываем.

        Но на Windows 95 у пользователя и Хрома может не оказаться. И настройки в Хроме пользователю придется менять с "безопасных". Так что вариант "скачать архивом" выглядит как более жизнеспособный. (В какой системе нет возможности работать с зип-архивом "искаропки"?)

          +2
          Например, в некоторых сборках Линукс этот полезный инструмент может отсутствовать )
            +2
            7-zip все заменяет.
              +4
              Пользователи таких некоторых сборок Linux обычно достаточно грамотны, чтобы справится с zip архивом. Но, за идею, спасибо.
                +1
                не за что )
                +2

                В таких сборках ни guiя может не быть. Я уж не говорю о смелом предположении, что у пользователя нет архиватора, но Хром обязательно есть.

                  0
                  Эдак мы дойдем до предположения, что у пользователя и компьютера нет. :)
                    +1

                    Не я это начал.

              +1
              В yandex.mail можно скачать все вложения в виде zip-архива, но при его распаковке в Linux портятся имена файлов с русскими буквами. Техподдержка сказала, что у них все нормально.
                +2

                Для зип архивов, если мне память не изменяет, нет спецификации на кодировку имени файла. Поэтому имена там скорее всего в CP1251. Когда-то давно в gmail даже спрашивало, в какой кодировке формировать архив.

                  0
                  Ну вообще то есть
                  If general purpose bit 11 is set, the filename and comment MUST support The Unicode Standard, Version 4.1.0 or greater using the character encoding form defined by the UTF-8 storage specification.
                  pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
                    0

                    Good point. Только появилось оно там довольно поздно. На 2017 год, как минимум, не было поддержки в популярном 7zip. Ну и вот этот коммент сильно ограничивает в применении 11 бита:


                    Note that, for backward compatibility, bit 11 SHOULD only be used if the native character set of the paths and comments being zipped up are already in UTF-8.
                      0
                      Да, похоже этот бит никто не использует. Winrar умеет добавлять «Unicode Path Extra Field (0x7075)» — альтернативный метод задания имен в utf8. И вот такие архивы в линуксе нормально распаковываются.

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

              Самое читаемое