Creating signed request for S3 upload using Django

Viewed 308

I have a simple django app on heroku for uploading images and it works as expected when accessed from desktop but fails to sign the requests for upload to AWS S3 when I access the site on mobile.

The signing code is shown below:

def sign_s3(request):
    request = request.GET
    S3_BUCKET = os.environ.get('AWS_STORAGE_BUCKET_NAME')

    file_name = request.get('file_name')
    file_type = request.get('file_type')

    s3 = boto3.client('s3')

    presigned_post = s3.generate_presigned_post(
    Bucket = S3_BUCKET,
    Key = file_name,
    Fields = {"acl": "public-read", "Content-Type": file_type},
    Conditions = [
        {"acl": "public-read"},
        {"Content-Type": file_type}
    ],
    ExpiresIn = 3600
    )

    return HttpResponse(json.dumps({
    'data': presigned_post,
    'url': 'https://%s.s3.amazonaws.com/%s' % (S3_BUCKET, file_name)
    }))

I set everything in the S3 bucket to public and have the CORS set as

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

This is a sample of the javascript used to send the request. I have realised it doesnt seem to send at all when its on mobile. The status code comes out as 0.

function getSignedRequest(file){
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/sign_s3?file_name="+file.name+"&file_type="+file.type);
  xhr.onreadystatechange = function(){
    if(xhr.readyState === 4){
      if(xhr.status === 200){
        var response = JSON.parse(xhr.responseText);
        uploadFile(file, response.data, response.url);
      }
      else{
        alert("Could not get signed URL.");
      }
    }
  };
  xhr.send();
}

uploadFile is just a separate function that finally posts the image.

I cant tell what could be causing this difference. How can I get this working?

2 Answers

Instead of:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

try adding the CORSRules this way:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
...
</CORSConfiguration>

Just found out the issue is with the XMLHttpRequest object which doesn't seem to work well /unsupported on iPhone if async is set to true which is the default value, not sure on android.

I changed to xhr.open("GET", "/sign_s3?file_name="+file.name+"&file_type="+file.type, false); and everything works. This makes the request synchronous so seems asynchronous requests are being blocked by ios.

When doing the actual upload, having the async set to false means its not possible to track the progress.

Again, async set to false is not allowed in Firefox, what a pain. Until I find a better solution, I'll have to implement different code for Firefox and mobile browsers by detecting the type of browser being used.

Related