在我们CDK系列的第三部分,项目3,在相同的信息库,将用来说明一些先进的夸AWS集成特性,连同几个技巧特定的休息,众所周知,红帽实现雅加达休息规范。
让我们首先查看项目的pom.xml文件,它驱动了Maven构建进程。您将看到以下依赖项:
...
io.quarkiverse.amazonservices
quarkus-amazon-s3
io.quarkus
quarkus-amazon-lambda-http
io.quarkus
quarkus-rest-jackson
io.quarkus
quarkus-rest-client
...
software.amazon.awssdk
netty-nio-client
software.amazon.awssdk
url-connection-client
...
上面列表中的第一个依赖项,夸克斯-亚马逊-s3是一个Quarkus扩展,允许您的代码作为AWS S3客户端,并在桶中存储和删除对象,或实现备份和恢复策略,归档数据等。
下一个依赖项,夸克斯-亚马逊-lambda-http,是另一个旨在支持AWS HTTP网关API的夸克斯扩展。正如读者已经从本系列的前两个部分中所知道的那样,使用Quarkus,您可以使用AWS HTTP网关API或AWS REST网关API将REST API部署为AWS Lambda。这里我们将使用前一个,不那么扩展,因此提到的扩展。如果我们想使用AWS REST网关API,那么我们将不得不将夸克-亚马逊-http扩展替换为夸克-亚马逊-http扩展。
期待什么
在这个项目中,我们将使用Quarkus 3.11,在撰写本文时,它是最近的版本。与以前的版本相比,一些RESTeasy依赖已经发生了变化,因此依赖夸克-依赖-杰克逊取代了现在在3.10和以前使用的夸克-依赖依赖。此外,实现Eclipse MP REST客户端规范的夸克斯-休息-客户端扩展对于测试目的是必需的,我们将在稍后看到。最后但并非最不重要的是,需要url-连接-客户端Quarkus扩展,因为MP REST客户端实现默认使用它,因此,它必须包含在构建过程中。
现在,让我们来看看我们新的REST API。在cdk-quarkus-s3项目中打开Java类S3文件管理api,您将看到它定义了三个操作:下载文件、上传文件和列表文件。这三个应用程序都使用作为CDK应用程序堆栈的一部分创建的相同的S3桶。
Java
@Path("/s3")
public class S3FileManagementApi
{
@Inject
S3Client s3;
@ConfigProperty(name = "bucket.name")
String bucketName;
@POST
@Path("upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@Valid FileMetadata fileMetadata) throws Exception
{
PutObjectRequest request = PutObjectRequest.builder()
.bucket(bucketName)
.key(fileMetadata.filename)
.contentType(fileMetadata.mimetype)
.build();
s3.putObject(request, RequestBody.fromFile(fileMetadata.file));
return Response.ok().status(Response.Status.CREATED).build();
}
...
}
解释代码
上面的代码片段只复制了上传文件的操作,另外两个非常相似。通过利用Quarkus CDI,观察S3客户端的实例化是多么简单,它避免了需要多个样板代码行。此外,我们正在使用Eclipse MP配置规范来定义目标S3桶的名称。
我们的端点上传文件()接受POST请求并使用MULTIPART_FORM_DATA MIME数据分为两个不同的部分,一个用于有效负载,另一个包含要上传的文件。端点接受类文件元数据的输入参数,如下图所示:
Java
public class FileMetadata
{
@RestForm
@NotNull
public File file;
@RestForm
@PartType(MediaType.TEXT_PLAIN)
@NotEmpty
@Size(min = 3, max = 40)
public String filename;
@RestForm
@PartType(MediaType.TEXT_PLAIN)
@NotEmpty
@Size(min = 10, max = 127)
public String mimetype;
...
}
这个类是一个数据对象,将要上传的文件及其名称和MIME类型一起分组。它使用@RestForm RESTeasy特定注释来处理具有多部分/表单数据的HTTP请求。jakarta.validation.constraints注释的使用对于验证目的也非常实用。
要回到上面的端点,它创建一个putobbect请求,输入参数是目标桶名,一个唯一标识桶中存储文件的键,在这种情况下,是文件名和关联的MIME类型,例如文本文件的TEXT_PLAIN。一旦创建了输入请求,它将通过HTTP PUT请求发送到AWS S3服务。请注意,使用RequestBody.fromFile(…)语句将要上传的文件插入到请求体中是多么容易。
这都是作为AWSAambda函数公开的REST API的全部。现在让我们来看看在我们的CDK应用程序的堆栈中有什么新功能:
Java
...
HttpApi httpApi = HttpApi.Builder.create(this, "HttpApiGatewayIntegration")
.defaultIntegration(HttpLambdaIntegration.Builder.create("HttpApiGatewayIntegration", function).build()).build();
httpApiGatewayUrl = httpApi.getUrl();
CfnOutput.Builder.create(this, "HttpApiGatewayUrlOutput").value(httpApi.getUrl()).build();
...
这些行已经被添加到cdk-简单构造项目中的小桶构造类中。我们希望我们在当前堆栈中创建的Lambda函数位于HTTP网关的后面,并对其进行备份。这可能有一些好处。所以我们需要为我们的Lambda函数创建一个集成。
由AWS定义的集成的概念意味着为API端点提供一个后端。在HTTP网关的情况下,应该为每个API网关的端点提供一个或多个后端。这些集成有它们自己的请求和响应,不同于API本身。有两种集成类型:
· 其中后端是一个Lambda函数;
· HTTP集成,其中后端可能是任何已部署的web应用程序;
在我们的示例中,我们使用的是Lambda集成。还有两种类型的Lambda集成:
· Lambda代理集成,其中不需要集成的请求和响应的定义,以及它们到原始请求和响应的映射,因为它们是自动提供的;
· Lambda非代理集成,其中我们需要明确地指定如何将传入的请求数据映射到集成请求,以及如何将结果的集成响应数据映射到方法响应;
为了简单起见,我们在项目中使用了第一个案例。这就是上面的语句。默认集成(...)正在做的事情。一旦创建了集成,我们就需要显示新创建的API网关的URL,我们的Lambda函数是备份的。通过这种方式,除了能够像我们之前那样直接调用Lambda函数之外,我们还可以通过API网关来实现它。在一个有几十个REST端点的项目中,有一个单一的接触点非常重要,在那里应用安全策略、日志记录、日志化和其他交叉问题。API网关是一个理想的单个接触点。
该项目附带了两个单元测试和集成测试。例如,类S3文件管理测试使用REST保证执行单元测试,如下图所示:
Java
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class S3FileManagementTest
{
private static File readme = new File("./src/test/resources/README.md");
@Test
@Order(10)
public void testUploadFile()
{
given()
.contentType(MediaType.MULTIPART_FORM_DATA)
.multiPart("file", readme)
.multiPart("filename", "README.md")
.multiPart("mimetype", MediaType.TEXT_PLAIN)
.when()
.post("/s3/upload")
.then()
.statusCode(HttpStatus.SC_CREATED);
}
@Test
@Order(20)
public void testListFiles()
{
given()
.when().get("/s3/list")
.then()
.statusCode(200)
.body("size()", equalTo(1))
.body("[0].objectKey", equalTo("README.md"))
.body("[0].size", greaterThan(0));
}
@Test
@Order(30)
public void testDownloadFile() throws IOException
{
given()
.pathParam("objectKey", "README.md")
.when().get("/s3/download/{objectKey}")
.then()
.statusCode(200)
.body(equalTo(Files.readString(readme.toPath())));
}
}
这个单元测试首先将文件README.md上传到为此目的定义的S3桶。然后它列出桶中存在的所有文件,并通过下载刚刚上传的文件完成。请注意应用程序。属性文件中的以下几行:
Plain Text
bucket.name=my-bucket-8701
%test.quarkus.s3.devservices.buckets=${bucket.name}
第一个定义目标桶的名称,第二个定义自动创建目标桶。这只在通过Quarkus Mock服务器执行时工作。当这个单元测试是在Maven测试阶段执行的,但是针对由Quarkus自动管理的测试容器运行的本地堆栈实例,一旦部署了CDK应用程序,集成实例,S3文件管理,是针对真正的AWS基础设施执行的。
集成测试使用了一种不同的范式,它们利用了Eclipse客户端规范,取代了由Quarkus实现的EPREST客户端规范,如下代码片段所示:
Java
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class S3FileManagementIT
{
private static File readme = new File("./src/test/resources/README.md");
@Inject
@RestClient
S3FileManagementClient s3FileManagementTestClient;
@Inject
@ConfigProperty(name = "base_uri/mp-rest/url")
String baseURI;
@Test
@Order(40)
public void testUploadFile() throws Exception
{
Response response = s3FileManagementTestClient.uploadFile(new FileMetadata(readme, "README.md", MediaType.TEXT_PLAIN));
assertThat(response).isNotNull();
assertThat(response.getStatusInfo().toEnum()).isEqualTo(Response.Status.CREATED);
}
...
}
我们注入了S3文件管理客户端,这是一个定义API端点的简单接口,Quarkus也完成了剩下的工作。它将生成所需的客户端代码。我们只需要在这个接口上调用端点,例如上传文件(……),仅此而已。看看cdk-夸库斯-s3项目中的S3文件管理客户端,看看一切是如何工作的,请注意注释是如何定义一个名为base_uri的配置键,在部署.sh脚本中进一步使用的。
现在,要测试AWS的真实基础设施,您需要执行deplend.sh脚本,如下所示:
Shell
$ cd cdk
$ ./deploy.sh cdk-quarkus/cdk-quarkus-api-gateway cdk-quarkus/cdk-quarkus-s3
这将编译和构建应用程序,执行单元测试,在AWS上部署Cloud形成堆栈,并针对此基础设施执行集成测试。在执行结束时,你应该看到类似的东西:
Plain Text
Outputs:
QuarkusApiGatewayStack.FunctionURLOutput = https://.lambda-url.eu-west-3.on.aws/
QuarkusApiGatewayStack.LambdaWithBucketConstructIdHttpApiGatewayUrlOutput = https://.execute-api.eu-west-3.amazonaws.com/
Stack ARN:
arn:aws:cloudformation:eu-west-3:...:stack/QuarkusApiGatewayStack/
现在,除了您在前面的示例中已经看到的Lambda函数URL之外,您还可以看到API HTTP网关URL现在可以用于测试目的,而不是Lambda。
同时,还提供了一个从邮递员(S3文件管理网)导出的E2E测试用例。它是通过Docker图像邮递员/新手来执行的:最新的,在测试容器中运行。下面是一个片段:Java
@QuarkusTest
public class S3FileManagementPostmanIT
{
...
private static GenericContainer postman = new GenericContainer<>("postman/newman")
.withNetwork(Network.newNetwork())
.withCopyFileToContainer(MountableFile.forClasspathResource("postman/AWS.postman_collection.json"),
"/etc/newman/AWS.postman_collection.json")
.withStartupCheckStrategy(new OneShotStartupCheckStrategy().withTimeout(Duration.ofSeconds(10)));
@Test
public void run()
{
String apiEndpoint = System.getenv("API_ENDPOINT");
assertThat(apiEndpoint).isNotEmpty();
postman.withCommand("run", "AWS.postman_collection.json",
"--global-var base_uri=" + apiEndpoint.substring(8).replaceAll(".$", ""));
postman.start();
LOG.info(postman.getLogs());
assertThat(postman.getCurrentContainerInfo().getState().getExitCodeLong()).isZero();
postman.stop();
}
}
结论
正如你所看到的,启动邮递员/新闻:最新图像测试容器,我们运行E2E测试用例输出从邮递员传递选项全局变量标记base_uri的初始化保持API网址的值保存的部署.sh脚本在API端点环境变量。不幸的是,可能是由于一个错误,邮差/新闻人员的图像不能识别这个选项,因此,等待这个问题被修复,这个测试现在被禁用了。
当然,您可以在邮差中导入文件AWS.postman_collection.json,并在用AWS生成的API URL的当前值替换全局变量{{base_uri}}后以这种方式运行它。