为 S3 对象 Lambda 接入点编写 Lambda 函数 - Amazon Simple Storage Service
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

为 S3 对象 Lambda 接入点编写 Lambda 函数

本节详细介绍如何编写与 Amazon S3 对象 Lambda 接入点结合使用的 Amazon Lambda 函数。

要了解一些 S3 对象 Lambda 任务的完整端到端过程,请参阅以下内容:

在 Lambda 中处理 GetObject 请求

本节假设您的对象 Lambda 接入点配置为对于 GetObject 调用 Lambda 函数。S3 对象 Lambda 包括 Amazon S3 API 操作 WriteGetObjectResponse,该操作可以让 Lambda 函数向 GetObject 调用方提供自定义数据和响应标头。

WriteGetObjectResponse 可根据您的处理需要,让您对状态代码、响应标头和响应正文进行广泛的控制。您可以使用 WriteGetObjectResponse 来响应整个变换对象、变换对象的某些部分或其他基于应用程序上下文的响应。以下部分介绍了使用 WriteGetObjectResponse API 操作的独特示例。

  • 示例 1:使用 HTTP 状态代码 403(禁止访问)进行响应

  • 示例 2: 通过转换后的图像进行响应

  • 示例 3: 流式压缩内容

示例 1:使用 HTTP 状态代码 403(禁止访问)进行响应

您可以使用 WriteGetObjectResponse 根据对象的内容使用 HTTP 状态代码 403(禁止)进行响应。

Java

