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

Amazon Lambda con RESTEasy Reactive, Undertow o Reactive Routes

Con Quarkus puede implementar sus marcos Java HTTP favoritos como Amazon Lambda utilizando la API HTTP de AWS Gateway o la API REST de AWS Gateway. Esto significa que puede implementar sus microservicios escritos con RESTEasy Reactive (JAX-RS), Undertow (servlet), Reactive Routes, Funqy HTTP o cualquier otro marco HTTP de Quarkus como un AWS Lambda.

Puede desplegar su Lambda como una jar de Java, o puede compilar su proyecto en una imagen nativa y desplegarla para obtener una menor huella de memoria y tiempo de inicio. Nuestra integración también genera archivos de despliegue SAM que pueden ser consumidos por el framework SAM de Amazon.

Quarkus tiene una extensión diferente para cada API de Gateway. La API HTTP Gateway se implementa dentro de la extensión quarkus-amazon-lambda-http. La API REST Gateway se implementa dentro de la extensión quarkus-amazon-lambda-rest. Si estás confundido sobre qué producto Gateway utilizar, Amazon tiene una excelente guía para ayudarte a tomar esta decisión.

Como la mayoría de las extensiones de Quarkus, las extensiones HTTP/REST de Quarkus AWS Lambda admiten Live Coding.

This technology is considered preview.

En la preview, la compatibilidad con versiones anteriores y la presencia en el ecosistema no están garantizadas. Las mejoras específicas podrían requerir cambios en la configuración o en las API, y los planes para convertirse en estables están en marcha. Los comentarios son bienvenidos en nuestra lista de correo o como problemas en nuestro GitHub issue tracker.

For a full list of possible statuses, check our FAQ entry.

Requisitos previos

To complete this guide, you need:

Cómo empezar

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 Amazon Lambda.

Instalación de los bits de AWS

Instalar todos los bits de AWS es probablemente lo más difícil de esta guía. Asegúrate de seguir todos los pasos para instalar AWS SAM CLI.

Creación del proyecto de implantación de Maven

Crea el proyecto Maven de Quarkus AWS Lambda utilizando nuestro arquetipo Maven.

Si desea utilizar la API HTTP de AWS Gateway, genere su proyecto con este script:

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

Si desea utilizar la API REST de AWS Gateway, genere su proyecto con este script:

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

Construir y desplegar

Construye el proyecto:

CLI
quarkus build
Maven
./mvnw install

Esto compilará el código y ejecutará las pruebas unitarias incluidas en el proyecto generado. Las pruebas unitarias son las mismas que en cualquier otro proyecto Java y no requieren ser ejecutadas en Amazon. El modo Quarkus dev también está disponible con esta extensión.

Si quieres construir un ejecutable nativo, asegúrate de que tienes GraalVM instalado correctamente y simplemente añade una propiedad native a la construcción

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Si estás construyendo en un sistema que no es Linux, tendrás que pasar también una propiedad que indique a quarkus que utilice una construcción Docker, ya que Amazon Lambda requiere binarios Linux. Puedes hacer esto pasando -Dquarkus.native.container-build=true a tu comando de construcción. Sin embargo, esto requiere que tengas Docker instalado localmente.
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

Archivos extra generados por la compilación

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 - archivo de despliegue de lambda

  • sam.jvm.yaml - script de despliegue de sam cli

  • sam.native.yaml - script de despliegue de sam cli para nativos

Codificación en vivo y simulación del entorno de AWS Lambda a nivel local

En el modo de desarrollo y prueba, Quarkus iniciará un servidor de eventos AWS Lambda falso que convertirá las solicitudes HTTP en los tipos de eventos de API Gateway correspondientes y los enviará al entorno HTTP Lambda subyacente de Quarkus para su procesamiento. Esto simula el entorno de AWS Lambda tanto como sea posible localmente sin requerir herramientas como Docker y SAM CLI.

Cuando se utiliza el Modo Dev de Quarkus sólo hay que invocar peticiones HTTP en http://localhost:8080 como lo harías normalmente al probar tus endpoints REST. Esta solicitud llegará al Servidor de Eventos Mock y se convertirá en el mensaje json de API Gateway que es consumido por el bucle Quarkus Lambda.

