Перейти к основному содержимому

Использование AWS Lambda со шлюзом API

· 15 мин. чтения

1. Обзор

AWS Lambda — это сервис бессерверных вычислений, предоставляемый Amazon Web Services.

В двух предыдущих статьях мы обсуждали, как создать функцию AWS Lambda с помощью Java , а также как получить доступ к DynamoDB из функции Lambda .

В этом руководстве мы обсудим , как опубликовать функцию Lambda в качестве конечной точки REST с помощью AWS Gateway .

Мы подробно рассмотрим следующие темы:

  • Основные понятия и термины API Gateway
  • Интеграция функций Lambda с API Gateway с помощью интеграции Lambda Proxy
  • Создание API, его структура и способ сопоставления ресурсов API с функциями Lambda.
  • Развертывание и тестирование API

2. Основы и термины

Шлюз API — это полностью управляемая служба, которая позволяет разработчикам создавать, публиковать, поддерживать, отслеживать и защищать API любого масштаба .

Мы можем реализовать согласованный и масштабируемый интерфейс программирования на основе HTTP (также называемый службами RESTful) для доступа к серверным службам, таким как функции Lambda, другим службам AWS (например, EC2, S3, DynamoDB) и любым конечным точкам HTTP .

Особенности включают, но не ограничиваются:

  • Управление движением
  • Авторизация и контроль доступа
  • Мониторинг
  • Управление версиями API
  • Регулирование запросов для предотвращения атак

Как и AWS Lambda, API Gateway автоматически масштабируется и оплачивается за каждый вызов API.

Подробную информацию можно найти в официальной документации .

2.1. Условия

API Gateway — это сервис AWS, который поддерживает создание, развертывание и управление интерфейсом прикладного программирования RESTful для предоставления серверных конечных точек HTTP, функций AWS Lambda и других сервисов AWS.

API шлюза API — это набор ресурсов и методов, которые можно интегрировать с функциями Lambda, другими сервисами AWS или конечными точками HTTP в серверной части. API состоит из ресурсов, которые формируют структуру API. Каждый ресурс API может предоставлять один или несколько методов API, которые должны иметь уникальные HTTP-команды.

Чтобы опубликовать API, мы должны создать развертывание API и связать его с так называемым этапом . Стадия похожа на моментальный снимок API во времени. Если мы повторно развертываем API, мы можем либо обновить существующий этап, либо создать новый. При этом возможны разные версии API одновременно, например, этап разработки , этап тестирования и даже несколько производственных версий, таких как v1 , v2 и т. д.

Интеграция Lambda Proxy — это упрощенная конфигурация для интеграции между функциями Lambda и шлюзом API.

Шлюз API отправляет весь запрос в качестве входных данных для внутренней функции Lambda. Что касается ответа, шлюз API преобразует выходные данные функции Lambda обратно в HTTP-ответ внешнего интерфейса.

3. Зависимости

Нам потребуются те же зависимости, что и в статье AWS Lambda Using DynamoDB с Java .

Кроме того, нам также понадобится библиотека JSON Simple :

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

4. Разработка и развертывание лямбда-функций

В этом разделе мы разработаем и создадим наши функции Lambda на Java, развернем их с помощью консоли AWS и проведем быстрый тест.

Поскольку мы хотим продемонстрировать основные возможности интеграции API Gateway с Lambda, мы создадим две функции:

  • Функция 1: получает полезную нагрузку от API, используя метод PUT.
  • Функция 2: демонстрирует, как использовать параметр HTTP-пути или параметр HTTP-запроса, поступающий из API.

Что касается реализации, мы создадим один класс 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
}

Наша модель состоит из одного простого класса Person , который имеет два свойства. Единственная заметная часть — это конструктор Person(String) , который принимает строку JSON.

4.2. Реализация класса RequestHandler

Как и в статье AWS Lambda With Java , мы создадим реализацию интерфейса 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 {

// implementation
}

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

// implementation
}
}

Как мы видим, интерфейс RequestStreamHander определяет только один метод, handeRequest() . В любом случае, мы можем определить дополнительные функции в том же классе, как мы сделали здесь. Другой вариант — создать по одной реализации RequestStreamHander для каждой функции.

В нашем конкретном случае мы выбрали первое для простоты. Однако выбор необходимо делать в каждом конкретном случае с учетом таких факторов, как производительность и удобство сопровождения кода.

