【问题标题】:Upload to AWS S3 bucket in Angular 2 project based on angular/quickstart基于angular/quickstart的Angular 2项目中的AWS S3存储桶上传
【发布时间】:2017-06-17 15:15:04
【问题描述】:

我需要从 angular.io 的 Angular 2 Quickstart 库上传(他们不使用 webpack)

  1. 我使用了 npm install aws-sdk
  2. 我在 index.html 中添加了这个:
  3. 这是组件中的代码:

    @Injectable()
    export class S3Service {
    
    private uploadSuccess = true;
    
    private creds = {
        "bucket": "nameOfBucket", 
        "access_key": "accessKey",
        "secret_key": "secretKey",
        "region": "us-east-1"
    }
    
    upload(file: File){
        if (file){
            console.log('verified with file');
        }else{
            console.log('without file');
        }
    console.log('filetype verified as images/png: ', file.type);
    
    AWS.config.update({
        accessKeyId: this.creds.access_key,  
        secretAccessKey: this.creds.secret_key, 
    });
    AWS.config.region = this.creds.region;
    AWS.config.sslEnabled = false;
    
    console.log('aws.s3 is verified to be a function: ', AWS.S3);
    let bucket = new AWS.S3({ params: { Bucket: this.creds.bucket }});
    
    let key = `categories/${file.name}`;
    console.log('verified key is : ', key);
    let params = {Key: key, Body: file, ContentType: file.type, ServerSideEncryption: 'AES256'};
    
    bucket.putObject(params, function (err: Response | any, data: Response) {
        if (err){
            console.log('there is an error: ', err);
        }
        else{
            console.log('there is no error in s3 upload');
        }
    }); 
    

    }

这是 Firefox Web 控制台中的错误日志:

有一个错误:Object { __zone_symbol__error: Error, fileName: Getter, lineNumber: Getter, columnNumber: Getter, message: Getter, stack: Getter, originalStack: Getter, zoneAwareStack: Getter, toString: createMethodProperty/props[key] .value(), toSource: createMethodProperty/props[key].value(), 还有 7 个……}

这是为 Chrome 准备的:

XMLHttpRequest 无法加载 http://bucketname.s3.amazonaws.com/categories/imagename.png。对预检请求的响应未通过访问控制检查:请求的资源上不存在“Access-Control-Allow-Origin”标头。因此,Origin 'localhost:3000' 不允许访问。响应的 HTTP 状态代码为 400。

由于我刚刚学习,我正在尝试使用宽松的 CORS:

<CORSConfiguration xmlns="removed this from being displayed">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedOrigin>http://localhost:3000</AllowedOrigin>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <ExposeHeader>x-amz-server-side-encryption</ExposeHeader>
        <ExposeHeader>x-amz-request-id</ExposeHeader>
        <ExposeHeader>x-amz-id-2</ExposeHeader>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

Angular 1 代码成功上传图片。

请帮助并提前感谢。

【问题讨论】:

标签: angular amazon-s3 cors


【解决方案1】:

您在 S3 存储桶上的 CORS 配置看起来适合您描述的场景。

我认为问题出在您的代码产生的端点 URL 上。我有一个类似的问题,它令人困惑,而且很难追踪。

由于某种原因,AWS 开发工具包生成端点 URL 的方式似乎有所不同,具体取决于您用于设置区域信息和/或存储桶名称的方法。当生成的 URL 不包含区域信息(您的不包含)时,它会导致预检请求失败,从而导致浏览器控制台中出现有关 CORS 的误导性错误消息(这有时可能是预检失败的根源) )。

“问题”端点格式:http(s)://&lt;bucketname&gt;.s3.amazonaws.com/&lt;key&gt;
“所需”端点格式:http(s)://s3-&lt;region&gt;.amazonaws.com/&lt;bucketname&gt;/&lt;key&gt;

尝试使用我在此处提供的组件来验证您的 AWS S3 配置、访问和 CORS 设置。然后,如果您愿意,您可以轻松地将 S3 特定内容提取到服务中。

步骤如下:

  1. 确认您正在运行当前版本的 AWS 开发工具包(2017-02-08 为 2.10.0)。万一您不熟悉如何检查此内容,请打开 /node_modules/aws-sdk/dist/aws-sdk.js 并查看文件顶部的注释以确定版本。
  2. 将以下组件添加到您的项目中。我已经成功地针对我自己的 S3 实例进行了测试。
  3. 确保更新配置值并明确指定 S3 存储桶所在的区域。如果您不确定,可以使用 AWS 控制台找到此信息。当然,您还需要使用您的凭据和 S3 存储桶名称替换其他配置值。
  4. 为 AWS 开发工具包库配置 systemjs(参见下面的 system.config.js)
  5. 将组件添加到模块的声明中(请参阅下面的 app.module.ts)
  6. 在 AppComponent 的模板中引用组件 (&lt;s3-upload-test&gt;&lt;/s3-upload-test&gt;)(请参阅下面的 app.component.ts)。

s3-upload-test.component.ts:

import { Component } from '@angular/core';
import { Credentials, S3 } from 'aws-sdk';

