The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

AWS Lambda com Quarkus REST, Undertow ou Reactive Routes

preview

With Quarkus, you can deploy your favorite Java HTTP frameworks as AWS Lambda’s using either the AWS Gateway HTTP API or AWS Gateway REST API. This means that you can deploy your microservices written with Quarkus REST (our Jakarta REST implementation), Undertow (servlet), Reactive Routes, Funqy HTTP or any other Quarkus HTTP framework as an AWS Lambda.

You should only use a single HTTP framework together with the AWS Lambda extension to avoid unexpected conflicts and errors.

Você pode implantar seu Lambda como um jar Java puro ou compilar seu projeto em uma imagem nativa e implantá-la para obter menor consumo de memória e tempo de inicialização. Nossa integração também gera arquivos de implantação SAM que podem ser consumidos pelo https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html [estrutura SAM da Amazon].

O Quarkus tem uma extensão diferente para cada API de gateway. A API do Gateway HTTP é implementada na extensão quarkus-amazon-lambda-http . A API do Gateway REST é implementada na extensão quarkus-amazon-lambda-rest . Se você estiver confuso sobre qual produto de Gateway usar, a Amazon tem um ótimo guia para ajudá-lo a tomar essa decisão.

Tal como a maioria das extensões Quarkus, as extensões Quarkus AWS Lambda HTTP/REST suportam Live Coding.

Essa tecnologia é considerada preview.

In preview, backward compatibility and presence in the ecosystem is not guaranteed. Specific improvements might require changing configuration or APIs, and plans to become stable are under way. Feedback is welcome on our mailing list or as issues in our GitHub issue tracker.

Para obter uma lista completa de possíveis status, consulte nosso FAQ.

Pré-requisitos

Para concluir este guia, você precisa:

  • Mais ou menos 30 minutes

  • Um IDE

  • JDK 17+ instalado com JAVA_HOME configurado corretamente

  • Apache Maven 3.9.12

  • Opcionalmente, o Quarkus CLI se você quiser usá-lo

  • Opcionalmente, Mandrel ou GraalVM instalado e configurado apropriadamente se você quiser criar um executável nativo (ou Docker se você usar uma compilação de contêiner nativo)

  • Uma conta Amazon AWS

  • CLI DO AWS SAM

Iniciando

Este guia orienta você na geração de um projeto Java de exemplo por meio de um arquétipo Maven. Posteriormente, ele oriente você na estrutura do projeto para que você possa adaptar quaisquer projetos existentes para usar o AWS Lambda.

Instalando os componentes AWS

A instalação de todos os componentes da AWS é provavelmente a parte mais difícil deste guia. Certifique-se de seguir todas as etapas para instalar o AWS SAM CLI.

Criando o Maven Deployment Project

Crie o projeto Quarkus AWS Lambda Maven utilizando o nosso Arquétipo Maven.

Se você quiser utilizar a API HTTP do AWS Gateway, gere o seu projeto com este script:

mvn archetype:generate \
       -DarchetypeGroupId=io.quarkus \
       -DarchetypeArtifactId=quarkus-amazon-lambda-http-archetype \
       -DarchetypeVersion=3.32.3

Se você quiser utilizar a API REST do AWS Gateway, gere o seu projeto com este script:

mvn archetype:generate \
       -DarchetypeGroupId=io.quarkus \
       -DarchetypeArtifactId=quarkus-amazon-lambda-rest-archetype \
       -DarchetypeVersion=3.32.3

Build e Deploy

Compile o projeto:

CLI
quarkus build
Maven
./mvnw install

Isso irá compilar o código e executar os testes unitários incluídos no projeto gerado. Os testes unitários são iguais a qualquer outro projeto Java e não precisam ser executados na Amazon. O modo de desenvolvimento do Quarkus também está disponível com essa extensão.

Se você deseja criar um executável nativo, certifique-se de que o GraalVM esteja instalado corretamente e adicione uma propriedade native à compilação.

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Se estiver construindo em um sistema que não seja Linux, também será necessário passar uma propriedade instruindo o quarkus a usar uma compilação Docker, pois o Amazon Lambda exige binários do Linux. Poderá fazer isso passando -Dquarkus.native.container-build=true para seu comando de compilação. No entanto, isso requer que você tenha o Docker instalado localmente.

De acordo com a documentação da AWS , os binários AL2023 x86-64 são criados para a revisão x86-64-v2 da arquitetura x86-64. No Quarkus, o valor padrão de quarkus.native.march é x86-64-v3 . Isso pode causar problemas se o AWS Lambda provisionar hardware mais antigo. Para maximizar a compatibilidade do Lambda, você pode definir quarkus.native.march como x86-64-v2 . Consulte o guia de referência nativo para obter mais informações.

