Home > Blockchain >  Express.JS: Force download of PDF file by cross-origin URL
Express.JS: Force download of PDF file by cross-origin URL

Time:09-29

EDIT:

The title has been changed so that anyone can suggest an alternative solution that achieves the same result with similar technology and platform. Not necessary has to be res.attachment.

I was trying to achieve a force download of PDF file by cross-origin URL. The code seems to work as expected but the downloaded file is ZERO BYTES, why?

On server:

app.get("/download", (req, res) => {
    res.type("application/octet-stream");
    // Note that the PDF URL is Cross-Origin.
    res.attachment("https://cross-origin-domain-name.com/downloads/fiename.pdf");
    res.send();
});

On HTML:

<a class="a-very-big-button" href="/download">Download PDF</a>

Do I miss anything? I did try out many other options like res.download() and readStream.pipe(res) methods, but most of them require the files to be on the same server. For my app, I need to help my clients to offer a download PDF button based on the URL submitted by them, which could be on their web server. Any advice would be appreciated! Thank you.

CodePudding user response:

res.attachment does take a string as its only argument, but that string is used as a hint to the browser what the filename should be if the user decides to save the file. It does not allow you to specify a URL or filename to fetch.

Because you're not sending any data (res.send() without a Buffer or .write() calls), just a suggestion as to what the filename should be, the download is 0 bytes.

What you could do is pipe a HTTP request to res, which will have your server download and forward the file. The file will not be cached on your server and will 'cost' both upload and download capacity (but no storage).


An example on how to pipe a HTTPS request to a response.

Instead of Node's built-in https.request you could use many other libraries. Most of them support streaming files. These libraries can make it easier to handle errors.

const express = require('express');
const https = require('https');

const app = express();
const url = 'https://full-url-to-your/remote-file.pdf';
const headerAllowList = [
  'content-type', 'content-length', 'last-modified', 'etag'
];

app.use('/', async (req, res, next) => {
  // Create a HTTPS request
  const externalRequest = https.request(url, {
    headers: {
      // You can add headers like authorization or user agent strings here.
      // Accept: '*/*',
      // 'User-Agent': '',
    },
  }, (externalResponse) => {
    // This callback won't start until `.end()` is called.

    // To make life easier on ourselves, we can copy some of the headers
    // that the server sent your Node app and pass them along to the user.
    headerAllowList
      .filter(header => header in externalResponse.headers)
      .forEach(header => res.set(header, externalResponse.headers[header]));

    // If we didn't have content-type in the above headerAllowList, 
    // you could manually tell browser to expect a PDF file.
    // res.set('Content-Type', 'application/pdf');

    // Suggest a filename
    res.attachment('some-file.pdf');

    // Start piping the ReadableStream to Express' res.
    externalResponse.pipe(res);
  });

  externalRequest.on('error', (err) => {
    next(err);
  });

  // Start executing the HTTPS request
  externalRequest.end();
});
app.listen(8000);

If you visit localhost:8000 you'll be served a PDF with a save-file dialog with the suggested filename, served from the specified URL.

  • Related