Usando o AWS Lambda com API Gateway
1. Visão geral
AWS Lambda é um serviço de computação sem servidor fornecido pela Amazon Web Services.
Em dois artigos anteriores, discutimoshow to create an AWS Lambda function using Java, bem comohow to access DynamoDB from a Lambda function.
Neste tutorial, discutiremoshow to publish a Lambda function as a REST endpoint, using AWS Gateway.
Teremos uma visão detalhada dos seguintes tópicos:
-
Conceitos básicos e termos do API Gateway
-
Integração de funções do Lambda com o API Gateway usando a integração do Lambda Proxy
-
Criação de uma API, sua estrutura e como mapear os recursos da API nas funções Lambda
-
Implantação e teste da API
2. Noções básicas e termos
O API Gateway é umfully managed service that enables developers to create, publish, maintain, monitor, and secure APIs at any scale.
Podemos implementar uma interface de programação baseada em HTTP consistente e escalonável (também conhecida como serviços RESTful)to access backend services like Lambda functions, further AWS services (e.g., EC2, S3, DynamoDB), and any HTTP endpoints.
Os recursos incluem, mas não estão limitados a:
-
Gestão de tráfego
-
Autorização e controle de acesso
-
Monitoramento
-
Gerenciamento de versão da API
-
Solicitações de limitação para evitar ataques
Como o AWS Lambda, o API Gateway é dimensionado automaticamente e é cobrado por chamada da API.
Informações detalhadas podem ser encontradas emofficial documentation.
2.1. Termos
API Gateway é um serviço da AWS que dá suporte à criação, implantação e gerenciamento de uma interface de programação de aplicativo RESTful para expor endpoints HTTP de back-end, funções do AWS Lambda e outros serviços da AWS.
UmAPI Gateway API é uma coleção de recursos e métodos que podem ser integrados com funções Lambda, outros serviços AWS ou endpoints HTTP no back-end. A API consiste em recursos que formam a estrutura da API. Cada recurso da API pode expor um ou mais métodos da API que devem ter verbos HTTP exclusivos.
Para publicar uma API, temos que criar umAPI deployment e associá-lo ao chamadostage. Um estágio é como um instantâneo no tempo da API. Se reimplementarmos uma API, podemos atualizar um estágio existente ou criar um novo. Com isso, diferentes versões de uma API ao mesmo tempo são possíveis, por exemplo, um estágiodev, um estágiotest e até várias versões de produção, comov1,v2, etc.
Lambda Proxy integration é uma configuração simplificada para a integração entre as funções Lambda e o API Gateway.
O API Gateway envia a solicitação inteira como uma entrada para uma função Lambda de back-end. Em termos de resposta, o API Gateway transforma a saída da função Lambda em uma resposta HTTP de front-end.
3. Dependências
Precisaremos das mesmas dependências do artigoAWS Lambda Using DynamoDB With Java.
Além disso, também precisamos da bibliotecaJSON Simple:
com.googlecode.json-simple
json-simple
1.1.1
4. Desenvolvendo e implantando as funções Lambda
Nesta seção, vamos desenvolver e construir nossas funções Lambda em Java, vamos implantá-lo usando o AWS Console e vamos executar um teste rápido.
Como queremos demonstrar os recursos básicos de integração do API Gateway com Lambda, criaremos duas funções:
-
Function 1: recebe uma carga útil da API, usando um método PUT
-
Function 2: demonstra como usar um parâmetro de caminho HTTP ou parâmetro de consulta HTTP proveniente da API
Em termos de implementação, criaremos uma classeRequestHandler, que possui dois métodos - um para cada função.
4.1. Modelo
Antes de implementar o gerenciador de solicitação real, vamos dar uma olhada rápida em nosso modelo de dados:
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
}
Nosso modelo consiste em uma classePerson simples, que possui duas propriedades. A única parte notável é o construtorPerson(String), que aceita uma string JSON.
4.2. Implementação da classe RequestHandler
Assim como no artigoAWS Lambda With Java, criaremos uma implementação da interfaceRequestStreamHandler:
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
}
}
Como podemos ver, a interfaceRequestStreamHander define apenas um método,handeRequest(). De qualquer forma, podemos definir outras funções na mesma classe, como fizemos aqui. Outra opção seria criar uma implementação deRequestStreamHander para cada função.
No nosso caso específico, escolhemos o primeiro por simplicidade. No entanto, a escolha deve ser feita caso a caso, levando em consideração fatores como desempenho e manutenção do código.
Também lemos o nome de nossa tabela DynamoDB na variávelTABLE_NAME environment. Definiremos essa variável posteriormente durante a implantação.
4.3. Implementação da Função 1
Em nossa primeira função, queremos demonstrarhow to get a payload (like from a PUT or POST request) from the API Gateway:
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();
}
Conforme discutido antes, vamos configurar a API mais tarde para usar a integração de proxy Lambda. Esperamos que o API Gateway passe a solicitação completa para a função Lambda no parâmetroInputStream.
Tudo o que precisamos fazer é escolher os atributos relevantes da estrutura JSON contida.
Como podemos ver, o método consiste basicamente em três etapas:
-
Buscando o objetobody de nosso fluxo de entrada e criando um objetoPerson a partir desse
-
Armazenar esse objetoPerson em uma tabela DynamoDB
-
Construindo um objeto JSON, que pode conter vários atributos, comobody para a resposta, cabeçalhos personalizados, bem como um código de status HTTP
Um ponto que vale a pena mencionar aqui: o API Gateway espera quebody seja umString (para solicitação e resposta).
Como esperamos obter umString comobody do API Gateway, lançamosbody paraStringe inicializamos nosso objetoPerson:
Person person = new Person((String) event.get("body"));
O API Gateway também espera que a respostabody seja umString:
responseJson.put("body", responseBody.toString());
Este tópico não é mencionado explicitamente na documentação oficial. No entanto, se olharmos mais de perto, podemos ver que o atributo body é umString em ambos os snippetsfor the request, bem comofor the response.
A vantagem deve ser clara: mesmo que JSON seja o formato entre o API Gateway e a função Lambda, o corpo real poderá conter texto sem formatação, JSON, XML ou qualquer outra coisa. É responsabilidade da função Lambda manipular o formato corretamente.
Veremos como fica o corpo da solicitação e da resposta mais tarde, quando testarmos nossas funções no Console da AWS.
O mesmo se aplica às duas funções a seguir.
4.4. Implementation of Função 2
Em uma segunda etapa, queremos demonstrarhow to use a path parameter or a query string parameter para recuperar um itemPerson do banco de dados usando seu ID:
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();
}
Novamente, três etapas são relevantes:
-
Verificamos se um arraypathParameters ouqueryStringParameters com um atributoid está presente.
-
Setrue, usamos o valor pertencente para solicitar um itemPerson com aquele ID do banco de dados.
-
Nós adicionamos uma representação JSON do item recebido à resposta.
A documentação oficial fornece uma explicação mais detalhada deinput format eoutput format para integração de proxy.
4.5. Código de construção
Novamente, podemos simplesmente construir nosso código usando o Maven:
mvn clean package shade:shade
O arquivo JAR será criado na pastatarget.
4.6. Criação da tabela DynamoDB
Podemos criar a tabela conforme explicado emAWS Lambda Using DynamoDB With Java.
Vamos escolherPerson como nome da tabela,id como nome de chave primária eNumber como tipo de chave primária.
4.7. Implantando código por meio do console AWS
Depois de criar nosso código e criar a tabela, agora podemos criar as funções e fazer upload do código.
Isso pode ser feito repetindo as etapas 1 a 5 do artigoAWS Lambda with Java, uma vez para cada um de nossos dois métodos.
Vamos usar os seguintes nomes de função:
-
StorePersonFunction para o métodohandleRequest (função 1)
-
GetPersonByHTTPParamFunction para o métodohandleGetByParam (função 2)
Também temos que definir uma variável de ambienteTABLE_NAME com o valor“Person”.
4.8. Testando as funções
Antes de continuar com a parte real do API Gateway, podemos executar um teste rápido no AWS Console, apenas para verificar se nossas funções Lambda estão funcionando corretamente e para lidar com o formato Proxy Integration.
O teste de uma função Lambda no console da AWS funciona conforme descrito no artigoAWS Lambda with Java.
No entanto,when we create a test event, we have to consider the special Proxy Integration format, que nossas funções estão esperando. Podemos usar o modeloAPI Gateway AWS Proxy e personalizá-lo de acordo com nossas necessidades ou podemos copiar e colar os seguintes eventos:
ParaStorePersonFunction, devemos usar isto:
{
"body": "{\"id\": 1, \"name\": \"John Doe\"}"
}
Conforme discutido antes, obody deve ter o tipoString, mesmo que contenha uma estrutura JSON. O motivo é que o API Gateway enviará suas solicitações no mesmo formato.
A seguinte resposta deve ser retornada:
{
"isBase64Encoded": false,
"headers": {
"x-custom-header": "my custom header value"
},
"body": "{\"message\":\"New item created\"}",
"statusCode": 200
}
Aqui, podemos ver quebody de nossa resposta é umString, embora contenha uma estrutura JSON.
Vejamos a entrada para oGetPersonByHTTPParamFunction.
Para testar a funcionalidade do parâmetro path, a entrada ficaria assim:
{
"pathParameters": {
"id": "1"
}
}
E a entrada para o envio de um parâmetro de string de consulta seria:
{
"queryStringParameters": {
"id": "1"
}
}
Como resposta, devemos obter o seguinte para os dois métodos de casos:
{
"headers": {
"x-custom-header": "my custom header value"
},
"body": "{\"Person\":{\n \"id\": 88,\n \"name\": \"John Doe\"\n}}",
"statusCode": 200
}
Novamente, obody é umString.
5. Criação e teste da API
Depois de criar e implantar as funções Lambda na seção anterior,we can now create the actual API using the AWS Console.
Vejamos o fluxo de trabalho básico:
-
Crie uma API em nossa conta da AWS.
-
Adicione um recurso à hierarquia de recursos da API.
-
Crie um ou mais métodos para o recurso.
-
Configure a integração entre um método e a função Lambda pertencente.
Repetiremos as etapas 2 a 4 para cada uma de nossas duas funções nas seções a seguir.
5.1. Criação da API
Para criar a API, teremos que:
-
Faça login no console do API Gateway emhttps://console.aws.amazon.com/apigateway
-
Clique em "Introdução" e selecione "Nova API"
-
Digite o nome da nossa API (TestAPI) e confirme clicando em “Criar API”
Depois de criar a API, agora podemos criar a estrutura da API e vinculá-la às nossas funções Lambda.
5.2. Estrutura API para Função 1
As seguintes etapas são necessárias para nossoStorePersonFunction:
-
Escolha o item de recurso pai na árvore "Recursos" e selecione "Criar recurso" no menu suspenso "Ações". Em seguida, precisamos fazer o seguinte no painel "Novo recurso filho":
-
Digite "Pessoas" como um nome no campo de texto de entrada "Nome do Recurso"
-
Deixe o valor padrão no campo de texto de entrada "Caminho do Recurso"
-
Escolha "Criar recurso"
-
-
Escolha o recurso que acabou de criar, escolha "Criar método" no menu suspenso "Ações" e execute as seguintes etapas:
-
Escolha PUT na lista suspensa do método HTTP e escolha o ícone de marca de seleção para salvar a opção
-
Deixe “Função Lambda” como tipo de integração e selecione a opção “Usar integração com o Lambda Proxy”
-
Escolha a região de "Região Lambda", onde implantamos nossas funções Lambda antes
-
Digite“StorePersonFunction” em “Função Lambda”
-
-
Escolha "Salvar" e confirme com "OK" quando solicitado em "Adicionar permissão à função Lambda"
5.3. Estrutura da API para a função 2 - parâmetros de caminho
As etapas para nossos parâmetros de caminho de recuperação são semelhantes:
-
Escolha o item de recurso/persons na árvore “Recursos” e, em seguida, selecione “Criar Recurso” no menu suspenso “Ações”. Em seguida, precisamos fazer o seguinte no painel Novo recurso filho:
-
Digite“Person” como um nome no campo de texto de entrada “Nome do recurso”
-
Altere o campo de texto de entrada “Caminho do recurso” para“{id}”
-
Escolha "Criar recurso"
-
-
Escolha o recurso que acabou de criar, selecione "Criar método" no menu suspenso "Ações" e execute as seguintes etapas:
-
Escolha GET na lista suspensa do método HTTP e escolha o ícone de marca de seleção para salvar a opção
-
Deixe “Função Lambda” como tipo de integração e selecione a opção “Usar integração com o Lambda Proxy”
-
Escolha a região de "Região Lambda", onde implantamos nossas funções Lambda antes
-
Digite“GetPersonByHTTPParamFunction” em “Função Lambda”
-
-
Escolha "Salvar" e confirme com "OK" quando solicitado em "Adicionar permissão à função Lambda"
Nota: é importante aqui definir o parâmetro “Resource Path” para“{id}”, pois nossoGetPersonByPathParamFunction expecta que este parâmetro seja nomeado exatamente assim.
5.4. Estrutura da API para a função 2 - Parâmetros da string de consulta
As etapas para receber parâmetros de string de consulta são um pouco diferentes, poiswe don’t have to create a resource, but instead have to create a query parameter for the id parameter:
-
Escolha o item de recurso/persons na árvore “Recursos”, selecione “Criar Método” no menu suspenso “Ações” e execute as seguintes etapas:
-
Escolha GET na lista suspensa do método HTTP e selecione o ícone de marca de seleção para salvar a opção
-
Deixe “Função Lambda” como tipo de integração e selecione a opção “Usar integração com o Lambda Proxy”
-
Escolha a região de "Região Lambda", onde implantamos nossas funções Lambda antes
-
Digite“GetPersonByHTTPParamFunction” em “Função Lambda”.
-
-
Escolha "Salvar" e confirme com "OK" quando solicitado em "Adicionar permissão à função Lambda"
-
Escolha "Solicitação de método" à direita e execute as seguintes etapas:
-
Expanda a lista Parâmetros da string de consulta da URL
-
Clique em "Adicionar string de consulta"
-
Digite“id” no campo do nome e escolha o ícone de marca de seleção para salvar
-
Marque a caixa de seleção "Necessário"
-
Clique no símbolo da caneta ao lado de "Solicitar validador" na parte superior do painel, selecione "Validar parâmetros e cabeçalhos da string de consulta" e escolha o ícone de marca de seleção
-
Nota: É importante definir o parâmetro “Query String” para“id”, pois nossoGetPersonByHTTPParamFunction expecta que este parâmetro seja nomeado exatamente assim.
5.5. Testando a API
Nossa API já está pronta, mas ainda não é pública. Before we publish it, we want to run a quick test from the Console first.
Para isso, podemos selecionar o método respectivo a ser testado na árvore “Recursos” e clicar no botão “Testar”. Na tela a seguir, podemos digitar nossa entrada, como a enviaríamos a um cliente via HTTP.
ParaStorePersonFunction, temos que digitar a seguinte estrutura no campo “Corpo da Solicitação”:
{
"id": 2,
"name": "Jane Doe"
}
ParaGetPersonByHTTPParamFunction com parâmetros de caminho, temos que digitar2 como um valor no campo “{id}” em “Caminho”.
Para os parâmetros de string de consulta de swithGetPersonByHTTPParamFunction , temos que digitarid=2 como um valor no campo “{people}” em “Query Strings”.
5.6. Implantar a API
Até agora, nossa API não era pública e, portanto, estava disponível apenas no console da AWS.
Como discutido antes,when we deploy an API, we have to associate it with a stage, which is like a snapshot in time of the API. If we redeploy an API, we can either update an existing stage or create a new one.
Vamos ver como ficará o esquema de URL de nossa API:
https://{restapi-id}.execute-api.{region}.amazonaws.com/{stageName}
As etapas a seguir são necessárias para a implantação:
-
Escolha a API específica no painel de navegação "APIs"
-
Escolha "Ações" no painel de navegação Recursos e selecione "Implantar API" no menu suspenso "Ações"
-
Escolha “[Novo Estágio]” no menu suspenso “Estágio de implantação”, digite“test” em “Nome do estágio” e, opcionalmente, forneça uma descrição do estágio e implantação
-
Acione a implantação escolhendo "Implementar"
Após a última etapa, o console fornecerá a URL raiz da API, por exemplo,https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test.
5.7. Invocando o Endpoint
Como a API é pública agora,we can call it using any HTTP client we want.
ComcURL, as chamadas seriam semelhantes à seguinte.
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 para parâmetros de caminho:
curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons/3' \
-H 'content-type: application/json'
GetPersonByHTTPParamFunction para parâmetros de string de consulta:
curl -X GET 'https://0skaqfgdw4.execute-api.eu-central-1.amazonaws.com/test/persons?id=3' \
-H 'content-type: application/json'
6. Conclusão
Neste artigo, vimos como disponibilizar as funções do AWS Lambda como pontos de extremidade REST, usando o AWS API Gateway.
Exploramos os conceitos básicos e a terminologia do API Gateway e aprendemos como integrar as funções do Lambda usando a integração de proxy do Lambda.
Por fim, vimos como criar, implantar e testar uma API.
Como de costume, todo o código deste artigo está disponível emon GitHub.