Naveen Karnam
Naveen Karnam

Reputation: 493

How do we display images from s3 on browser using vue/nuxt?

I am displaying profile picture of a user on a vue/nuxt app. This picture is uploaded to S3 through a file API. While receiving the image from file API and displaying the profile pic, I am not able to convert it to the right format.

My problems:

  1. s3 says the file content type is application/octet-stream. I was expecting a specific type like image/jpeg or image/png. There's an npm that came to rescue here.
  2. Wrapping the returned file in a blob using createObjectURL creates a dummy URL / link. We can set the mime type to blob while creating like so const blob = new Blob([response.data],{type=response.type}
  3. Using responseType - Is it blob, arrayBuffer or stream? I went ahead with experimenting on blob

Step 1: File API - Reading from S3 using AWS client v3 (File: FileService.js)

let goc = new GetObjectCommand({
          ...
        });
s3client.send(goc)
  .then(response => {
     if(response){
       const body = response.Body;
       const tempFileName = path.join(config.FILE_DOWNLOAD_PATH, file_name);
       const tempFile = fs.createWriteStream(tempFileName);
       body.pipe(tempFile);
       resolve(Service.createSuccessResponse(
        {
          file_name_local: tempFileName,
          file_name: file_name,
          content_length: response.ContentLength,
        }
        ,"SUCCESS")); // This is **responsePayload** in the next snippet.
...

Step 2: Send the response using expressjs (File: Controller.js)

if(responsePayload.file_name_local){        
  response.set('Content-Length',responsePayload.content_length);
  response.write(fs.readFileSync(responsePayload.file_name_local));
  response.end();
  response.connection.end();
}

Step 3: Define image (File: view-profile.vue)

<template>
...
  <v-img src="dpURL"/>
...
</template>

Step 4 - EAGER DOWNLOAD: Receive image as blob (File: view-profile.vue)

<script>
...
mounted(){
...
  this.getDP(this.profileInfo.dp_url).then(r => {
    this.dpURL = window.URL.createObjectURL(new Blob([r.data])); // Do we need explicit MIME here? 
  }).catch(e => {
    console.error("DP not retrieved: "+JSON.stringify(e));
  })
...
},
methods: {
...
getDP(file_name){
  return new Promise(
    async(resolve, reject) => {
      callGenericAPI({
        url: this.$config.API_HOST_URL+'/file', 
        configObj: {
          method: 'get',
          params: {
            file_name: file_name,
            file_category: 'user-dp'
          },
          headers: {
            'api_token': idToken,
             'Cache-Control':'no-cache'
          },
          responseType: 'blob'
        },
        $axios: this.$axios  //Just some crazy way of calling axios. Don't judge me here.
      })
      .then(r => {
        console.log("DP received.");
        resolve(r.data);
      })
      .catch(e => {
        console.error("DP not received." + JSON.stringify(e));
        reject(e);
      })
     }
    )
  },
...
}

Step 5 - LAZY DOWNLOAD: Receive image as blob (File: view-profile.vue > custom-link.vue - child) after clicking the link

File: view-profile.vue

<template>
...
  <CustomLink file_name="fileName" file_category="USER_DP" label="User Profile Picture"/>
...
</template>

File: custom-link.vue

<template>
<a v-text="label"
  @click.prevent="downloadItem()">
</template>
<script>
...
methods:{
  downloadItem(){
    this.$axios.get(...)
    .then(response => {
      const blob = new Blob([response.data],{ type: response.data.type }) // Here I tried Uint8Array.from(response.data) as well
      const link = document.createElement('a')
      link.href = window.URL.createObjectURL(blob,{ type: response.data.type });
      link.download = this.file_name
      link.click()
      URL.revokeObjectURL(link.href)
    }
...
  }
...
}
...
</script>

The problem here is that image is being downloaded. I can see it from devtools network tab. But I just can't link/display it reactively (shown in Step 3).

After Step 5, the file downloads but has a bloated size compared to original. When I convert response.data to Uint8Array, the file shrinks compared to original.

File Downloaded Able to see image in the preview tab But the created temporary blob URL does not display anything. I can see that the content length is also wrong

It looks like a very simple get and display image problem but haven't got the combination of mimetypes and utilities right. Express supports sendFile and download options for the files. But both the calls don't seem to work for some reason!

Do you have any pointers? Can't we just download the image somewhere on the webserver directory and reactive link it? Even that should be fine with me. Can avoid some API calls.

Upvotes: 1

Views: 4452

Answers (2)

Naveen Karnam
Naveen Karnam

Reputation: 493

@coder.in.me's post leads us to the answer - obtain S3 Object's presignedURL.
Added benefit - you can also expire the URL beyond a time-limit.

Upvotes: 1

coder.in.me
coder.in.me

Reputation: 1068

The following code is taken from https://aws.plainenglish.io/using-node-js-to-display-images-in-a-private-aws-s3-bucket-4c043ed5c5d0 :

function encode(data){
    let buf = Buffer.from(data);
    let base64 = buf.toString('base64');
    return base64
    }

getImage()
 .then((img)=>{
  let image="<img src='data:image/jpeg;base64," + encode(img.Body) + "'" + "/>";
  let startHTML="<html><body></body>";
  let endHTML="</body></html>";
  let html=startHTML + image + endHTML;
  res.send(html)
  }).catch((e)=>{
        res.send(e)
  })

Upvotes: 1

Related Questions