具有 Quarkus REST、Undertow 或 Reactive Routes 的 AWS Lambda
使用 Quarkus,您可以部署您最喜欢的 Java HTTP 框架作为 AWS Lambda,使用 AWS Gateway HTTP API 或 AWS 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 并进行适当的配置
创建 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
构建和部署
构建项目
quarkus build
./mvnw install
这将编译代码并运行生成项目中的单元测试。单元测试与任何其他 Java 项目相同,不需要在 Amazon 上运行。此扩展也支持 Quarkus 开发模式。
如果您想构建本地可执行文件,请确保已正确安装 GraalVM,只需为构建添加 native
属性即可
quarkus build --native
./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 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
额外的构建生成文件
运行构建后,您正在使用的 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.AwsProxyRequest
和 io.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 进行构建。
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
然后您可以使用 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-rest
、quarkus-reactive-routes
和 quarkus-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.Context
和 com.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.Context
和 io.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
这是映射方式
认证类型 | Principal 类 | Principal 名称的 JSON 路径 |
---|---|---|
Cognito JWT |
|
|
IAM |
|
|
自定义 Lambda |
|
|
认证类型 | Principal 类 | Principal 名称的 JSON 路径 |
---|---|---|
Cognito |
|
|
IAM |
|
|
自定义 Lambda |
|
|
如果存在 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 安全集成提供的其他属性等操作。
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。
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 配置文档。