【问题标题】:Javascript not working when uploading file to AWS S3 via presigned URL通过预签名 URL 将文件上传到 AWS S3 时 Javascript 不起作用
【发布时间】:2020-01-15 20:07:27
【问题描述】:

我生成了一个预签名 URL,用于通过 Python 支持的 API 将文件上传到 AWS S3。一旦用户在浏览器中选择文件(见下文),这部分就会接收文件名信息,并返回带有基本 URL 和字段的 JSON 有效负载(见下文)。

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

这是我从这个函数得到的 JSON 响应。 (我更改/缩写了一些值,但你明白了。)

{
  "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...="
  }
}

这是我用来上传文件的 HTML 表单。我有一些原始的 Javascript 可以跟踪表单的更改,并在更改时更新表单的 URL_VALUE 和每个表单项的 VALUE(例如文件选择)。

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

这个 HTML 表单本身可以正常工作,但我尝试添加一些 Javascript(vanilla 和 JQuery),所以我可以跟踪文件进度并禁用表单输入,直到上传完成。

我无法让 Javascript 工作!!!

我已经尝试了很多例子(同样,vanilla JS 和 JQuery)。

最近有没有人实现过这个并且可以提供帮助?

【问题讨论】:

标签: javascript python amazon-web-services amazon-s3


【解决方案1】:

我相信你必须通过诸如

之类的AWS秘密
"key": "some.txt",
    "AWSAccessKeyId": "ASI...",
    "x-amz-security-token": "Ag9o...",
    "policy": "eyJ...=",
    "signature": "jn...="

作为标题。

您在使用fetch 库吗? 可以把JS代码发到吗?

【讨论】:

  • 是的,但由于某种原因,当我通过 JS 发送这些内容时,它不喜欢它。通过 Python POST 文件作为原始 API 测试时工作正常。
  • 顺便说一下,我没有发布 JS,因为我确实尝试了十几种 JQuery / vanilla JS 的变体......不过,谢谢你的回答
【解决方案2】:

好的,找到了一个有效的ridiculously simple vanilla JS 示例!


$(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("")
    })
});



有很多紧密的变化,但这个效果很好。

(我相信这对很多人来说似乎很明显,但我只是在必要时才做前端开发......)

【讨论】:

  • 这显然是 JQuery 和 vanilla JS 的混合,但同样失败的部分是底部附近的 POST 请求。喜欢我可以用简单的旧 JS 写东西。
  • 根本不需要这个表格和所有其他东西。看我的回答...你可以直接使用预签名的 PUT URL。
  • 我会尝试使用 PUT,但 boto3 docs 指定预签名 URL 将仅接受 POST
  • 这根本不是真的...您正在查看表单上传。您可以使用 PUT 方法生成 URL。看起来你想要的东西类似于create_presigned_url_expanded('put_object', ...)
  • 好的,还没有尝试使用预签名 URL 的 put 对象客户端方法...我使用 PUT 将标准文件上传到 S3,所以这是有道理的。我只是使用包装好的 POST 方法。
【解决方案3】:

这比你发布的更容易。

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');
});

【讨论】:

  • 酷,我试试看
  • 仍然收到 403 错误...响应提供了基本 URL,我尝试将其编码为查询字符串并将其连接到一个长 URL 中
  • @openwonk 哦,我明白了,问题是您实际上并没有生成完整的预签名 URL。我对 Python SDK 不熟悉……如果您能找到任何方法生成您可以使用的 URL,这将起作用。对于 JavaScript SDK,就像s3.getSignedUrl('putObject', ...) 一样简单。
  • 是的,有利也有弊。我知道一些 SDK 只生成完整的字符串,而 Python 将其分解出来。
  • 您在将完整表单作为正文传递时遇到任何问题吗?我想它的工作原理是一样的。
猜你喜欢
  • 2020-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-05
  • 2020-09-30
  • 2022-01-27
  • 1970-01-01
  • 2021-02-15
相关资源
最近更新 更多