简体   繁体   中英

Handling the response from `res.download()` on the client side (Rest api, express)

So the express docs have a download function in the following form:

res.download(cvUrl, cvName, function (err) {
  if (err) {
     // ...
  } else {
     // ...
  }
})

I think this would normally trigger the browser to download the file automatically, with the correct filename, as the response headers are correctly set and I'm receiving a file. But I'm handling the download like this:

this.admin.getCv(cvUrl).then(cv => {
    const url = window.URL.createObjectURL(new Blob([cv]));
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute('download', 'test'); // how can I access the filename here?
    document.body.appendChild(link);
    link.click();
})

Is it possible to access the filename (cvName) on the frontend? Adding it as a second parameter seemed like the obvious solution, but didn't work. Any hints would be great,

Thanks,

Nick

**Edit:

import JRS from '../api/jrs';

export default class Admin {
    getCv (applicantId) {
        return JRS.get(`/admin/cvs/${applicantId}`);
    }
}

// JRS.js

import axios from 'axios';

export default axios.create({
    baseURL: 'http://localhost:8080',
    headers: {
        'Content-Type':  'application/json',
    },
    withCredentials: true
});

The download method adds a Content-Disposition header to the response, so you just need to read that.

It looks like your getCv function makes an HTTP request, extracts the body from the response, then resolves a promise with that body.

You'll need to modify the getCv function so it:

  • Reads the Content-Disposition header (how you do that depends on which HTTP API you are using)
  • Extracts the filename from it (something like contentDisposition.match(/filename=(.*)/) )
  • Includes that in the data you are resolving the promise with (which will probably mean passing an object containing the filename and body instead of just the body)

Then you'll need to change your then callback so it handles that object instead of expecting the body directly.


That said, it would probably be easier to just link to the URL directly instead of fetching the data with JSON, converting it to a data: URL, generating a link and triggering a client on it.

This works for accessing the filename and ext, and fixes the problem I was having with axios/streams/unreadable content, in case it helps anyone else.

For the unreadable content, it was important to add the responseType to the request.

// Client side

axios.get('http://localhost:4000/download', {responseType: 'blob'}).then(res => {
    downloadFile(res);
}).catch(err => console.log(err));

const downloadFile = (res) => {
    const contentDisposition = res.headers['content-disposition'];
    const fileName = contentDisposition.split(';')[1].split('=')[1];

    const url = window.URL.createObjectURL(new Blob([res.data]));
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute('download', `${fileName}`);
    document.body.appendChild(link);
    link.click();
}

// Server

app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', 'http://localhost:2000');
    res.setHeader('Access-Control-Expose-Headers', 'Content-Disposition');
    next();
});

app.get('/download', (req, res, next) => {
    const test = 'sample.doc';
    const fileUrl = path.join(__dirname, test);
    const fileName = path.basename(fileUrl);
    res.header('Content-Disposition', `attachment; filename=${fileName}`);
    const myReadableStream = fs.createReadStream(path.join(__dirname, test));
    res.status(200);
    myReadableStream.pipe(res);
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM