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

AWS Lambda con Quarkus REST, Undertow o Rutas Reactivas

Con Quarkus puede implementar sus marcos Java HTTP favoritos como AWS Lambda utilizando HTTP API de AWS Gateway o la API REST de AWS Gateway. Esto significa que puede implementar sus microservicios escritos con Quarkus REST (nuestra implementación de Jakarta REST), Undertow (servlet), Reactive Routes, Funqy HTTP o cualquier otro marco HTTP de Quarkus como una AWS Lambda.

Sólo debe utilizar un único marco HTTP junto con la extensión AWS Lambda para evitar conflictos y errores inesperados.

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

Quarkus tiene una extensión diferente para cada Gateway API. 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á confundido sobre qué producto Gateway utilizar, Amazon tiene una gran guía para ayudarle 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.

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.

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

Requisitos previos

To complete this guide, you need:

Primeros pasos

Esta guía le lleva a través de la generación de un proyecto Java de ejemplo mediante un arquetipo de Maven. Más adelante, recorre la estructura del proyecto para que pueda adaptar cualquier proyecto existente que tenga para utilizar AWS 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úrese de seguir todos los pasos para instalar AWS SAM CLI.

Creación del proyecto de Maven

Crea el proyecto Quarkus AWS Lambda Maven 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=3.16.3

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=3.16.3

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 dev de Quarkus también está disponible con esta extensión.

Si desea construir un ejecutable nativo, asegúrese de que tiene GraalVM instalado correctamente y sólo tiene que añadir una propiedad native a la construcción

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Si está construyendo en un sistema que no sea Linux, necesitará también pasar una propiedad indicando a quarkus que utilice una construcción Docker ya que Amazon Lambda requiere binarios Linux. Puede hacerlo pasando -Dquarkus.native.container-build=true a su comando de compilación. Esto requiere que usted tenga Docker instalado localmente, sin embargo.
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

Después de ejecutar la construcción, hay algunos archivos adicionales generados por la extensión lambda de Quarkus que estás utilizando. Estos archivos están en el directorio de construcción: target/ para Maven, build/ para Gradle.

  • function.zip - archivo de despliegue de lambda

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

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

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

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

Cuando utilice el modo desarrollo de Quarkus sólo tiene que invocar peticiones HTTP en http://localhost:8080 como haría normalmente al probar sus puntos finales REST. Esta solicitud llegará al Servidor de Eventos Mock y se convertirá en el mensaje json API Gateway que es consumido por el bucle de sondeo Lambda de Quarkus.

Para las pruebas, Quarkus pone en marcha un servidor Mock Event independiente en el puerto 8081. El puerto por defecto para Rest Assured es configurado automáticamente a 8081 por Quarkus, por lo que no tiene que preocuparse de configurarlo.

Si desea simular eventos API Gateway más complejos en sus pruebas, entonces haga manualmente un POST HTTP 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 sondeo Lambda de Quarkus para su procesamiento. He aquí un ejemplo de ello:

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 una entidad de seguridad Cognito con una solicitud HTTP a su Lambda HTTP.

Si desea codificar a mano eventos sin procesar para la API HTTP de AWS, la biblioteca 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 HTTP de AWS.

Si desea codificar a mano eventos sin procesar para la API REST de AWS, 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 de los modos 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 de cero resultará en un puerto asignado aleatoriamente.

Para desactivar el servidor de eventos simulado:

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

Simular la implementación de AWS Lambda con SAM CLI

La CLI SAM de AWS le permite ejecutar sus Lambda localmente en su portátil en un entorno Lambda simulado. Para ello es necesario tener instalado Docker. 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, puede invocar la lambda de ejemplo en su navegador dirigiéndose a:

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

Implementación en AWS

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

Responda a todas las preguntas y su lambda se desplegará y se configurarán los ganchos necesarios a la API Gateway. Si todo se despliega correctamente, la URL raíz de su microservicio se mostrará en la consola. Algo como esto:

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.

Las respuestas para los tipos binarios se codificarán automáticamente con base64. Esto es diferente al comportamiento usando quarkus:dev que devolverá los bytes sin procesar. La API de Amazon tiene restricciones adicionales que requieren la codificación base64. En general, el código del cliente manejará automáticamente esta codificación, pero en ciertas situaciones personalizadas, debes tener en cuenta que puedes necesitar manejar manualmente esa codificación.

Despliegue de 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 implementar en AWS Lambda:

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

Examinar el POM

No hay nada especial en el POM aparte de la inclusión de la extensión quarkus-amazon-lambda-http (si está implementando una API HTTP de AWS Gateway) o la extensión quarkus-amazon-lambda-rest (si está implementando una API REST de AWS Gateway). Estas extensiones generan automáticamente todo lo que pueda necesitar para su implementación de Lambda.

Además, al menos en el arquetipo de Maven generado pom.xml, las dependencias quarkus-rest, quarkus-reactive-routes, y quarkus-undertow son todas opcionales. Elija qué framework(s) HTTP desea utilizar (Jakarta REST, Reactive Routes, y/o Servlet) y elimine las demás dependencias para reducir su despliegue.

