I am using React, API gateway, and Lambda (Python) to obtain a pre-signed url to upload a file to an s3 bucket. But the files uploaded to the s3 bucket are always empty (0 bytes). And for some reason, when I upload a CSV in my React App. The uploaded file in s3 turns into an .XLS file but still named the original csv.
Here is my code:
<div>
<input type="file" name="fileOne" onChange={fileUploadOne} />
</div>
...
const fileUploadOne = (event) => {
const { files } = event.target;
getPresignedUrl(files[0]).then((response) => {
putToS3(files[0], response);
});
};
...
export async function getPresignedUrl(fileObject) {
const requestOptions = {
method: "GET",
headers: {
"Content-Type": "application/json",
},
};
const response = await fetch(
"https://<api_gateway_url>.amazonaws.com/<api_name>?filename=" +
fileObject.name +
"&filetype=" +
fileObject.type,
requestOptions
);
return await response.json();
}
export async function putToS3(fileObject, presignedUrl) {
const requestOptions = {
method: "PUT",
headers: {
"Content-Type": fileObject.type,
},
data: fileObject,
};
const response = await fetch(presignedUrl, requestOptions);
return await response;
}
My Lambda code (which is accessed with getPresignedUrl function):
import json
import boto3
from botocore.exceptions import ClientError
def lambda_handler(event, context):
params = event["queryStringParameters"]
key = params.get('filename')
filetype = params.get('filetype')
s3_client = boto3.client('s3')
client_action = 'put_object'
bucket_name = '<bucket_name>'
resp = generate_presigned_url(
s3_client, client_action, {'Bucket': bucket_name, 'Key': key,'ContentType':filetype}, 1000)
return {
'statusCode': 200,
'headers': {
"Access-Control-Allow-Origin" : "*", # Required for CORS support to work
},
'body': json.dumps(resp)
}
def generate_presigned_url(s3_client, client_method, method_parameters, expires_in):
"""
Generate a presigned Amazon S3 URL that can be used to perform an action.
:param s3_client: A Boto3 Amazon S3 client.
:param client_method: The name of the client method that the URL performs.
:param method_parameters: The parameters of the specified client method.
:param expires_in: The number of seconds the presigned URL is valid for.
:return: The presigned URL.
"""
try:
url = s3_client.generate_presigned_url(
ClientMethod=client_method,
Params=method_parameters,
ExpiresIn=expires_in
)
print("Got presigned URL: %s", url)
except ClientError:
print(
"Couldn't get a presigned URL for client method '%s'.", client_method)
raise
return url
I figured out my mistake. My HTTP PUT request should have the fileObject in body not data.
export async function putToS3(fileObject, presignedUrl) {
const requestOptions = {
method: "PUT",
headers: {
"Content-Type": fileObject.type,
},
body: fileObject,
};
const response = await fetch(presignedUrl, requestOptions);
return await response;
}
It was really very simple and a stupid mistake. Took me hours to figure it out.