The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.
返回指南目录

带有RESTEasy Reactive、Undertow或Reactive Routes的Amazon Lambda

通过Quarkus,您可以使用 AWS Gateway HTTP APIAWS Gateway REST API 将您喜欢的Java HTTP框架部署为Amazon Lambda。这意味着你可以将用RESTEasy Reactive(JAX-RS)、Undertow(servlet)、Reactive Routes、 Funqy HTTP 或任何其他Quarkus HTTP框架编写的微服务部署为AWS Lambda。

你可以将你的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 11以上版本并正确配置了 JAVA_HOME

  • Apache Maven 3.8.6

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

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

  • 一个亚马逊AWS账户

  • AWS SAM CLI

入门

本指南将引导您通过Maven原型生成一个Java项目示例。随后,还将介绍项目的结构,以便您能够调整现有的项目以使用Amazon 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=2.14.2.Final

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

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

构建和部署

构建项目:

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

  • 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开发模式时,只需对 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相对应。

模拟事件服务器也是为 @NativeImageTest@QuarkusIntegrationTest 测试而启动的,因此也将与本地二进制文件一起工作。所有这些都提供了与SAM CLI本地测试类似的功能,而没有Docker的开销。

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

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

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

用SAM CLI模拟Amazon Lambda部署

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部署可能需要的一切。

另外,至少在生成的Maven原型 pom.xml 中, quarkus-resteasy-reactivequarkus-reactive-routesquarkus-undertow 依赖项都是可选的。选择你想使用的HTTP框架(JAX-RS、Reactive Routes和/或Servlet),并删除其他依赖项以缩小部署规模。

检查sam.yaml

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

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

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

这个处理程序是lambda运行时和你正在使用的Quarkus HTTP框架(JAX-RS、Servlet等)之间的桥梁。

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

        Environment:
          Variables:
            DISABLE_SIGNAL_HANDLERS: true

这个环境变量解决了Quarkus和Amazon Lambda Custom Runtime环境之间的一些不兼容问题。

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

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

可注入的AWS上下文变量

如果您使用RESTEasy Reactive和JAX-RS,您可以使用JAX-RS @Context 注解将各种AWS上下文变量注入到JAX-RS资源类中,或者注入到带有CDI @Inject 注解的任何其他地方。

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

import javax.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 javax.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并不完全兼容,因此我们不得不做一些整合工作来使其可以正常工作。

安全集成

当你在API网关上调用一个HTTP请求时,网关会把这个HTTP请求转换成一个JSON事件文档,并转发给Quarkus Lambda。Quarkus Lambda解析这个json,并将其转换为HTTP请求的内部表示,这样它就可以被Quarkus支持的任何HTTP框架(JAX-RS、servlet、Reactive Routes)所消费。

API Gateway支持许多不同的方式来对你的HTTP端点进行安全调用,这些端点是由Lambda和Quarkus支持的。如果你启用了它,Quarkus会自动解析 事件json文档 的相关部分,查找基于安全的元数据,并在内部注册一个 java.security.Principal ,通过注入 javax.ws.rs.core.SecurityContext ,通过Servlet中的 HttpServletRequest.getUserPrincipal() ,以及Reactive Routes中的 RouteContext.user() ,可以在JAX-RS中进行查找。如果你了解更多的安全信息, Principal 对象可以被类型转化为一个能给你更多信息的类。

要启用这一安全特性,请将此添加到你的 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]+

自定义安全集成

对AWS安全的默认支持只是将主体名称映射到Quarkus安全API,并没有对声明、角色或权限进行映射。你可以使用 io.quarkus.amazon.lambda.http.LambdaIdentityProvider 接口的实现来完全控制lambda HTTP事件中的安全元数据是如何被映射到Quarkus安全API的。通过实现这个接口,你可以做一些事情,比如为你的主体定义角色映射,或者发布由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 javax.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 javax.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 环境变量来硬编码一个主体名,以便在应用程序运行时使用