«1. Обзор

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

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

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

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

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

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

API Gateway — это полностью управляемая служба, которая позволяет разработчикам создавать, публиковать, поддерживать, отслеживать и защищать 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 Использование DynamoDB с Java.

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

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

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

В этом разделе мы разработаем и создадим наши лямбда-функции на 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

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

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

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

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

  1. Fetching the body object from our input stream and creating a Person object from that
  2. Storing that Person object in a DynamoDB table
  3. Building a JSON object, which can hold several attributes, like a body for the response, custom headers, as well as an HTTP status code

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

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

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

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

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

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

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

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

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

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

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

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

  1. We check whether a pathParameters or an queryStringParameters array with an id attribute are present.
  2. If true, we use the belonging value to request a Person item with that ID from the database.
  3. We add a JSON representation of the received item to the response.

Опять же, три шага релевантно:

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

4.5. Создание кода

mvn clean package shade:shade

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

Файл 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 с Java.

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

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

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

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

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

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

Здесь мы видим, что тело нашего ответа представляет собой строку, хотя и содержит структуру 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
}

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

Опять же, тело является строкой.

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

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

  1. Create an API in our AWS account.
  2. Add a resource to the resources hierarchy of the API.
  3. Create one or more methods for the resource.
  4. Set up the integration between a method and the belonging Lambda function.

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

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

5.1. Создание API

  1. Sign in to the API Gateway console at https://console.aws.amazon.com/apigateway
  2. Click on “Get Started” and then select “New API”
  3. Type in the name of our API (TestAPI) and acknowledge by clicking on “Create API”

Для создания API нам необходимо:

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

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

  1. Choose the parent resource item under the “Resources” tree and then select “Create Resource” from the “Actions” drop-down menu. Then, we have to do the following in the “New Child Resource” pane:
    • Type “Persons” as a name in the “Resource Name” input text field
    • Leave the default value in the “Resource Path” input text field
    • Choose “Create Resource”
  2. Choose the resource just created, choose “Create Method” from the “Actions” drop-down menu, and carry out the following steps:
    • Choose PUT from the HTTP method drop-down list and then choose the check mark icon to save the choice
    • Leave “Lambda Function” as integration type, and select the “Use Lambda Proxy integration” option
    • Choose the region from “Lambda Region”, where we deployed our Lambda functions before
    • Type “StorePersonFunction” in “Lambda Function”
  3. Choose “Save” and acknowledge with “OK” when prompted with “Add Permission to Lambda Function”

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

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

  1. Choose the /persons resource item under the “Resources” tree and then select “Create Resource” from the “Actions” drop-down menu. Then, we have to do the following in the New Child Resource pane:
    • Type “Person” as a name in the “Resource Name” input text field
    • Change the “Resource Path” input text field to “{id}”
    • Choose “Create Resource”
  2. Choose the resource just created, select “Create Method” from the “Actions” drop-down menu, and carry out the following steps:
    • Choose GET from the HTTP method drop-down list and then choose the check mark icon to save the choice
    • Leave “Lambda Function” as integration type, and select the “Use Lambda Proxy integration” option
    • Choose the region from “Lambda Region”, where we deployed our Lambda functions before
    • Type “GetPersonByHTTPParamFunction” in “Lambda Function”
  3. Choose “Save” and acknowledge with “OK” when prompted with “Add Permission to Lambda Function”

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

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

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

  1. Choose the /persons resource item under the “Resources” tree, select “Create Method” from the “Actions” drop-down menu, and carry out the following steps:
    • Choose GET from the HTTP method drop-down list and then select the checkmark icon to save the choice
    • Leave “Lambda Function” as integration type, and select the “Use Lambda Proxy integration” option
    • Choose the region from “Lambda Region”, where we deployed our Lambda functions before
    • Type “GetPersonByHTTPParamFunction” in “Lambda Function”.
  2. Choose “Save” and acknowledge with “OK” when prompted with “Add Permission to Lambda Function”
  3. Choose “Method Request” on the right and carry out the following steps:
    • Expand the URL Query String Parameters list
    • Click on “Add Query String”
    • Type “id” in the name field, and choose the check mark icon to save
    • Select the “Required” checkbox
    • Click on the pen symbol next to “Request validator” on the top of the panel, select “Validate query string parameters and headers”, and choose the check mark icon

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. Choose the particular API in the “APIs” navigation pane
  2. Choose “Actions” in the Resources navigation pane and select “Deploy API” from the “Actions” drop-down menu
  3. Choose “[New Stage]” from the “Deployment stage” drop-down, type “test” in “Stage name”, and optionally provide a description of the stage and deployment
  4. Trigger the deployment by choosing “Deploy”

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

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

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

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

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

StorePersonFunction:

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'

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

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

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

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

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