

# 教程：利用 Lambda 非代理集成创建 REST API
<a name="getting-started-lambda-non-proxy-integration"></a>

在本演练中，我们使用 API Gateway 控制台构建一个 API，该 API 允许客户端通过 Lambda 非代理集成（又称为“自定义集成”）来调用 Lambda 函数。有关 AWS Lambda 和 Lambda 函数的更多信息，请参阅 [AWS Lambda 开发人员指南](https://docs.aws.amazon.com/lambda/latest/dg/)。

为了便于学习，我们选择了一个所需 API 设置最少的简单 Lambda 函数，逐步指导您完成使用 Lambda 自定义集成构建 API Gateway API 的步骤。必要时，我们会介绍一些逻辑。有关 Lambda 自定义集成的更详细示例，请参阅 [教程：通过两种 AWS 服务集成和一种 Lambda 非代理集成创建计算器 REST API](integrating-api-with-aws-services-lambda.md)。

在创建 API 之前，请通过在 AWS Lambda 中创建 Lambda 函数来设置 Lambda 后端，如下所述。

**Topics**
+ [为 Lambda 非代理集成创建 Lambda 函数](#getting-started-new-lambda)
+ [使用 Lambda 非代理集成创建 API](#getting-started-new-api)
+ [测试 API 方法的调用](#getting-started-new-get)
+ [部署 API](#getting-started-deploy-api)
+ [在部署阶段测试 API](#getting-started-test)
+ [清除](#getting-started-clean-up)

## 为 Lambda 非代理集成创建 Lambda 函数
<a name="getting-started-new-lambda"></a>

**注意**  
创建 Lambda 函数可能会导致您的AWS账户产生费用。

 在此步骤中，您将为 Lambda 自定义集成创建一个如“Hello, World\$1”的 Lambda 函数。在本演练中，该函数称为 `GetStartedLambdaIntegration`。

 该 `GetStartedLambdaIntegration` Lambda 函数的实现如下所示：

------
#### [ Node.js ]

```
'use strict';
var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];            
var times = ['morning', 'afternoon', 'evening', 'night', 'day'];

export const handler = async(event) => {
  console.log(event);
  // Parse the input for the name, city, time and day property values
  let name = event.name === null || event.name === undefined || event.name === "" ? 'you' : event.name;
  let city = event.city === undefined ? 'World' : event.city;
  let time = times.indexOf(event.time)<0 ? 'day' : event.time;
  let day = days.indexOf(event.day)<0 ? null : event.day;

  // Generate a greeting
  let greeting = 'Good ' + time + ', ' + name + ' of ' + city + '. ';
  if (day) greeting += 'Happy ' + day + '!';
  
  // Log the greeting to CloudWatch
  console.log('Hello: ', greeting);
  
  // Return a greeting to the caller
  return {"greeting": greeting}
};
```

------
#### [ Python ]

```
import json

days = {
    'Sunday',
    'Monday',
    'Tuesday',
    'Wednesday',
    'Thursday',
    'Friday',
    'Saturday'}
times = {'morning', 'afternoon', 'evening', 'night', 'day'}


def lambda_handler(event, context):
    print(event)
    # parse the input for the name, city, time, and day property values
    name = event.get("name") or 'you'
    city = event.get("city") or 'World'
    try:
        if event['time'] in times:
            time = event['time']
        else:
            time = 'day'
    except KeyError:
        time = 'day'
    try:
        if event['day'] in days:
            day = event['day']
        else:
            day = ''
    except KeyError:
        day = ''
    # Generate a greeting
    greeting = 'Good ' + time + ', ' + name + ' of ' + \
        city + '.' + ['', ' Happy ' + day + '!'][day != '']
    # Log the greeting to CloudWatch
    print(greeting)

    # Return a greeting to the caller
    return {"greeting": greeting}
```

------

对于 Lambda 自定义集成，API Gateway 会将来自客户端的 Lambda 函数的输入作为集成请求正文来传递。Lambda 函数处理程序的 `event` 对象是输入。

我们的 Lambda 函数很简单。它会解析输入 `event` 对象的 `name`、`city`、`time` 和 `day` 属性。然后，它会以 `{"message":greeting}` 的 JSON 对象的形式向调用方返回问候语。该消息采用 `"Good [morning|afternoon|day], [name|you] in [city|World]. Happy day!"` 模式。假设 Lambda 函数的输入属于以下 JSON 对象：

```
{
  "city": "...",
  "time": "...",
  "day": "...",
  "name" : "..."
}
```

有关更多信息，请参阅 [AWS Lambda 开发人员指南](https://docs.aws.amazon.com/lambda/latest/dg/welcome.html)。

此外，该函数通过调用 `console.log(...)` 将其执行记录到 Amazon CloudWatch。这有助于在调试函数时跟踪调用。要允许 `GetStartedLambdaIntegration` 函数记录调用，请用用于 Lambda 函数的适当策略来设置 IAM 角色，以创建 CloudWatch 流并向流中添加日志条目。Lambda 控制台将指导您创建所需的 IAM 角色和策略。

如果您设置 API 时不使用 API Gateway 控制台（例如，当[从 OpenAPI 导入 API](https://github.com/aws-samples/api-gateway-secure-pet-store/blob/master/src/main/resources/swagger.yaml#L39) 时），您必须显式创建（如有必要）并设置调用角色和策略以便 API Gateway 调用 Lambda 函数。有关如何为 API Gateway API 设置 Lambda 调用和执行角色的更多信息，请参阅[使用 IAM 权限控制对 REST API 的访问](permissions.md)。

 与用于 Lambda 代理集成的 Lambda 函数 `GetStartedLambdaProxyIntegration` 相比，用于 Lambda 自定义集成的 `GetStartedLambdaIntegration` Lambda 函数仅从 API Gateway API 集成请求正文中获取输入。该函数可以返回任何 JSON 对象、字符串、数字、布尔值甚至是二进制 blob 形式的输出。相比而言，用于 Lambda 代理集成的 Lambda 函数可从任何请求数据获取输入，但必须返回特定 JSON 对象形式的输出。用于 Lambda 自定义集成的 `GetStartedLambdaIntegration` 函数可以使用 API 请求参数作为输入，前提是 API Gateway 在将客户端请求转发至后端之前，将所需的 API 请求参数映射至集成请求正文。要实现这一点，API 开发人员必须在创建 API 时创建一个映射模板并在 API 方法中对其进行配置。

现在，创建 `GetStartedLambdaIntegration` Lambda 函数。

**为 Lambda 自定义集成创建 `GetStartedLambdaIntegration` Lambda 函数**

1. 打开 AWS Lambda 控制台，地址：[https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/)。

1. 请执行下列操作之一：
   + 如果显示欢迎页面，请选择**立即开始使用**，然后选择**创建函数**。
   + 如果显示 **Lambda > 函数**列表页面，请选择**创建函数**。

1. 选择**从头开始创作**。

1. 在**从头开始创作**窗格中，执行以下操作：

   1. 在**名称**中，输入 **GetStartedLambdaIntegration** 作为 Lambda 函数名称。

   1. 在**运行时**中，选择受支持的最新 **Node.js** 或 **Python** 运行时。

   1. 对于**架构**，请保留默认设置。

   1. 在**权限**下，展开**更改默认执行角色**。在**执行角色**下拉列表中，选择**从 AWS 策略模板创建新角色**。

   1. 对于**角色名称**，输入您角色的名称（例如 **GetStartedLambdaIntegrationRole**）。

   1. 对于**策略模板**，选择**简单微服务权限**。

   1. 选择**创建函数**。

1. 在**配置函数**窗格中的**函数代码**下，执行以下操作：

   1. 复制本节开头部分列出的 Lambda 函数代码并将其粘贴到内联代码编辑器中。

   1. 对本部分中的所有其他字段保留默认选择。

   1. 选择**部署**。

1. 要测试新创建的函数，请选择**测试**选项卡。

   1. 对于**事件名称**，输入 **HelloWorldTest**。

   1. 对于**事件 JSON**，请将默认代码替换为以下代码。

      ```
      {
        "name": "Jonny",
        "city": "Seattle",
        "time": "morning",
        "day": "Wednesday"
      }
      ```

   1.  选择**测试**以调用该函数。将显示**执行结果: 成功**部分。展开**详细信息**，您会看到以下输出。

      ```
      {
          "greeting": "Good morning, Jonny of Seattle. Happy Wednesday!"
      }
      ```

      还会将输出写入 CloudWatch Logs 中。

 作为同步练习，您可以使用 IAM 控制台查看 IAM 角色 (`GetStartedLambdaIntegrationRole`)，此角色是在创建 Lambda 函数的过程中创建的。此 IAM 角色附加了两个内联策略。一个策略规定 Lambda 执行的最基本权限。它允许在创建 Lambda 函数的区域中为您账户的任何 CloudWatch 资源调用 CloudWatch `CreateLogGroup`。此策略还允许创建 CloudWatch 流和为 `GetStartedLambdaIntegration` Lambda 函数记录事件。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:us-east-1:111111111111:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:us-east-1:111111111111:log-group:/aws/lambda/GetStartedLambdaIntegration:*"
            ]
        }
    ]
}
```

------

另一个策略文档适用于调用此示例中未使用的其他 AWS 服务。您可以暂时跳过此策略。

 与 IAM 角色关联的是可信实体 `lambda.amazonaws.com`。下面是信任关系：

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

------

 此信任关系与内联策略的组合，使得 Lambda 函数能够调用 `console.log()` 函数来将事件记录到 CloudWatch Logs。

## 使用 Lambda 非代理集成创建 API
<a name="getting-started-new-api"></a>

 创建并测试 Lambda 函数 (`GetStartedLambdaIntegration`) 后，您便已准备就绪，可以通过 API Gateway API 来公开该函数。为了便于说明，我们用通用 HTTP 方法公开 Lambda 函数。我们使用请求正文、URL 路径变量、查询字符串和标头来接收来自客户端的所需输入数据。我们为 API 启用 API Gateway 请求验证程序，以确保正确定义并指定所有必需的数据。我们为 API Gateway 配置映射模板，以根据后端 Lambda 函数的要求将客户端提供的请求数据转换为有效格式。

**使用 Lambda 非代理集成创建 API**

1. 通过以下网址登录到 API Gateway 控制台：[https://console.aws.amazon.com/apigateway](https://console.aws.amazon.com/apigateway)。

1. 如果您是第一次使用 API Gateway，您会看到一个介绍服务特征的页面。在 **REST API** 下，选择**生成**。当**创建示例 API** 弹出框出现时，选择**确定**。

   如果这不是您首次使用 API Gateway，请选择**创建 API**。在 **REST API** 下，选择**生成**。

1.  对于 **API 名称**，请输入 **LambdaNonProxyAPI**。

1. （可选）对于**描述**，输入描述。

1. 将 **API 端点类型**设置保留为**区域**。

1. 为 **IP 地址类型**选择 **IPv4**。

1. 选择**创建 API**。

创建 API 后，您将创建一个 **/\$1city\$1** 资源。这是带有从客户端获取输入的路径变量的资源示例。稍后，您将使用映射模板将此路径变量映射到 Lambda 函数输入。

**创建资源**

1. 选择**创建资源**。

1. 将**代理资源**保持为关闭状态。

1. 将**资源路径**保持为 `/`。

1. 对于**资源名称**，输入 **\$1city\$1**。

1. 将 **CORS（跨源资源共享）**保持为关闭状态。

1. 选择**创建资源**。

创建 **/\$1city\$1** 资源后，您将创建一个 `ANY` 方法。`ANY` HTTP 动词是客户端在运行时提交的有效 HTTP 方法的占位符。此示例显示，`ANY` 方法可用于 Lambda 自定义集成和 Lambda 代理集成。

**创建 `ANY` 方法**

1. 选择 **/\$1city\$1** 资源，然后选择**创建方法**。

1. 对于**方法类型**，选择 **ANY**。

1. 对于**集成类型**，选择 **Lambda 函数**。

1. 保持 **Lambda 代理集成**处于关闭状态。

1. 对于 **Lambda 函数**，选择您创建 Lambda 函数的 AWS 区域，然后输入函数名称。

1. 选择**方法请求设置**。

   现在，您可以为 URL 路径变量、查询字符串参数和标头开启请求验证程序，来确保定义了所有必需的数据。在本示例中，您将创建一个 `time` 查询字符串参数和一个 `day` 标头。

1. 对于**请求验证程序**，选择**验证查询字符串参数和标头**。

1. 选择 **URL 查询字符串参数**并执行以下操作：

   1. 选择**添加查询字符串**。

   1. 在**名称**中，输入 **time**。

   1. 打开**必需**。

   1. 将**缓存**保持为关闭状态。

1. 选择 **HTTP 请求标头**并执行以下操作：

   1. 选择**添加标头**。

   1. 在**名称**中，输入 **day**。

   1. 打开**必需**。

   1. 将**缓存**保持为关闭状态。

1. 选择**创建方法**。

打开请求验证程序后，您可以根据后端 Lambda 函数的要求，通过添加正文映射模板来配置 `ANY` 方法的集成请求，以将传入的请求转换为 JSON 负载。

**配置集成请求**

1. 在**集成请求**选项卡的**集成请求设置**下，选择**编辑**。

1. 对于**请求正文传递**，选择**当未定义模板时（推荐）**。

1. 选择**映射模板**。

1. 选择**添加映射模板**。

1. 对于**内容类型**，输入 **application/json**。

1. 对于**模板正文**，输入以下代码：

   ```
   #set($inputRoot = $input.path('$'))
   {
     "city": "$input.params('city')",
     "time": "$input.params('time')",
     "day":  "$input.params('day')",
     "name": "$inputRoot.callerName"
   }
   ```

1. 选择 **Save**。

## 测试 API 方法的调用
<a name="getting-started-new-get"></a>

 API Gateway 控制台提供了测试工具，以供您在部署 API 之前测试 API 的调用。您使用控制台的测试特征通过提交以下请求来测试 API：

```
POST /Seattle?time=morning
day:Wednesday

{
    "callerName": "John"
}
```

 在此测试请求中，您可以将 `ANY` 设置为 `POST`、将 `{city}` 设置为 `Seattle`、将 `Wednesday` 分配为 `day` 标头值，将 `"John"` 分配为 `callerName` 值。

**测试 `ANY` 方法**

1. 选择**测试**选项卡。您可能需要选择右箭头按钮，以显示该选项卡。

1. 对于**方法类型**，选择 `POST`。

1. 对于**路径**，在**城市**下输入 **Seattle**。

1. 对于**查询字符串**，输入 **time=morning**。

1. 对于**标头**，输入 **day:Wednesday**。

1. 对于**请求正文**，输入 **\$1 "callerName": "John" \$1**。

1. 选择**测试**。

验证返回的响应负载是否如下所示：

```
{
  "greeting": "Good morning, John of Seattle. Happy Wednesday!"
}
```

您还可以查看日志，以确定 API Gateway 如何处理请求和响应。

```
Execution log for request test-request
Thu Aug 31 01:07:25 UTC 2017 : Starting execution for request: test-invoke-request
Thu Aug 31 01:07:25 UTC 2017 : HTTP Method: POST, Resource Path: /Seattle
Thu Aug 31 01:07:25 UTC 2017 : Method request path: {city=Seattle}
Thu Aug 31 01:07:25 UTC 2017 : Method request query string: {time=morning}
Thu Aug 31 01:07:25 UTC 2017 : Method request headers: {day=Wednesday}
Thu Aug 31 01:07:25 UTC 2017 : Method request body before transformations: { "callerName": "John" }
Thu Aug 31 01:07:25 UTC 2017 : Request validation succeeded for content type application/json
Thu Aug 31 01:07:25 UTC 2017 : Endpoint request URI: https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-west-2:123456789012:function:GetStartedLambdaIntegration/invocations
Thu Aug 31 01:07:25 UTC 2017 : Endpoint request headers: {x-amzn-lambda-integration-tag=test-request, Authorization=****************************************************************************************************************************************************************************************************************************************************************************************************************************************338c72, X-Amz-Date=20170831T010725Z, x-amzn-apigateway-api-id=beags1mnid, X-Amz-Source-Arn=arn:aws:execute-api:us-west-2:123456789012:beags1mnid/null/POST/{city}, Accept=application/json, User-Agent=AmazonAPIGateway_beags1mnid, X-Amz-Security-Token=FQoDYXdzELL//////////wEaDMHGzEdEOT/VvGhabiK3AzgKrJw+3zLqJZG4PhOq12K6W21+QotY2rrZyOzqhLoiuRg3CAYNQ2eqgL5D54+63ey9bIdtwHGoyBdq8ecWxJK/YUnT2Rau0L9HCG5p7FC05h3IvwlFfvcidQNXeYvsKJTLXI05/yEnY3ttIAnpNYLOezD9Es8rBfyruHfJfOqextKlsC8DymCcqlGkig8qLKcZ0hWJWVwiPJiFgL7laabXs++ZhCa4hdZo4iqlG729DE4gaV1mJVdoAagIUwLMo+y4NxFDu0r7I0/EO5nYcCrppGVVBYiGk7H4T6sXuhTkbNNqVmXtV3ch5bOlh7 [TRUNCATED]
Thu Aug 31 01:07:25 UTC 2017 : Endpoint request body after transformations: {
  "city": "Seattle",
  "time": "morning",
  "day": "Wednesday",
  "name" : "John"
}
Thu Aug 31 01:07:25 UTC 2017 : Sending request to https://lambda.us-west-2.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-west-2:123456789012:function:GetStartedLambdaIntegration/invocations
Thu Aug 31 01:07:25 UTC 2017 : Received response. Integration latency: 328 ms
Thu Aug 31 01:07:25 UTC 2017 : Endpoint response body before transformations: {"greeting":"Good morning, John of Seattle. Happy Wednesday!"}
Thu Aug 31 01:07:25 UTC 2017 : Endpoint response headers: {x-amzn-Remapped-Content-Length=0, x-amzn-RequestId=c0475a28-8de8-11e7-8d3f-4183da788f0f, Connection=keep-alive, Content-Length=62, Date=Thu, 31 Aug 2017 01:07:25 GMT, X-Amzn-Trace-Id=root=1-59a7614d-373151b01b0713127e646635;sampled=0, Content-Type=application/json}
Thu Aug 31 01:07:25 UTC 2017 : Method response body after transformations: {"greeting":"Good morning, John of Seattle. Happy Wednesday!"}
Thu Aug 31 01:07:25 UTC 2017 : Method response headers: {X-Amzn-Trace-Id=sampled=0;root=1-59a7614d-373151b01b0713127e646635, Content-Type=application/json}
Thu Aug 31 01:07:25 UTC 2017 : Successfully completed execution
Thu Aug 31 01:07:25 UTC 2017 : Method completed with status: 200
```

日志在映射之前显示传入请求并在映射之后显示集成请求。当测试失败时，日志对于评估原始输入是否正确或映射模板工作是否正常很有用。

## 部署 API
<a name="getting-started-deploy-api"></a>

 测试调用是一种模拟，会受到一些限制。例如，它会绕过 API 中应用的任何授权机制。要实时测试 API 执行，您必须先部署 API。要部署 API，您需创建一个阶段，以创建当时的 API 快照。阶段名称还定义在 API 的默认主机名后面的基本路径。API 的根资源附加在阶段名称之后。当您修改 API 时，必须将其重新部署到新阶段或现有阶段，然后更改才会生效。

**将 API 部署到某个阶段**

1. 选择**部署 API**。

1. 对于**阶段**，选择**新建阶段**。

1. 对于**阶段名称**，输入 **test**。
**注意**  
输入必须是 UTF-8 编码（即未本地化）的文本。

1. （可选）对于**描述**，输入描述。

1. 选择**部署**。

在**阶段详细信息**下，选择复制图标以复制您 API 的调用 URL。API 的基本 URL 的一般模式是 `https://api-id.region.amazonaws.com/stageName`。例如，在 `beags1mnid` 区域中创建并部署到 `us-west-2` 阶段的 API (`test`) 的基本 URL 是 `https://beags1mnid.execute-api.us-west-2.amazonaws.com/test`。

## 在部署阶段测试 API
<a name="getting-started-test"></a>

可通过若干种方法来测试已部署的 API。对于仅使用 URL 路径变量或查询字符串参数的 GET 请求，您可以在浏览器中输入 API 资源 URL。对于其他方法，您必须使用更高级的 REST API 测试实用程序，如 [POSTMAN](https://www.postman.com/) 或 [cURL](https://curl.se/)。

**使用 cURL 测试 API**

1. 在连接到 Internet 的本地计算机上打开终端窗口。

1. 测试 `POST /Seattle?time=evening`：

   复制以下 cURL 命令并将其粘贴到终端窗口中。

   ```
   curl -v -X POST \
     'https://beags1mnid.execute-api.us-west-2.amazonaws.com/test/Seattle?time=evening' \
     -H 'content-type: application/json' \
     -H 'day: Thursday' \
     -H 'x-amz-docs-region: us-west-2' \
     -d '{
   	"callerName": "John"
   }'
   ```

   您应获得一个包含以下负载的成功响应：

   ```
   {"greeting":"Good evening, John of Seattle. Happy Thursday!"}
   ```

   如果您在此方法请求中将 `POST` 更改为 `PUT`，则会获得相同的响应。

## 清除
<a name="getting-started-clean-up"></a>

如果您不再需要您为本演练创建的 Lambda 函数，现在可以将其删除。您也可以删除附带的 IAM 资源。

**警告**  
如果您计划完成本系列中的其他演练，请不要删除 Lambda 执行角色或 Lambda 调用角色。如果您删除您的 API 所依赖的某个 Lambda 函数，这些 API 将不再有效。Lambda 函数删除操作无法撤消。如果您要再次使用 Lambda 函数，必须重新创建该函数。  
如果您删除 Lambda 函数所依赖的 IAM 资源，Lambda 函数将不再有效，依赖于此函数的 API 也不再有效。IAM 资源删除操作无法撤消。如果您要再次使用 IAM 资源，必须重新创建该资源。

**删除 Lambda 函数**

1. 登录到 AWS 管理控制台，然后通过以下网址打开 AWS Lambda 控制台：[https://console.aws.amazon.com/lambda/](https://console.aws.amazon.com/lambda/)。

1. 从函数列表中选择 **GetStartedLambdaIntegration**，再选择**操作**，然后选择**删除函数**。当系统提示时，再次选择**删除**。

**删除相关联的 IAM 资源**

1. 通过以下网址打开 IAM 控制台：[https://console.aws.amazon.com/iam/](https://console.aws.amazon.com/iam/)。

1. 从**详细信息**中，选择**角色**。

1. 从角色列表中选择 **GetStartedLambdaIntegrationRole**，再选择**角色操作**，然后选择**删除角色**。按照控制台中的步骤删除该角色。