编辑此页面

具有 Quarkus REST、Undertow 或 Reactive Routes 的 AWS Lambda

使用 Quarkus,您可以部署您最喜欢的 Java HTTP 框架作为 AWS Lambda,使用 AWS Gateway HTTP APIAWS Gateway REST API。这意味着您可以使用 Quarkus REST(我们的 Jakarta REST 实现)、Undertow(servlet)、Reactive Routes、Funqy HTTP 或任何其他 Quarkus HTTP 框架编写的微服务部署到 AWS Lambda。

您应该只将一个 HTTP 框架与 AWS Lambda 扩展一起使用,以避免意外的冲突和错误。

您可以将 Lambda 部署为纯 Java jar,或者将项目编译为本地镜像以减小内存占用和启动时间。我们的集成还会生成 SAM 部署文件,这些文件可以被 Amazon 的 SAM 框架使用。

Quarkus 为每个 Gateway API 都有不同的扩展。HTTP Gateway API 在 quarkus-amazon-lambda-http 扩展中实现。REST Gateway API 在 quarkus-amazon-lambda-rest 扩展中实现。如果您不确定使用哪个 Gateway 产品,Amazon 有一个 很棒的指南 来帮助您做出决定。

与大多数 Quarkus 扩展一样,Quarkus AWS Lambda HTTP/REST 扩展支持热部署。

此技术被认为是预览版。

预览阶段,不保证向后兼容性和在生态系统中的存在。特定的改进可能需要更改配置或 API,并且有计划成为稳定版本。欢迎在我们的 邮件列表 或我们的 GitHub 问题跟踪器 中提出问题和反馈。

有关可能的完整状态列表,请查看我们的常见问题解答条目

先决条件

要完成本指南,您需要

  • 大约 30 分钟

  • 一个 IDE

  • 已安装 JDK 17+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

  • Amazon AWS 帐户

  • AWS SAM CLI

入门

本指南将引导您通过 Maven archetype 生成一个示例 Java 项目。之后,它将介绍项目结构,以便您可以将任何现有项目改编为使用 AWS Lambda。

安装 AWS 组件

安装所有 AWS 组件可能是本指南中最困难的部分。请确保您遵循安装 AWS SAM CLI 的所有步骤。

创建 Maven 部署项目

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

如果您想使用 AWS Gateway HTTP API,请使用此脚本生成您的项目

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

如果您想使用 AWS Gateway REST API,请使用此脚本生成您的项目

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

构建和部署

构建项目

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。

根据 AWS 文档,AL2023 x86-64 二进制文件是为 x86-64 架构的 x86-64-v2 修订版构建的。在 Quarkus 中,quarkus.native.march 的默认值为 x86-64-v3。这可能会导致 问题,如果 AWS Lambda 配置了较旧的硬件。为了最大限度地提高 Lambda 兼容性,您可以将 quarkus.native.march 设置为 x86-64-v2。有关更多信息,请参阅 Native Reference 指南。

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 部署文件

  • sam.jvm.yaml - sam cli 部署脚本

  • sam.native.yaml - 本地 sam cli 部署脚本

热部署和本地模拟 AWS Lambda 环境

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

当使用 Quarkus 开发模式时,只需像往常一样通过 https://:8080 调用 HTTP 请求来测试您的 REST 端点。此请求将命中模拟事件服务器,并转换为 Quarkus Lambda 轮询循环所使用的 API Gateway json 消息。

为了进行测试,Quarkus 在 8081 端口下启动了一个单独的模拟事件服务器。Quarkus 自动将 Rest Assured 的默认端口设置为 8081,因此您无需担心设置此项。

如果您想在测试中模拟更复杂的 API Gateway 事件,请手动向 https://:8080/_lambda_(测试模式下的 8081 端口)发送 HTTP POST 请求,并附带原始 API Gateway json 事件。这些事件将直接放入 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"));
    }

上面的示例模拟了将 Cognito principal 与 HTTP 请求一起发送到您的 HTTP Lambda。

如果您想手动编码 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。

模拟事件服务器也为 @QuarkusIntegrationTest 测试启动,因此也可以与本地镜像一起使用。所有这些功能都类似于 SAM CLI 的本地测试,而没有 Docker 的开销。

最后,如果您的计算机上没有 8080 或 8081 端口可用,您可以通过 application.properties 修改开发和测试模式的端口

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

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

关闭模拟事件服务器

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

使用 SAM CLI 模拟 AWS Lambda 部署

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

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

这将启动一个模拟 Amazon Lambda 部署环境的 Docker 容器。环境启动后,您可以通过访问以下地址在浏览器中调用示例 lambda:

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

部署到 AWS

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

回答所有问题,您的 lambda 将被部署,并且将设置必要的 API Gateway 钩子。如果所有部署都成功,您的微服务的根 URL 将输出到控制台。类似这样

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

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

