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

AWS Lambda with Quarkus REST, Undertow, or Reactive Routes

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

You should only use single HTTP framework together with AWS Lambda extension to avoid unexpected conflicts and errors.

你可以将你的Lambda部署为一个纯Java jar,或者你可以将你的项目编译为一个原生镜像,并以较少的内存占用和启动时间来部署。我们的集成还可以生成SAM部署文件,这些文件可以被 亚马逊的SAM框架 来使用。

Quarkus对每个网关API都有一个不同的扩展。HTTP网关API是在 quarkus-amazon-lambda-http 扩展中实现的。REST网关API是在 quarkus-amazon-lambda-rest 扩展中实现的。如果你对使用哪种网关产品感到困惑,亚马逊有一个 很好的指南 来帮助你做这个决定。

与大多数Quarkus扩展一样,Quarkus AWS Lambda HTTP/REST扩展支持实时编码。

这项技术被认为是preview。

preview(预览) 中,不保证向后兼容和在生态系统中的存在。具体的改进可能需要改变配置或API,并且正在计划变得 稳定 。欢迎在我们的 邮件列表 中提供反馈,或在我们的 GitHub问题列表 中提出问题。

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

先决条件

完成这个指南,你需要:

  • 大概30 minutes

  • 编辑器

  • JDK 17+ installed with JAVA_HOME configured appropriately

  • Apache Maven 3.9.6

  • 如果你愿意的话,还可以选择使用Quarkus CLI

  • 如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)

  • An Amazon AWS account

  • AWS SAM CLI

入门指南

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

安装AWS位

安装所有的AWS位可能是本指南中最困难的事情。请确保你遵循安装AWS SAM CLI的所有步骤。

创建Maven部署项目

使用我们的Maven 原型创建Quarkus AWS Lambda Maven项目。

如果你想使用AWS Gateway HTTP API,请用这个脚本生成你的项目:

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

如果你想使用AWS Gateway REST API,请用这个脚本生成你的项目:

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

构建和部署

构建项目。

CLI
quarkus build
Maven
./mvnw install

这将编译代码并运行生成的项目中包含的单元测试。单元测试和其他的Java项目一样,不需要运行在Amazon上。Quarkus开发模式也可以通过这个扩展来提供。

如果你想构建一个本地可执行文件,请确保你已经正确安装了GraalVM,并且只需向构建添加一个 native 属性

CLI
quarkus build --native
Maven
./mvnw install -Dnative
如果你在非Linux系统上进行构建,你还需要传入一个属性,指示quarkus使用Docker构建,因为Amazon Lambda需要Linux二进制文件。你可以通过将 -Dquarkus.native.container-build=true 传给你的构建命令来实现。不过,这需要你在本地安装了Docker。
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

构建额外生成的文件

运行构建后,你所使用的Quarkus lambda扩展会生成一些额外的文件。对于Maven,这些文件位于构建目录: target/ 中,对于Gradle,则位于 build/ 中。

  • function.zip - lambda deployment file

  • sam.jvm.yaml - sam cli deployment script

  • sam.native.yaml - sam cli deployment script for native

实时编码和本地模拟AWS Lambda环境

在开发和测试模式下,Quarkus会启动一个模拟的AWS Lambda事件服务器,该服务器将HTTP请求转换为相应的API Gateway事件类型,并将其发布到底层的Quarkus HTTP lambda环境进行处理。这尽可能在本地模拟AWS Lambda环境,而不需要Docker和SAM CLI等工具。

当使用Quarkus开发模式时,只需对 http://localhost:8080 调用HTTP请求,就像通常测试REST端点时所做的那样。这个请求将访问模拟事件服务器,并将被转换为API Gateway json消息,被Quarkus Lambda Poll循环所消费。

为了测试,Quarkus在端口8081下启动了一个单独的模拟事件服务器。Rest Assured的默认端口被Quarkus自动设置为8081,所以你不必担心设置这个。

如果你想在你的测试中模拟更复杂的API Gateway事件,那么请使用API Gateway json事件手动对 http://localhost:8080/_lambda_ (测试模式下的8081端口)做一个HTTP POST。这些事件将被直接放在Quarkus Lambda轮询循环中进行处理。下面是一个例子:

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

上面的例子模拟向你的HTTP Lambda发送一个带有HTTP请求的Cognito 主体。

如果你想为AWS HTTP API手工编码原始事件,AWS Lambda库有请求事件类型,是 com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent ,响应事件类型是 com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse 。这与 quarkus-amazon-lambda-http 扩展和AWS HTTP API相对应。

如果你想为AWS REST API手工编码原始事件,Quarkus有自己的实现: io.quarkus.amazon.lambda.http.model.AwsProxyRequestio.quarkus.amazon.lambda.http.model.AwsProxyResponse 。这与 quarkus-amazon-lambda-rest 扩展和AWS REST API相对应。

The mock event server is also started for @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.

最后,如果端口8080或端口8081在你的电脑上不可用,你可以使用application.properties修改开发和测试模式的端口

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

端口值为零将导致随机分配端口。

To turn off the mock event server:

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

Simulate AWS Lambda Deployment with SAM CLI