Para las pruebas, Quarkus inicia un servidor de Eventos Mock separado bajo el puerto 8081. El puerto por defecto para Rest Assured se establece automáticamente en 8081 por Quarkus, por lo que no tiene que preocuparse de configurar esto.

Si quieres simular eventos más complejos de la API Gateway en tus pruebas, entonces haz manualmente un HTTP POST a http://localhost:8080/lambda (puerto 8081 en modo de prueba) con los eventos json de API Gateway sin procesar. Estos eventos se colocarán directamente en el bucle de Quarkus Lambda para su procesamiento. Aquí hay un ejemplo de eso:

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

El ejemplo anterior simula el envío de un principal Cognito con una solicitud HTTP a su Lambda HTTP.

Si desea codificar a mano eventos sin procesar para la API de AWS HTTP, la biblioteca de AWS Lambda tiene el tipo de evento de solicitud que es com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent y el tipo de evento de respuesta de com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse. Esto corresponde a la extensión quarkus-amazon-lambda-http y a la API de AWS HTTP.

Si quieres codificar a mano los eventos en bruto para la API de AWS REST, Quarkus tiene su propia implementación: io.quarkus.amazon.lambda.http.model.AwsProxyRequest y io.quarkus.amazon.lambda.http.model.AwsProxyResponse. Esto corresponde a la extensión quarkus-amazon-lambda-rest y a la API REST de AWS.

El servidor de eventos simulados también se inicia para las pruebas de @NativeImageTest y @QuarkusIntegrationTest, por lo que también funcionará con binarios nativos. Todo esto proporciona una funcionalidad similar a las pruebas locales de SAM CLI, sin la sobrecarga de Docker.

Por último, si el puerto 8080 o el puerto 8081 no están disponibles en su ordenador, puede modificar los puertos en modo dev y test con application.properties

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

Un valor de puerto cero resultará en un puerto asignado aleatoriamente.

Simular el despliegue de Amazon Lambda con SAM CLI

La CLI de AWS SAM le permite ejecutar sus lambdas localmente en su portátil en un entorno de lambda simulado. Esto requiere que Docker esté instalado. Después de haber construido su proyecto Maven, ejecute este comando:

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

Esto iniciará un contenedor Docker que imita el entorno de despliegue de Amazon Lambda. Una vez iniciado el entorno, puedes invocar la lambda de ejemplo en tu navegador yendo a:

En la consola verás los mensajes de inicio de la lambda. Este despliegue particular inicia una JVM y carga tu lambda como Java puro.

Implementación en AWS

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

Responde a todas las preguntas y tu lambda se desplegará y se configurarán los hooks necesarios para el API Gateway. Si todo se despliega con éxito, la URL raíz de tu microservicio se mostrará en la consola. Algo así:

Key                 LambdaHttpApi
Description         URL for application
Value               https://234asdf234as.execute-api.us-east-1.amazonaws.com/

El atributo Value es la URL raíz de tu lambda. Cópialo en tu navegador y añade hello al 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.

Desplegar un ejecutable nativo

Para desplegar un ejecutable nativo, debes construirlo con 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

A continuación, puede probar el ejecutable localmente con sam local

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

Para desplegar en AWS Lambda:

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

Examinar el 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.

Además, al menos en el arquetipo de Maven generado pom.xml, las dependencias quarkus-resteasy-reactive, quarkus-reactive-routes, y quarkus-undertow son todas opcionales. Escoge qué marco(s) HTTP quieres usar (JAX-RS, Reactive Routes, y/o Servlet) y elimina las otras dependencias para reducir tu despliegue.

Examinar sam.yaml

La sintaxis de sam.yaml está fuera del alcance de este documento. Hay un par de cosas que hay que resaltar en caso de que vayas a elaborar tus propios archivos de despliegue personalizados de sam.yaml.

Lo primero que hay que tener en cuenta es que para las implantaciones de lambda en Java puro se requiere una clase manejadora específica. No cambie el nombre del manejador Lambda.

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