二进制类型的响应将自动使用 base64 进行编码。这与使用 quarkus:dev 的行为不同,后者将返回原始字节。Amazon 的 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

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

此外,至少在生成的 Maven archetype pom.xml 中,quarkus-restquarkus-reactive-routesquarkus-undertow 依赖项都是可选的。选择您想使用的 HTTP 框架(Jakarta REST、Reactive Routes 和/或 Servlet),并删除其他依赖项以减小部署大小。

检查 sam.yaml

sam.yaml 的语法超出了本文档的范围。有几点需要强调,以防您要自己创建自定义的 sam.yaml 部署文件。

首先需要注意的是,纯 Java Lambda 部署需要一个特定的处理程序类。请勿更改 Lambda 处理程序名称。

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

这个处理程序是 Lambda 运行时和您正在使用的 Quarkus HTTP 框架(Jakarta REST、Servlet 等)之间的桥梁。

如果您想使用本地模式,则必须为本地 GraalVM 部署设置一个环境变量。如果您查看 sam.native.yaml,您会看到这个

        Environment:
          Variables:
            DISABLE_SIGNAL_HANDLERS: true

这个环境变量解决了一些 Quarkus 与 AWS Lambda Custom Runtime 环境之间的不兼容性。

最后,AWS Gateway REST API 部署有一个特定之处。该 API 假定 HTTP 响应体是文本,除非您通过配置明确告诉它哪些媒体类型是二进制的。为了方便起见,Quarkus 扩展将所有 HTTP 响应消息强制进行二进制(base 64)编码,并且 sam.yaml 文件必须将 API Gateway 配置为假定所有媒体类型都是二进制的。

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

可注入的 AWS 上下文变量

如果您使用 Quarkus REST 和 Jakarta REST,您可以通过 Jakarta REST 的 @Context 注释或使用 CDI 的 @Inject 注释在任何地方将各种 AWS 上下文变量注入到您的 Jakarta REST 资源类中。

对于 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 Tracing,您需要在 pom 中包含 quarkus-amazon-lambda-xray 作为依赖项。AWS X-Ray 库与 GraalVM 的兼容性不是完全的,所以我们做了一些集成工作来使其正常工作。

安全集成

当您调用 API Gateway 上的 HTTP 请求时,Gateway 会将该 HTTP 请求转换为 JSON 事件文档,并转发给 Quarkus Lambda。Quarkus Lambda 解析此 JSON,并将其转换为内部表示形式的 HTTP 请求,该请求可以被 Quarkus 支持的任何 HTTP 框架(Jakarta REST、Servlet、Reactive Routes)使用。

API Gateway 支持多种安全调用由 Lambda 和 Quarkus 支持的 HTTP 端点的方法。如果启用,Quarkus 将自动解析 事件 JSON 文档 的相关部分,查找基于安全的元数据,并在内部注册一个 java.security.Principal,该 Principal 可以在 Jakarta REST 中通过注入 jakarta.ws.rs.core.SecurityContext 来查找,在 Servlet 中通过 HttpServletRequest.getUserPrincipal() 来查找,在 Reactive Routes 中通过 RouteContext.user() 来查找。如果您想要更多安全信息,Principal 对象可以被转换为一个类,该类将提供更多信息。

要启用此安全功能,请将此添加到您的 application.properties 文件中

quarkus.lambda-http.enable-security=true

这是映射方式

表 1. HTTP quarkus-amazon-lambda-http
认证类型 Principal 类 Principal 名称的 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

表 2. REST quarkus-amazon-lambda-rest
认证类型 Principal 类 Principal 名称的 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

如果存在 cognito:groups 声明,则 Quarkus 将提取这些组并映射到 Quarkus 角色,然后可以使用这些角色进行授权,例如使用 @RolesAllowed 注释。如果您不想将 cognito:groups 映射到 Quarkus 角色,则必须在配置中明确禁用它。

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

您还可以指定不同的 Cognito 声明来提取角色

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

默认情况下,它期望角色以空格分隔的列表形式括在括号中,即 [ user admin ]。您还可以指定用于在声明字符串中查找单个角色的正则表达式。

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

自定义安全集成

AWS 安全的默认支持仅将 principal 名称映射到 Quarkus 安全 API,而不映射声明、角色或权限。您可以通过实现 io.quarkus.amazon.lambda.http.LambdaIdentityProvider 接口,完全控制 Lambda HTTP 事件中的安全元数据如何映射到 Quarkus 安全 API。通过实现此接口,您可以执行诸如定义 principal 的角色映射或发布 IAM 或 Cognito 或您的自定义 Lambda 安全集成提供的其他属性等操作。

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 本地 Principal

如果您使用 sam local 测试您的应用程序,您可以通过设置 QUARKUS_AWS_LAMBDA_FORCE_USER_NAME 环境变量来硬编码要在应用程序运行时使用的 principal 名称。

SnapStart

要优化您的应用程序以适应 Lambda SnapStart,请查看 SnapStart 配置文档

相关内容