Мы также читаем имя нашей таблицы DynamoDB из переменной среды TABLE_NAME . Мы определим эту переменную позже во время развертывания.

4.3. Реализация функции 1

В нашей первой функции мы хотим продемонстрировать , как получить полезную нагрузку (например, из запроса PUT или POST) от шлюза API :

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();
}

Как обсуждалось ранее, позже мы настроим API для использования интеграции прокси-сервера Lambda. Мы ожидаем, что шлюз API передаст полный запрос функции Lambda в параметре InputStream .

Все, что нам нужно сделать, это выбрать соответствующие атрибуты из содержащейся структуры JSON.

Как мы видим, метод в основном состоит из трех шагов:

  1. Извлечение объекта body из нашего входного потока и создание объекта Person из этого
  2. Сохранение этого объекта Person в таблице DynamoDB
  3. Создание объекта JSON, который может содержать несколько атрибутов, таких как тело ответа, настраиваемые заголовки, а также код состояния HTTP.

Здесь стоит упомянуть один момент: шлюз API ожидает, что тело будет строкой (как для запроса, так и для ответа).

Поскольку мы ожидаем получить String как тело от шлюза API, мы приводим тело к String и инициализируем наш объект Person :

Person person = new Person((String) event.get("body"));

Шлюз API также ожидает, что тело ответа будет строкой :

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

Эта тема не упоминается явно в официальной документации. Однако, если мы внимательно посмотрим, мы увидим, что атрибут body является строкой в обоих фрагментах для запроса и для ответа .

Преимущество должно быть очевидным: даже если JSON является форматом между шлюзом API и функцией Lambda, фактическое тело может содержать обычный текст, JSON, XML или что-то еще. Тогда функция Lambda отвечает за правильную обработку формата.

Мы увидим, как будут выглядеть тело запроса и ответа позже, когда будем тестировать наши функции в консоли AWS.

То же самое относится и к следующим двум функциям.

4.4. Реализация функции 2

На втором этапе мы хотим продемонстрировать , как использовать параметр пути или параметр строки запроса для извлечения элемента Person из базы данных с использованием его идентификатора:

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. Мы проверяем наличие массива pathParameters или queryStringParameters с атрибутом id .
  2. Если true , мы используем значение принадлежности для запроса элемента Person с этим идентификатором из базы данных.
  3. Мы добавляем JSON-представление полученного элемента в ответ.

Официальная документация содержит более подробное объяснение формата ввода и формата вывода для интеграции прокси.

4.5. Строительный кодекс

Опять же, мы можем просто создать наш код с помощью Maven:

mvn clean package shade:shade

Файл JAR будет создан в целевой папке.

4.6. Создание таблицы DynamoDB

Мы можем создать таблицу, как описано в AWS Lambda Использование DynamoDB с Java .

Давайте выберем Person в качестве имени таблицы, id в качестве имени первичного ключа и Number в качестве типа первичного ключа.

4.7. Развертывание кода через консоль AWS

После построения нашего кода и создания таблицы теперь мы можем создать функции и загрузить код.

Это можно сделать, повторив шаги 1–5 из статьи AWS Lambda with Java по одному разу для каждого из двух наших методов.

Давайте использовать следующие имена функций:

  • StorePersonFunction для метода handleRequest (функция 1)
  • GetPersonByHTTPParamFunction для метода handleGetByParam (функция 2)

Мы также должны определить переменную среды TABLE_NAME со значением «Person» .

4.8. Тестирование функций

Прежде чем перейти к фактической части шлюза API, мы можем запустить быстрый тест в консоли AWS, просто чтобы убедиться, что наши функции Lambda работают правильно и могут обрабатывать формат интеграции прокси.

Тестирование функции Lambda из консоли AWS выполняется так, как описано в статье AWS Lambda with Java .

Однако, когда мы создаем тестовое событие, мы должны учитывать специальный формат Proxy Integration , который ожидают наши функции. Мы можем либо использовать шаблон API Gateway AWS Proxy и настроить его для наших нужд, либо скопировать и вставить следующие события:

Для StorePersonFunction мы должны использовать это:

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

Как обсуждалось ранее, тело должно иметь тип String , даже если оно содержит структуру JSON. Причина в том, что шлюз API будет отправлять свои запросы в том же формате.