AWS SAM CLI允许你在模拟的Lambda环境中的笔记本电脑上,本地运行你的lambda。这需要安装Docker。在构建了Maven项目后,执行这个命令:

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

这将启动一个模仿亚马逊的Lambda部署环境的Docker容器。一旦环境启动了,你就可以在你的浏览器中调用lambda示例,方法是去:

在控制台,你会看到lambda的启动信息。这个特定的部署启动了一个JVM,并将你的lambda作为纯Java加载。

部署到AWS

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

回答所有的问题,你的lambda将被部署,必要的钩子将被设置到API网关。如果一切部署成功,你的微服务的根URL将被输出到控制台。类似这样:

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

Value 属性是你的lambda的根URL。把它复制到你的浏览器,并在末尾添加 hello

对二进制类型的响应将会使用base64自动编码。这与使用 quarkus:dev 的行为不同,后者将返回原始字节。亚马逊的API有额外的限制,要求base64编码。一般来说,客户端代码会自动处理这种编码,但在某些自定义情况下,你应该知道你可能需要手动管理这种编码。

部署一个本地可执行文件

要部署一个本地可执行文件,你必须用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

然后你可以使用sam local在本地测试可执行文件

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

要部署到AWS Lambda:

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

检查POM

除了包含 quarkus-amazon-lambda-http 扩展(如果你要部署AWS Gateway HTTP API)或 quarkus-amazon-lambda-rest 扩展(如果你要部署AWS Gateway REST API)外,POM没有什么特别之处。这些扩展会自动生成你的lambda部署可能需要的一切。

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

检查sam.yaml

sam.yaml 语法已经超出了本文档的范围。有几件事情必须强调,以防你要制作你自己的自定义 sam.yaml 部署文件。

首先需要注意的是,对于纯Java lambda部署,需要一个特定的处理程序类。不要改变Lambda处理程序的名称。

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

This handler is a bridge between the lambda runtime and the Quarkus HTTP framework you are using (Jakarta REST, Servlet, etc.)

如果你想使用原生,那么必须为本地GraalVM部署设置一个环境变量。如果你看一下 sam.native.yaml ,你会看到这个变量:

        Environment:
          Variables:
            DISABLE_SIGNAL_HANDLERS: true

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

最后,对于AWS Gateway REST API的部署,有一件特别的事情。该API假定HTTP响应体是文本,除非你通过配置明确告诉它哪些媒体类型是二进制。为了使事情更简单,Quarkus扩展强制对所有的HTTP响应信息进行二进制(base 64)编码, sam.yaml 文件必须配置API网关,以假设所有的媒体类型都是二进制:

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

可注入的AWS上下文变量

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

对于AWS HTTP API,你可以注入AWS变量 com.amazonaws.services.lambda.runtime.Contextcom.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent 。下面是一个例子:

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


}

对于AWS REST API,你可以注入AWS变量 com.amazonaws.services.lambda.runtime.Contextio.quarkus.amazon.lambda.http.model.AwsProxyRequestContext 。下面是一个例子:

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

}

使用AWS XRay和GraalVM进行追踪

如果你正在构建原生镜像,并希望在你的lambda中使用 AWS X-Ray 追踪 ,你将需要在你的pom中把 quarkus-amazon-lambda-xray 作为一个依赖项来包括。AWS X-Ray库与GraalVM并不完全兼容,因此我们不得不做一些整合工作来使其可以正常工作。

安全集成

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

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

要启用这一安全特性,请将此添加到你的 application.properties 文件中:

quarkus.lambda-http.enable-security=true

下面是它的映射方式:

Table 1. HTTP quarkus-amazon-lambda-http
认证类型 主体类 主体名的Json路径

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

io.quarkus.amazon.lambda.http.CustomPrincipal

requestContext.authorizer.lambda.principalId

Table 2. REST quarkus-amazon-lambda-rest
认证类型 主体类 主体名的Json路径

Cognito

io.quarkus.amazon.lambda.http.CognitoPrincipal

requestContext.authorizer.claims.cognito:username

IAM

io.quarkus.amazon.lambda.http.IAMPrincipal

requestContext.identity.user

自定义Lambda

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

自定义安全集成

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

对于HTTP,要覆盖的重要方法是 LambdaIdentityProvider.authenticate(APIGatewayV2HTTPEvent event) 。为此,你将根据你从 APIGatewayV2HTTPEvent 映射安全数据的方式来分配一个SecurityIdentity

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

对于REST,要覆盖的重要方法是 LambdaIdentityProvider.authenticate(AwsProxyRequest event) 。为此,你将根据你从 AwsProxyRequest 映射安全数据的方式来分配一个SecurityIdentity。

您实现的提供者必须是一个CDI bean。这里是一个例子:

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

下面是同样的例子,但是使用了AWS Gateway REST API:

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应该自动发现这个实现并使用它,而不是前面讨论的默认实现。

简单的SAM本地主体

如果你使用 sam local 测试你的应用程序,你可以通过设置 QUARKUS_AWS_LAMBDA_FORCE_USER_NAME 环境变量来硬编码一个主体名,以便在应用程序运行时使用

SnapStart

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

Related content