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

AWS Lambda with Quarkus REST, Undertow, or Reactive Routes

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 single HTTP framework together with AWS Lambda extension to avoid unexpected conflicts and errors.

You can deploy your Lambda as a pure Java jar, or you can compile your project to a native image and deploy that for a smaller memory footprint and startup time. Our integration also generates SAM deployment files that can be consumed by Amazon’s SAM framework.

Quarkus has a different extension for each Gateway API. The HTTP Gateway API is implemented within the quarkus-amazon-lambda-http extension. The REST Gateway API is implemented within the quarkus-amazon-lambda-rest extension. If you are confused on which Gateway product to use, Amazon has a great guide to help you navigate this decision.

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+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.9.9

  • 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

This guide walks you through generating an example Java project via a Maven archetype. Later on, it walks through the structure of the project so you can adapt any existing projects you have to use AWS Lambda.

Instalando os componentes AWS

Installing all the AWS bits is probably the most difficult thing about this guide. Make sure that you follow all the steps for installing 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.16.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.16.3

Build e Deploy

Compile o projeto:

CLI
quarkus build
Maven
./mvnw install

This will compile the code and run the unit tests included within the generated project. Unit testing is the same as any other Java project and does not require running on Amazon. Quarkus dev mode is also available with this extension.

If you want to build a native executable, make sure you have GraalVM installed correctly and just add a native property to the build

CLI
quarkus build --native
Maven
./mvnw install -Dnative
If you are building on a non-Linux system, you will need to also pass in a property instructing quarkus to use a Docker build as Amazon Lambda requires Linux binaries. You can do this by passing -Dquarkus.native.container-build=true to your build command. This requires you to have Docker installed locally, however.
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

After you run the build, there are a few extra files generated by the Quarkus lambda extension you are using. These files are in the build directory: target/ for Maven, build/ for 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

In dev and test mode, Quarkus will start a mock AWS Lambda event server that will convert HTTP requests to the corresponding API Gateway event types and post them to the underlying Quarkus HTTP lambda environment for processing. This simulates the AWS Lambda environment as much as possible locally without requiring tools like Docker and SAM CLI.

When using Quarkus Dev Mode just invoke HTTP requests on http://localhost:8080 as you normally would when testing your REST endpoints. This request will hit the Mock Event Server and will be converted to the API Gateway json message that is consumed by the Quarkus Lambda Poll loop.

For testing, Quarkus starts up a separate Mock Event server under port 8081. The default port for Rest Assured is automatically set to 8081 by Quarkus, so you don’t have to worry about setting this up.

If you want to simulate more complex API Gateway events in your tests, then manually do an HTTP POST to http://localhost:8080/_lambda_ (port 8081 in test mode) with the raw API Gateway json events. These events will be placed directly on the Quarkus Lambda poll loop for processing. Here’s an example of that:

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.

If you want to hand code raw events for the AWS REST API, Quarkus has its own implementation: io.quarkus.amazon.lambda.http.model.AwsProxyRequest and io.quarkus.amazon.lambda.http.model.AwsProxyResponse. This corresponds to quarkus-amazon-lambda-rest extension and the AWS REST API.

O servidor de eventos simulado também é iniciado para os testes do @QuarkusIntegrationTest , portanto, também funcionará com binários nativos. Tudo isso oferece funcionalidade semelhante ao teste local da SAM CLI, sem a sobrecarga do 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

The AWS SAM CLI allows you to run your lambda’s locally on your laptop in a simulated Lambda environment. This requires Docker to be installed. After you have built your Maven project, execute this command:

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:

Key                 LambdaHttpApi
Description         URL for application
Value               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

There is nothing special about the POM other than the inclusion of the quarkus-amazon-lambda-http extension (if you are deploying an AWS Gateway HTTP API) or the quarkus-amazon-lambda-rest extension (if you are deploying an AWS Gateway REST API). These extensions automatically generate everything you might need for your lambda deployment.

Also, at least in the generated Maven archetype pom.xml, the quarkus-rest, quarkus-reactive-routes, and quarkus-undertow dependencies are all optional. Pick which HTTP framework(s) you want to use (Jakarta REST, Reactive Routes, and/or Servlet) and remove the other dependencies to shrink your deployment.

Examinando o sam.yaml

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

The first thing to note is that for pure Java lambda deployments require a specific handler class. Do not change the Lambda handler name.

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

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.)

If you want to go native, there’s an environment variable that must be set for native GraalVM deployments. If you look at sam.native.yaml you’ll see this:

        Environment:
          Variables:
            DISABLE_SIGNAL_HANDLERS: true

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

Finally, there is one specific thing for AWS Gateway REST API deployments. That API assumes that HTTP response bodies are text unless you explicitly tell it which media types are binary through configuration. To make things easier, the Quarkus extension forces a binary (base 64) encoding of all HTTP response messages and the sam.yaml file must configure the API Gateway to assume all media types are binary:

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

Variáveis de contexto AWS injetáveis

If you are using Quarkus REST and Jakarta REST, you can inject various AWS Context variables into your Jakarta REST resource classes using the Jakarta REST @Context annotation or anywhere else with the CDI @Inject annotation.

For the AWS HTTP API you can inject the AWS variables com.amazonaws.services.lambda.runtime.Context and com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent. Here is an example:

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) { }


}

For the AWS REST API you can inject the AWS variables com.amazonaws.services.lambda.runtime.Context and io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext. Here is an example:

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

When you invoke an HTTP request on the API Gateway, the Gateway turns that HTTP request into a JSON event document that is forwarded to a Quarkus Lambda. The Quarkus Lambda parses this json and converts in into an internal representation of an HTTP request that can be consumed by any HTTP framework Quarkus supports (Jakarta REST, servlet, Reactive Routes).

API Gateway supports many ways to securely invoke on your HTTP endpoints that are backed by Lambda and Quarkus. If you enable it, Quarkus will automatically parse relevant parts of the event json document and look for security based metadata and register a java.security.Principal internally that can be looked up in Jakarta REST by injecting a jakarta.ws.rs.core.SecurityContext, via HttpServletRequest.getUserPrincipal() in servlet, and RouteContext.user() in Reactive Routes. If you want more security information, the Principal object can be typecast to a class that will give you more information.

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

If the cognito:groups claim is present, then Quarkus will extract and map those groups to Quarkus roles which can then be used in authorization with annotations like @RolesAllowed. If you do not want to map cognito:groups to Quarkus roles, then you must explicitly disable it in configuration:

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

By default, it expects roles in a space delimited list enclosed in brackets i.e. [ user admin ]. You can specify the regular expression to use to find individual roles in the claim string too:

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 does 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");
    }
}

For HTTP, the important method to override is LambdaIdentityProvider.authenticate(APIGatewayV2HTTPEvent event). From this you will allocate a SecurityIdentity based on how you want to map security data from 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");
    }
}

For REST, the important method to override is LambdaIdentityProvider.authenticate(AwsProxyRequest event). From this you will allocate a SecurityIdentity based on how you want to map security data from 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();
    }
}

Quarkus should automatically discover this implementation and use it instead of the default implementation discussed earlier.

Principal local SAM simples

If you are testing your application with sam local you can hardcode a principal name to use when your application runs by setting the QUARKUS_AWS_LAMBDA_FORCE_USER_NAME environment variable

SnapStart

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

Conteúdo Relacionado