教程:使用 Amazon S3 触发器调用 Lambda 函数
在本教程中,您将使用控制台创建 Lambda 函数,然后为 Amazon Simple Storage Service(Amazon S3)存储桶配置触发器。每次向 Amazon S3 存储桶添加对象时,函数都会运行并将该对象类型输出到 Amazon CloudWatch Logs 中。
本教程演示如何:
-
创建 Amazon S3 存储桶。
-
创建一个 Lambda 函数,该函数会在 Amazon S3 存储桶中返回对象的类型。
-
配置一个 Lambda 触发器,该触发器将在对象上传到存储桶时调用函数。
-
先后使用虚拟事件和触发器测试函数。
完成这些步骤后,您将了解如何配置 Lambda 函数,使其在向 Amazon S3 存储桶添加或删除对象时运行。您仅可以使用 AWS Management Console 完成此教程。
先决条件
如果您还没有 AWS 账户,请完成以下步骤来创建一个。
注册过程完成后,AWS 会向您发送一封确认电子邮件。在任何时候,您都可以通过转至 https://aws.amazon.com/ 并选择我的账户来查看当前的账户活动并管理您的账户。
注册 AWS 账户 后,请保护好您的 AWS 账户根用户,启用 AWS IAM Identity Center,并创建一个管理用户,以避免使用根用户执行日常任务。
将访问权限分配给其他用户
-
在 IAM Identity Center 中,创建一个权限集,该权限集遵循应用最低权限的最佳做法。
有关说明,请参阅《AWS IAM Identity Center 用户指南》中的创建权限集。
-
将用户分配到一个组,然后为该组分配单点登录访问权限。
有关说明,请参阅《AWS IAM Identity Center 用户指南》中的添加组。
创建 Amazon S3 存储桶
创建 Amazon S3 存储桶
-
打开 Amazon S3 控制台并选择存储桶页面。
-
选择 Create bucket(创建存储桶)。
-
在 General configuration(常规配置)下,执行以下操作:
-
对于存储桶名称,输入符合 Amazon S3 存储桶命名规则的全局唯一名称。存储桶名称只能由小写字母、数字、句点(.)和连字符(-)组成。
-
对于 AWS Region(亚马逊云科技区域),选择一个区域。在本教程的后面部分,您必须在同个区域中创建 Lambda 函数。
-
将所有其他选项设置为默认值并选择创建存储桶。
将测试对象上传到存储桶
要上传测试对象
-
打开 Amazon S3 控制台的存储桶页面,选择您在上一步中创建的存储桶。
-
选择上传。
-
选择添加文件,然后选择要上传的对象。您可以选择任何文件(例如 HappyFace.jpg
)。
-
选择打开,然后选择上传。
在本教程的后面部分,您要使用此对象测试 Lambda 函数。
创建权限策略
创建权限策略,允许 Lambda 从 Amazon S3 存储桶获取对象并写入 Amazon CloudWatch Logs。
创建策略
-
打开 IAM 控制台的 Policies(策略)页面。
-
请选择创建策略。
-
选择 JSON 选项卡,然后将以下自定义策略粘贴到 JSON 编辑器中。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:PutLogEvents",
"logs:CreateLogGroup",
"logs:CreateLogStream"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::*/*"
}
]
}
-
选择下一步:标签。
-
选择下一步:审核。
-
在 Review policy (查看策略) 下,为策略 Name (名称) 输入 s3-trigger-tutorial
。
-
选择 创建策略。
创建执行角色
执行角色是一个 AWS Identity and Access Management(IAM)角色,用于向 Lambda 函数授予访问 AWS 服务 和资源的权限。在此步骤中,您要使用在之前步骤中创建的权限策略来创建执行角色。
创建执行角色并附加自定义权限策略
-
打开 IAM 控制台的角色页面。
-
选择 Create role(创建角色)。
-
对于可信实体,选择 AWS 服务,对于使用案例,选择 Lambda。
-
选择下一步。
-
在策略搜索框中,输入 s3-trigger-tutorial
。
-
在搜索结果中,选择您创建的策略(s3-trigger-tutorial
),然后选择 Next(下一步)。
-
在 Role details(角色详细信息)下,为 Role name(角色名称)输入 lambda-s3-trigger-role
,然后选择 Create role(创建角色)。
创建 Lambda 函数
使用 Python 3.12 运行时系统在控制台中创建 Lambda 函数。
创建 Lambda 函数
-
打开 Lamba 控制台的 Functions(函数)页面。
-
确保您在创建 Amazon S3 存储桶所在的同一 AWS 区域 内操作。您可以使用屏幕顶部的下拉列表更改区域。
-
选择 Create function (创建函数)。
-
选择从头开始编写。
-
在基本信息中,执行以下操作:
-
对于函数名称,输入 s3-trigger-tutorial
。
-
对于运行时系统,选择 Python 3.12。
-
对于架构,选择 x86_64。
-
在更改默认执行角色选项卡中,执行以下操作:
-
展开选项卡,然后选择使用现有角色。
-
选择您之前创建的 lambda-s3-trigger-role
。
-
选择 Create function (创建函数)。
部署函数代码
本教程使用 Python 3.12 运行时系统,但我们还提供了适用于其他运行时系统的示例代码文件。您可以选择以下框中的选项卡,查看适用于您感兴趣的运行时系统的代码。
Lambda 函数检索已上传对象的键名称和来自该对象从 Amazon S3 收到的 event
参数的存储桶名称。然后,该函数使用 AWS SDK for Python (Boto3) 中的 get_object 方法来检索对象的元数据,包括已上传对象的内容类型(MIME 类型)。
要部署函数代码
-
在下框中选择 Python 选项卡并复制代码。
- .NET
-
- AWS SDK for .NET
-
查看 GitHub,了解更多信息。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。
使用 .NET 将 S3 事件与 Lambda 结合使用。
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
using System.Threading.Tasks;
using Amazon.Lambda.Core;
using Amazon.S3;
using System;
using Amazon.Lambda.S3Events;
using System.Web;
// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]
namespace S3Integration
{
public class Function
{
private static AmazonS3Client _s3Client;
public Function() : this(null)
{
}
internal Function(AmazonS3Client s3Client)
{
_s3Client = s3Client ?? new AmazonS3Client();
}
public async Task<string> Handler(S3Event evt, ILambdaContext context)
{
try
{
if (evt.Records.Count <= 0)
{
context.Logger.LogLine("Empty S3 Event received");
return string.Empty;
}
var bucket = evt.Records[0].S3.Bucket.Name;
var key = HttpUtility.UrlDecode(evt.Records[0].S3.Object.Key);
context.Logger.LogLine($"Request is for {bucket} and {key}");
var objectResult = await _s3Client.GetObjectAsync(bucket, key);
context.Logger.LogLine($"Returning {objectResult.Key}");
return objectResult.Key;
}
catch (Exception e)
{
context.Logger.LogLine($"Error processing request - {e.Message}");
return string.Empty;
}
}
}
}
- Go
-
- 适用于 Go V2 的 SDK
-
查看 GitHub,了解更多信息。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。
使用 Go 将 S3 事件与 Lambda 结合使用。
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package main
import (
"context"
"log"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func handler(ctx context.Context, s3Event events.S3Event) error {
sdkConfig, err := config.LoadDefaultConfig(ctx)
if err != nil {
log.Printf("failed to load default config: %s", err)
return err
}
s3Client := s3.NewFromConfig(sdkConfig)
for _, record := range s3Event.Records {
bucket := record.S3.Bucket.Name
key := record.S3.Object.URLDecodedKey
headOutput, err := s3Client.HeadObject(ctx, &s3.HeadObjectInput{
Bucket: &bucket,
Key: &key,
})
if err != nil {
log.Printf("error getting head of object %s/%s: %s", bucket, key, err)
return err
}
log.Printf("successfully retrieved %s/%s of type %s", bucket, key, *headOutput.ContentType)
}
return nil
}
func main() {
lambda.Start(handler)
}
- Java
-
- SDK for Java 2.x
-
查看 GitHub,了解更多信息。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。
使用 Java 将 S3 事件与 Lambda 结合使用。
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package example;
import software.amazon.awssdk.services.s3.model.HeadObjectRequest;
import software.amazon.awssdk.services.s3.model.HeadObjectResponse;
import software.amazon.awssdk.services.s3.S3Client;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.S3Event;
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3EventNotificationRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Handler implements RequestHandler<S3Event, String> {
private static final Logger logger = LoggerFactory.getLogger(Handler.class);
@Override
public String handleRequest(S3Event s3event, Context context) {
try {
S3EventNotificationRecord record = s3event.getRecords().get(0);
String srcBucket = record.getS3().getBucket().getName();
String srcKey = record.getS3().getObject().getUrlDecodedKey();
S3Client s3Client = S3Client.builder().build();
HeadObjectResponse headObject = getHeadObject(s3Client, srcBucket, srcKey);
logger.info("Successfully retrieved " + srcBucket + "/" + srcKey + " of type " + headObject.contentType());
return "Ok";
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private HeadObjectResponse getHeadObject(S3Client s3Client, String bucket, String key) {
HeadObjectRequest headObjectRequest = HeadObjectRequest.builder()
.bucket(bucket)
.key(key)
.build();
return s3Client.headObject(headObjectRequest);
}
}
- JavaScript
-
- 适用于 JavaScript 的 SDK(v3)
-
查看 GitHub,了解更多信息。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。
使用 JavaScript 将 S3 事件与 Lambda 结合使用。
import { S3Client, HeadObjectCommand } from "@aws-sdk/client-s3";
const client = new S3Client();
export const handler = async (event, context) => {
// Get the object from the event and show its content type
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
try {
const { ContentType } = await client.send(new HeadObjectCommand({
Bucket: bucket,
Key: key,
}));
console.log('CONTENT TYPE:', ContentType);
return ContentType;
} catch (err) {
console.log(err);
const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
console.log(message);
throw new Error(message);
}
};
使用 TypeScript 将 S3 事件与 Lambda 结合使用。
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { S3Event } from 'aws-lambda';
import { S3Client, HeadObjectCommand } from '@aws-sdk/client-s3';
const s3 = new S3Client({ region: process.env.AWS_REGION });
export const handler = async (event: S3Event): Promise<string | undefined> => {
// Get the object from the event and show its content type
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
const params = {
Bucket: bucket,
Key: key,
};
try {
const { ContentType } = await s3.send(new HeadObjectCommand(params));
console.log('CONTENT TYPE:', ContentType);
return ContentType;
} catch (err) {
console.log(err);
const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
console.log(message);
throw new Error(message);
}
};
- PHP
-
- 适用于 PHP 的 SDK
-
查看 GitHub,了解更多信息。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。
通过 PHP 将 S3 事件与 Lambda 结合使用。
<?php
use Bref\Context\Context;
use Bref\Event\S3\S3Event;
use Bref\Event\S3\S3Handler;
use Bref\Logger\StderrLogger;
require __DIR__ . '/vendor/autoload.php';
class Handler extends S3Handler
{
private StderrLogger $logger;
public function __construct(StderrLogger $logger)
{
$this->logger = $logger;
}
public function handleS3(S3Event $event, Context $context) : void
{
$this->logger->info("Processing S3 records");
// Get the object from the event and show its content type
$records = $event->getRecords();
foreach ($records as $record)
{
$bucket = $record->getBucket()->getName();
$key = urldecode($record->getObject()->getKey());
try {
$fileSize = urldecode($record->getObject()->getSize());
echo "File Size: " . $fileSize . "\n";
// TODO: Implement your custom processing logic here
} catch (Exception $e) {
echo $e->getMessage() . "\n";
echo 'Error getting object ' . $key . ' from bucket ' . $bucket . '. Make sure they exist and your bucket is in the same region as this function.' . "\n";
throw $e;
}
}
}
}
$logger = new StderrLogger();
return new Handler($logger);
- Python
-
- SDK for Python (Boto3)
-
查看 GitHub,了解更多信息。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。
使用 Python 将 S3 事件与 Lambda 结合使用。
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0
import json
import urllib.parse
import boto3
print('Loading function')
s3 = boto3.client('s3')
def lambda_handler(event, context):
#print("Received event: " + json.dumps(event, indent=2))
# Get the object from the event and show its content type
bucket = event['Records'][0]['s3']['bucket']['name']
key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
try:
response = s3.get_object(Bucket=bucket, Key=key)
print("CONTENT TYPE: " + response['ContentType'])
return response['ContentType']
except Exception as e:
print(e)
print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket))
raise e
- Ruby
-
- 适用于 Ruby 的 SDK
-
查看 GitHub,了解更多信息。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。
通过 Ruby 将 S3 事件与 Lambda 结合使用。
require 'json'
require 'uri'
require 'aws-sdk'
puts 'Loading function'
def lambda_handler(event:, context:)
s3 = Aws::S3::Client.new(region: 'region') # Your AWS region
# puts "Received event: #{JSON.dump(event)}"
# Get the object from the event and show its content type
bucket = event['Records'][0]['s3']['bucket']['name']
key = URI.decode_www_form_component(event['Records'][0]['s3']['object']['key'], Encoding::UTF_8)
begin
response = s3.get_object(bucket: bucket, key: key)
puts "CONTENT TYPE: #{response.content_type}"
return response.content_type
rescue StandardError => e
puts e.message
puts "Error getting object #{key} from bucket #{bucket}. Make sure they exist and your bucket is in the same region as this function."
raise e
end
end
- Rust
-
- 适用于 Rust 的 SDK
-
查看 GitHub,了解更多信息。在无服务器示例存储库中查找完整示例,并了解如何进行设置和运行。
使用 Rust 将 S3 事件与 Lambda 结合使用。
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
use aws_lambda_events::event::s3::S3Event;
use aws_sdk_s3::{Client};
use lambda_runtime::{run, service_fn, Error, LambdaEvent};
/// Main function
#[tokio::main]
async fn main() -> Result<(), Error> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.with_target(false)
.without_time()
.init();
// Initialize the AWS SDK for Rust
let config = aws_config::load_from_env().await;
let s3_client = Client::new(&config);
let res = run(service_fn(|request: LambdaEvent<S3Event>| {
function_handler(&s3_client, request)
})).await;
res
}
async fn function_handler(
s3_client: &Client,
evt: LambdaEvent<S3Event>
) -> Result<(), Error> {
tracing::info!(records = ?evt.payload.records.len(), "Received request from SQS");
if evt.payload.records.len() == 0 {
tracing::info!("Empty S3 event received");
}
let bucket = evt.payload.records[0].s3.bucket.name.as_ref().expect("Bucket name to exist");
let key = evt.payload.records[0].s3.object.key.as_ref().expect("Object key to exist");
tracing::info!("Request is for {} and object {}", bucket, key);
let s3_get_object_result = s3_client
.get_object()
.bucket(bucket)
.key(key)
.send()
.await;
match s3_get_object_result {
Ok(_) => tracing::info!("S3 Get Object success, the s3GetObjectResult contains a 'body' property of type ByteStream"),
Err(_) => tracing::info!("Failure with S3 Get Object request")
}
Ok(())
}
-
在 Lambda 控制台的代码源窗格中,将代码粘贴到代码编辑器中,替换 Lambda 创建的代码。
-
在部署部分,选择部署以更新函数的代码:
创建 Amazon S3 触发器
创建 Amazon S3 触发器
-
在函数概述窗格中,选择添加触发器。
-
选择 S3。
-
在存储桶下,选择您在本教程前面步骤中创建的存储桶。
-
在事件类型下,确保已选择所有对象创建事件。
-
在递归调用下,选中复选框以确认知晓不建议使用相同的 Amazon S3 存储桶用于输入和输出。
-
选择 添加。
当您使用 Lambda 控制台为 Lambda 函数创建 Amazon S3 触发器时,Amazon S3 会在您指定的存储桶上配置事件通知。在配置此事件通知之前,Amazon S3 会执行一系列检查以确认事件目标是否存在并具有所需的 IAM 策略。Amazon S3 还会对为该存储桶配置的任何其他事件通知执行这些测试。
由于这项检查,如果存储桶之前为已不再存在的资源或没有所需权限策略的资源配置了事件目标,则 Amazon S3 将无法创建新的事件通知。您将看到以下错误消息,表明无法创建触发器:
An error occurred when creating the trigger: Unable to validate the following destination configurations.
如果您之前使用同一存储桶为另一个 Lambda 函数配置了触发器,并且此后又删除了该函数或修改了其权限策略,则会看到此错误。
使用虚拟事件测试 Lambda 函数
要使用虚拟事件测试 Lambda 函数
-
在函数的 Lambda 控制台页面中,选择测试选项卡。
-
对于事件名称,输入 MyTestEvent
。
-
在事件 JSON 中,粘贴以下测试事件。请务必替换以下值:
-
使用 us-east-1
替换要在其中创建 Amazon S3 存储桶的区域。
-
将 amzn-s3-demo-bucket
的两个实例都替换为 Amazon S3 存储桶的名称。
-
将 test%2FKey
替换为您之前上传到存储桶的测试对象的名称(例如,HappyFace.jpg
)。
{
"Records": [
{
"eventVersion": "2.0",
"eventSource": "aws:s3",
"awsRegion": "us-east-1
",
"eventTime": "1970-01-01T00:00:00.000Z",
"eventName": "ObjectCreated:Put",
"userIdentity": {
"principalId": "EXAMPLE"
},
"requestParameters": {
"sourceIPAddress": "127.0.0.1"
},
"responseElements": {
"x-amz-request-id": "EXAMPLE123456789",
"x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
},
"s3": {
"s3SchemaVersion": "1.0",
"configurationId": "testConfigRule",
"bucket": {
"name": "amzn-s3-demo-bucket
",
"ownerIdentity": {
"principalId": "EXAMPLE"
},
"arn": "arn:aws:s3:::amzn-s3-demo-bucket
"
},
"object": {
"key": "test%2Fkey
",
"size": 1024,
"eTag": "0123456789abcdef0123456789abcdef",
"sequencer": "0A1B2C3D4E5F678901"
}
}
}
]
}
-
选择保存。
-
选择测试。
-
如果函数成功运行,您将在执行结果选项卡中看到如下输出。
Response
"image/jpeg"
Function Logs
START RequestId: 12b3cae7-5f4e-415e-93e6-416b8f8b66e6 Version: $LATEST
2021-02-18T21:40:59.280Z 12b3cae7-5f4e-415e-93e6-416b8f8b66e6 INFO INPUT BUCKET AND KEY: { Bucket: 'amzn-s3-demo-bucket', Key: 'HappyFace.jpg' }
2021-02-18T21:41:00.215Z 12b3cae7-5f4e-415e-93e6-416b8f8b66e6 INFO CONTENT TYPE: image/jpeg
END RequestId: 12b3cae7-5f4e-415e-93e6-416b8f8b66e6
REPORT RequestId: 12b3cae7-5f4e-415e-93e6-416b8f8b66e6 Duration: 976.25 ms Billed Duration: 977 ms Memory Size: 128 MB Max Memory Used: 90 MB Init Duration: 430.47 ms
Request ID
12b3cae7-5f4e-415e-93e6-416b8f8b66e6
使用 Amazon S3 触发器测试 Lambda 函数
要使用配置的触发器测试函数,请使用控制台将对象上传到 Amazon S3 存储桶。要验证 Lambda 函数是否按预期运行,请使用 CloudWatch Logs 查看函数的输出。
要将对象上传到 Amazon S3 存储桶
-
打开 Amazon S3 控制台的存储桶页面,选择之前创建的存储桶。
-
选择上传。
-
选择添加文件,然后使用文件选择器选择要上传的对象。此对象可以是您选择的任何文件。
-
选择打开,然后选择上传。
使用 CloudWatch Logs 验证函数调用情况
-
打开 CloudWatch 控制台。
-
确保您在创建 Lambda 函数所在相同的 AWS 区域 操作。您可以使用屏幕顶部的下拉列表更改区域。
-
选择日志,然后选择日志组。
-
选择函数 (/aws/lambda/s3-trigger-tutorial
) 的日志组。
-
在日志流下,选择最新的日志流。
-
如果已正确调用函数来响应 Amazon S3 触发器,您会看到如下输出。您看到的 CONTENT TYPE
取决于上传到存储桶的文件类型。
2022-05-09T23:17:28.702Z 0cae7f5a-b0af-4c73-8563-a3430333cc10 INFO CONTENT TYPE: image/jpeg
清除资源
除非您想要保留为本教程创建的资源,否则可立即将其删除。通过删除您不再使用的 AWS 资源,可防止您的 AWS 账户 产生不必要的费用。
删除 Lambda 函数
-
打开 Lamba 控制台的 Functions(函数)页面。
-
选择您创建的函数。
-
依次选择操作和删除。
-
在文本输入字段中键入 delete
,然后选择删除。
删除执行角色
-
打开 IAM 控制台的角色页面。
-
选择您创建的执行角色。
-
选择删除。
-
在文本输入字段中输入角色名称,然后选择 Delete(删除)。
删除 S3 存储桶
-
打开 Amazon S3 控制台。
-
选择您创建的存储桶。
-
选择删除。
-
在文本输入字段中输入存储桶的名称。
-
选择删除存储桶。
后续步骤
在 教程:使用 Amazon S3 触发器创建缩略图 中,Amazon S3 触发器会调用一个函数,该函数会为上传到存储桶的每个图像文件创建缩略图。本教程需要适度的AWS和 Lambda 领域知识水平。其展示了如何使用 AWS Command Line Interface(AWS CLI)来创建资源,以及如何为函数及其依赖项创建 .zip 文件存档部署包。