Using the legacy REST Client with Multipart

本指南介绍了与 RESTEasy Classic 兼容的 REST 客户端的多部分支持,该指南曾经是默认的 Jakarta REST(以前称为 JAX-RS)实现,直到 Quarkus 2.8。 现在建议使用 Quarkus REST(以前称为 RESTEasy Reactive),它同样支持传统的阻塞工作负载和反应式工作负载。有关 Quarkus REST 的更多信息,请参阅 REST Client guide,对于服务器端,请参阅 introductory REST JSON guide 或更详细的 Quarkus REST guide

RESTEasy 丰富地支持 multipart/*multipart/form-data mime 类型。多部分 mime 格式用于传递内容正文列表。多个内容正文嵌入在一则消息中。multipart/form-data 通常在 Web 应用程序 HTML 表单文档中找到,通常用于上传文件。表单数据格式与其他多部分格式相同,只是每段内联内容都有与之关联的名称。 本指南解释了如何使用 RESTEasy REST 客户端与 Multipart 协同使用,以便与几乎不需要任何内容的 multipart/form-data 内容类型所需的 REST API 进行交互。

Prerequisites

如要完成本指南,您需要:

  • Roughly 15 minutes

  • An IDE

  • 安装了 JDK 17+,已正确配置 JAVA_HOME

  • Apache Maven ${proposed-maven-version}

  • 如果你想使用 Quarkus CLI, 则可以选择使用

  • 如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately

Solution

我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。

克隆 Git 存储库: git clone $${quickstarts-base-url}.git,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。

解决方案位于 resteasy-client-multipart-quickstart directory

Creating the Maven project

首先,我们需要一个新项目。使用以下命令创建一个新项目:

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

适用于 Windows 用户:

  • 如果使用 cmd,(不要使用反斜杠 \ ,并将所有内容放在同一行上)

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

此命令生成一个具有 REST 端点的 Maven 项目,并导入 resteasy-clientresteasy 扩展。它还添加了 resteasy-multipart 扩展来支持 multipart/form-data 请求。

如果你已经配置了 Quarkus 项目,则可以通过在项目基目录中运行以下命令将 resteasy-multipart 扩展添加到项目:

CLI
quarkus extension add {add-extension-extensions}
Maven
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
Gradle
./gradlew addExtension --extensions='{add-extension-extensions}'

这会将以下内容添加到构建文件中:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-multipart</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-resteasy-multipart")

Setting up the model

在本指南中,我们将演示如何调用接受 multipart/form-data 输入的 REST 服务。我们假设在发送请求之前有效负载是众所周知的,因此我们可以建模为 POJO。

如果有效负载未知,您还可以使用 RESTEasy 自定义 API。如果是这种情况,请参阅本指南末尾处的 RESTEasy Multipart Providers 链接。

我们的首要任务是设置我们将用来定义 multipart/form-data 有效负载的模型,形式为 MultipartBody POJO。

创建 src/main/java/org/acme/rest/client/multipart/MultipartBody.java 文件并设置以下内容:

package org.acme.rest.client.multipart;

import java.io.InputStream;

import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.providers.multipart.PartType;

public class MultipartBody {

    @FormParam("file")
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    public InputStream file;

    @FormParam("fileName")
    @PartType(MediaType.TEXT_PLAIN)
    public String fileName;
}

上述代码中注释的目的如下:

  • @FormParam 是一个标准 Jakarta REST 注解,用于定义请求实体主体中包含的表单参数

  • @PartType 是 RESTEasy 特定的注解,当客户端执行 multipart 请求并定义该部分的内容类型时需要。

Create the interface

使用 RESTEasy REST 客户端很简单,只需使用适当的 Jakarta REST 和 MicroProfile 注解创建一个接口即可。在我们的示例中,应该在 src/main/java/org/acme/rest/client/multipart/MultipartService.java 创建接口并具有以下内容:

package org.acme.rest.client.multipart;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;

@Path("/echo")
@RegisterRestClient
public interface MultipartService {

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_PLAIN)
    String sendMultipartData(@MultipartForm MultipartBody data);

}

sendMultipartData 方法使我们的代码能够向 Echo 服务(出于演示目的在同一服务器中运行)POST 一个 multipart/form-data 请求。由于在这个演示中我们对 multipart/form-data 数据包有确切的了解,因此我们可以使用 @org.jboss.resteasy.annotations.providers.multipart.MultipartForm 注解将它们映射到上一节中创建的模型类。

客户端将处理所有网络和编组,从而使我们的代码摆脱此类技术细节。

上述代码中注释的目的如下:

  • @RegisterRestClient 允许 Quarkus 知道此接口旨在作为 REST 客户端用于 CDI 注入

  • @Path@POST 是标准 Jakarta REST 注解,用于定义如何访问服务

  • @MultipartForm 定义参数作为 multipart/form-data MIME 类型的传入/传出请求/响应的值对象。

  • @Consumes 定义此请求消耗的预期内容类型(参数)

  • @Produces 定义此请求产生的预期内容类型(返回类型)

虽然 @Consumes@Produces 是可选的,因为支持自动协商,但强烈建议使用它们对端点进行注释以精确定义预期内容类型。 它将允许缩小包含在原生可执行文件中的 Jakarta REST 提供程序(可以看作转换器)的数量。

Create the configuration

要确定发起 REST 调用时的基本 URL,REST 客户端使用 application.properties 的配置。属性的名称需要遵循特定的约定,这在下面的代码中展示得最好:

# Your configuration properties
quarkus.rest-client."org.acme.rest.client.multipart.MultipartService".url=http://localhost:8080/

此配置表示使用 org.acme.rest.client.multipart.MultipartService 执行的所有请求都将使用 http://localhost:8080/ 作为基本 URL。

请注意,org.acme.rest.client.multipart.MultipartService must 匹配我们在上一节中创建的 MultipartService 接口的完全限定名称。

Create the Jakarta REST resource

使用以下内容创建 src/main/java/org/acme/rest/client/multipart/MultipartClientResource.java 文件:

package org.acme.rest.client.multipart;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;

import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import org.eclipse.microprofile.rest.client.inject.RestClient;

@Path("/client")
public class MultipartClientResource {

    @Inject
    @RestClient
    MultipartService service;

    @POST
    @Path("/multipart")
    @Produces(MediaType.TEXT_PLAIN)
    public String sendFile() throws Exception {
        MultipartBody body = new MultipartBody();
        body.fileName = "greeting.txt";
        body.file = new ByteArrayInputStream("HELLO WORLD".getBytes(StandardCharsets.UTF_8));
        return service.sendMultipartData(body);
    }
}

请注意,除了标准 CDI @Inject 注解之外,我们还需要使用 MicroProfile @RestClient 注解来注入 MultipartService

Creating the server

为了演示目的,我们创建一个简单的 Echo 终端,作为服务器部分。

创建目录 src/main/java/org/acme/rest/client/multipart/server 并包含一个 EchoService.java 文件,内含以下内容:

package org.acme.rest.client.multipart.server;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/echo")
public class EchoService {

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.TEXT_PLAIN)
    public String echo(String requestBody) throws Exception {
        return requestBody;
    }
}

这将仅返回请求正文,这对于测试目的很有用。

Update the test

我们还需要更新功能测试以反映对终端做的更改。编辑 src/test/java/org/acme/rest/client/multipart/MultipartClientResourceTest.java 文件为:

package org.acme.rest.client.multipart;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;

@QuarkusTest
public class MultipartClientResourceTest {

    @Test
    public void testMultipartDataIsSent() {
        given()
                .when().post("/client/multipart")
                .then()
                .statusCode(200)
                .body( containsString("Content-Disposition: form-data; name=\"file\""),
                        containsString("HELLO WORLD"),
                        containsString("Content-Disposition: form-data; name=\"fileName\""),
                        containsString("greeting.txt"));
    }

}

上面的代码使用 REST Assured 来断言从 echo 服务返回的内容包含多部分元素

因为测试在不同的端口中运行,所以我们还需要在 src/test/resources 中包含一个 application.properties,内含以下内容:

# Your configuration properties
quarkus.rest-client."org.acme.rest.client.multipart.MultipartService".url=http://localhost:8081/

Package and run the application

使用以下内容运行应用程序:

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

在终端中,运行 curl -X POST [role="bare"]http://localhost:8080/client/multipart

你会看到类似以下的输出:

--89d288bd-960f-460c-b266-64c5b4d170fa
Content-Disposition: form-data; name="fileName"
Content-Type: text/plain

greeting.txt
--89d288bd-960f-460c-b266-64c5b4d170fa
Content-Disposition: form-data; name="file"
Content-Type: application/octet-stream

HELLO WORLD
--89d288bd-960f-460c-b266-64c5b4d170fa--

和往常一样,可以使用以下命令打包应用程序:

CLI
quarkus build
Maven
./mvnw install
Gradle
./gradlew build

并且使用 java -jar target/quarkus-app/quarkus-run.jar 执行。

你还可以按如下方式生成本机可执行文件:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true