草庐IT

node.js - 如何在客户端将可视文件上传到 Amazon S3?

coder 2023-05-29 原文

首先让我说,我通常非常不愿意发布这些问题,因为我总是觉得互联网上的任何事情都有答案。在花了无数个小时寻找这个问题的答案之后,我终于放弃了这个说法。

假设

这行得通:

s3.getSignedUrl('putObject', params);

我想做什么?

  1. 使用 getSignedUrl 方法通过 PUT(从客户端)将文件上传到 Amazon S3
  2. 允许任何人查看上传到 S3 的文件

注意:如果有更简单的方法允许客户端 (iPhone) 使用预签名 URL(并且不暴露客户端凭据)上传到 Amazon S3,我会全力以赴。 p>

主要问题*

  1. 查看 AWS 管理控制台时,上传的文件的权限和元数据集为空白。
  2. 查看上传的文件时(即在 AWS 管理控制台中双击文件)我收到 AccessDenied 错误。

我尝试了什么?

尝试 #1:我的原始代码

在 NodeJS 中,我生成一个预签名的 URL,如下所示:

var params = {Bucket: mybucket, Key: "test.jpg", Expires: 600};
s3.getSignedUrl('putObject', params, function (err, url){
  console.log(url); // this is the pre-signed URL
});

预签名的 URL 如下所示:

https://mybucket.s3.amazonaws.com/test.jpg?AWSAccessKeyId=AABFBIAWAEAUKAYGAFAA&Expires=1391069292&Signature=u%2BrqUtt3t6BfKHAlbXcZcTJIOWQ%3D

现在我通过 PUT 上传文件

curl -v -T myimage.jpg https://mybucket.s3.amazonaws.com/test.jpg?AWSAccessKeyId=AABFBIAWAEAUKAYGAFAA&Expires=1391069292&Signature=u%2BrqUtt3t6BfKHAlbXcZcTJIOWQ%3D

问题
我得到了上面列出的*主要问题

尝试 #2:在 PUT 上添加 Content-Type 和 ACL

我还尝试通过替换参数在我的代码中添加 Content-Type 和 x-amz-acl,如下所示:

var params = {Bucket: mybucket, Key: "test.jpg", Expires: 600, ACL: "public-read-write", ContentType: "image/jpeg"};

然后我尝试一个好的 ol' PUT:

curl -v -H "image/jpeg" -T myimage.jpg https://mybucket.s3.amazonaws.com/test.jpg?AWSAccessKeyId=AABFBIAWAEAUKAYGAFAA&Content-Type=image%2Fjpeg&Expires=1391068501&Signature=0yF%2BmzDhyU3g2hr%2BfIcVSnE22rY%3D&x-amz-acl=public-read-write

问题
我的终端输出一些错误:

-bash: Content-Type=image%2Fjpeg: command not found
-bash: x-amz-acl=public-read-write: command not found

我还遇到了上面列出的*主要问题

尝试 #3:将存储桶权限修改为公开

下面列出的所有项目都在 AWS 管理控制台中打勾)

Grantee: Everyone can [List, Upload/Delete, View Permissions, Edit Permissions]
Grantee: Authenticated Users can [List, Upload/Delete, View Permissions, Edit Permissions]

存储桶政策

{
"Version": "2012-10-17",
"Statement": [
    {
        "Sid": "Stmt1390381397000",
        "Effect": "Allow",
        "Principal": {
            "AWS": "*"
        },
        "Action": "s3:*",
        "Resource": "arn:aws:s3:::mybucket/*"
    }
]
}

尝试 #4:设置 IAM 权限

我将用户策略设置为:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": "*"
    }
  ]
}

