Reputation: 15567
I generate a pre-signed URL for uploading files to AWS S3 via a Python-backed API. This part receives filename info once the user selects the file in a browser (see below), and returns a JSON payload (see below) with the base URL and fields.
import logging
import boto3
from botocore.exceptions import ClientError
def create_presigned_post(bucket_name, object_name,
fields=None, conditions=None, expiration=3600):
# Generate a presigned S3 POST URL
s3_client = boto3.client('s3')
try:
response = s3_client.generate_presigned_post(bucket_name,
object_name,
Fields=fields,
Conditions=conditions,
ExpiresIn=expiration)
except ClientError as e:
logging.error(e)
return None
# The response contains the presigned URL and required fields
return response
Here's the JSON response I get from this function. (I changed/abbreviated some of the values, but you get the point.)
{
"url": "https://s3.us-east-2.amazonaws.com/my_bucket_name",
"fields": {
"key": "some.txt",
"AWSAccessKeyId": "ASI...",
"x-amz-security-token": "Ag9o...",
"policy": "eyJ...=",
"signature": "jn...="
}
}
Here's the HTML form that I'm using to upload the file. I have some vanilla Javascript that tracks changes to the form and updates the URL_VALUE
for the form and the VALUE
for each form item upon changes (e.g. file selection).
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<!-- Copy the 'url' value returned by S3Client.generate_presigned_post() -->
<form action="URL_VALUE" method="post" enctype="multipart/form-data">
<!-- Copy the 'fields' key:values returned by S3Client.generate_presigned_post() -->
<input type="hidden" name="key" value="VALUE" />
<input type="hidden" name="AWSAccessKeyId" value="VALUE" />
<input type="hidden" name="policy" value="VALUE" />
<input type="hidden" name="signature" value="VALUE" />
File:
<input type="file" name="file" /> <br />
<input type="submit" name="submit" value="Upload to Amazon S3" />
</form>
</body>
</html>
This HTML form works fine by itself, but I tried adding some Javascript (both vanilla and JQuery), so I can track file progress and disable form inputs until the upload is complete.
I can't get Javascript to work!!!
I have tried so many examples (again, both vanilla JS and JQuery).
Has anyone recently implemented this and can help?
Upvotes: 2
Views: 2854
Reputation: 558
I finally figure it out. This is the code.
formData = new FormData()
Object.keys(presignedPostData.fields).forEach(key => {
formData.append(key, presignedPostData.fields[key])
})
file = $('input[type="file"]')[0].files[0]
formData.append("file", file)
fetch(presignedPostData.url, {
method: 'POST',
body: formData,
mode: 'no-cors'
}).then((response) => {
lg('response', response)
})
Upvotes: 0
Reputation: 525
I believe you have to pass the AWS secrets such as
"key": "some.txt",
"AWSAccessKeyId": "ASI...",
"x-amz-security-token": "Ag9o...",
"policy": "eyJ...=",
"signature": "jn...="
as headers.
Are you using the fetch
library?
Can you please post the JS code to?
Upvotes: 1
Reputation: 163311
It's even easier than what you've posted.
fetch(yourSignedUrl, {
method: 'PUT',
body: file,
headers: {
'Content-Type': file.type
}
}).then((res) => {
if (!res.ok) {
throw new Error(res.statusText);
}
return res.headers.get('ETag');
});
Upvotes: 1
Reputation: 15567
Alright, found a ridiculously simple vanilla JS example that works!
$(document).ready(function () {
var PRESIGNED_URL = ""
$('input[type="file"]').change(function (e) {
var fileName = e.target.files[0].name;
var settings = {
"async": true,
"crossDomain": true,
"url": "https://my_api.com",
"method": "POST",
"headers": {
"Content-Type": "application/x-www-form-urlencoded",
},
"data": {
"filename": fileName
}
}
$.ajax(settings).done(function (response) {
$("#filename").html(fileName)
PRESIGNED_URL = response["url"]
$("#form").attr("action", response["url"])
$("#key").val(response["fields"]["key"])
$("#AWSAccessKeyId").val(response["fields"]["AWSAccessKeyId"])
$("#policy").val(response["fields"]["policy"])
$("#signature").val(response["fields"]["signature"])
$("#x-amz-security-token").val(response["fields"]["x-amz-security-token"])
return
});
});
$("#button").on("click", function (e) {
var form = document.getElementById('form');
var formData = new FormData(form);
var xhr = new XMLHttpRequest();
// Add any event handlers here...
xhr.upload.addEventListener('progress', function(e) {
var percent_complete = (e.loaded / e.total)*100;
// Percentage of upload completed
console.log(percent_complete);
});
xhr.onloadstart = function (e) {
console.log("start")
}
xhr.onloadend = function (e) {
console.log("end")
}
xhr.open('POST', PRESIGNED_URL, true);
xhr.send(formData);
$("#filename").html("")
})
});
There are so many close variations, but this on worked perfectly.
(I'm sure it seems obvious to many, but I only do front-end dev by necessity...)
Upvotes: -1