1. 概述

AWS Lambda 是亚马逊提供的一种无服务器(Serverless)计算服务,开发者无需管理服务器即可运行代码。

在之前的两篇文章中,我们已经介绍了 如何用 Java 创建 AWS Lambda 函数,以及 如何在 Lambda 中访问 DynamoDB

本文将重点讲解:如何通过 AWS API Gateway 将 Lambda 函数发布为 REST 接口

我们将深入探讨以下内容:

  • API Gateway 的核心概念与术语
  • 使用 Lambda 代理集成(Lambda Proxy Integration)将 Lambda 与 API Gateway 集成
  • 创建 API 的结构,并将资源映射到 Lambda 函数
  • API 的部署与测试

✅ 适合有一定 AWS 使用经验的开发者,避免基础概念啰嗦。


2. 基础概念与术语

API Gateway 是一项 完全托管的服务,帮助开发者创建、发布、维护、监控和保护任意规模的 API

你可以通过它暴露后端服务,比如 Lambda 函数、其他 AWS 服务(如 EC2、S3、DynamoDB)或任意 HTTP 接口,形成统一的 RESTful 接口。

主要特性包括:

  • 流量管理(限流、缓存)
  • 认证与权限控制
  • 监控与日志
  • API 版本管理
  • 请求限速,防止恶意攻击

和 Lambda 一样,API Gateway 自动弹性伸缩,按调用次数计费。

详细文档见:官方文档

2.1 关键术语

  • API Gateway:AWS 提供的服务,用于创建和管理 RESTful 接口,可集成 Lambda、HTTP 接口等后端。
  • API Gateway API:一个 API 由一组资源(Resources)和方法(Methods)组成。每个资源可以绑定多个 HTTP 方法(GET、PUT、POST 等),且方法必须唯一。
  • Deployment(部署)与 Stage(阶段):要发布 API,必须创建部署并关联一个 Stage。Stage 相当于 API 的快照,支持多环境并行,比如 devtestprod 或版本化的 v1v2
  • Lambda 代理集成(Lambda Proxy Integration):一种简化集成方式。API Gateway 将整个 HTTP 请求打包成一个 JSON 传给 Lambda,Lambda 返回的 JSON 再由 API Gateway 转为 HTTP 响应。

⚠️ 使用代理集成后,Lambda 函数需要处理完整的请求结构,但开发更简单,推荐新手使用。


3. 依赖项

除了之前文章中使用的依赖,我们还需要 json-simple 库来处理 JSON:

<dependency>
    <groupId>com.googlecode.json-simple</groupId>
    <artifactId>json-simple</artifactId>
    <version>1.1.1</version>
</dependency>

✅ 依赖简单,没有引入 Spring 或复杂框架,保持轻量。


4. 开发与部署 Lambda 函数

本节将用 Java 实现两个 Lambda 函数,通过 AWS 控制台部署并测试。

我们设计两个功能:

  • 函数1:接收 PUT 请求的 JSON 载荷,存入 DynamoDB
  • 函数2:通过路径参数或查询参数获取 Person 数据

为简化,我们将两个处理逻辑放在同一个 RequestHandler 类中。

4.1 数据模型

public class Person {

    private int id;
    private String name;

    public Person(String json) {
        Gson gson = new Gson();
        Person request = gson.fromJson(json, Person.class);
        this.id = request.getId();
        this.name = request.getName();
    }

    public String toString() {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();
        return gson.toJson(this);
    }

    // getters and setters
}

📌 注意:构造函数接收 JSON 字符串,直接反序列化为 Person 对象。


4.2 RequestHandler 实现

我们实现 RequestStreamHandler 接口,定义两个方法分别处理不同请求:

public class APIDemoHandler implements RequestStreamHandler {

    private static final String DYNAMODB_TABLE_NAME = System.getenv("TABLE_NAME"); 
    
    @Override
    public void handleRequest(
      InputStream inputStream, OutputStream outputStream, Context context)
      throws IOException {
        // 处理 PUT 请求
    }

    public void handleGetByParam(
      InputStream inputStream, OutputStream outputStream, Context context)
      throws IOException {
        // 处理 GET 请求(路径或查询参数)
    }
}

📌 虽然接口只强制实现 handleRequest,但我们可以在类中定义多个方法。部署时通过不同函数名绑定不同入口。

