Загрузить файл из Express API с помощью React и Axios - PullRequest
1 голос
/ 30 октября 2019

При использовании клиента React с Express API, как клиент React может загрузить файл, отправленный с помощью Express API?

Проблема:

  • Если я наберу URL-адрес в строке браузера и нажму, введите файл успешно загружен.
  • Но если я вызову тот же URL в моем приложении React с помощью Axios, файл не загрузится.

Сервер экспресс-обслуживания

// Route handler for /api/files/testfile
const getFile = async (req, res, next) => {

    // File
    const fileName = 'file.csv';
    const filePath = path.join(__dirname, '/../../public/', fileName);

    // File options
     const options = {
        headers: {
            'x-timestamp': Date.now(),
            'x-sent': true,
            'content-disposition': "attachment; filename=" + fileName, // gets ignored
            'content-type': "text/csv"
        }
    }

    try {
        res.download(
            filePath,
            fileName,
            options
        );
        console.log("File sent successfully!");
    }
    catch (error) {
        console.error("File could not be sent!");
        next(error);
    }
});

React client

// When the user clicks the "Download as CSV" button
handleDownloadFile = () => {
    axios
        .get(
            `/api/files/testfile`, {
                responseType: 'blob',
                headers: {
                    'Content-Type': 'text/csv',
                }
            }
        )
        .then(response => {
            console.log(response.headers); // does not include content-disposition
            console.log("File downloading successfully!");
        })
        .catch( (error) => {
            console.error("File could not be downloaded:", error);
        });
}

Я прочитал, что это может быть связано с заголовком content-disposition. Я попытался установить (см. Мой код выше), но заголовок не отправляется клиенту.


Нежелательные "решения":

  • В приложении React: создайте новый элемент a, установите его атрибут href и активируйте click через JavaScript. Я ищу решение, которое не требует этого взлома JS.

  • В приложении React: используйте a с target="_blank" вместо Axios. Однако это мне не подходит, так как это обойдёт мои настройки конфигурации axios (URL-адрес API, токен аутентификации и т. Д.)

Ответы [ 2 ]

1 голос
/ 30 октября 2019

Похоже, что вы должны указать axios, где файл напрямую основан на этом примере:

axios({
  url: 'http://localhost:5000/static/example.pdf',
  method: 'GET',
  responseType: 'blob', // important
}).then((response) => {
  const url = window.URL.createObjectURL(new Blob([response.data]));
  const link = document.createElement('a');
  link.href = url;
  link.setAttribute('download', 'file.pdf');
  document.body.appendChild(link);
  link.click();
});

Я мог бы предположить, что вы можете просто изменить ответ на API для возврата BLOB-объекта с использованием нового BLOB-объектадля файла. Но основная часть того, что, по-видимому, требуется, - это ответ на ваш аксиос. Таким образом, вы все равно можете аутентифицировать статус пользователя с помощью jwt и соответствующим образом защитить ваши файлы.

1 голос
/ 30 октября 2019

К сожалению, нет надежных кроссплатформенных методов для запуска поведения загрузки браузера для обычных веб-страниц, которые бы отвечали всем требованиям. Поскольку вы не можете использовать обычный URL с расположением содержимого, перенаправлениями или URI данных в обычном теге привязки DOM, я не вижу другого способа вызвать загрузку без создания скрытого a и щелчка по нему. Тем не менее, это, кажется, работает хорошо (и это действительно механизм, используемый популярными утилитами, такими как filesaver.js )

Создание сырого DownloadButton компонента для этого в React довольно просто. Вот рабочий код , который высмеивает ответ Axios и в остальном работает от начала до конца, за исключением любого рефакторинга, который вы хотели бы сделать. Я использую крючки и async/await для моего здравомыслия / ясности, но ни один из них не является строго необходимым. Он использует атрибут download для тегов привязки, что хорошо поддерживается в современных браузерах.

function getFileNameFromContentDisposition(contentDisposition) {
  if (!contentDisposition) return null;

  const match = contentDisposition.match(/filename="?([^"]+)"?/);

  return match ? match[1] : null;
}

const DownloadButton = ({ children, fileName, loadingText }) => {
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);

  const handleClick = async () => {
    setLoading(true);
    setError(null);

    let res = null;

    try {
      // add any additional headers, such as authorization, as the second parameter to get below
      // also, remember to use responseType: 'blob' if working with blobs instead, and use res.blob() instead of res.data below
      res = await axios.get(`/api/files/${fileName}`);
      setLoading(false);
    } catch (err) {
      setLoading(false);
      setError(err);
      return;
    }

    const data = res.data; // or res.blob() if using blob responses

    const url = window.URL.createObjectURL(
      new Blob([data], {
        type: res.headers["content-type"]
      })
    );

    const actualFileName = getFileNameFromContentDisposition(
      res.headers["content-disposition"]
    );

    // uses the download attribute on a temporary anchor to trigger the browser
    // download behavior. if you need wider compatibility, you can replace this
    // part with a library such as filesaver.js
    const link = document.createElement("a");
    link.href = url;
    link.setAttribute("download", actualFileName);
    document.body.appendChild(link);
    link.click();
    link.parentNode.removeChild(link);
  };

  if (error) {
    return (<div>Unable to download file: {error.message}</div>);
  }

  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? loadingText || "Please wait..." : children}
    </button>
  );
};

Что касается content-disposition, не отображаемого в заголовках ответа от ExpressJS, яЯ не уверен, в чем проблема. Однако, согласно документам ExpressJS , вторым параметром является имя файла, которое будет автоматически отправлено в виде заголовка content-disposition, поэтому вам не нужно указывать его самостоятельно в параметре options. Другие параметры отображаются? Если это так, возможно, существует конфликт в переопределении этого options. Тем не менее, я не испытываю проблем в любом случае, когда запускаю пример локально с маршрутом, похожим на ваш.

res.download (путь [, имя файла] [, опции] [, fn])

Опциональный аргумент опций поддерживается Express v4.16.0 и далее.

Передает файл по пути как «вложение». Как правило, браузеры предлагают пользователю загрузить файл. По умолчанию параметр «filename =» заголовка Content-Disposition - это путь (обычно он отображается в диалоговом окне браузера). Переопределите это значение по умолчанию с помощью параметра имени файла.

Когда происходит ошибка или передача завершена, метод вызывает дополнительную функцию обратного вызова fn. Этот метод использует res.sendFile () для передачи файла.

Необязательный аргумент параметров передается базовому вызову res.sendFile () и принимает те же параметры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...