@Component({
  selector: 's3-upload-test',
  template: `
    <div class="uploadSection">

      <hr>
      <h3>S3 File Upload Test</h3>

      <div class="subsection">
        <h4>Confirm Endpoint Format:</h4>
        <div class="indent">
          The endpoint should be in the following format <span class="monospace">s3-&lt;region&gt;.amazonaws.com</span>.
          <pre>
            Based on the configuration information you provided:
                Expect Endpoint: {{expectEndpoint}}
                Actual Endpoint: {{actualEndpoint}}
          </pre>
        </div>
      </div>

      <div class="subsection">
        <h4>Select File:</h4>
        <div class="indent">
          <input type="file" (change)="fileEvent($event)" />
        </div>
      </div>

      <div class="subsection">
        <h4>Upload Status/Results:</h4>
        <div class="indent">
          <span class="monospace result">{{uploadStatus}}</span>
        </div>
      </div>

      <hr>
    </div>
  `,
  styles: [`
    .uploadSection { font-family: sans-serif; }
    .monospace { font-family: monospace; }
    .subsection { margin-top: 35px;}
    .indent { margin-left: 20px;}
    .result { background-color: lightyellow }
  `]
})
export class S3UploadTestComponent {

  // Replace the values with your own
  private readonly _awsConfig = {
    accessKeyId: "<your keyId>",
    secretAccessKey: "<your secret>",
    s3BucketRegion: "<your region>", // example: "us-west-2"
    s3BucketName: "<your bucket>"    // example: "mycompany.testbucket"
  }
  private _awsCredentials: Credentials;
  private _s3ClientConfig: S3.ClientConfiguration;
  private _s3: S3;

  uploadStatus: string = "(no upload yet)";
  expectEndpoint: string;
  actualEndpoint: string;

  constructor() {
    // Create an AWS S3 client
    this._awsCredentials = new Credentials(this._awsConfig.accessKeyId, this._awsConfig.secretAccessKey);
    this._s3ClientConfig = {
      credentials: this._awsCredentials,
      region: this._awsConfig.s3BucketRegion,
      sslEnabled: true
    };
    this._s3 = new S3(this._s3ClientConfig);

    // Set the expected and actual endpoints
    var isRegionUSEast :boolean = (this._awsConfig.s3BucketRegion).toLowerCase() == "us-east-1";
    var endpointHost :string = isRegionUSEast ? "s3" : "s3-" + this._awsConfig.s3BucketRegion
    this.expectEndpoint = endpointHost + ".amazonaws.com";
    this.actualEndpoint = this._s3.config.endpoint;
  }

  // Event triggered when a file has been specified 
  fileEvent(fileInput: any) {
    this.uploadStatus = "starting upload...";

    // get the file to upload
    let file: File = fileInput.target.files[0];
    console.log(file);

    // upload file to S3
    let putObjectRequest: S3.PutObjectRequest = {
      Key: 'categories/' + file.name,
      Body: file,
      Bucket: this._awsConfig.s3BucketName,
      ContentType: file.type,
      ServerSideEncryption: "AES256"
    };

    // use "that" to be able to reach component properties within the then/catch callback functions
    let that = this;

    // upload to S3
    this._s3.upload(putObjectRequest).promise()
      .then(function (response: S3.ManagedUpload.SendData) {
        that.uploadStatus = "Success!\n File URI: " + response.Location;
        // alert("upload successful!");
      })
      .catch(function (err: Error) {
        var errMsg = "";
        errMsg += "upload failed.\n ";
        errMsg += "Error Message: " + err.message + "\n ";
        errMsg += "NOTE: an error message of 'Network Failure' may mean that you have the wrong region or the wrong bucket name.";
        that.uploadStatus = errMsg;
        // alert(errMsg);
      });
  }
}

systemjs.config.js 补充:

(function (global) {
  System.config({
    ...
    map: {
      ...
      'aws-sdk': 'npm:aws-sdk'
    },
    packages: {
      ...
      'aws-sdk': {
        defaultExtension: 'js',
        main: 'dist/aws-sdk.js',
        format: 'global'
      }
    }
  });
})(this);

app.module.ts:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { S3UploadTestComponent } from './s3-upload-test.component';

@NgModule({
  imports: [BrowserModule],
  declarations: [
    AppComponent,
    S3UploadTestComponent,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts:

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <h1>Hello {{name}}</h1>
    <s3-upload-test></s3-upload-test>
  `,
})
export class AppComponent  { name = 'Angular'; }

AWS S3 存储桶 CORS 配置:
注意:您可能希望根据您的安全需求设置更多限制

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

AWS IAM 政策(附加到用户或组):
注意:您几乎肯定会希望使允许的操作更具限制性,以满足您的安全需求
注意:将&lt;your bucketname&gt; 替换为适当的值

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1485926968000",
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::<your bucketname>/*"
            ]
        }
    ]
}


如果这不能解决您的问题,请使用 Chrome 开发工具并查看“网络”选项卡以查看对 S3 API 的 OPTIONS 请求并使用整个响应更新您的问题。当 AWS S3 预检失败时,它们通常会在响应中提供良好的信息。

【讨论】:

  • 感谢您的尝试。但是,上面的代码无法运行:bucketname.s3.amazonaws.com/categories/imagename.pngnet::ERR_TUNNEL_CONNECTION_FAILED
  • 我更新并提供了一个完整的组件供您使用——我已经使用我自己的 S3 存储桶进行了测试。此外,还提供了非常详细的说明。
  • 嗨 Rob,我正在使用来自 angular.io 的快速入门。使用此代码时: import { Credentials, S3 } from 'aws-sdk';我需要在 system.js 中配置什么以便导入工作吗?
  • "(SystemJS) XHR 错误 (404 Not Found) loading localhost:3000/aws-sdk↵ Error: XHR error (404 Not Found) loading localhost:3000/aws-sdk ...
  • 我添加了更多信息和配置。虽然最终很简单,但让 systemjs 的东西正确使用我花了一些时间......在原始答案中,我使用的是一个干净的 angular-cli 项目(使用 webpack),它开箱即用。
猜你喜欢
  • 2019-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-07
  • 2022-01-23
  • 1970-01-01
  • 2017-10-17
  • 2018-04-25
相关资源
最近更新 更多