AuthenticatedUsers 组策略是这样的:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "Stmt1391063032000",
      "Effect": "Allow",
      "Action": [
        "s3:*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

尝试 #5:设置 CORS 政策

我将 CORS 政策设置为:

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

而且...现在我在这里。

最佳答案

更新

我有坏消息。根据 SDK 2.1.6 的发行说明 http://aws.amazon.com/releasenotes/1473534964062833 :

"The SDK will now throw an error if ContentLength is passed into an 
Amazon S3 presigned URL (AWS.S3.getSignedUrl()). Passing a 
ContentLength is not supported by the SDK, since it is not enforced on 
S3's side given the way the SDK is currently generating these URLs. 
See GitHub issue #457."

我发现在某些情况下,必须包含 ContentLength(特别是如果您的客户端通过它以便签名匹配),然后在其他情况下,如果您包含 ContentLength 参数错误,getSignedUrl 会提示:“不支持内容长度在预签名的网址中”。我注意到当我更改正在调用电话的机器时,行为会发生变化。据推测,另一台机器与农场中的另一台亚马逊服务器建立了连接。

我只能猜测为什么这种行为在某些情况下存在,而在其他情况下不存在。也许并非所有亚马逊的服务器都已完全升级?在任何一种情况下,为了处理这个问题,我现在都尝试使用 ContentLength,如果它给了我参数错误,那么我在没有它的情况下再次调用 getSignedUrl。这是使用 SDK 处理这种奇怪行为的解决方法。

一个小例子......看起来不是很漂亮,但你明白了:

MediaBucketManager.getPutSignedUrl = function ( params, next ) {
    var _self = this;
    _self._s3.getSignedUrl('putObject', params, function ( error, data ) {
        if (error) {
            console.log("An error occurred retrieving a signed url for putObject", error);
            // TODO: build contextual error
            if (error.code == "UnexpectedParameter" && error.message.search("ContentLength") > -1) {
                if (params.ContentLength) delete params.ContentLength
                MediaBucketManager.getPutSignedUrl(bucket, key, expires, params, function ( error, data ) {
                    if (error) {
                        console.log("An error occurred retrieving a signed url for putObject", error);
                    } else {
                        console.log("Retrieved a signed url for putObject:", data);
                        return next(null, data)
                    }
                }); 
            } else {
                return next(error); 
            }
        } else {
            console.log("Retrieved a signed url for putObject:", data);
            return next(null, data);
        }
    });
};

因此,以下内容并不完全正确(在某些情况下会正确,但在其他情况下会出现参数错误),但可能会帮助您入门。

旧答案

似乎(对于将文件放到只有公共(public)读取 ACL 的 S3 的 signedUrl)当请求将 PUT 放到 S3 时,将比较一些 header 。将它们与传递给 getSignedUrl 的内容进行比较:

CacheControl: 'STRING_VALUE',
ContentDisposition: 'STRING_VALUE',
ContentEncoding: 'STRING_VALUE',
ContentLanguage: 'STRING_VALUE',
ContentLength: 0,
ContentMD5: 'STRING_VALUE',
ContentType: 'STRING_VALUE',
Expires: new Date || 'Wed De...'

在此处查看完整列表:http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property

当您调用 getSignedUrl 时,您将传递一个包含 Bucket、Key 和 Expires 数据的“params”对象(在文档中非常清楚)。这是一个(NodeJS)示例:

var params = { Bucket:bucket, Key:key, Expires:expires };
s3.getSignedUrl('putObject', params, function ( error, data ) {
    if (error) {
        // handle error
    } else {
        // handle data
    }
});

不太清楚的是将 ACL 设置为“public-read”:

var params = { Bucket:bucket, Key:key, Expires:expires, ACL:'public-read' };

非常模糊的是传递 header 的概念,您希望客户端使用签名的 url 将连同 PUT 操作一起传递给 S3:

var params = {
    Bucket:bucket,
    Key:key,
    Expires:expires,
    ACL:'public-read',
    ContentType:'image/png',
    ContentLength:7469
};

在上面的示例中,我包含了 ContentType 和 ContentLength,因为在 javascript 中使用 XmlHTTPRequest 时包含了这两个 header ,并且在 Content-Length 的情况下无法更改。我怀疑其他 HTTP 请求的实现(例如 Curl 等)也会出现这种情况,因为在提交包含(数据)主体的 HTTP 请求时,它们是必需的 header 。

如果客户端在请求 signedUrl 时不包含有关文件的 ContentType 和 ContentLength 数据,则当需要将文件 PUT 到 S3(使用该 signedUrl)时,S3 服务将查找包含在客户端请求中的 header (因为它们是必需的 header )但签名不会包含它们 - 因此它们将不匹配并且操作将失败。

因此,在调用 getSignedUrl 之前,您似乎必须知道要 PUT 到 S3 的文件的内容类型和内容长度。这对我来说不是问题,因为我公开了一个 REST 端点以允许我们的客户在对 S3 进行 PUT 操作之前请求一个签名的 url。由于客户端可以访问要提交的文件(在他们准备提交的那一刻),客户端访问文件大小和类型并从我的端点请求带有该数据的签名 url 是一个简单的操作。

关于node.js - 如何在客户端将可视文件上传到 Amazon S3?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21452756/

有关node.js - 如何在客户端将可视文件上传到 Amazon S3?的更多相关文章

  1. ruby - 如何在 Ruby 中顺序创建 PI - 2

    出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

  2. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  3. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  4. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  5. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  6. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  7. ruby - 如何在 Lion 上安装 Xcode 4.6,需要用 RVM 升级 ruby - 2

    我实际上是在尝试使用RVM在我的OSX10.7.5上更新ruby,并在输入以下命令后:rvminstallruby我得到了以下回复:Searchingforbinaryrubies,thismighttakesometime.Checkingrequirementsforosx.Installingrequirementsforosx.Updatingsystem.......Errorrunning'requirements_osx_brew_update_systemruby-2.0.0-p247',pleaseread/Users/username/.rvm/log/138121

  8. ruby-on-rails - 如何在 ruby​​ 交互式 shell 中有多行? - 2

    这可能是个愚蠢的问题。但是,我是一个新手......你怎么能在交互式ruby​​shell中有多行代码?好像你只能有一条长线。按回车键运行代码。无论如何我可以在不运行代码的情况下跳到下一行吗?再次抱歉,如果这是一个愚蠢的问题。谢谢。 最佳答案 这是一个例子:2.1.2:053>a=1=>12.1.2:054>b=2=>22.1.2:055>a+b=>32.1.2:056>ifa>b#Thecode‘if..."startsthedefinitionoftheconditionalstatement.2.1.2:057?>puts"f

  9. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  10. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

随机推荐