💡 环境变量 TABLE_NAME 用于解耦配置,部署时注入。


4.3 函数1:处理请求体(PUT)

public void handleRequest(
  InputStream inputStream, 
  OutputStream outputStream, 
  Context context)
  throws IOException {

    JSONParser parser = new JSONParser();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    JSONObject responseJson = new JSONObject();

    AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient();
    DynamoDB dynamoDb = new DynamoDB(client);

    try {
        JSONObject event = (JSONObject) parser.parse(reader);

        if (event.get("body") != null) {
            Person person = new Person((String) event.get("body"));

            dynamoDb.getTable(DYNAMODB_TABLE_NAME)
              .putItem(new PutItemSpec().withItem(new Item().withNumber("id", person.getId())
                .withString("name", person.getName())));
        }

        JSONObject responseBody = new JSONObject();
        responseBody.put("message", "New item created");

        JSONObject headerJson = new JSONObject();
        headerJson.put("x-custom-header", "my custom header value");

        responseJson.put("statusCode", 200);
        responseJson.put("headers", headerJson);
        responseJson.put("body", responseBody.toString());

    } catch (ParseException pex) {
        responseJson.put("statusCode", 400);
        responseJson.put("exception", pex);
    }

    OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8");
    writer.write(responseJson.toString());
    writer.close();
}

📌 核心逻辑三步走:

  1. inputStream 解析 JSON,提取 body 字段(字符串)
  2. 构造 Person 对象并写入 DynamoDB
  3. 构造响应 JSON,包含 statusCodeheadersbody(字符串)

⚠️ 关键点:API Gateway 要求 body 字段必须是字符串(即使是 JSON 内容)。Lambda 返回时也必须是字符串,否则会报错。


4.4 函数2:处理路径/查询参数

public void handleGetByParam(
  InputStream inputStream, OutputStream outputStream, Context context)
  throws IOException {

    JSONParser parser = new JSONParser();
    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
    JSONObject responseJson = new JSONObject();

    AmazonDynamoDB client = AmazonDynamoDBClientBuilder.defaultClient();
    DynamoDB dynamoDb = new DynamoDB(client);

    Item result = null;
    try {
        JSONObject event = (JSONObject) parser.parse(reader);
        JSONObject responseBody = new JSONObject();

        if (event.get("pathParameters") != null) {
            JSONObject pps = (JSONObject) event.get("pathParameters");
            if (pps.get("id") != null) {
                int id = Integer.parseInt((String) pps.get("id"));
                result = dynamoDb.getTable(DYNAMODB_TABLE_NAME).getItem("id", id);
            }
        } else if (event.get("queryStringParameters") != null) {
            JSONObject qps = (JSONObject) event.get("queryStringParameters");
            if (qps.get("id") != null) {
                int id = Integer.parseInt((String) qps.get("id"));
                result = dynamoDb.getTable(DYNAMODB_TABLE_NAME).getItem("id", id);
            }
        }
        if (result != null) {
            Person person = new Person(result.toJSON());
            responseBody.put("Person", person);
            responseJson.put("statusCode", 200);
        } else {
            responseBody.put("message", "No item found");
            responseJson.put("statusCode", 404);
        }

        JSONObject headerJson = new JSONObject();
        headerJson.put("x-custom-header", "my custom header value");

        responseJson.put("headers", headerJson);
        responseJson.put("body", responseBody.toString());

    } catch (ParseException pex) {
        responseJson.put("statusCode", 400);
        responseJson.put("exception", pex);
    }

    OutputStreamWriter writer = new OutputStreamWriter(outputStream, "UTF-8");
    writer.write(responseJson.toString());
    writer.close();
}

📌 逻辑流程:

  1. 检查 pathParametersqueryStringParameters 是否包含 id
  2. 解析 id 并查询 DynamoDB
  3. 构造响应,注意 body 仍为字符串

📌 官方文档参考:


4.5 打包代码

使用 Maven 打包:

mvn clean package shade:shade

生成的 JAR 文件位于 target/ 目录。


4.6 创建 DynamoDB 表

创建名为 Person 的表,主键为 id(Number 类型)。

操作方式参考:AWS Lambda 使用 DynamoDB


4.7 控制台部署 Lambda

