Preserving Content-Type Header on Files Uploaded Using S3 signed URL

Published on March 12, 2022

By Hyuntaek Park

Senior full-stack engineer at Twigfarm

One thing I don’t like about Amazon API Gateway is that it has a 30-second timeout. It means uploading and downloading files through API Gateway might not work very well depending on the file size or network speed. The solution is simple. You upload files directly to S3 without API Gateway.

This is not a secure way since your S3 bucket would have public access. S3 signed URL allows you to access the S3 bucket only for a specified period while the S3 bucket stays private. Many tutorials about the S3 signed URL are online and duplicating them is not the purpose of this article.

In this article, I want to share a mistake that I have made; hopefully, this article helps save your time.

Architectures

Since our lambda can access to S3 and get the signed URL using JavaScript SDK: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html. Search for getSignedUrl on the page.

Uploads

image

Downloads

image

Problem

Uploading and downloading files are smooth as butter. Now you are free from timeouts. A few gigabyte files can be handled easily. However, I found something strange after I downloaded the video file I uploaded.

image

The Content-Type header must be video/mp4. Most of the time we acknowledge the file type by reading the Content-Type header. Thus, we have to be sure the content-type header is consistent from the moment of uploading and downloading.

Solution

Although I tried various parameters while invoking the getSignedUrl function, nothing really worked. But the solution was super simple.

export const putSingleFile = async (url, file) => {
  const instance = axios.create();
  const response = await instance({
    method: "put",
    url,
    data: file,
    headers: {
      "Content-Type": file.type, // This is what I forgot
    },
  });
  console.log("response: ", response);
};

Axios doesn’t specify the Content-Type header automatically, which I overlooked. I added the Content-Type header and it worked like a charm. I wasted too much time stupidly :).

image

Now its Content-Type is video/mp4 as it is supposed to be.

I hope this article saves some time for you.