Должен быть возвращен следующий ответ:

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

Здесь мы видим, что тело нашего ответа представляет собой String , хотя оно содержит структуру JSON.

Давайте посмотрим на ввод для GetPersonByHTTPParamFunction.

Для тестирования функциональности параметра пути ввод будет выглядеть следующим образом:

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

И ввод для отправки параметра строки запроса будет:

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

В ответ мы должны получить для обоих случаев следующие методы:

{
"headers": {
"x-custom-header": "my custom header value"
},
"body": "{\"Person\":{\n \"id\": 88,\n \"name\": \"John Doe\"\n}}",
"statusCode": 200
}

Опять же, тело — это String .

5. Создание и тестирование API

После того как мы создали и развернули функции Lambda в предыдущем разделе, теперь мы можем создать фактический API с помощью консоли AWS .

Давайте посмотрим на основной рабочий процесс:

  1. Создайте API в нашей учетной записи AWS.
  2. Добавьте ресурс в иерархию ресурсов API.
  3. Создайте один или несколько методов для ресурса.
  4. Настройте интеграцию между методом и соответствующей функцией Lambda.

Мы повторим шаги 2-4 для каждой из наших двух функций в следующих разделах.

5.1. Создание API

Для создания API нам потребуется:

  1. Войдите в консоль API Gateway по адресу https://console.aws.amazon.com/apigateway .
  2. Нажмите «Начать», а затем выберите «Новый API».
  3. Введите имя нашего API ( TestAPI ) и подтвердите, нажав «Создать API».

Создав API, теперь мы можем создать структуру API и связать ее с нашими функциями Lambda.

5.2. Структура API для функции 1

Для нашей StorePersonFunction необходимы следующие шаги :

  1. Выберите элемент родительского ресурса в дереве «Ресурсы», а затем выберите «Создать ресурс» в раскрывающемся меню «Действия». Затем мы должны сделать следующее на панели «Новый дочерний ресурс»:
  • Введите «Люди» в качестве имени в текстовом поле ввода «Имя ресурса».
  • Оставьте значение по умолчанию в текстовом поле ввода «Путь к ресурсу».
  • Выберите «Создать ресурс».
  1. Выберите только что созданный ресурс, выберите «Создать метод» в раскрывающемся меню «Действия» и выполните следующие шаги:
  • Выберите PUT из раскрывающегося списка методов HTTP, а затем выберите значок галочки, чтобы сохранить выбор.
  • Оставьте «Лямбда-функция» в качестве типа интеграции и выберите параметр «Использовать интеграцию с лямбда-прокси».
  • Выберите регион из «Lambda Region», где мы ранее развернули наши функции Lambda.
  • Введите «StorePersonFunction» в «Лямбда-функция» .
  1. Выберите «Сохранить» и подтвердите, нажав «ОК», когда будет предложено «Добавить разрешение на функцию Lambda».

5.3. Структура API для функции 2 — параметры пути

Шаги для получения параметров пути аналогичны:

  1. Выберите элемент ресурса / person в дереве «Ресурсы», а затем выберите «Создать ресурс» в раскрывающемся меню «Действия». Затем мы должны сделать следующее на панели «Новый дочерний ресурс»:
  • Введите «Человек» в качестве имени в текстовом поле ввода «Имя ресурса».
  • Измените текстовое поле ввода «Путь к ресурсу» на «{id}» .
  • Выберите «Создать ресурс».
  1. Выберите только что созданный ресурс, выберите «Создать метод» в раскрывающемся меню «Действия» и выполните следующие шаги:
  • Выберите GET из раскрывающегося списка методов HTTP, а затем выберите значок галочки, чтобы сохранить выбор.
  • Оставьте «Лямбда-функция» в качестве типа интеграции и выберите параметр «Использовать интеграцию с лямбда-прокси».
  • Выберите регион из «Lambda Region», где мы ранее развернули наши функции Lambda.
  • Введите «GetPersonByHTTPParamFunction» в «Лямбда-функция» .
  1. Выберите «Сохранить» и подтвердите, нажав «ОК», когда будет предложено «Добавить разрешение на функцию Lambda».

Примечание: здесь важно установить для параметра «Путь к ресурсу» значение «{id}» , поскольку наша функция GetPersonByPathParamFunction ожидает, что этот параметр будет называться именно так.