步骤如下:

  1. 进入 AWS Lambda 控制台
  2. 创建函数 StorePersonFunction,上传 JAR,入口类为 APIDemoHandler::handleRequest
  3. 创建函数 GetPersonByHTTPParamFunction,入口为 APIDemoHandler::handleGetByParam
  4. 为两个函数设置环境变量:TABLE_NAME=Person

⚠️ 记得给 Lambda 添加执行角色(IAM 权限),允许访问 DynamoDB。


4.8 测试 Lambda 函数

在控制台测试时,需使用 API Gateway AWS Proxy 模板,确保输入格式正确。

测试 StorePersonFunction 的事件:

{
    "body": "{\"id\": 1, \"name\": \"John Doe\"}"
}

✅ 预期响应:

{
    "isBase64Encoded": false,
    "headers": {
        "x-custom-header": "my custom header value"
    },
    "body": "{\"message\":\"New item created\"}",
    "statusCode": 200
}

测试 GetPersonByHTTPParamFunction(路径参数):

{
    "pathParameters": {
        "id": "1"
    }
}

测试查询参数:

{
    "queryStringParameters": {
        "id": "1"
    }
}

✅ 两种输入应返回类似响应(body 为字符串)。


5. 创建与测试 API

5.1 创建 API

  1. 进入 API Gateway 控制台
  2. 点击 “Get Started” → “New API”
  3. 命名 API 为 TestAPI,点击 “Create API”

5.2 为函数1创建资源(PUT)

  1. 选择根资源 → “Create Resource”
    • 名称:Persons
    • 路径:默认(即 /persons
  2. 选中 /persons → “Create Method” → 选择 PUT
  3. 配置集成:
    • 集成类型:Lambda Function
    • ✅ 启用 “Lambda 代理集成”
    • 选择区域和函数:StorePersonFunction
  4. 点击 “Save”,确认添加权限

5.3 为函数2创建路径参数资源(GET /persons/{id})

  1. 选中 /persons → “Create Resource”
    • 名称:Person
    • 路径:{id}(必须匹配代码中的参数名)
  2. 选中 /persons/{id} → “Create Method” → 选择 GET
  3. 集成配置同上,函数选择 GetPersonByHTTPParamFunction
  4. 保存并授权

5.4 为函数2创建查询参数方法(GET /persons?id=)

  1. 选中 /persons → “Create Method” → 选择 GET
  2. 配置 Lambda 代理集成,绑定 GetPersonByHTTPParamFunction
  3. 保存后,进入 “Method Request” 配置:
    • 在 “URL Query String Parameters” 中添加 id
    • 设置为 “Required”
    • 配置 “Request Validator”:启用查询参数和 Header 校验

📌 注意:查询参数无需创建新资源,直接在 /persons 上添加 GET 方法即可。


5.5 控制台测试 API

在 API Gateway 控制台点击 “Test” 按钮:

  • PUT /persons:在 “Request Body” 输入:
    {"id": 2, "name": "Jane Doe"}
    
  • GET /persons/2:在 {id} 字段填 2
  • GET /persons?id=2:在 “Query Strings” 输入 id=2

✅ 查看响应状态码与内容是否符合预期。


5.6 部署 API

  1. 选择 API → “Actions” → “Deploy API”
  2. 选择 [New Stage],命名 test,填写描述
  3. 点击 “Deploy”

部署成功后,控制台会显示调用 URL,例如:

https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test

5.7 调用接口

使用 curl 测试:

创建用户:

curl -X PUT 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons' \
  -H 'content-type: application/json' \
  -d '{"id": 3, "name": "Richard Roe"}'

通过路径参数查询:

curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons/3' \
  -H 'content-type: application/json'

通过查询参数查询:

curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons?id=3' \
  -H 'content-type: application/json'

✅ 三个接口应分别返回预期结果。


6. 总结

本文实战演示了如何通过 API Gateway + Lambda 代理集成 快速暴露 REST 接口。

关键点回顾:

  • ✅ 使用 Lambda 代理集成,简化前后端数据映射
  • body 字段必须为字符串,别踩坑
  • ✅ 支持路径参数、查询参数、请求体等多种输入方式
  • ✅ 多 Stage 支持,便于环境隔离

所有代码已上传至 GitHub:https://github.com/eugenp/tutorials/tree/master/aws-modules/aws-lambda-modules

💡 建议:生产环境可结合 CloudFront 做缓存,或使用自定义域名提升可读性。


原始标题:Using AWS Lambda with API Gateway