CLI
quarkus build --native --no-tests -Dquarkus.native.container-build=true
# The --no-tests flag is required only on Windows and macOS.
Maven
./mvnw install -Dnative -DskipTests -Dquarkus.native.container-build=true

Arquivos extras gerados pela compilação

Depois de executar a compilação, alguns arquivos extras gerados pela extensão lambda do Quarkus que você está usando. Esses arquivos estão no diretório de compilação: target/ para Maven, build/ para Gradle.

  • function.zip - arquivo de deployment lambda

  • sam.jvm.yaml - script de deployment do sam cli

  • sam.native.yaml - script de deployment do sam cli para nativo

Live Coding e simulação local do ambiente AWS Lambda

No modo de desenvolvimento e teste, o Quarkus iniciará um servidor de eventos AWS Lambda simulado que converterá as solicitações HTTP nos tipos de eventos API Gateway correspondentes e as enviará ao ambiente Quarkus HTTP lambda subjacente para processamento. Isso simula o ambiente AWS Lambda tanto quanto possível localmente, sem a necessidade de ferramentas como Docker e SAM CLI.

Ao utilizar o modo Quarkus Dev Mode, basta realizar requisições HTTP em http://localhost:8080 como faria normalmente ao testar seus endpoints REST. Essa requisição chegará ao Mock Event Server e será convertida na mensagem json do API Gateway, que por sua vez é consumida pelo loop de polling do Quarkus Lambda.

Para testes, o Quarkus inicia um servidor Mock Event separado na porta 8081. A porta padrão do Rest Assured é automaticamente definida como 8081 pelo Quarkus, portanto, você não precisa se preocupar em configurar isso.

Se quiser simular eventos mais complexos do API Gateway em seus testes, faça manualmente um HTTP POST para http://localhost:8080/lambda (porta 8081 no modo de teste) com os eventos json brutos do API Gateway. Esses eventos serão colocados diretamente no loop de polling do Quarkus Lambda para processamento. Veja um exemplo a seguir:

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.equalTo;

import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;

