AWS Lambda with Quarkus REST, Undertow, or Reactive Routes
有了 Quarkus,您可以使用 AWS Gateway HTTP API或 AWS Gateway REST API将您最喜欢的 Java HTTP 框架部署为 AWS Lambda。这意味着您可以将使用 Quarkus REST(我们的 Jakarta REST 实现)、Undertow(servlet)、Reactive Routes、Funqy HTTP或任何其他 Quarkus HTTP 框架编写的微服务部署为 AWS Lambda。
您应该仅将单个 HTTP 框架与 AWS Lambda 扩展一起使用,以避免意外的冲突和错误。
您可以将 Lambda 部署为纯 Java jar,也可以将项目编译为原生镜像并部署,以实现更小的内存占用和启动时间。我们的集成还会生成 SAM 部署文件, Amazon’s SAM framework可以使用这些文件。 Quarkus 为每个 Gateway API 提供不同的扩展。HTTP Gateway API 在 `quarkus-amazon-lambda-http`扩展中实现。REST Gateway API 在 `quarkus-amazon-lambda-rest`扩展中实现。如果您对使用哪个 Gateway 产品感到困惑,亚马逊有一个 great guide来帮助您进行权衡。 像大多数 Quarkus 扩展一样,Quarkus AWS Lambda HTTP/REST 扩展支持实时编码。 :iokays-category: quarkus :iokays-path: modules/ROOT/pages/_includes/extension-status.adoc :keywords: Quarkus, 中文文档, 编程技术
该技术被认为是 {extension-status}。 有关可能状态的完整列表,请查看我们的 FAQ entry. |
- Prerequisites
- Getting Started
- Installing AWS bits
- Creating the Maven Deployment Project
- Build and Deploy
- Extra Build Generated Files
- Live Coding and Simulating AWS Lambda Environment Locally
- Simulate AWS Lambda Deployment with SAM CLI
- Deploy to AWS
- Deploying a native executable
- Examine the POM
- Injectable AWS Context Variables
- Tracing with AWS XRay and GraalVM
- Security Integration
- Custom Security Integration
- Simple SAM Local Principal
- SnapStart
Prerequisites
如要完成本指南,您需要:
-
Roughly 15 minutes
-
An IDE
-
安装了 JDK 17+,已正确配置
JAVA_HOME
-
Apache Maven ${proposed-maven-version}
-
如果你想使用 Quarkus CLI, 则可以选择使用
-
如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately
Creating the Maven Deployment Project
使用我们的 Maven 原型创建 Quarkus AWS Lambda Maven 项目。
如果你想使用 AWS Gateway HTTP API,请使用以下脚本生成你的项目:
mvn archetype:generate \
-DarchetypeGroupId=io.quarkus \
-DarchetypeArtifactId=quarkus-amazon-lambda-http-archetype \
-DarchetypeVersion={quarkus-version}
如果你想使用 AWS Gateway REST API,请使用以下脚本生成你的项目:
mvn archetype:generate \
-DarchetypeGroupId=io.quarkus \
-DarchetypeArtifactId=quarkus-amazon-lambda-rest-archetype \
-DarchetypeVersion={quarkus-version}
Build and Deploy
构建项目:
quarkus build
./mvnw install
./gradlew build
这会编译代码并运行生成项目中包含的单元测试。单元测试与任何其他 Java 项目相同,不需要在 Amazon 上运行。Quarkus 开发模式也可用作此扩展。
如果你想构建一个本机可执行文件,请确保正确安装 GraalVM,并向构建添加 native
属性
quarkus build --native
./mvnw install -Dnative
./gradlew build -Dquarkus.native.enabled=true
如果你不是在 Linux 系统上构建,你还需要传递一个属性来指示 quarkus 使用 Docker 构建,因为 AmazonLambda 需要 Linux 二进制文件。你可以通过将 |
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
./gradlew build -Dquarkus.native.enabled=true -Dquarkus.native.container-build=true
Extra Build Generated Files
运行构建后,你使用的 Quarkus lambda 扩展会生成几个额外文件。这些文件位于构建目录中:Maven 的 target/
,Gradle 的 build/
。
-
function.zip
- lambda 部署文件 -
sam.jvm.yaml
- sam cli 部署脚本 -
sam.native.yaml
- 本机的 sam cli 部署脚本
Live Coding and Simulating AWS Lambda Environment Locally
在开发和测试模式下,Quarkus 将启动一个模拟 AWS Lambda 事件服务器,该服务器会将 HTTP 请求转换为相应的 API Gateway 事件类型,并将其发布到底层的 Quarkus HTTP lambda 环境进行处理。这尽可能地在本地模拟 AWS Lambda 环境,而无需 Docker 和 SAM CLI 等工具。
在使用 Quarkus 开发模式时,只要像通常测试 REST 端点时一样在 http://localhost:8080
上调用 HTTP 请求。此请求将命中模拟事件服务器,并将转换为 Quarkus Lambda 轮询循环使用的 API Gateway json 消息。
为了进行测试,Quarkus 在 8081 端口下启动一个单独的模拟事件服务器。Rest Assured 的默认端口由 Quarkus 自动设置为 8081,因此你不必担心设置此端口。
如果你想在测试中模拟更复杂的 API Gateway 事件,请手动使用原始 API Gateway json 事件以 HTTP POST 方式访问 http://localhost:8080/lambda
(在测试模式下为端口 8081)。这些事件将直接放置在 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 请求向你的 HTTP Lambda 发送 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.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
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:
[role="bare"][role="bare"]http://127.0.0.1:3000/hello
在控制台中,您将看到来自 lambda 的启动消息。此特定部署启动了 JVM 并将您的 lambda 加载为纯 Java。
Deploy to 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 编码。这与使用 |
Deploying a native executable
要部署原生可执行文件,您必须使用 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
./gradlew build -Dquarkus.native.enabled=true -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
Examine the POM
POM 没有什么特别的,除了包含 quarkus-amazon-lambda-http
扩展(如果您要部署 AWS Gateway HTTP API)或 quarkus-amazon-lambda-rest
扩展(如果您要部署 AWS Gateway REST API)。这些扩展会自动生成 lambda 部署所需的一切。
此外,至少在生成的 Maven 典型文件 pom.xml
中,quarkus-rest
、quarkus-reactive-routes
和 quarkus-undertow
依赖项都是可选的。选择您要使用的 HTTP 框架(Jakarta REST、Reactive Routes 和/或 Servlet)并删除其他依赖项以缩小部署大小。
Examine 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 自定义运行时环境之间的一些不兼容性。
最后,对于 AWS Gateway REST API 部署还有一项特殊设置。该 API 假定 HTTP 响应主体为文本,除非您通过配置明确告诉它哪些媒体类型是二进制的。为了简化操作,Quarkus 扩展会强制对所有 HTTP 响应消息进行二进制(base 64)编码,并且 sam.yaml
文件必须配置 API Gateway 以假定所有媒体类型都是二进制的:
Globals:
Api:
EndpointConfiguration: REGIONAL
BinaryMediaTypes:
- "*/*"
Injectable AWS Context Variables
如果您正在使用 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) { }
}
Tracing with AWS XRay and GraalVM
如果您正在构建本机镜像,并且希望在 lambda 中使用 AWS X-Ray Tracing,您需要将 quarkus-amazon-lambda-xray
包含到 pom 中作为依赖项。AWS X-Ray 库与 GraalVM 并不完全兼容,因此我们必须做一些集成工作才能使其正常运行。
Security Integration
当您在 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 将自动解析 event json document 的相关部分并查找基于安全性的元数据,并在内部注册一个 java.security.Principal
,它可以通过在 Jakarta REST 中注入一个 jakarta.ws.rs.core.SecurityContext
、在 servlet 中通过 HttpServletRequest.getUserPrincipal()
和在 Reactive Routes 中通过 RouteContext.user()
来查找。如果您想要更多安全信息,可以将 Principal
对象强制转换为一个类,该类将为您提供更多信息。
要启用此安全功能,请将以下内容添加到您的 application.properties
文件:
quarkus.lambda-http.enable-security=true
以下是它的映射方式:
Auth Type | Principal Class | 主体名称的 Json 路径 |
---|---|---|
Cognito JWT |
|
|
IAM |
|
|
Custom Lambda |
|
|
Auth Type | Principal Class | 主体名称的 Json 路径 |
---|---|---|
Cognito |
|
|
IAM |
|
|
Custom 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]+
Custom Security Integration
对 AWS 安全的默认支持只会将主体名称映射到 Quarkus Security API,而不会对声明、角色或权限进行任何映射。您可以使用 io.quarkus.amazon.lambda.http.LambdaIdentityProvider
界面实现来完全控制 lambda HTTP 事件中的安全元数据如何映射到 Quarkus Security API。通过实现此界面,您可以执行操作,例如为您的主体定义角色映射,或发布由 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 应该自动发现此实现并使用它,而不是前面讨论的默认实现。
Simple SAM Local Principal
如果使用 sam local
测试应用程序,可以通过设置 QUARKUS_AWS_LAMBDA_FORCE_USER_NAME
环境变量来硬编码应用程序运行时要使用的主体名称。
SnapStart
要针对 Lambda SnapStart 优化您的应用程序,请查看 the SnapStart Configuration Documentation。