0

Goal: Try to download a pdf file from Amazon S3 to my local machine via a NodeJS/VueJS application without creating a file on the server's filesystem.

Server: NodeJs(v 18.9.0) Express (4.17.1)

Middleware function that retrieves the file from S3 and converts the stream into a base64 string and sends that string to the client:

const filename = 'lets_go_to_the_snackbar.pdf';

const s3 = new AWS.S3(some access parameters);
const params =  {
    Bucket: do_not_kick_this_bucket,
    Key: `yellowbrickroad/${filename}`
}

try {
    const data = await s3
        .getObject(params)
        .promise();

    const byte_string = Buffer.from(data.Body).toString('base64');
    res.send(byte_string);
} catch (err) {
    console.log(err);
}

Client: VueJS( v 3.2.33)

Function in component receives byte string via an axios (v 0.26.1) GET call to the server. The code to download is as follows:

getPdfContent: async function (filename) {
    const resp = await AxiosService.getPdf(filename) // Get request to server made here.

    const uriContent = `data:application/pdf;base64,${resp.data}`

    const link = document.createElement('a')
    link.href = uriContent
    link.download = filename
    document.body.appendChild(link)   // Also tried omitting this line along with...
    link.click()
    link.remove()                     // ...omitting this line
}

Expected Result(s):

  1. Browser opens a window to allow a directory to be selected as the file's destination.
  2. Directory Selected.
  3. File is downloaded.
  4. Ice cream and mooncakes are served.

Actual Results(s):

  1. Browser opens a window to allow a directory to be selected as the file's destination
  2. Directory Selected.
  3. Receive Failed - Network Error message.
  4. Lots of crying...

Browser: Chrome (Version 105.0.5195.125 (Official Build) (x86_64))

Read somewhere that Chrome will balk at files larger than 4MB, so I checked the S3 bucket and according to Amazon S3 the file size is a svelte 41.7KB.

After doing some reading, a possible solution was presented that I tried to implement. It involved making a change to the VueJs getPdfContent function as follows:

getPdfContent: async function (filename) {
    const resp = await AxiosService.getPdf(filename) // Get request to server made here.

    /**** This is the line that was changed ****/
    const uriContent = window.URL.createObjectURL(new Blob([resp.data], { type: 'application/pdf' } ))

    const link = document.createElement('a')
    link.href = uriContent
    link.download = filename
    document.body.appendChild(link)   // Also tried omitting this line along with...
    link.click()
    link.remove()                     // ...omitting this line
}

Actual Results(s) for updated code:

  1. Browser opens a window to allow a directory to be selected as the file's destination
  2. Directory Selected.
  3. PDF file downloaded.
  4. Trying to open the file produces the message:
The file “lets_go_to_the_snackbar.pdf” could not be opened.

It may be damaged or use a file format that Preview doesn’t recognize.

I am able to download the file directly from S3 using the AWS S3 console with no problems opening the file.

I've read through similar postings and tried implementing their solutions, but found no joy. I would be highly appreciative if someone can

  1. Give me an idea of where I am going off the path towards reaching the goal
  2. Point me towards the correct path.

Thank you in advance for your help.

1 Answers1

0

After doing some more research I found the problem was how I was returning the data from the server back to the client. I did not need to modify the data received from the S3 service.

Server Code:

let filename = req.params.filename;

const params = {
  Bucket: do_not_kick_this_bucket,
  Key: `yellowbrickroad/${filename}`
}

try {
  const data = await s3
    .getObject(params)
    .promise();

  /* Here I did not modify the information returned */
  res.send(data.Body);
  res.end();
} catch (err) {
  console.log(err);
}

On the client side my VueJS component receives a Blob object as the response

Client Code:

async getFile (filename) {
  let response = await AuthenticationService.downloadFile(filename)

  const uriContent = window.URL.createObjectURL(new Blob([response.data]))

  const link = document.createElement('a')
  link.setAttribute('href', uriContent)
  link.setAttribute('download', filename)
  document.body.appendChild(link)
  link.click()
  link.remove()
}

In the end the goal was achieved; a file on S3 can be downloaded directly to a user's local machine without the application storing a file on the server.

I would like to mention Sunpun Sandaruwan's answer which gave me the final clue I needed to reach my goal.

Tyler2P
  • 2,324
  • 26
  • 22
  • 31