Res.download() working with html form submit but n

2020-03-31 03:06发布

问题:

I am writing a small app that submits information from a React app to an Express server's "/download" API where it then writes a new file to the local file system and downloads the newly created file on the client side using the Express res.download() in the fs.writeFile() callback.

I've been using a regular html form submit to post the data but due to an increase in complexity I've switched over using Axios and it's no longer working.

The strange thing is only the download on the client side seems to have stopped working. Writing the file works just fine, all the console logging is the same ("File downloaded!" logs below). When I switch back to the form submit it continues to work so the only change is using Axios to send the post request. As far as I'm aware there shouldn't be any difference between the two once the data gets there but I'm hoping someone has greater insight into this than I.

In addition to testing between form and Axios post requests I've also tried changing the content-type of the Axios request to be "x-www-form-urlencoded" from "application/json" thinking that matching up the content type to what the form was sending might be the answer

The below is the relevant code snippets from the app in question:

server.js (Node JS)

app.post('/download', (req, res) => {
  console.log("Requst data");
  console.log(req.body.html);


  fs.writeFile("./dist/test.txt", res.body.test,
    (err) => {
      if(err) {
        return console.log(err);
      } else{
        console.log("The file was saved!");
      }

      let file = __dirname + '/text.txt';
      /*This is the issue, the file is not downloading client side for the Axios iteration below*/
      res.download(file,(err)=>{
        if(err){
          console.log(err);
        } else {
          console.log(file);
          /*This logs for both View.js iterations below*/
          console.log("File downloaded!");
        }
      });
    });
})

App.js (React)

handleSubmit(e){
    e.preventDefault();

    axios.post(`/download`, {test: "test"})
      .then(res => {
        console.log("REQUEST SENT");
      })
      .catch((error) => {
        console.log(error);
      });
}

render(){
      return(
        <div>
          <View handleSubmit={this.handleSubmit} />
        </div>
      )
}

View.js (React)

This works:

render(){
      return(
        <form action="/download" method="post">
            <input type="submit">
        </form>
      )
}

This doesn't initiate the download on the client side, but otherwise works just fine:

render(){
      return(
        <form onSubmit={this.props.handleSubmit}>
            <input type="submit">
        </form>
      )
}

I'm not getting any error, everything seems to be working properly except the download on the client side.

The expected result is that the file downloads on the client side using Axios but that's not the case.

Update: Bump, not getting any traction on this

回答1:

Actually, you CAN download file in Ajax POST request, with some blob operation. Example code is listed below, with explanation comment:

handleSubmit(e){
  var req = new XMLHttpRequest();
  req.open('POST', '/download', true); // Open an async AJAX request.
  req.setRequestHeader('Content-Type', 'application/json'); // Send JSON due to the {test: "test"} in question
  req.responseType = 'blob'; // Define the expected data as blob
  req.onreadystatechange = function () {
    if (req.readyState === 4) {
      if (req.status === 200) { // When data is received successfully
        var data = req.response;
        var defaultFilename = 'default.pdf';
        // Or, you can get filename sent from backend through req.getResponseHeader('Content-Disposition')
        if (typeof window.navigator.msSaveBlob === 'function') {
          // If it is IE that support download blob directly.
          window.navigator.msSaveBlob(data, defaultFilename);
        } else {
          var blob = data;
          var link = document.createElement('a');
          link.href = window.URL.createObjectURL(blob);
          link.download = defaultFilename;

          document.body.appendChild(link);

          link.click(); // create an <a> element and simulate the click operation.
        }
      }
    }
  };
  req.send(JSON.stringify({test: 'test'}));
}

For backend, there is nothing special, just a plain res.download statement:

app.post('/download', function(req, res) {
  res.download('./example.pdf');
});

For axios, the frontend code would look like:

axios.post(`/download`, {test: "test"}, {responseType: 'blob'})
  .then(function(res) {
        ...
        var data = new Blob([res.data]);
        if (typeof window.navigator.msSaveBlob === 'function') {
          // If it is IE that support download blob directly.
          window.navigator.msSaveBlob(data, defaultFilename);
        } else {
          var blob = data;
          var link = document.createElement('a');
          link.href = window.URL.createObjectURL(blob);
          link.download = defaultFilename;

          document.body.appendChild(link);

          link.click(); // create an <a> element and simulate the click operation.
        }
  })
  .catch((error) => {
    console.log(error);
  });


回答2:

The browser won't handle the response of a POST request as a file download, so you either need to make another request or handle the response differently.

Below is an example of making another request, to go fetch the file after it's been created on the back-end. The other answer gives a nice example of using the Blob API to process the response data directly from the POST request.

 axios.post(`/download`, {test: "test"})
  .then(res => {
    console.log("REQUEST SENT");

    // Send back an identifier, or whatever so that you can then
    // retrieve the file with another request. 

    // res.id is hypothetical, you simply need to be able to access the file
    // whether by filename, id, however you want.
    window.open("/download?id=" + res.id);
  })
  .catch((error) => {
    console.log(error);
  });