Este manejador es un puente entre el runtime de lambda y el marco de trabajo HTTP de Quarkus que está utilizando (JAX-RS, Servlet, etc.)

Si quieres ir de forma nativa, hay una variable de entorno que se debe establecer para los despliegues nativos de GraalVM. Si miras en sam.native.yaml verás esto:

        Environment:
          Variables:
            DISABLE_SIGNAL_HANDLERS: true

Esta variable de entorno resuelve algunas incompatibilidades entre Quarkus y el entorno de Amazon Lambda Custom Runtime.

Por último, hay una cosa específica para las implementaciones de la API REST de AWS Gateway. Esa API asume que los cuerpos de las respuestas HTTP son texto, a menos que se le indique explícitamente qué tipos de medios son binarios mediante la configuración. Para facilitar las cosas, la extensión de Quarkus fuerza una codificación binaria (base 64) de todos los mensajes de respuesta HTTP y el archivo sam.yaml debe configurar la API Gateway para asumir que todos los tipos de medios son binarios:

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

Variables de contexto de AWS inyectables

Si utiliza RESTEasy Reactive y JAX-RS, puede inyectar varias variables de contexto de AWS en sus clases de recursos JAX-RS utilizando la anotación JAX-RS @Context o en cualquier otro lugar con la anotación CDI @Inject.

Para la API HTTP de AWS puedes inyectar las variables de AWS com.amazonaws.services.lambda.runtime.Context y com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent. Aquí tienes un ejemplo:

import javax.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 la API de AWS REST puedes inyectar las variables de AWS com.amazonaws.services.lambda.runtime.Context y io.quarkus.amazon.lambda.http.model.AwsProxyRequestContext. Aquí tienes un ejemplo:

import javax.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) { }

}

Rastreo con AWS XRay y GraalVM

If you are building native images, and want to use AWS X-Ray Tracing with your lambda you will need to include quarkus-amazon-lambda-xray as a dependency in your pom. The AWS X-Ray library is not fully compatible with GraalVM, so we had to do some integration work to make this work.

Integración de la seguridad

Cuando se invoca una solicitud HTTP en la pasarela de la API, la pasarela convierte esa solicitud HTTP en un documento de evento JSON que se reenvía a un Quarkus Lambda. La Lambda de Quarkus analiza este json y lo convierte en una representación interna de una solicitud HTTP que puede ser consumida por cualquier marco HTTP que Quarkus admita (JAX-RS, servlet, Rutas Reactivas).

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 JAX-RS by injecting a javax.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 activar esta función de seguridad, añada esto a su archivo application.properties:

quarkus.lambda-http.enable-security=true

Así es como se mapea:

Table 1. HTTP quarkus-amazon-lambda-http
Tipo de autorización Clase principal Ruta Json del nombre 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
Tipo de autorización Clase principal Ruta Json del nombre 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

You can also specify a different Cognito claim to extract roles from:

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]+

Integración de seguridad 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");
    }
}

En el caso de HTTP, el método importante a anular es LambdaIdentityProvider.authenticate(APIGatewayV2HTTPEvent event). A partir de él, asignarás una SecurityIdentity basada en cómo quieres asignar los datos de seguridad de 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, el método importante que hay que anular es LambdaIdentityProvider.authenticate(AwsProxyRequest event). A partir de esto, asignarás una SecurityIdentity basada en cómo quieres asignar los datos de seguridad de AwsProxyRequest.

Su proveedor implementado debe ser un bean CDI. He aquí un ejemplo:

package org.acme;

import java.security.Principal;

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

Aquí está el mismo ejemplo, pero con la API REST de AWS Gateway:

package org.acme;

import java.security.Principal;

import javax.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 debería descubrir automáticamente esta implementación y utilizarla en lugar de la implementación por defecto comentada anteriormente.

Simple SAM Local Principal

Si está probando su aplicación con sam local, puede codificar un nombre principal para utilizarlo cuando se ejecute su aplicación, estableciendo la variable de entorno QUARKUS_AWS_LAMBDA_FORCE_USER_NAME