5.4. Структура API для функции 2 — параметры строки запроса

Шаги для получения параметров строки запроса немного отличаются, так как нам не нужно создавать ресурс, а вместо этого нужно создать параметр запроса для параметра id :

  1. Выберите элемент ресурса /persons в дереве «Ресурсы», выберите «Создать метод» в раскрывающемся меню «Действия» и выполните следующие шаги:
  • Выберите GET из раскрывающегося списка методов HTTP, а затем щелкните значок галочки, чтобы сохранить выбор.
  • Оставьте «Лямбда-функция» в качестве типа интеграции и выберите параметр «Использовать интеграцию с лямбда-прокси».
  • Выберите регион из «Lambda Region», где мы ранее развернули наши функции Lambda.
  • Введите «GetPersonByHTTPParamFunction» в «Лямбда-функция».
  1. Выберите «Сохранить» и подтвердите, нажав «ОК», когда будет предложено «Добавить разрешение на функцию Lambda».
  2. Выберите «Запрос метода» справа и выполните следующие шаги:
  • Разверните список параметров строки запроса URL.
  • Нажмите «Добавить строку запроса».
  • Введите «id» в поле имени и выберите значок галочки, чтобы сохранить
  • Установите флажок «Обязательно»
  • Нажмите на символ пера рядом с «Проверка запроса» в верхней части панели, выберите «Проверить параметры и заголовки строки запроса» и выберите значок галочки.

Примечание. Важно установить для параметра «Строка запроса» значение «id» , так как наша функция GetPersonByHTTPParamFunction ожидает, что этот параметр будет называться именно так.

5.5. Тестирование API

Наш API уже готов, но еще не общедоступен. Прежде чем мы опубликуем его, мы хотим сначала запустить быстрый тест из консоли .

Для этого мы можем выбрать соответствующий метод для тестирования в дереве «Ресурсы» и нажать кнопку «Тест». На следующем экране мы можем ввести наш ввод, как если бы мы отправили его клиенту через HTTP.

Для StorePersonFunction мы должны ввести следующую структуру в поле «Тело запроса»:

{
"id": 2,
"name": "Jane Doe"
}

Для функции GetPersonByHTTPParamFunction с параметрами пути мы должны ввести 2 в качестве значения в поле «{id}» в разделе «Путь».

Для GetPersonByHTTPParamFunction с параметрами строки запроса мы должны ввести id=2 в качестве значения в поле «{persons}» в разделе «Query Strings».

5.6. Развертывание API

До сих пор наш API не был общедоступным и поэтому был доступен только из консоли AWS.

Как обсуждалось ранее, когда мы развертываем API, мы должны связать его с этапом, который похож на моментальный снимок API во времени. Если мы повторно развертываем API, мы можем либо обновить существующий этап, либо создать новый .

Посмотрим, как будет выглядеть схема URL для нашего API:

https://{restapi-id}.execute-api.{region}.amazonaws.com/{stageName}

Для развертывания необходимы следующие шаги:

  1. Выберите конкретный API на панели навигации «API».
  2. Выберите «Действия» на панели навигации «Ресурсы» и выберите «Развернуть API» в раскрывающемся меню «Действия».
  3. Выберите «[Новый этап]» в раскрывающемся списке «Этап развертывания», введите «тест» в «Имя этапа» и, при желании, предоставьте описание этапа и развертывания.
  4. Запустите развертывание, выбрав «Развернуть».

После последнего шага консоль предоставит корневой URL-адрес API, например, https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test .

5.7. Вызов конечной точки

Поскольку API теперь общедоступен, мы можем вызывать его с помощью любого HTTP-клиента, который захотим .

С cURL вызовы будут выглядеть следующим образом.

StorePersonFunction :

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"}'

GetPersonByHTTPParamFunction для параметров пути:

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

GetPersonByHTTPParamFunction для параметров строки запроса:

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

6. Заключение

В этой статье мы рассмотрели, как сделать функции AWS Lambda доступными в качестве конечных точек REST с помощью AWS API Gateway.

Мы изучили основные понятия и терминологию API Gateway и узнали, как интегрировать функции Lambda с помощью интеграции Lambda Proxy.

Наконец, мы увидели, как создавать, развертывать и тестировать API.

Как обычно, весь код для этой статьи доступен на GitHub .