

# 为 S3 对象 Lambda 接入点编写 Lambda 函数
<a name="olap-writing-lambda"></a>

**注意**  
自 2025 年 11 月 7 日起，S3 对象 Lambda 仅可供当前在使用该服务的现有客户以及部分 AWS 合作伙伴网络（APN）合作伙伴使用。要了解与 S3 对象 Lambda 类似的功能，请在此处了解更多信息：[Amazon S3 Object Lambda availability change](https://docs.aws.amazon.com/AmazonS3/latest/userguide/amazons3-ol-change.html)。

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

要了解一些 S3 对象 Lambda 任务的完整端到端过程，请参阅以下内容：
+ [教程：使用 S3 对象 Lambda 转换应用程序的数据](tutorial-s3-object-lambda-uppercase.md)
+ [教程：使用 S3 对象 Lambda 和 Amazon Comprehend 检测和修订 PII 数据](tutorial-s3-object-lambda-redact-pii.md)
+ [教程：使用 S3 对象 Lambda 在检索图像时对其动态加水印](https://aws.amazon.com/getting-started/hands-on/amazon-s3-object-lambda-to-dynamically-watermark-images/?ref=docs_gateway/amazons3/olap-writing-lambda.html)

**Topics**
+ [在 Lambda 中处理 `GetObject` 请求](#olap-getobject-response)
+ [在 Lambda 中处理 `HeadObject` 请求](#olap-headobject)
+ [在 Lambda 中处理 `ListObjects` 请求](#olap-listobjects)
+ [在 Lambda 中处理 `ListObjectsV2` 请求](#olap-listobjectsv2)
+ [事件上下文格式和用法](olap-event-context.md)
+ [使用 Range 和 partNumber 标头](range-get-olap.md)

## 在 Lambda 中处理 `GetObject` 请求
<a name="olap-getobject-response"></a>

本节假设您的对象 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 software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.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 {
        S3Client s3Client = S3Client.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(WriteGetObjectResponseRequest.builder()
                    .requestRoute(event.outputRoute())
                    .requestToken(event.outputToken())
                    .statusCode(403)
                    .contentLength(0L)
                    .errorCode("MissingRequiredToken")
                    .errorMessage("The required token was not present in the request.")
                    .build(),
                    RequestBody.fromInputStream(new ByteArrayInputStream(new byte[0]), 0L));
            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(WriteGetObjectResponseRequest.builder()
                .requestRoute(event.outputRoute())
                .requestToken(event.outputToken())
                .build(),
                RequestBody.fromInputStream(presignedResponse.body(),
                    presignedResponse.headers().firstValueAsLong("content-length").orElse(-1L)));
    }
}
```

------
#### [ 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 software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.WriteGetObjectResponseRequest;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.Image;
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 Example2V2 {

    private static final int HEIGHT = 250;
    private static final int WIDTH = 250;

    public void handleRequest(S3ObjectLambdaEvent event, Context context) throws Exception {
        S3Client s3Client = S3Client.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(WriteGetObjectResponseRequest.builder()
                .requestRoute(event.outputRoute())
                .requestToken(event.outputToken())
                .build(), RequestBody.fromBytes(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 software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.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 {
        S3Client s3Client = S3Client.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(WriteGetObjectResponseRequest.builder()
                .requestRoute(event.outputRoute())
                .requestToken(event.outputToken())
                .build(),
                RequestBody.fromInputStream(bodyStream,
                    presignedResponse.headers().firstValueAsLong("content-length").orElse(-1L)));
    }
}
```

------
#### [ 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 需要事件上下文中的路由和请求令牌。有关更多信息，请参阅 [事件上下文格式和用法](olap-event-context.md)。

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

## 在 Lambda 中处理 `HeadObject` 请求
<a name="olap-headobject"></a>

本节假设您的对象 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 参考》**中的 [https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html)。

以下示例显示了 `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` 请求
<a name="olap-listobjects"></a>

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

与 `GetObject` 和 `HeadObject` 不同，预签名 URL 将包含以下属性（如果已指定）：
+ 所有查询参数
+ `requestPayer`（在 `x-amz-request-payer` 标头中） 
+ `expectedBucketOwner`（在 `x-amz-expected-bucket-owner` 标头中）

有关请求语法 URI 参数，请参阅《Amazon Simple Storage Service API 参考》**中的 [https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjects.html](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjects.html)。

**重要**  
我们建议您在开发应用程序时使用较新的版本 [ListObjectsV2](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html)。为了向后兼容，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 期望某些属性属于特定类型，如果它无法解析它们，则会引发异常。无法同时提供 `listResultXml` 和 `listBucketResult`。

以下示例演示如何使用预签名 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` 请求
<a name="olap-listobjectsv2"></a>

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

与 `GetObject` 和 `HeadObject` 不同，预签名 URL 将包含以下属性（如果已指定）：
+ 所有查询参数
+ `requestPayer`（在 `x-amz-request-payer` 标头中） 
+ `expectedBucketOwner`（在 `x-amz-expected-bucket-owner` 标头中）

有关请求语法 URI 参数，请参阅《Amazon Simple Storage Service API 参考》**中的 [https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html)。

以下示例显示了 `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 期望某些属性属于特定类型，如果它无法解析它们，则会引发异常。无法同时提供 `listResultXml` 和 `listBucketResult`。

以下示例演示如何使用预签名 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'  
            },  
        ...  
        ],  
    }  
}
```

# 事件上下文格式和用法
<a name="olap-event-context"></a>

**注意**  
自 2025 年 11 月 7 日起，S3 对象 Lambda 仅可供当前在使用该服务的现有客户以及部分 AWS 合作伙伴网络（APN）合作伙伴使用。要了解与 S3 对象 Lambda 类似的功能，请在此处了解更多信息：[Amazon S3 Object Lambda availability change](https://docs.aws.amazon.com/AmazonS3/latest/userguide/amazons3-ol-change.html)。

Amazon S3 对象 Lambda 提供了有关在传递给 AWS Lambda 函数的事件中发出的请求的上下文。以下屏幕截图显示一个示例请求。示例后面包含对各个字段的描述。

```
{
    "xAmzRequestId": "requestId",
    "getObjectContext": {
        "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=<snip>",
        "outputRoute": "io-use1-001",
        "outputToken": "OutputToken"
    },
    "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"
}
```

请求中包含以下字段：
+ `xAmzRequestId` – 此请求的 Amazon S3 请求 ID。我们建议您记录此值以帮助调试。
+ `getObjectContext` – 连接到 Amazon S3 和 S3 对象 Lambda 的输入和输出详细信息。
  + `inputS3Url` – 可用于从 Amazon S3 获取原始对象的预签名 URL。URL 是使用原始调用方的身份进行签名的，使用 URL 时该用户的权限将适用。如果 URL 中有签名标头，则 Lambda 函数必须将这些标头包含在对 Amazon S3 的调用中，`Host` 标头除外。
  + `outputRoute` – 在 Lambda 函数调用 `WriteGetObjectResponse` 时添加到 S3 对象 Lambda URL 的路由令牌。
  + `outputToken` – S3 对象 Lambda 使用的不透明令牌，用于将 `WriteGetObjectResponse` 调用与原始调用方相匹配。
+ `configuration` – 有关对象 Lambda 接入点的配置信息。
  + `accessPointArn` – 收到此请求的对象 Lambda 接入点的 Amazon 资源名称（ARN）。
  + `supportingAccessPointArn` – 在对象 Lambda 接入点配置中指定的支持接入点的 ARN。
  + `payload` – 应用于对象 Lambda 接入点配置的自定义数据。S3 对象 Lambda 将此数据视为不透明字符串，因此在使用前可能需要对其进行解码。
+ `userRequest` – 有关对 S3 对象 Lambda 的原始调用的信息。
  + `url` – S3 对象 Lambda 接收的请求的解码 URL，不包括任何与授权相关的查询参数。
  + `headers` – 字符串到字符串的映射，包含原始调用中的 HTTP 标头及其值，不包括任何与授权相关的标头。如果同一个标头多次出现，会将相同标头的每个实例中的值组合成一个以逗号分隔的列表。此映射中会保留原始标头的大小写。
+ `userIdentity` – 有关对 S3 对象 Lambda 发出调用的身份的详细信息。有关更多信息，请参阅 *AWS CloudTrail 用户指南*中的[记录数据事件以便跟踪](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/logging-data-events-with-cloudtrail.html)。
  + `type` – 身份的类型。
  + `accountId` – 身份所属的 AWS 账户。
  + `userName` – 已发出调用的身份的友好名称。
  + `principalId` – 已发出调用的身份的唯一标识符。
  + `arn` – 已发出调用的主体的 ARN。ARN 的最后一个部分包含已发出调用的用户或角色。
  + `sessionContext` – 如果已使用临时安全证书发出请求，此元素提供有关已为这些证书创建的会话的信息。
  + `invokedBy` – 发出请求的 AWS 服务 的名称，例如 Amazon EC2 Auto Scaling 或 AWS Elastic Beanstalk。
  + `sessionIssuer` – 如果已使用临时安全证书发出请求，此元素提供有关证书获取方式的信息。
+ `protocolVersion` – 提供的上下文的版本 ID。此字段的格式为 `{Major Version}.{Minor Version}`。次要版本号始终是两位数字。对字段的语义进行任何删除或更改都将导致主要版本冲突，并且需要主动选择加入。Amazon S3 可以随时添加新字段，此时您可能会遇到次要版本冲突。由于软件部署的性质，您可能会同时看到多个次要版本处于使用状态。

# 使用 Range 和 partNumber 标头
<a name="range-get-olap"></a>

**注意**  
自 2025 年 11 月 7 日起，S3 对象 Lambda 仅可供当前在使用该服务的现有客户以及部分 AWS 合作伙伴网络（APN）合作伙伴使用。要了解与 S3 对象 Lambda 类似的功能，请在此处了解更多信息：[Amazon S3 Object Lambda availability change](https://docs.aws.amazon.com/AmazonS3/latest/userguide/amazons3-ol-change.html)。

在 Amazon S3 对象 Lambda 中使用大型对象时，您可以使用 `Range` HTTP 标头从对象中下载指定的字节范围。要从相同对象中提取不同的字节范围，您可以使用到 Amazon S3 的并发连接。还可以指定 `partNumber` 参数（1 到 10000 之间的整数），它对于对象的指定分段执行范围内的请求。

由于您可能希望通过多种方法来处理包括 `Range` 或 `partNumber` 参数的请求，S3 对象 Lambda 不会将这些参数应用于转换后的对象。相反，您的 AWS Lambda 函数必须根据应用程序的需要实施此功能。

要对 S3 对象 Lambda 使用 `Range` 和 `partNumber` 参数，请执行以下操作：
+ 在对象 Lambda 接入点配置中启用这些参数。
+ 编写一个 Lambda 函数，以处理包含这些参数的请求。

以下步骤介绍了如何完成此操作。

## 步骤 1：配置对象 Lambda 接入点
<a name="range-get-olap-step-1"></a>

默认情况下，对象 Lambda 接入点会对在标头或查询参数中包含 `Range` 或 `partNumber` 参数的任何 `GetObject` 或 `HeadObject` 请求响应 HTTP 状态代码 501（未实施）错误。

要使对象 Lambda 接入点能够接受此类请求，您必须在对象 Lambda 接入点配置的 `AllowedFeatures` 部分中加入 `GetObject-Range`、`GetObject-PartNumber`、`HeadObject-Range` 或 `HeadObject-PartNumber`。有关更新对象 Lambda 接入点配置的更多信息，请参阅[创建对象 Lambda 接入点](olap-create.md)。

## 步骤 2：在 Lambda 函数中实施 `Range` 或 `partNumber` 处理
<a name="range-get-olap-step-2"></a>

当对象 Lambda 接入点使用范围内的 `GetObject` 或 `HeadObject` 请求调用 Lambda 函数时，`Range` 或 `partNumber` 参数将包含在事件上下文中。如下表所述，参数在事件上下文中的位置取决于使用的参数以及如何将其包含在对于对象 Lambda 接入点的原始请求中。


| 参数 | 事件上下文位置 | 
| --- | --- | 
|  `Range`（标头）  |  `userRequest.headers.Range`  | 
|  `Range`（查询参数）  |  `userRequest.url`（查询参数 `Range`）  | 
|  `partNumber`  |  `userRequest.url`（查询参数 `partNumber`）  | 

**重要**  
为对象 Lambda 接入点提供的预签名 URL 不包含原始请求中的 `Range` 或 `partNumber` 参数。请参阅以下选项，了解如何在 AWS Lambda 函数中处理这些参数。

在您提取 `Range` 或 `partNumber` 值后，您可以根据应用程序的需要采取以下方法之一：

1. **将请求的 `Range` 或 `partNumber` 映射到转换后的对象（建议）。**

   处理 `Range` 或 `partNumber` 请求的最可靠方法是执行以下操作：
   + 从 Amazon S3 中检索完整的对象。
   + 转换对象。
   + 将请求的 `Range` 或 `partNumber` 参数应用于转换的对象。

   为此，请使用提供的预签名 URL 从 Amazon S3 获取整个对象，然后根据需要处理该对象。对于 Lambda 函数以此方式处理 `Range` 函数的示例，请参阅 AWS Samples GitHub 存储库中的[此示例](https://github.com/aws-samples/amazon-s3-object-lambda-default-configuration/blob/main/function/nodejs_20_x/src/response/range_mapper.ts)。

1. **将请求的 `Range` 映射到预签名的 URL。**

   在某些情况下，您的 Lambda 函数可以将请求的 `Range` 直接映射到预签名 URL，以便仅从 Amazon S3 中检索对象的一部分。仅当您的转换符合以下两个条件时，此方法才适用：

   1. 您的转换函数可以应用于部分对象范围。

   1. 在转换函数之前或之后应用 `Range` 参数会得到相同的已转换对象。

   例如，将 ASCII 编码对象中的所有字符转换为大写的转换函数符合上述两个条件。转换可以应用于对象的一部分，在转换之前应用 `Range` 参数获得的结果与在转换后应用此参数相同。

   相比之下，反转 ASCII 编码对象中的字符的函数不符合这些条件。这样的函数符合标准 1，因为它可以应用于部分对象范围。但是，它不符合标准 2，因为在转换之前应用 `Range` 参数达到的结果与在转换后应用参数不同。

   考虑请求将函数应用于包含内容 `abcdefg` 的对象的前三个字符。在转换前应用 `Range` 参数将仅检索 `abc`，然后反转数据，从而返回 `cba`。但是，如果在转换之后应用该参数，则函数将检索整个对象，将其反转，然后应用 `Range` 参数，以返回 `gfe`。由于这些结果不同，此函数在从 Amazon S3 中检索对象时不应应用 `Range` 参数。相反，它应该检索整个对象，执行转换，然后仅应用 `Range` 参数。
**警告**  
在很多情况下，向预签名 URL 应用 `Range` 参数将导致 Lambda 函数或发出请求的客户端出现意外行为。除非您确定应用程序在从 Amazon S3 中仅检索部分对象时能够正常工作，否则我们建议您按照前面的方法 A 中所述检索和转换完整对象。

   如果您的应用程序符合前面在方法 B 中介绍的条件，您可以简化您的 AWS Lambda 函数，方法是仅获取所请求的对象范围，然后在该范围内运行转换。

   以下 Java 代码示例演示如何执行以下操作：
   + 从 `GetObject` 请求中检索 `Range` 标头。
   + 将 `Range` 标头添加到预签名 URL 中，Lambda 可以使用此标头从 Amazon S3 中检索请求的范围。

   ```
   private HttpRequest.Builder applyRangeHeader(ObjectLambdaEvent event, HttpRequest.Builder presignedRequest) {
       var header = event.getUserRequest().getHeaders().entrySet().stream()
               .filter(e -> e.getKey().toLowerCase(Locale.ROOT).equals("range"))
               .findFirst();
   
       // Add check in the query string itself.
       header.ifPresent(entry -> presignedRequest.header(entry.getKey(), entry.getValue()));
       return presignedRequest;
   }
   ```