package com.amazon.s3.objectlambda; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.S3ObjectLambdaEvent; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.WriteGetObjectResponseRequest; import java.io.ByteArrayInputStream; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; public class Example1 { public void handleRequest(S3ObjectLambdaEvent event, Context context) throws Exception { AmazonS3 s3Client = AmazonS3Client.builder().build(); // Check to see if the request contains all of the necessary information. // If it does not, send a 4XX response and a custom error code and message. // Otherwise, retrieve the object from S3 and stream it // to the client unchanged. var tokenIsNotPresent = !event.getUserRequest().getHeaders().containsKey("requiredToken"); if (tokenIsNotPresent) { s3Client.writeGetObjectResponse(new WriteGetObjectResponseRequest() .withRequestRoute(event.outputRoute()) .withRequestToken(event.outputToken()) .withStatusCode(403) .withContentLength(0L).withInputStream(new ByteArrayInputStream(new byte[0])) .withErrorCode("MissingRequiredToken") .withErrorMessage("The required token was not present in the request.")); return; } // Prepare the presigned URL for use and make the request to S3. HttpClient httpClient = HttpClient.newBuilder().build(); var presignedResponse = httpClient.send( HttpRequest.newBuilder(new URI(event.inputS3Url())).GET().build(), HttpResponse.BodyHandlers.ofInputStream()); // Stream the original bytes back to the caller. s3Client.writeGetObjectResponse(new WriteGetObjectResponseRequest() .withRequestRoute(event.outputRoute()) .withRequestToken(event.outputToken()) .withInputStream(presignedResponse.body())); } }
Python

import boto3 import requests def handler(event, context): s3 = boto3.client('s3') """ Retrieve the operation context object from the event. This object indicates where the WriteGetObjectResponse request should be delivered and contains a presigned URL in 'inputS3Url' where we can download the requested object from. The 'userRequest' object has information related to the user who made this 'GetObject' request to S3 Object Lambda. """ get_context = event["getObjectContext"] user_request_headers = event["userRequest"]["headers"] route = get_context["outputRoute"] token = get_context["outputToken"] s3_url = get_context["inputS3Url"] # Check for the presence of a 'CustomHeader' header and deny or allow based on that header. is_token_present = "SuperSecretToken" in user_request_headers if is_token_present: # If the user presented our custom 'SuperSecretToken' header, we send the requested object back to the user. response = requests.get(s3_url) s3.write_get_object_response(RequestRoute=route, RequestToken=token, Body=response.content) else: # If the token is not present, we send an error back to the user. s3.write_get_object_response(RequestRoute=route, RequestToken=token, StatusCode=403, ErrorCode="NoSuperSecretTokenFound", ErrorMessage="The request was not secret enough.") # Gracefully exit the Lambda function. return { 'status_code': 200 }
Node.js

const { S3 } = require('aws-sdk'); const axios = require('axios').default; exports.handler = async (event) => { const s3 = new S3(); // Retrieve the operation context object from the event. This object indicates where the WriteGetObjectResponse request // should be delivered and contains a presigned URL in 'inputS3Url' where we can download the requested object from. // The 'userRequest' object has information related to the user who made this 'GetObject' request to S3 Object Lambda. const { userRequest, getObjectContext } = event; const { outputRoute, outputToken, inputS3Url } = getObjectContext; // Check for the presence of a 'CustomHeader' header and deny or allow based on that header. const isTokenPresent = Object .keys(userRequest.headers) .includes("SuperSecretToken"); if (!isTokenPresent) { // If the token is not present, we send an error back to the user. The 'await' in front of the request // indicates that we want to wait for this request to finish sending before moving on. await s3.writeGetObjectResponse({ RequestRoute: outputRoute, RequestToken: outputToken, StatusCode: 403, ErrorCode: "NoSuperSecretTokenFound", ErrorMessage: "The request was not secret enough.", }).promise(); } else { // If the user presented our custom 'SuperSecretToken' header, we send the requested object back to the user. // Again, note the presence of 'await'. const presignedResponse = await axios.get(inputS3Url); await s3.writeGetObjectResponse({ RequestRoute: outputRoute, RequestToken: outputToken, Body: presignedResponse.data, }).promise(); } // Gracefully exit the Lambda function. return { statusCode: 200 }; }

示例 2: 通过转换后的图像进行响应

执行图像转换时,您可能会发现需要源对象的所有字节,然后才能开始处理它们。在这种情况下,您的 WriteGetObjectResponse 请求会在一次调用中将整个对象返回给请求的应用程序。

Java

package com.amazon.s3.objectlambda; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.events.S3ObjectLambdaEvent; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.WriteGetObjectResponseRequest; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.awt.Image; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; public class Example2 { private static final int HEIGHT = 250; private static final int WIDTH = 250; public void handleRequest(S3ObjectLambdaEvent event, Context context) throws Exception { AmazonS3 s3Client = AmazonS3Client.builder().build(); HttpClient httpClient = HttpClient.newBuilder().build(); // Prepare the presigned URL for use and make the request to S3. var presignedResponse = httpClient.send( HttpRequest.newBuilder(new URI(event.inputS3Url())).GET().build(), HttpResponse.BodyHandlers.ofInputStream()); // The entire image is loaded into memory here so that we can resize it. // Once the resizing is completed, we write the bytes into the body // of the WriteGetObjectResponse request. var originalImage = ImageIO.read(presignedResponse.body()); var resizingImage = originalImage.getScaledInstance(WIDTH, HEIGHT, Image.SCALE_DEFAULT); var resizedImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); resizedImage.createGraphics().drawImage(resizingImage, 0, 0, WIDTH, HEIGHT, null); var baos = new ByteArrayOutputStream(); ImageIO.write(resizedImage, "png", baos); // Stream the bytes back to the caller. s3Client.writeGetObjectResponse(new WriteGetObjectResponseRequest() .withRequestRoute(event.outputRoute()) .withRequestToken(event.outputToken()) .withInputStream(new ByteArrayInputStream(baos.toByteArray()))); } }
Python

import boto3 import requests import io from PIL import Image def handler(event, context): """ Retrieve the operation context object from the event. This object indicates where the WriteGetObjectResponse request should be delivered and has a presigned URL in 'inputS3Url' where we can download the requested object from. The 'userRequest' object has information related to the user who made this 'GetObject' request to S3 Object Lambda. """ get_context = event["getObjectContext"] route = get_context["outputRoute"] token = get_context["outputToken"] s3_url = get_context["inputS3Url"] """ In this case, we're resizing .png images that are stored in S3 and are accessible through the presigned URL 'inputS3Url'. """ image_request = requests.get(s3_url) image = Image.open(io.BytesIO(image_request.content)) image.thumbnail((256,256), Image.ANTIALIAS) transformed = io.BytesIO() image.save(transformed, "png") # Send the resized image back to the client. s3 = boto3.client('s3') s3.write_get_object_response(Body=transformed.getvalue(), RequestRoute=route, RequestToken=token) # Gracefully exit the Lambda function. return { 'status_code': 200 }
Node.js

const { S3 } = require('aws-sdk'); const axios = require('axios').default; const sharp = require('sharp'); exports.handler = async (event) => { const s3 = new S3(); // Retrieve the operation context object from the event. This object indicates where the WriteGetObjectResponse request // should be delivered and has a presigned URL in 'inputS3Url' where we can download the requested object from. const { getObjectContext } = event; const { outputRoute, outputToken, inputS3Url } = getObjectContext; // In this case, we're resizing .png images that are stored in S3 and are accessible through the presigned URL // 'inputS3Url'. const { data } = await axios.get(inputS3Url, { responseType: 'arraybuffer' }); // Resize the image. const resized = await sharp(data) .resize({ width: 256, height: 256 }) .toBuffer(); // Send the resized image back to the client. await s3.writeGetObjectResponse({ RequestRoute: outputRoute, RequestToken: outputToken, Body: resized, }).promise(); // Gracefully exit the Lambda function. return { statusCode: 200 }; }

示例 3: 流式压缩内容

压缩对象时,压缩数据是以增量方式生成的。因此,您可以使用您的 WriteGetObjectResponse 请求在压缩数据准备就绪后立即返回压缩的数据。如本示例所示,您不需要知道已完成转换的长度。

Java

package com.amazon.s3.objectlambda; import com.amazonaws.services.lambda.runtime.events.S3ObjectLambdaEvent; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.WriteGetObjectResponseRequest; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; public class Example3 { public void handleRequest(S3ObjectLambdaEvent event, Context context) throws Exception { AmazonS3 s3Client = AmazonS3Client.builder().build(); HttpClient httpClient = HttpClient.newBuilder().build(); // Request the original object from S3. var presignedResponse = httpClient.send( HttpRequest.newBuilder(new URI(event.inputS3Url())).GET().build(), HttpResponse.BodyHandlers.ofInputStream()); // Consume the incoming response body from the presigned request, // apply our transformation on that data, and emit the transformed bytes // into the body of the WriteGetObjectResponse request as soon as they're ready. // This example compresses the data from S3, but any processing pertinent // to your application can be performed here. var bodyStream = new GZIPCompressingInputStream(presignedResponse.body()); // Stream the bytes back to the caller. s3Client.writeGetObjectResponse(new WriteGetObjectResponseRequest() .withRequestRoute(event.outputRoute()) .withRequestToken(event.outputToken()) .withInputStream(bodyStream)); } }
Python

import boto3 import requests import zlib from botocore.config import Config """ A helper class to work with content iterators. Takes an interator and compresses the bytes that come from it. It implements 'read' and '__iter__' so that the SDK can stream the response. """ class Compress: def __init__(self, content_iter): self.content = content_iter self.compressed_obj = zlib.compressobj() def read(self, _size): for data in self.__iter__() return data def __iter__(self): while True: data = next(self.content) chunk = self.compressed_obj.compress(data) if not chunk: break yield chunk yield self.compressed_obj.flush() def handler(event, context): """ Setting the 'payload_signing_enabled' property to False allows us to send a streamed response back to the client. in this scenario, a streamed response means that the bytes are not buffered into memory as we're compressing them, but instead are sent straight to the user. """ my_config = Config( region_name='eu-west-1', signature_version='s3v4', s3={ "payload_signing_enabled": False } ) s3 = boto3.client('s3', config=my_config) """ Retrieve the operation context object from the event. This object indicates where the WriteGetObjectResponse request should be delivered and has a presigned URL in 'inputS3Url' where we can download the requested object from. The 'userRequest' object has information related to the user who made this 'GetObject' request to S3 Object Lambda. """ get_context = event["getObjectContext"] route = get_context["outputRoute"] token = get_context["outputToken"] s3_url = get_context["inputS3Url"] # Compress the 'get' request stream. with requests.get(s3_url, stream=True) as r: compressed = Compress(r.iter_content()) # Send the stream back to the client. s3.write_get_object_response(Body=compressed, RequestRoute=route, RequestToken=token, ContentType="text/plain", ContentEncoding="gzip") # Gracefully exit the Lambda function. return {'status_code': 200}
Node.js

const { S3 } = require('aws-sdk'); const axios = require('axios').default; const zlib = require('zlib'); exports.handler = async (event) => { const s3 = new S3(); // Retrieve the operation context object from the event. This object indicates where the WriteGetObjectResponse request // should be delivered and has a presigned URL in 'inputS3Url' where we can download the requested object from. const { getObjectContext } = event; const { outputRoute, outputToken, inputS3Url } = getObjectContext; // Download the object from S3 and process it as a stream, because it might be a huge object and we don't want to // buffer it in memory. Note the use of 'await' because we want to wait for 'writeGetObjectResponse' to finish // before we can exit the Lambda function. await axios({ method: 'GET', url: inputS3Url, responseType: 'stream', }).then( // Gzip the stream. response => response.data.pipe(zlib.createGzip()) ).then( // Finally send the gzip-ed stream back to the client. stream => s3.writeGetObjectResponse({ RequestRoute: outputRoute, RequestToken: outputToken, Body: stream, ContentType: "text/plain", ContentEncoding: "gzip", }).promise() ); // Gracefully exit the Lambda function. return { statusCode: 200 }; }
注意

虽然 S3 对象 Lambda 允许使用长达 60 秒的时间通过 WriteGetObjectResponse 请求来将完整响应发送给发起人,但实际可用时间可能会减少。例如,Lambda 函数超时可能少于 60 秒。在其他情况下,发起人可能会有更严格的超时。

要使原始调用方能够收到非 HTTP 状态代码 500(内部服务器错误)响应,必须完成 WriteGetObjectResponse 调用。如果 Lambda 函数返回时引发了异常或其他情况,则在调用 WriteGetObjectResponse API 操作之前,原始调用方将收到 500(内部服务器错误)响应。在完成响应所需的时间内引发的异常将导致截断对发起人的响应。如果 Lambda 函数从 WriteGetObjectResponse API 调用收到 HTTP 状态代码 200(OK)响应,即表示原始调用方已经发送了完整的请求。S3 对象 Lambda 会忽略 Lambda 函数的响应,无论是否抛出异常。

调用 WriteGetObjectResponse API 操作时,Amazon S3 需要事件上下文中的路由和请求令牌。有关更多信息,请参阅事件上下文格式和用法

WriteGetObjectResult 请求与原始调用方连接起来需要这些路由和请求令牌参数。尽管重试 500(内部服务器错误)响应始终是适当的,但请注意,由于请求令牌是一个单一用途令牌,后续尝试使用它可能会导致 HTTP 状态代码 400(错误请求)响应。虽然使用路由和请求令牌调用 WriteGetObjectResponse 不需要从被调用的 Lambda 函数发出,但它必须由同一账户中的身份发出。还必须在 Lambda 函数完成执行之前完成调用。

在 Lambda 中处理 HeadObject 请求

本节假设您的对象 Lambda 接入点配置为对于 HeadObject 调用 Lambda 函数。Lambda 将收到一个 JSON 有效负载,其中包含名为 headObjectContext 的密钥。在上下文中,有一个名为 inputS3Url 的属性,它是 HeadObject 的支持接入点的预签名 URL。

预签名 URL 将包含以下属性(如果已指定):

  • versionId(在查询参数中)

  • requestPayer(在 x-amz-request-payer 标头中)

  • expectedBucketOwner(在 x-amz-expected-bucket-owner 标头中)

其他属性未预签名,因此不会包含在内。当调用在 userRequest 标头中找到的预签名 URL 时,可以手动将作为标头发送的未签名选项添加到请求中。对于 HeadObject,不支持服务器端加密选项。

有关请求语法 URI 参数,请参阅《Amazon Simple Storage Service API 参考》中的 HeadObject

以下示例显示了 HeadObject 的 Lambda JSON 输入有效负载。

{ "xAmzRequestId": "requestId", "**headObjectContext**": { "**inputS3Url**": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=<snip>" }, "configuration": { "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", "payload": "{}" }, "userRequest": { "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", "headers": { "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", "Accept-Encoding": "identity", "X-Amz-Content-SHA256": "e3b0c44298fc1example" } }, "userIdentity": { "type": "AssumedRole", "principalId": "principalId", "arn": "arn:aws:sts::111122223333:assumed-role/Admin/example", "accountId": "111122223333", "accessKeyId": "accessKeyId", "sessionContext": { "attributes": { "mfaAuthenticated": "false", "creationDate": "Wed Mar 10 23:41:52 UTC 2021" }, "sessionIssuer": { "type": "Role", "principalId": "principalId", "arn": "arn:aws:iam::111122223333:role/Admin", "accountId": "111122223333", "userName": "Admin" } } }, "protocolVersion": "1.00" }

您的 Lambda 函数应返回一个 JSON 对象,其中包含将针对 HeadObject 调用返回的标头和值。

下面的示例显示 HeadObject 的 Lambda 响应 JSON 的结构。

{ "statusCode": <number>; // Required "errorCode": <string>; "errorMessage": <string>; "headers": { "Accept-Ranges": <string>, "x-amz-archive-status": <string>, "x-amz-server-side-encryption-bucket-key-enabled": <boolean>, "Cache-Control": <string>, "Content-Disposition": <string>, "Content-Encoding": <string>, "Content-Language": <string>, "Content-Length": <number>, // Required "Content-Type": <string>, "x-amz-delete-marker": <boolean>, "ETag": <string>, "Expires": <string>, "x-amz-expiration": <string>, "Last-Modified": <string>, "x-amz-missing-meta": <number>, "x-amz-object-lock-mode": <string>, "x-amz-object-lock-legal-hold": <string>, "x-amz-object-lock-retain-until-date": <string>, "x-amz-mp-parts-count": <number>, "x-amz-replication-status": <string>, "x-amz-request-charged": <string>, "x-amz-restore": <string>, "x-amz-server-side-encryption": <string>, "x-amz-server-side-encryption-customer-algorithm": <string>, "x-amz-server-side-encryption-aws-kms-key-id": <string>, "x-amz-server-side-encryption-customer-key-MD5": <string>, "x-amz-storage-class": <string>, "x-amz-tagging-count": <number>, "x-amz-version-id": <string>, <x-amz-meta-headers>: <string>, // user-defined metadata "x-amz-meta-meta1": <string>, // example of the user-defined metadata header, it will need the x-amz-meta prefix "x-amz-meta-meta2": <string> ... }; }

以下示例显示如何在返回 JSON 之前根据需要修改标头值,以使用预签名 URL 来填充您的响应。

Python

import requests def lambda_handler(event, context): print(event) # Extract the presigned URL from the input. s3_url = event["headObjectContext"]["inputS3Url"] # Get the head of the object from S3. response = requests.head(s3_url) # Return the error to S3 Object Lambda (if applicable). if (response.status_code >= 400): return { "statusCode": response.status_code, "errorCode": "RequestFailure", "errorMessage": "Request to S3 failed" } # Store the headers in a dictionary. response_headers = dict(response.headers) # This obscures Content-Type in a transformation, it is optional to add response_headers["Content-Type"] = "" # Return the headers to S3 Object Lambda. return { "statusCode": response.status_code, "headers": response_headers }

在 Lambda 中处理 ListObjects 请求

本节假设您的对象 Lambda 接入点配置为对于 ListObjects 调用 Lambda 函数。Lambda 将收到带有名为 listObjectsContext 的新对象的 JSON 有效负载。listObjectsContext 包含单个属性 inputS3Url,它是 ListObjects 的支持接入点的预签名 URL。

GetObjectHeadObject 不同,预签名 URL 将包含以下属性(如果已指定):

  • 所有查询参数

  • requestPayer(在 x-amz-request-payer 标头中)

  • expectedBucketOwner(在 x-amz-expected-bucket-owner 标头中)

有关请求语法 URI 参数,请参阅《Amazon Simple Storage Service API 参考》中的 ListObjects

重要

我们建议您在开发应用程序时使用较新的版本 ListObjectsV2。为了向后兼容,Amazon S3 仍继续支持 ListObjects

以下示例显示了 ListObjects 的 Lambda JSON 输入有效负载。

{ "xAmzRequestId": "requestId", "**listObjectsContext**": { "**inputS3Url**": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/?X-Amz-Security-Token=<snip>", }, "configuration": { "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", "payload": "{}" }, "userRequest": { "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", "headers": { "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", "Accept-Encoding": "identity", "X-Amz-Content-SHA256": "e3b0c44298fc1example" } }, "userIdentity": { "type": "AssumedRole", "principalId": "principalId", "arn": "arn:aws:sts::111122223333:assumed-role/Admin/example", "accountId": "111122223333", "accessKeyId": "accessKeyId", "sessionContext": { "attributes": { "mfaAuthenticated": "false", "creationDate": "Wed Mar 10 23:41:52 UTC 2021" }, "sessionIssuer": { "type": "Role", "principalId": "principalId", "arn": "arn:aws:iam::111122223333:role/Admin", "accountId": "111122223333", "userName": "Admin" } } }, "protocolVersion": "1.00" }

您的 Lambda 函数应返回一个 JSON 对象,其中包含将从 S3 对象 Lambda 返回的状态码、列表 XML 结果或错误信息。

S3 对象 Lambda 不处理或验证 listResultXml,而是将其转发给 ListObjects 调用方。对于 listBucketResult,S3 对象 Lambda 期望某些属性属于特定类型,如果它无法解析它们,则会引发异常。无法同时提供 listResultXmllistBucketResult

以下示例演示如何使用预签名 URL 调用 Amazon S3 并使用结果填充响应,包括错误检查。

Python
import requests import xmltodict def lambda_handler(event, context): # Extract the presigned URL from the input. s3_url = event["listObjectsContext"]["inputS3Url"] # Get the head of the object from Amazon S3. response = requests.get(s3_url) # Return the error to S3 Object Lambda (if applicable). if (response.status_code >= 400): error = xmltodict.parse(response.content) return { "statusCode": response.status_code, "errorCode": error["Error"]["Code"], "errorMessage": error["Error"]["Message"] } # Store the XML result in a dict. response_dict = xmltodict.parse(response.content) # This obscures StorageClass in a transformation, it is optional to add for item in response_dict['ListBucketResult']['Contents']: item['StorageClass'] = "" # Convert back to XML. listResultXml = xmltodict.unparse(response_dict) # Create response with listResultXml. response_with_list_result_xml = { 'statusCode': 200, 'listResultXml': listResultXml } # Create response with listBucketResult. response_dict['ListBucketResult'] = sanitize_response_dict(response_dict['ListBucketResult']) response_with_list_bucket_result = { 'statusCode': 200, 'listBucketResult': response_dict['ListBucketResult'] } # Return the list to S3 Object Lambda. # Can return response_with_list_result_xml or response_with_list_bucket_result return response_with_list_result_xml # Converting the response_dict's key to correct casing def sanitize_response_dict(response_dict: dict): new_response_dict = dict() for key, value in response_dict.items(): new_key = key[0].lower() + key[1:] if key != "ID" else 'id' if type(value) == list: newlist = [] for element in value: if type(element) == type(dict()): element = sanitize_response_dict(element) newlist.append(element) value = newlist elif type(value) == dict: value = sanitize_response_dict(value) new_response_dict[new_key] = value return new_response_dict

下面的示例显示 ListObjects 的 Lambda 响应 JSON 的结构。

{ "statusCode": <number>; // Required "errorCode": <string>; "errorMessage": <string>; "listResultXml": <string>; // This can also be Error XML string in case S3 returned error response when calling the pre-signed URL "listBucketResult": { // listBucketResult can be provided instead of listResultXml, however they can not both be provided in the JSON response "name": <string>, // Required for 'listBucketResult' "prefix": <string>, "marker": <string>, "nextMarker": <string>, "maxKeys": <int>, // Required for 'listBucketResult' "delimiter": <string>, "encodingType": <string> "isTruncated": <boolean>, // Required for 'listBucketResult' "contents": [ { "key": <string>, // Required for 'content' "lastModified": <string>, "eTag": <string>, "checksumAlgorithm": <string>, // CRC32, CRC32C, SHA1, SHA256 "size": <int>, // Required for 'content' "owner": { "displayName": <string>, // Required for 'owner' "id": <string>, // Required for 'owner' }, "storageClass": <string> }, ... ], "commonPrefixes": [ { "prefix": <string> // Required for 'commonPrefix' }, ... ], } }

在 Lambda 中处理 ListObjectsV2 请求

本节假设您的对象 Lambda 接入点配置为对于 ListObjectsV2 调用 Lambda 函数。Lambda 将收到带有名为 listObjectsV2Context 的新对象的 JSON 有效负载。listObjectsV2Context 包含单个属性 inputS3Url,它是 ListObjectsV2 的支持接入点的预签名 URL。

GetObjectHeadObject 不同,预签名 URL 将包含以下属性(如果已指定):

  • 所有查询参数

  • requestPayer(在 x-amz-request-payer 标头中)

  • expectedBucketOwner(在 x-amz-expected-bucket-owner 标头中)

有关请求语法 URI 参数,请参阅《Amazon Simple Storage Service API 参考》中的 ListObjectsV2

以下示例显示了 ListObjectsV2 的 Lambda JSON 输入有效负载。

{ "xAmzRequestId": "requestId", "**listObjectsV2Context**": { "**inputS3Url**": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/?list-type=2&X-Amz-Security-Token=<snip>", }, "configuration": { "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", "payload": "{}" }, "userRequest": { "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", "headers": { "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", "Accept-Encoding": "identity", "X-Amz-Content-SHA256": "e3b0c44298fc1example" } }, "userIdentity": { "type": "AssumedRole", "principalId": "principalId", "arn": "arn:aws:sts::111122223333:assumed-role/Admin/example", "accountId": "111122223333", "accessKeyId": "accessKeyId", "sessionContext": { "attributes": { "mfaAuthenticated": "false", "creationDate": "Wed Mar 10 23:41:52 UTC 2021" }, "sessionIssuer": { "type": "Role", "principalId": "principalId", "arn": "arn:aws:iam::111122223333:role/Admin", "accountId": "111122223333", "userName": "Admin" } } }, "protocolVersion": "1.00" }

您的 Lambda 函数应返回一个 JSON 对象,其中包含将从 S3 对象 Lambda 返回的状态码、列表 XML 结果或错误信息。

S3 对象 Lambda 不处理或验证 listResultXml,而是将其转发给 ListObjectsV2 调用方。对于 listBucketResult,S3 对象 Lambda 期望某些属性属于特定类型,如果它无法解析它们,则会引发异常。无法同时提供 listResultXmllistBucketResult

以下示例演示如何使用预签名 URL 调用 Amazon S3 并使用结果填充响应,包括错误检查。

Python
import requests import xmltodict def lambda_handler(event, context): # Extract the presigned URL from the input. s3_url = event["listObjectsV2Context"]["inputS3Url"] # Get the head of the object from Amazon S3. response = requests.get(s3_url) # Return the error to S3 Object Lambda (if applicable). if (response.status_code >= 400): error = xmltodict.parse(response.content) return { "statusCode": response.status_code, "errorCode": error["Error"]["Code"], "errorMessage": error["Error"]["Message"] } # Store the XML result in a dict. response_dict = xmltodict.parse(response.content) # This obscures StorageClass in a transformation, it is optional to add for item in response_dict['ListBucketResult']['Contents']: item['StorageClass'] = "" # Convert back to XML. listResultXml = xmltodict.unparse(response_dict) # Create response with listResultXml. response_with_list_result_xml = { 'statusCode': 200, 'listResultXml': listResultXml } # Create response with listBucketResult. response_dict['ListBucketResult'] = sanitize_response_dict(response_dict['ListBucketResult']) response_with_list_bucket_result = { 'statusCode': 200, 'listBucketResult': response_dict['ListBucketResult'] } # Return the list to S3 Object Lambda. # Can return response_with_list_result_xml or response_with_list_bucket_result return response_with_list_result_xml # Converting the response_dict's key to correct casing def sanitize_response_dict(response_dict: dict): new_response_dict = dict() for key, value in response_dict.items(): new_key = key[0].lower() + key[1:] if key != "ID" else 'id' if type(value) == list: newlist = [] for element in value: if type(element) == type(dict()): element = sanitize_response_dict(element) newlist.append(element) value = newlist elif type(value) == dict: value = sanitize_response_dict(value) new_response_dict[new_key] = value return new_response_dict

下面的示例显示 ListObjectsV2 的 Lambda 响应 JSON 的结构。

{ "statusCode": <number>; // Required "errorCode": <string>; "errorMessage": <string>; "listResultXml": <string>; // This can also be Error XML string in case S3 returned error response when calling the pre-signed URL "listBucketResult": { // listBucketResult can be provided instead of listResultXml, however they can not both be provided in the JSON response "name": <string>, // Required for 'listBucketResult' "prefix": <string>, "startAfter": <string>, "continuationToken": <string>, "nextContinuationToken": <string>, "keyCount": <int>, // Required for 'listBucketResult' "maxKeys": <int>, // Required for 'listBucketResult' "delimiter": <string>, "encodingType": <string> "isTruncated": <boolean>, // Required for 'listBucketResult' "contents": [ { "key": <string>, // Required for 'content' "lastModified": <string>, "eTag": <string>, "checksumAlgorithm": <string>, // CRC32, CRC32C, SHA1, SHA256 "size": <int>, // Required for 'content' "owner": { "displayName": <string>, // Required for 'owner' "id": <string>, // Required for 'owner' }, "storageClass": <string> }, ... ], "commonPrefixes": [ { "prefix": <string> // Required for 'commonPrefix' }, ... ], } }