import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class AmazonLambdaSimpleTestCase {
    @Test
    public void testJaxrsCognitoJWTSecurityContext() throws Exception {
        APIGatewayV2HTTPEvent request = request("/security/username");
        request.getRequestContext().setAuthorizer(new APIGatewayV2HTTPEvent.RequestContext.Authorizer());
        request.getRequestContext().getAuthorizer().setJwt(new APIGatewayV2HTTPEvent.RequestContext.Authorizer.JWT());
        request.getRequestContext().getAuthorizer().getJwt().setClaims(new HashMap<>());
        request.getRequestContext().getAuthorizer().getJwt().getClaims().put("cognito:username", "Bill");

        given()
                .contentType("application/json")
                .accept("application/json")
                .body(request)
                .when()
                .post("/_lambda_")
                .then()
                .statusCode(200)
                .body("body", equalTo("Bill"));
    }

O exemplo acima simula o envio de uma entidade Cognito com um pedido HTTP para o seu HTTP Lambda.

If you want to hand code raw events for the AWS HTTP API, the AWS Lambda library has the request event type, which is com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent, and the response event type of com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse. This corresponds to the quarkus-amazon-lambda-http extension and the AWS HTTP API.

Se desejar codificar manualmente eventos brutos para a AWS REST API, o Quarkus tem sua própria implementação: io.quarkus.amazon.lambda.http.model.AwsProxyRequest e io.quarkus.amazon.lambda.http.model.AwsProxyResponse . Isso corresponde à extensão quarkus-amazon-lambda-rest e à API REST da AWS.

The mock event server is also started for @QuarkusIntegrationTest tests, so it will work with native binaries too. All this provides similar functionality to the SAM CLI local testing, without the overhead of Docker.

Por fim, se a porta 8080 ou a porta 8081 não estiver disponível em seu computador, você poderá modificar as portas dos modos dev e test com application.properties

quarkus.lambda.mock-event-server.dev-port=8082
quarkus.lambda.mock-event-server.test-port=8083

Um valor de porta zero resultará numa porta atribuída aleatoriamente.

Para desativar o mock event server:

quarkus.lambda.mock-event-server.enabled=false

Simular o AWS Lambda Deployment com a CLI do SAM

A CLI do AWS SAM permite que você execute suas funções Lambda localmente em sua máquina em um ambiente Lambda simulado. Isso requer que o Docker esteja instalado. Após compilar seu projeto Maven, execute este comando:

sam local start-api --template target/sam.jvm.yaml

This will start a Docker container that mimics Amazon’s Lambda’s deployment environment. Once the environment is started, you can invoke the example lambda in your browser by going to:

In the console, you’ll see startup messages from the lambda. This particular deployment starts a JVM and loads your lambda as pure Java.

Deploy na AWS

sam deploy -t target/sam.jvm.yaml -g

Answer all the questions, and your lambda will be deployed, and the necessary hooks to the API Gateway will be set up. If everything deploys successfully, the root URL of your microservice will be output to the console. Something like this:

Chave               LambdaHttpApi
Descrição           URL da aplicação
Valor               https://234asdf234as.execute-api.us-east-1.amazonaws.com/

O atributo Value é o URL raiz do seu lambda. Copie-o para o seu navegador e adicione hello no final.

Responses for binary types will be automatically encoded with base64. This is different from the behavior using quarkus:dev, which will return the raw bytes. Amazon’s API has additional restrictions requiring the base64 encoding. In general, client code will automatically handle this encoding, but in certain custom situations, you should be aware you may need to manually manage that encoding.

Deploy de um executável nativo

Para fazer o deploy de um executável nativo, é necessário compilá-lo com o GraalVM.

CLI
quarkus build --native --no-tests -Dquarkus.native.container-build=true
# The --no-tests flag is required only on Windows and macOS.
Maven
./mvnw install -Dnative -DskipTests -Dquarkus.native.container-build=true

Você pode então testar o executável localmente com sam local

sam local start-api --template target/sam.native.yaml

Para fazer o deploy no AWS Lambda:

sam deploy -t target/sam.native.yaml -g

Examinando o POM

Não há nada de especial no POM, exceto pela inclusão da extensão quarkus-amazon-lambda-http (caso esteja implantando uma API HTTP do AWS Gateway) ou da extensão quarkus-amazon-lambda-rest (caso esteja implantando uma API REST do AWS Gateway). Essas extensões geram automaticamente tudo o que você possa precisar para a implantação da sua lambda.

Além disso, pelo menos no pom.xml do arquétipo Maven gerado , as dependências quarkus-rest , quarkus-reactive-routes e quarkus-undertow são todas opcionais. Escolha qual(is) Framework(s) HTTP que deseja utilizar (Jakarta REST, Reactive Routes e/ou Servlet) e remova as outras dependências para reduzir o tamanho da sua implantação.

Examinando o sam.yaml

The sam.yaml syntax is beyond the scope of this document. There are a couple of things that must be highlighted just in case you are going to craft your own custom sam.yaml deployment files.

A primeira observação é que as implantações de lambda em Java puro exigem uma classe handler específica. Não altere o nome do handler Lambda.

     Properties:
        Handler: io.quarkus.amazon.lambda.runtime.QuarkusStreamHandler::handleRequest
        Runtime: java21

Este handler é uma ponte entre o tempo de execução do lambda e a estrutura HTTP do Quarkus que está a utilizando (Jakarta REST, Servlet, etc.)

Se você quiser usar a versão nativa, há uma variável de ambiente que deve ser definida para implantações nativas do GraalVM. Se você consultar o site sam.native.yaml , verá isso:

        Environment:
          Variables:
            DISABLE_SIGNAL_HANDLERS: true

Esta variável de ambiente resolve algumas incompatibilidades entre o Quarkus e o ambiente AWS Lambda Custom Runtime.

Por fim, há um detalhe específico para as implantações do AWS Gateway REST API. Essa API assume que os corpos de resposta HTTP são texto, a menos que você informe explicitamente via configuração, quais tipos de media types são binários. Para facilitar as coisas, a extensão do Quarkus força uma codificação binária (base 64) de todas as mensagens de resposta HTTP e o arquivo sam.yaml deve configurar o API Gateway para tratar todos os media types como binários:

  Globals:
    Api:
      EndpointConfiguration: REGIONAL
      BinaryMediaTypes:
        - "*/*"

Variáveis de contexto AWS injetáveis

Se estiver usando o Quarkus REST e o Jakarta REST, você pode injetar diversas variáveis de contexto da AWS em suas classes de recurso Jakarta REST utilizando a anotação @Context do Jakarta REST ou em qualquer outro lugar com a anotação @Inject do CDI.

Para a AWS HTTP API, você pode injetar as variáveis da AWS com.amazonaws.services.lambda.runtime.Context e com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent . Aqui está um exemplo:

import jakarta.ws.rs.core.Context;
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;


@Path("/myresource")
public class MyResource {
    @GET
    public String ctx(@Context com.amazonaws.services.lambda.runtime.Context ctx) { }

    @GET
    public String event(@Context APIGatewayV2HTTPEvent event) { }

    @GET
    public String requestContext(@Context APIGatewayV2HTTPEvent.RequestContext req) { }


}

Para a AWS REST API, você pode injetar as variáveis da AWS com.amazonaws.services.lambda.runtime.Context e io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext . Veja um exemplo:

import jakarta.ws.rs.core.Context;
import io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext;
import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;


@Path("/myresource")
public class MyResource {
    @GET
    public String ctx(@Context com.amazonaws.services.lambda.runtime.Context ctx) { }

    @GET
    public String reqContext(@Context AwsProxyRequestContext req) { }

    @GET
    public String req(@Context AwsProxyRequest req) { }

}

Tracing com AWS XRay e GraalVM

Se estiver criando imagens nativas e quiser usar o AWS X-Ray Tracing com seu lambda, será necessário incluir quarkus-amazon-lambda-xray como uma dependência no seu pom. A biblioteca do AWS X-Ray não é totalmente compatível com o GraalVM, portanto, tivemos que fazer algum trabalho de integração para que isso funcionasse.

Integração da segurança

Quando você realiza uma requisição HTTP no API Gateway, o Gateway transforma essa requisição HTTP em um documento de evento JSON que é encaminhado para uma Lambda do Quarkus. A Lambda do Quarkus processa esse JSON e o converte em uma representação interna de uma requisição HTTP que pode ser consumida por qualquer framework HTTP suportado pelo Quarkus (Jakarta REST, servlet, Reactive Routes).

O API Gateway suporta diversas maneiras de invocar com segurança os endpoints HTTP que são baseados em Lambda e Quarkus. Se habilitado, o Quarkus processará automaticamente as partes relevantes do documento json do evento, buscará metadados de segurança e registrará internamente um java.security.Principal que poderá ser consultado no Jakarta REST injetando um jakarta.ws.rs.core.SecurityContext , via HttpServletRequest.getUserPrincipal() em servlet e RouteContext.user() em Reactive Routes. Se desejar mais informações de segurança, o objeto Principal pode ser convertido em uma classe que fornecerá mais detalhes adicionais.

Para ativar esta funcionalidade de segurança, adicione isto ao seu arquivo application.properties:

quarkus.lambda-http.enable-security=true

Eis o seu mapa:

Table 1. HTTP quarkus-amazon-lambda-http
Auth Type Classe principal Caminho Json do nome principal

Cognito JWT

io.quarkus.amazon.lambda.http.CognitoPrincipal

requestContext.authorizer.jwt.claims.cognito:username

IAM

io.quarkus.amazon.lambda.http.IAMPrincipal

requestContext.authorizer.iam.userId

Lambda personalizado

io.quarkus.amazon.lambda.http.CustomPrincipal

requestContext.authorizer.lambda.principalId

Table 2. REST quarkus-amazon-lambda-rest
Auth Type Classe principal Caminho Json do nome principal

Cognito

io.quarkus.amazon.lambda.http.CognitoPrincipal

requestContext.authorizer.claims.cognito:username

IAM

io.quarkus.amazon.lambda.http.IAMPrincipal

requestContext.identity.user

Lambda personalizado

io.quarkus.amazon.lambda.http.CustomPrincipal

requestContext.authorizer.principalId

Se a claim cognito:groups estiver presente, o Quarkus extrairá e mapeará esses grupos para roles do Quarkus, que poderão ser usadas na autorização com anotações como @RolesAllowed . Caso não queira mapear cognito:groups para roles do Quarkus, você deve desativar essa funcionalidade explicitamente na configuração:

quarkus.lambda-http.map-cognito-to-roles=false

Você também pode especificar uma reivindicação do Cognito diferente para extrair funções:

quarkus.lambda-http.cognito-role-claim=cognito:roles

Por padrão, ele espera roles em uma lista delimitada por espaço entre colchetes, ou seja, [ user admin ] . Você também pode especificar a expressão regular a ser usada para localizar roles individuais na string do claim:

quarkus.lambda-http.cognito-claim-matcher=[^\[\] \t]+

Integração de segurança personalizada

The default support for AWS security only maps the principal name to Quarkus security APIs and do nothing to map claims, or roles, or permissions. You have full control on how security metadata in the lambda HTTP event is mapped to Quarkus Security APIs using implementations of the io.quarkus.amazon.lambda.http.LambdaIdentityProvider interface. By implementing this interface, you can do things like define role mappings for your principal or publish additional attributes provided by IAM, or Cognito, or your Custom Lambda security integration.

HTTP quarkus-amazon-lambda-http
package io.quarkus.amazon.lambda.http;

/**
 * Helper interface that removes some boilerplate for creating
 * an IdentityProvider that processes APIGatewayV2HTTPEvent
 */
public interface LambdaIdentityProvider extends IdentityProvider<LambdaAuthenticationRequest> {
    @Override
    default public Class<LambdaAuthenticationRequest> getRequestType() {
        return LambdaAuthenticationRequest.class;
    }

    @Override
    default Uni<SecurityIdentity> authenticate(LambdaAuthenticationRequest request, AuthenticationRequestContext context) {
        APIGatewayV2HTTPEvent event = request.getEvent();
        SecurityIdentity identity = authenticate(event);
        if (identity == null) {
            return Uni.createFrom().optional(Optional.empty());
        }
        return Uni.createFrom().item(identity);
    }

    /**
     * You must override this method unless you directly override
     * IdentityProvider.authenticate
     *
     * @param event
     * @return
     */
    default SecurityIdentity authenticate(APIGatewayV2HTTPEvent event) {
        throw new IllegalStateException("You must override this method or IdentityProvider.authenticate");
    }
}

Para HTTP, o método importante a ser sobrescrito é LambdaIdentityProvider.authenticate(APIGatewayV2HTTPEvent event) . A partir dele, você alocará um SecurityIdentity com base em como deseja mapear os dados de segurança do APIGatewayV2HTTPEvent

REST quarkus-amazon-lambda-rest
package io.quarkus.amazon.lambda.http;

import java.util.Optional;

import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;

import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.IdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.smallrye.mutiny.Uni;

/**
 * Helper interface that removes some boilerplate for creating
 * an IdentityProvider that processes APIGatewayV2HTTPEvent
 */
public interface LambdaIdentityProvider extends IdentityProvider<LambdaAuthenticationRequest> {
...

    /**
     * You must override this method unless you directly override
     * IdentityProvider.authenticate
     *
     * @param event
     * @return
     */
    default SecurityIdentity authenticate(AwsProxyRequest event) {
        throw new IllegalStateException("You must override this method or IdentityProvider.authenticate");
    }
}

Para REST, o método importante a ser sobrescrito é LambdaIdentityProvider.authenticate(AwsProxyRequest event) . A partir dele, você alocará um SecurityIdentity com base em como deseja mapear os dados de segurança do AwsProxyRequest .

O seu provider implementado deve ser um bean CDI. Eis um exemplo:

package org.acme;

import java.security.Principal;

import jakarta.enterprise.context.ApplicationScoped;

import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;

import io.quarkus.amazon.lambda.http.LambdaIdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusPrincipal;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;

@ApplicationScoped
public class CustomSecurityProvider implements LambdaIdentityProvider {
    @Override
    public SecurityIdentity authenticate(APIGatewayV2HTTPEvent event) {
        if (event.getHeaders() == null || !event.getHeaders().containsKey("x-user"))
            return null;
        Principal principal = new QuarkusPrincipal(event.getHeaders().get("x-user"));
        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
        builder.setPrincipal(principal);
        return builder.build();
    }
}

Aqui está o mesmo exemplo, mas com a API REST do AWS Gateway:

package org.acme;

import java.security.Principal;

import jakarta.enterprise.context.ApplicationScoped;

import io.quarkus.amazon.lambda.http.model.AwsProxyRequest;

import io.quarkus.amazon.lambda.http.LambdaIdentityProvider;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusPrincipal;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;

@ApplicationScoped
public class CustomSecurityProvider implements LambdaIdentityProvider {
    @Override
    public SecurityIdentity authenticate(AwsProxyRequest event) {
        if (event.getMultiValueHeaders() == null || !event.getMultiValueHeaders().containsKey("x-user"))
            return null;
        Principal principal = new QuarkusPrincipal(event.getMultiValueHeaders().getFirst("x-user"));
        QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder();
        builder.setPrincipal(principal);
        return builder.build();
    }
}

O Quarkus deve descobrir automaticamente essa implementação e usá-la em vez da implementação padrão discutida anteriormente.

Principal local SAM simples

Caso esteja testando sua aplicação com sam local , você pode definir um nome principal para ser usado durante a execução, definindo a variável de ambiente QUARKUS_AWS_LAMBDA_FORCE_USER_NAME

SnapStart

Para otimizar a sua aplicação para o Lambda SnapStart, consulte a documentação de configuração do SnapStart.

Conteúdo Relacionado