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.9
-
如果你愿意的话,还可以选择使用Quarkus CLI
-
如果你想构建原生可执行程序,可以选择安装Mandrel或者GraalVM,并正确配置(或者使用Docker在容器中进行构建)
入门指南
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.
创建Maven部署项目
使用我们的Maven 原型创建Quarkus AWS Lambda Maven项目。
如果你想使用AWS Gateway HTTP API,请用这个脚本生成你的项目:
mvn archetype:generate \
-DarchetypeGroupId=io.quarkus \
-DarchetypeArtifactId=quarkus-amazon-lambda-http-archetype \
-DarchetypeVersion=3.16.3
如果你想使用AWS Gateway REST API,请用这个脚本生成你的项目:
mvn archetype:generate \
-DarchetypeGroupId=io.quarkus \
-DarchetypeArtifactId=quarkus-amazon-lambda-rest-archetype \
-DarchetypeVersion=3.16.3
构建和部署
构建项目。
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。
|
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 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.AwsProxyRequest
和 io.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构建它。
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
除了包含 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.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 追踪 ,你将需要在你的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
下面是它的映射方式:
认证类型 | 主体类 | 主体名的Json路径 |
---|---|---|
Cognito JWT |
|
|
IAM |
|
|
自定义Lambda |
|
|
认证类型 | 主体类 | 主体名的Json路径 |
---|---|---|
Cognito |
|
|
IAM |
|
|
自定义Lambda |
|
|
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.
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本地主体
如果你使用 sam local
测试你的应用程序,你可以通过设置 QUARKUS_AWS_LAMBDA_FORCE_USER_NAME
环境变量来硬编码一个主体名,以便在应用程序运行时使用
SnapStart
To optimize your application for Lambda SnapStart, check the SnapStart Configuration Documentation.