Examinar sam.yaml

La sintaxis de sam.yaml está fuera del alcance de este documento. Hay un par de cosas que deben resaltarse en caso de que vaya a elaborar sus propios archivos de despliegue personalizados de sam.yaml.

Lo primero que hay que tener en cuenta es que para los despliegues lambda puramente Java se requiere una clase manejadora específica. No cambie el nombre del manejador Lambda.

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

Este manejador es un puente entre el tiempo de ejecución de lambda y el framework HTTP de Quarkus que estés utilizando (Jakarta REST, Servlet, etc.)

Si desea ir nativo, hay una variable de entorno que se debe establecer para las implementaciones nativas de GraalVM. Si mira en sam.native.yaml verá esto:

        Environment:
          Variables:
            DISABLE_SIGNAL_HANDLERS: true

Esta variable de entorno resuelve algunas incompatibilidades entre Quarkus y el entorno de ejecución personalizado de AWS Lambda.

Por último, hay algo específico para las implementaciones de la API REST de AWS Gateway. Esa API asume que los cuerpos de respuesta 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 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 que asuma que todos los tipos de medios son binarios:

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

Variables de contexto de AWS inyectables

Si utiliza Quarkus REST y Jakarta REST, puede inyectar diversas variables de contexto de AWS en sus clases de recursos Jakarta REST utilizando la anotación Jakarta REST @Context o en cualquier otro lugar con la anotación CDI @Inject.

Para la API HTTP de AWS puede inyectar las variables de AWS com.amazonaws.services.lambda.runtime.Context y com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent. He aquí un ejemplo:

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

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

}

Rastreo con AWS XRay y GraalVM

Si estás construyendo imágenes nativas, y quieres usar AWS X-Ray Tracing con tu lambda necesitarás incluir quarkus-amazon-lambda-xray como una dependencia en tu pom. La biblioteca de AWS X-Ray no es totalmente compatible con GraalVM, por lo que hemos tenido que hacer un trabajo de integración para que funcione.

Integración de la seguridad

Cuando se invoca una solicitud HTTP en API Gateway, el gateway 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 admite muchas formas diferentes de invocar de forma segura en sus puntos finales HTTP que están respaldados por Lambda y Quarkus. Si lo habilitas, Quarkus analizará automáticamente las partes relevantes del documento json del evento y buscará metadatos basados en la seguridad y registrará un java.security.Principal internamente que puede ser buscado en JAX-RS inyectando un javax.ws.rs.core.SecurityContext, a través de HttpServletRequest.getUserPrincipal() en servlet, y RouteContext.user() en Reactive Routes. Si quieres más información de seguridad, el objeto Principal puede ser typecast a una clase que te dará más información.

Para activar esta función de seguridad, añada esto a su archivo application.properties:

quarkus.lambda-http.enable-security=true

Aquí está cómo está mapeado:

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

Si el reclamo cognito:groups está presente, entonces Quarkus extraerá y mapeará esos grupos a los roles de Quarkus que luego pueden ser usados en la autorización con anotaciones como @RolesAllowed. Si no desea asignar cognito:groups a los roles de Quarkus, entonces debe desactivarlo explícitamente en la configuración:

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

También puede especificar una demanda de Cognito diferente de la que extraer los roles:

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

Por defecto, espera los roles en una lista delimitada por espacios y encerrada entre paréntesis, es decir, [ user admin ]. También puede especificar la expresión regular que se utilizará para encontrar roles individuales en la cadena de reclamación:

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

Integración de seguridad personalizada

El soporte por defecto para la seguridad de AWS sólo asigna el nombre principal a las API de seguridad de Quarkus y no hace nada para asignar reclamaciones, funciones o permisos. Usted puede controlar completamente cómo se asignan los metadatos de seguridad en el evento HTTP lambda a las API de seguridad de Quarkus utilizando implementaciones de la interfaz io.quarkus.amazon.lambda.http.LambdaIdentityProvider. Al implementar esta interfaz, puede hacer cosas como definir asignaciones de roles para su director o publicar atributos adicionales proporcionados por IAM o Cognito o su integración de seguridad Lambda personalizada.

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, el método importante a sobreescribir es LambdaIdentityProvider.authenticate(APIGatewayV2HTTPEvent event). A partir de él asignará una SecurityIdentity en función de cómo desee 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 a sobreescribir es LambdaIdentityProvider.authenticate(AwsProxyRequest event). A partir de él, asignará una SecurityIdentity en función de cómo desee 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 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();
    }
}

He aquí el mismo ejemplo, pero con la API REST de 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 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 de entidad de seguridad para utilizarlo cuando se ejecute su aplicación estableciendo la variable de entorno QUARKUS_AWS_LAMBDA_FORCE_USER_NAME

SnapStart

Para optimizar su aplicación para Lambda SnapStart, consulte la documentación de configuración de SnapStart.

Related content