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

Amazon Lambda with RESTEasy Reactive, Undertow, or Reactive Routes

preview

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

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

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ú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=2.16.12.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.16.12.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 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.

The mock event server is also started for @NativeImageTest and @QuarkusIntegrationTest tests so will work with native binaries too. All this provides similar functionality to the SAM CLI local testing, without the overhead of 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.

Simulate Amazon Lambda Deployment with 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.

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

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: java11

This handler is a bridge between the lambda runtime and the Quarkus HTTP framework you are using (JAX-RS, 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

This environment variable resolves some incompatibilities between Quarkus and the Amazon Lambda Custom Runtime environment.

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

If you are using RESTEasy Reactive and JAX-RS, you can inject various AWS Context variables into your JAX-RS resource classes using the JAX-RS @Context annotation or anywhere else with the CDI @Inject annotation.

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

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

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 (JAX-RS, 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 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

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

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

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

He aquí 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 de entidad de seguridad para utilizarlo cuando se ejecute su aplicación estableciendo la variable de entorno QUARKUS_AWS_LAMBDA_FORCE_USER_NAME

SnapStart

To optimize your application for Lambda SnapStart, check the SnapStart Configuration Documentation.