Flask upload signen und direkt zu S3 führt zu Method Not Allowed 405

Django, Flask, Bottle, WSGI, CGI…
Antworten
Zoja
User
Beiträge: 129
Registriert: Freitag 28. Februar 2014, 14:04

Mittwoch 23. Mai 2018, 16:09

Hallo mal wieder,

ich versuche zu verstehen wie man direkt zu Amazon S3 über den client hochladen kann. Ich habe bereits eine Menge dazu gelesen und ich habe auch das Gefühl ich bin ganz nah an der Lösung. Ich vermute es muss etwas an den CORS und/oder Bucket Policy noch gemacht werden.

Hier schon mal der Code CORS & Bucket Policy:

Code: Alles auswählen

<?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> 

Code: Alles auswählen

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::mybucket/*"
        }
    ]
}
Server-side Python (Flask) Code zum Signen der Request:

Code: Alles auswählen

# Sign request for direct file upload through client for video
@app.route('/sign_s3/<path:file_name_data>/<path:file_type_data>/<up_type>', methods=["GET", "POST"])
@login_required
@check_confirmed
def sign_s3(file_name_data, file_type_data, up_type):
    if "localhost" in request.url_root:
        if up_type == "profile_vid":
            file_name = str(current_user.id) + get_random_code(5) + "local-profil-video." + file_name_data.split(".")[-1]
        else:
            file_name = str(current_user.id) + str(randint(1,100)) + "local-post-video-temp." + file_name_data.split(".")[-1]
    else:
        if up_type == "profile_vid":
            file_name = str(current_user.id) + get_random_code(5) + "-profil-video." + file_name_data.split(".")[-1]
        else:
            file_name = str(current_user.id) + str(randint(1,100)) + "-post-video-temp." + file_name_data.split(".")[-1]

    file_type = file_type_data

    session = boto3.Session(
        aws_access_key_id=app.config['MY_AWS_ID'],
        aws_secret_access_key=app.config['MY_AWS_SECRET'],
        region_name='eu-central-1'
    )

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

    if up_type == "profile_vid":
        if current_user.profile_video != None:
            delete_file_from_aws("videos/", current_user.profile_video)
        setattr(current_user, "profile_video", file_name)
    else:
        print ('post video has been uploaded, no need to delete or set here')

    db_session.commit()

    return json.dumps({'data': presigned_post, 'url': 'https://s3.eu-central-1.amazonaws.com/mybucket/' + 'videos/' + file_name, 'created_file_name' : file_name})
Ich nutze den JQuery File Upload, Client-side JS Code:

Code: Alles auswählen

/************************
 * Upload for video
 *************************/
$('#NEW_fileupload_video').fileupload({
    autoUpload: false,
    maxNumberOfFiles: 1,
    disableVideoPreview: false,
    // new
    forceIframeTransport: true, // VERY IMPORTANT.  you will get 405 Method Not Allowed if you don't add this.
    singleFileUploads: true, //default anyway
    // end new
    maxFileSize: 9999999000
}).on('fileuploadadd', function (e, data) {
    if ($('#NEW_fileupload_video_files div')[0]) {
        $('#NEW_fileupload_video_files div').remove();
    }   

    var files = data.files || [];
    var nextInQueue = files[0]; //this is a queue, not a list
    if (!nextInQueue) return;

    var fileData = {name: nextInQueue.name, mime: nextInQueue.type, size: nextInQueue.size};

    $.ajax({
        url: "/sign_s3/" + data.files[0].name + "/" + data.files[0].type + "/" + 'profile_vid',
        type: 'POST',
        dataType: 'json',
        // I am giving the data through the URL, probably a bad idea
        data: {'file': fileData},
        async: false,
        success: function(res) {
            var response_json_data = res;

            $('input[name="key"]').val(response_json_data.data.fields['key']);
            $('input[name="AWSAccessKeyId"]').val(response_json_data.data.fields['x-amz-credential']);
            $('input[name="policy"]').val(response_json_data.data.fields['policy']);
            $('input[name="signature"]').val(response_json_data.data.fields['x-amz-signature']);
            $('input[name="acl"]').val( response_json_data.data.fields['acl']);
            $('input[name="success_action_status"]').val("200");
            $('input[name="Content-Type"]').val(nextInQueue.type);
            //$('#direct_s3_profile_video_form').attr('action', response_json_data.url);
            $('#direct_s3_profile_video_form').attr('action', 'https://mybucket.s3.amazonaws.com/videos/' + response_json_data.created_file_name);
            data.submit();
        }
    }); 
}).on('fileuploadprocessalways', function (e, data) {
    console.log("NEW always");
}).on('fileuploadprogressall', function (e, data) {
    var progress = parseInt(data.loaded / data.total * 100, 10);
    $('#NEW_progress_fileupload_video .progress-bar').css(
        'width',
        progress + '%'
    );
}).on('fileuploaddone', function (e, data) {
    console.log("done client side video up profile");
}).on('fileuploadfail', function (e, data) {
    console.log("fail", e, data);
})
Und der HTML Code:

Code: Alles auswählen

<form id="direct_s3_profile_video_form" class="form-horizontal" role="form" method="POST" enctype="multipart/form-data">
 <input id="NEW_fileupload_video" type="file" name="NEW_fileupload_video_files[]" accept="video/*">
 <input type="hidden" name="key">
 <input type="hidden" name="AWSAccessKeyId">
 <input type="hidden" name="policy">
 <input type="hidden" name="signature">
 <input type="hidden" name="acl">
 <input type="hidden" name="success_action_status">
 <input type="hidden" name="Content-Type">
 <div id="NEW_progress_fileupload_video" class="progress">
     <div class="progress-bar progress-bar-success"></div>
 </div>
 <div id="NEW_fileupload_video_files" class="files"></div>
 <button type="submit"> Upload </button>
</form>
Das Problem ist nun, dass obwohl alles da zu sein scheint ich einen Error bekomme:

POST https://s3.eu-central-1.amazonaws.com/m ... -video.mp4 405 (Method Not Allowed)

Ich sehe leider auch keine LOGS oder sonst irgendetwas mehr. Ich habe schon sehr vieles Versucht umzuschreiben, z.B. andere URL Schreibweisen, Region hinzugefügt.
Ich habe unterschiedliche ACL methods ausprobiert, CORS ist so open wie es nur geht, da es jeden Origin erlaubt. Wenn jemand irgendeine Idee hat, lasst es mich wissen!
Ich sitze schon den ganzen Tag dran. Es muss client side funktionieren, da die Videos, die geuploaded werden sehr groß sind und ansonsten timeouts auf dem Server verursachen würden.
Antworten