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:
-
Roughly 30 minutes
-
An IDE
-
JDK 17+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.9
-
Optionally the Quarkus CLI if you want to use it
-
Optionally Mandrel or GraalVM installed and configured appropriately if you want to build a native executable (or Docker if you use a native container build)
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.17.4
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.17.4
Construir y desplegar
Construye el proyecto:
quarkus build
./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
quarkus build --native
./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.
|
According to the AWS documentation, AL2023
x86-64 binaries are built for the x86-64-v2 revision of the x86-64 architecture.
In Quarkus, the default value of quarkus.native.march is x86-64-v3.
This could cause
issues if AWS Lambda provisions older
hardware.
To maximize Lambda compatibility, you can set |
quarkus build --native --no-tests -Dquarkus.native.container-build=true
# The --no-tests flag is required only on Windows and macOS.
./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.
quarkus build --native --no-tests -Dquarkus.native.container-build=true
# The --no-tests flag is required only on Windows and macOS.
./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:
Tipo de autorización | Clase principal | Ruta Json del nombre principal |
---|---|---|
Cognito JWT |
|
|
IAM |
|
|
Lambda personalizado |
|
|
Tipo de autorización | Clase principal | Ruta Json del nombre principal |
---|---|---|
Cognito |
|
|
IAM |
|
|
Lambda personalizado |
|
|
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.
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
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.