簡體   English   中英

使用 React 和 Axios 從 Express API 下載文件

[英]Download file from Express API using React and Axios

當使用帶有 Express API 的 React 客戶端時,React 客戶端如何下載 Express API 發送的文件?

問題:

  • 如果我在瀏覽器欄中鍵入 url 並按 Enter 文件下載成功。
  • 但是,如果我在我的 React 應用程序中使用 Axios 調用相同的 url,則文件不會下載。

快遞服務器

// 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);
    }
});

反應客戶端

// 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 header 有關。 我嘗試設置(參見我上面的代碼),但 header 沒有發送到客戶端。


不受歡迎的“解決方案”:

  • 在 React 應用程序中:創建一個新a元素,設置其href屬性並通過 JavaScript 觸發click 我正在尋找一個不需要這個 JS hack 的解決方案。

  • 在 React 應用程序中: a with target="_blank"代替 Axios。 但是,這不適合我,因為它會繞過我的 axios 配置設置(API url、身份驗證令牌等)

似乎您必須根據此示例直接告訴 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。 但它似乎需要的主要部分是.then 響應您的 axios 接聽電話。 這樣您仍然可以使用 jwt 驗證用戶的狀態並適當地保護您的文件。

不幸的是,沒有可靠的、跨平台的方法來觸發瀏覽器對正常 web 頁面的下載行為可以滿足這里的要求。 由於您不能在普通 DOM 錨標記上使用帶有內容處置、重定向或數據 URI 的普通 URL,因此我看不到另一種在不創建隱藏a並單擊它的情況下導致下載的方法。 然而,這似乎運作良好(並且確實是流行的實用程序使用的機制,如filesaver.js

在 React 中構建一個粗略的DownloadButton組件來執行此操作非常簡單。 這是一個工作代碼筆,它模擬 Axios 響應,否則從頭到尾工作,除非您想做任何重構。 我正在使用 hooks 和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>
  );
};

至於 ExpressJS 的響應標頭中未顯示的content-disposition ,我不確定是什么問題。 但是,根據ExpressJS 文檔,第二個參數是文件名,它將作為content-disposition header 自動發送,因此您不需要在options參數中自己指定它。 是否顯示其他參數? 如果是這樣,可能在重新定義它時存在沖突options 但是,當使用與您的路線類似的路線在本地運行示例時,我都沒有遇到任何問題。

res.download(路徑 [, 文件名] [, 選項] [, fn])

Express v4.16.0 及更高版本支持可選選項參數。

將路徑中的文件作為“附件”傳輸。 通常,瀏覽器會提示用戶下載。 默認情況下,Content-Disposition header “filename=”參數是路徑(這通常出現在瀏覽器對話框中)。 使用文件名參數覆蓋此默認值。

當發生錯誤或傳輸完成時,該方法調用可選的回調 function fn。 此方法使用 res.sendFile() 來傳輸文件。

可選的 options 參數傳遞到底層 res.sendFile() 調用,並采用完全相同的參數。

您必須使用以下命令在 react 中安裝“js-file-download”庫

npm install --save js-file-download

使用 axios 的反應文件中的代碼如下:

 import download from 'js-file-download';
 downloadFile = () => {
   axios.get("localhost:3000/route/path/url")
     .then(resp => {
            download(resp.data, fileName);
     });
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM