S3 객체 Lambda 액세스 포인트에 대한 Lambda 함수 작성 - Amazon Simple Storage Service

S3 객체 Lambda 액세스 포인트에 대한 Lambda 함수 작성

이 섹션에서는 Amazon S3 객체 Lambda 액세스 포인트에 사용할 AWS Lambda 함수를 작성하는 방법에 대해 자세히 설명합니다.

일부 S3 객체 Lambda 태스크 절차를 처음부터 끝까지 완전히 알아보려면 다음을 참조하십시오.

Lambda에서 GetObject 요청 작업

이 섹션에서는 객체 Lambda 액세스 포인트가 GetObject에 대한 Lambda 함수를 호출하도록 구성되어 있다고 가정합니다. S3 객체 Lambda에는 Lambda 함수에서 GetObject 호출자에게 사용자 지정된 데이터 및 응답 헤더를 제공할 수 있는 Amazon S3 API 작업인 WriteGetObjectResponse가 포함되어 있습니다.

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를 사용할 때는 WriteGetObjectResponse 요청을 통해 최대 60초간 호출자에게 전체 응답을 전송할 수 있지만 실제 사용 가능한 시간은 더 적을 수 있습니다. 예를 들어 Lambda 함수 제한 시간이 60초 미만일 수 있습니다. 호출자의 제한 시간이 더 엄격한 경우도 있을 수 있습니다.

원래 호출자가 HTTP 상태 코드500(내부 서버 오류) 외의 응답을 수신하려면 WriteGetObjectResponse 호출이 완료되어야 합니다. 예외나 다른 이유로 WriteGetObjectResponse API 작업이 호출되기 전에 Lambda 함수가 반환되면 원래 호출자가 500(내부 서버 오류) 응답을 수신합니다. 응답을 완료하는 데 걸리는 시간 동안 예외가 발생하면 호출자가 잘린 응답을 수신하게 됩니다. Lambda 함수가 WriteGetObjectResponse API 호출에서 HTTP 상태 코드 200(OK) 응답을 수신하는 경우 원래 호출자의 전체 요청이 전송된 것입니다. Lambda 함수의 응답은 예외가 발생했는지 여부에 관계없이 S3 객체 Lambda에 의해 무시됩니다.

WriteGetObjectResponse API 작업을 호출할 때 Amazon S3에는 이벤트 컨텍스트의 라우팅 및 요청 토큰이 필요합니다. 자세한 내용은 이벤트 컨텍스트 형식 및 사용법 단원을 참조하십시오.

라우팅 및 요청 토큰 파라미터는 원래 호출자와 WriteGetObjectResult 응답을 연결하는 데 필요합니다. 500(내부 서버 오류) 응답이 수신되는 경우 다시 시도하는 것이 항상 적절하지만 요청 토큰은 1회 사용 토큰이기 때문에 이후 사용을 시도할 경우 HTTP 상태 코드 400(잘못된 요청) 응답이 발생할 수 있습니다. 라우팅 및 요청 토큰을 사용한 WriteGetObjectResponse 호출을 호출된 Lambda 함수에서 수행할 필요는 없지만 동일한 계정의 자격 증명으로 수행되어야 합니다. 또한 Lambda 함수가 실행을 완료하기 전에 호출이 완료되어야 합니다.

Lambda에서 HeadObject 요청 작업

이 섹션에서는 객체 Lambda 액세스 포인트가 HeadObject에 대한 Lambda 함수를 호출하도록 구성되어 있다고 가정합니다. Lambda는 headObjectContext라는 키가 포함된 JSON 페이로드를 수신합니다. 컨텍스트 내에는 HeadObject의 지원 액세스 지점에 대해 미리 서명된 URL인 inputS3Url이라는 하나의 속성이 있습니다.

미리 서명된 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 함수는 HeadObject 호출에 대해 반환될 헤더와 값이 포함된 JSON 객체를 반환해야 합니다.

다음 예는 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> ... }; }

다음 예는 미리 서명된 URL을 사용하여 JSON을 반환하기 전에 필요에 따라 헤더 값을 수정하여 응답을 채우는 방법을 보여줍니다.

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 페이로드를 수신합니다. listObjectsContextListObjects에 대한 지원 액세스 지점의 미리 서명된 URL인 하나의 속성, inputS3Url을 포함합니다.

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 함수는 S3 객체 Lambda에서 반환되는 상태 코드, 목록 XML 결과 또는 오류 정보가 포함된 JSON 객체를 반환해야 합니다.

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 페이로드를 수신합니다. listObjectsV2ContextListObjectsV2에 대한 지원 액세스 지점의 미리 서명된 URL인 하나의 속성, inputS3Url을 포함합니다.

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 함수는 S3 객체 Lambda에서 반환되는 상태 코드, 목록 XML 결과 또는 오류 정보가 포함된 JSON 객체를 반환해야 합니다.

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' }, ... ], } }