编辑此页面

使用 REST 客户端

本指南解释了如何使用 REST 客户端与 REST API 进行交互。REST 客户端是与 Quarkus REST(以前称为 RESTEasy Reactive)兼容的 REST 客户端实现。

如果您的应用程序使用客户端并公开 REST 端点,请使用 Quarkus REST 作为服务器端。

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

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

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

解决方案

我们建议您按照以下章节中的说明,逐步创建应用程序。但是,您可以直接转到完整的示例。

克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git,或下载 归档文件

解决方案位于 rest-client-quickstart 目录中。

创建 Maven 项目

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

CLI
quarkus create app org.acme:rest-client-quickstart \
    --extension='rest-jackson,rest-client-jackson' \
    --no-code
cd rest-client-quickstart

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-client-quickstart \
    -Dextensions='rest-jackson,rest-client-jackson' \
    -DnoCode
cd rest-client-quickstart

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

对于 Windows 用户

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

  • 如果使用 Powershell,请将 -D 参数用双引号括起来,例如 "-DprojectArtifactId=rest-client-quickstart"

此命令生成带有 REST 端点的 Maven 项目并导入

  • rest-jackson 扩展以支持 REST 服务器。如果您不想使用 Jackson,请改用 rest

  • rest-client-jackson 扩展以支持 REST 客户端。如果您不想使用 Jackson,请改用 rest-client

如果您已经配置了 Quarkus 项目,可以通过在项目根目录中运行以下命令将 rest-client-jackson 扩展添加到您的项目中

CLI
quarkus extension add rest-client-jackson
Maven
./mvnw quarkus:add-extension -Dextensions='rest-client-jackson'
Gradle
./gradlew addExtension --extensions='rest-client-jackson'

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

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client-jackson</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-rest-client-jackson")

设置模型

在本指南中,我们将演示如何使用 stage.code.quarkus.io 服务提供的 REST API 的一部分。我们的首要任务是设置我们将要使用的模型,以 Extension POJO 的形式。

创建一个 src/main/java/org/acme/rest/client/Extension.java 文件并设置以下内容

package org.acme.rest.client;

import java.util.List;

public class Extension {

    public String id;
    public String name;
    public String shortName;
    public List<String> keywords;

}

上面的模型只是服务提供的字段的一个子集,但足以用于本指南的目的。

创建接口

使用 REST 客户端就像使用适当的 Jakarta REST 和 MicroProfile 注解创建一个接口一样简单。在我们的例子中,接口应该在 src/main/java/org/acme/rest/client/ExtensionsService.java 创建并具有以下内容

package org.acme.rest.client;

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

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import java.util.Set;

@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);
}

getById 方法使我们的代码能够从 Code Quarkus API 获取扩展的 id。客户端将处理所有网络连接和编组,使我们的代码免受此类技术细节的干扰。

上面代码中注解的目的是以下几点

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

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

安装 quarkus-rest-client-jackson 扩展后,Quarkus 默认将 application/json 媒体类型用于大多数返回值,除非通过 @Produces@Consumes 注解显式设置媒体类型。

如果您不依赖 JSON 默认值,强烈建议您使用 @Produces@Consumes 注解来注释您的端点,以精确定义预期的内容类型。这将允许缩小本机可执行文件中包含的 Jakarta REST 提供程序(可以看作是转换器)的数量。

上面的 getById 方法是一个阻塞调用。不应在事件循环中调用它。异步支持部分描述了如何进行非阻塞调用。

查询参数

指定查询参数的最简单方法是使用 @QueryParam@RestQuery 注释客户端方法参数。@RestQuery@QueryParam 等效,但名称是可选的。此外,它还可以用于将查询参数作为 Map 传递,如果参数事先未知,这很方便。

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.RestQuery;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.Map;
import java.util.Set;
import java util.Optional;

@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);

    @GET
    Set<Extension> getByName(@RestQuery String name); (1)

    @GET
    Set<Extension> getByOptionalName(@RestQuery Optional<String> name);

    @GET
    Set<Extension> getByFilter(@RestQuery Map<String, String> filter); (2)

    @GET
    Set<Extension> getByFilters(@RestQuery MultivaluedMap<String, String> filters); (3)

}
1 @RestQuery 将包含带有键 name 的参数
2 每个 Map 条目恰好代表一个查询参数
3 MultivaluedMap 允许您发送数组值

使用 @ClientQueryParam

向请求添加查询参数的另一种方法是在 REST 客户端接口或接口的特定方法上使用 @io.quarkus.rest.client.reactive.ClientQueryParam。该注解可以指定查询参数名称,而值可以是常量、配置属性,也可以通过调用方法来确定。

以下示例显示了各种可能的用法

@ClientQueryParam(name = "my-param", value = "${my.property-value}") (1)
public interface Client {
    @GET
    String getWithParam();

    @GET
    @ClientQueryParam(name = "some-other-param", value = "other") (2)
    String getWithOtherParam();

    @GET
    @ClientQueryParam(name = "param-from-method", value = "{with-param}") (3)
    String getFromMethod();

    default String withParam(String name) {
        if ("param-from-method".equals(name)) {
            return "test";
        }
        throw new IllegalArgumentException();
    }
}
1 通过将 @ClientQueryParam 放在接口上,我们确保 my-param 将被添加到客户端的所有请求中。因为我们使用了 ${…​} 语法,所以参数的实际值将使用 my.property-value 配置属性获得。
2 当调用 getWithOtherParam 时,除了 my-param 查询参数外,还会添加值为 othersome-other-param
3 当调用 getFromMethod 时,除了 my-param 查询参数外,还会添加值为 testparam-from-method(因为这是当使用 param-from-method 调用 withParam 方法时返回的值)。

请注意,如果接口方法包含使用 @QueryParam 注释的参数,则该参数将优先于任何 @ClientQueryParam 注解中指定的任何内容。

有关此注解的更多信息,请参见 @ClientQueryParam 的 javadoc。

表单参数

可以使用 @RestForm(或 @FormParam)注解指定表单参数

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.RestForm;

import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.Map;
import java.util.Set;

@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    Set<Extension> postId(@FormParam("id") String id);

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    Set<Extension> postName(@RestForm String name);

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    Set<Extension> postFilter(@RestForm Map<String, String> filter);

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    Set<Extension> postFilters(@RestForm MultivaluedMap<String, String> filters);

}

使用 @ClientFormParam

也可以使用 @ClientFormParam 指定表单参数,类似于 @ClientQueryParam

@ClientFormParam(name = "my-param", value = "${my.property-value}")
public interface Client {
    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    String postWithParam();

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @ClientFormParam(name = "some-other-param", value = "other")
    String postWithOtherParam();

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @ClientFormParam(name = "param-from-method", value = "{with-param}")
    String postFromMethod();

    default String withParam(String name) {
        if ("param-from-method".equals(name)) {
            return "test";
        }
        throw new IllegalArgumentException();
    }
}

有关此注解的更多信息,请参见 @ClientFormParam 的 javadoc。

路径参数

如果 GET 请求需要路径参数,您可以利用 @PathParam("parameter-name") 注解代替(或除了)@QueryParam。可以根据需要组合路径和查询参数,如下例所示。

package org.acme.rest.client;

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

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.QueryParam;
import java.util.Set;

@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {

    @GET
    @Path("/stream/{stream}")
    Set<Extension> getByStream(@PathParam("stream") String stream, @QueryParam("id") String id);
}

动态基本 URL

REST 客户端允许使用 io.quarkus.rest.client.reactive.Url 注解为每次调用覆盖基本 URL。

这是一个简单的例子

package org.acme.rest.client;

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

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.QueryParam;
import java.util.Set;

import io.quarkus.rest.client.reactive.Url;

@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {

    @GET
    @Path("/stream/{stream}")
    Set<Extension> getByStream(@Url String url, @PathParam("stream") String stream, @QueryParam("id") String id);
}

url 参数非 null 时,它将覆盖为客户端配置的基本 URL(默认基本 URL 配置仍然是强制性的)。

发送大型负载

如果使用以下类型之一,REST 客户端能够发送任意大的 HTTP 主体,而无需在内存中缓冲内容

  • InputStream

  • Multi<io.vertx.mutiny.core.buffer.Buffer>

此外,如果使用以下类型之一,客户端还可以发送任意大的文件

  • File

  • Path

获取其他响应属性

使用 RestResponse

如果您需要获取 HTTP 响应的更多属性,而不仅仅是正文,例如状态代码或标头,您可以使您的方法从方法返回 org.jboss.resteasy.reactive.RestResponse。一个例子可能如下所示

package org.acme.rest.client;

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

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.jboss.resteasy.reactive.RestQuery;
import org.jboss.resteasy.reactive.RestResponse;

import java.util.Set;

@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {

    @GET
    RestResponse<Set<Extension>> getByIdResponseProperties(@RestQuery String id);
}
您也可以使用 Jakarta REST 类型 Response,但它不是强类型到您的实体。

创建 Jakarta REST 资源

创建带有以下内容的 src/main/java/org/acme/rest/client/ExtensionsResource.java 文件

package org.acme.rest.client;

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

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.Set;

@Path("/extension")
public class ExtensionsResource {

    @RestClient (1)
    ExtensionsService extensionsService;


    @GET
    @Path("/id/{id}")
    public Set<Extension> id(String id) {
        return extensionsService.getById(id);
    }

    @GET
    @Path("/properties")
    public RestResponse<Set<Extension>> responseProperties(@RestQuery String id) {
        RestResponse<Set<Extension>> clientResponse = extensionsService.getByIdResponseProperties(id); (2)
        String contentType = clientResponse.getHeaderString("Content-Type");
        int status = clientResponse.getStatus();
        String setCookie = clientResponse.getHeaderString("Set-Cookie");
        Date lastModified = clientResponse.getLastModified();

        Log.infof("content-Type: %s status: %s Last-Modified: %s Set-Cookie: %s", contentType, status, lastModified,
                setCookie);

        return RestResponse.fromResponse(clientResponse);
    }
}

此列表中有两个有趣的部分

1 客户端存根使用 @RestClient 注解而不是通常的 CDI @Inject 注入
2 org.jboss.resteasy.reactive.RestResponse 用作直接从 RestClient 获取响应属性的有效方法,如 使用 RestResponse 中所述

创建配置

为了确定将进行 REST 调用的基本 URL,REST 客户端使用 application.properties 中的配置。属性的名称需要遵循一定的约定,最好在以下代码中显示

# Your configuration properties
quarkus.rest-client."org.acme.rest.client.ExtensionsService".url=https://stage.code.quarkus.io/api # (1)
1 拥有此配置意味着使用 org.acme.rest.client.ExtensionsService 执行的所有请求都将使用 https://stage.code.quarkus.io/api 作为基本 URL。使用上面的配置,使用值 io.quarkus:quarkus-rest-client 调用 ExtensionsServicegetById 方法将导致向 https://stage.code.quarkus.io/api/extensions?id=io.quarkus:quarkus-rest-client 发出 HTTP GET 请求。

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

为了方便配置,您可以使用 @RegisterRestClient configKey 属性,该属性允许使用与接口的完全限定名称不同的配置根。

@RegisterRestClient(configKey="extensions-api")
public interface ExtensionsService {
    [...]
}
# Your configuration properties
quarkus.rest-client.extensions-api.url=https://stage.code.quarkus.io/api
quarkus.rest-client.extensions-api.scope=jakarta.inject.Singleton

设置客户端的基本 URL 是 强制性的,但是 REST 客户端支持使用 @io.quarkus.rest.client.reactive.Url 注解为每次调用覆盖基本 URL。

信任所有证书并禁用 SSL 主机名验证

此属性集不应在生产中使用。

您可以配置特定 REST 客户端的 TLS 连接以信任所有证书并使用 tls 扩展禁用主机名验证。首先,您应该配置 tls 配置桶。

要信任所有证书

quarkus.tls.tls-disabled.trust-all=true

要禁用 SSL 主机名验证

quarkus.tls.tls-disabled.hostname-verification-algorithm=NONE

最后,让我们配置带有适当 tls 配置名称的 REST 客户端

quarkus.rest-client.extensions-api.tls-configuration-name=tls-disabled

HTTP/2 支持

默认情况下,HTTP/2 在 REST 客户端中禁用。如果要启用它,可以设置

// for all REST Clients:
quarkus.rest-client.http2=true
// or for a single REST Client:
quarkus.rest-client.extensions-api.http2=true

或者,您可以启用应用层协议协商 (alpn) TLS 扩展,客户端将协商要使用的 HTTP 版本,该版本与服务器兼容的版本一致。默认情况下,它将首先尝试使用 HTTP/2,如果未启用,则将使用 HTTP/1.1。如果要启用它,可以设置

quarkus.rest-client.alpn=true
// or for a single REST Client:
quarkus.rest-client.extensions-api.alpn=true

使用 QuarkusRestClientBuilder 以编程方式创建客户端

除了使用 @RegisterRestClient 注释客户端,并使用 @RestClient 注入客户端之外,您还可以以编程方式创建 REST 客户端。您可以使用 QuarkusRestClientBuilder 来执行此操作。

使用此方法,客户端接口可以如下所示

package org.acme.rest.client;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import java.util.Set;

@Path("/extensions")
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);
}

服务如下所示

package org.acme.rest.client;

import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.net.URI;
import java.util.Set;

@Path("/extension")
public class ExtensionsResource {

    private final ExtensionsService extensionsService;

    public ExtensionsResource() {
        extensionsService = QuarkusRestClientBuilder.newBuilder()
            .baseUri(URI.create("https://stage.code.quarkus.io/api"))
            .build(ExtensionsService.class);
    }

    @GET
    @Path("/id/{id}")
    public Set<Extension> id(String id) {
        return extensionsService.getById(id);
    }
}

QuarkusRestClientBuilder 接口是一个 Quarkus 特定的 API,用于以编程方式创建具有其他配置选项的客户端。否则,您也可以使用 Microprofile API 中的 RestClientBuilder 接口

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.RestClientBuilder;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.net.URI;
import java.util.Set;

@Path("/extension")
public class ExtensionsResource {

    private final ExtensionsService extensionsService;

    public ExtensionsResource() {
        extensionsService = RestClientBuilder.newBuilder()
            .baseUri(URI.create("https://stage.code.quarkus.io/api"))
            .build(ExtensionsService.class);
    }

    // ...
}

使用自定义 HTTP 选项

REST 客户端在内部使用 Vert.x HTTP 客户端 进行网络连接。REST 客户端扩展允许通过属性配置一些设置,例如

  • quarkus.rest-client.client-prefix.connect-timeout 用于配置连接超时(以毫秒为单位)。

  • quarkus.rest-client.client-prefix.max-redirects 用于限制重定向次数。

但是,Vert.x HTTP 客户端中有更多选项可用于配置连接。请参阅 此链接 中 Vert.x HTTP 客户端选项 API 中的所有选项。

要完全自定义 REST 客户端在内部使用的 Vert.x HTTP 客户端实例,您可以通过 CDI 提供自定义 HTTP 客户端选项实例,或者在以编程方式创建客户端时提供。

让我们看一个关于如何通过 CDI 提供 HTTP 客户端选项的示例

package org.acme.rest.client;

import jakarta.enterprise.inject.Produces;
import jakarta.ws.rs.ext.ContextResolver;

import io.vertx.core.http.HttpClientOptions;
import io.quarkus.arc.Unremovable;

@Provider
public class CustomHttpClientOptions implements ContextResolver<HttpClientOptions> {

    @Override
    public HttpClientOptions getContext(Class<?> aClass) {
        HttpClientOptions options = new HttpClientOptions();
        // ...
        return options;
    }
}

现在,所有 REST 客户端都将使用您的自定义 HTTP 客户端选项。

另一种方法是在以编程方式创建客户端时提供自定义 HTTP 客户端选项

package org.acme.rest.client;

import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.net.URI;
import java.util.Set;

import io.vertx.core.http.HttpClientOptions;

@Path("/extension")
public class ExtensionsResource {

    private final ExtensionsService extensionsService;

    public ExtensionsResource() {
        HttpClientOptions options = new HttpClientOptions();
        // ...

        extensionsService = QuarkusRestClientBuilder.newBuilder()
            .baseUri(URI.create("https://stage.code.quarkus.io/api"))
            .httpClientOptions(options) (1)
            .build(ExtensionsService.class);
    }

    // ...
}
1 如果通过 CDI 提供 HTTP 客户端选项,则客户端将使用注册的 HTTP 客户端选项。

重定向

HTTP 服务器可以通过发送状态代码以“3”开头的响应和一个包含要重定向到的 URL 的 HTTP 标头“Location”将响应重定向到另一个位置。当 REST 客户端从 HTTP 服务器收到重定向响应时,它不会自动向新位置执行另一个请求。我们可以通过添加“follow-redirects”属性在 REST 客户端中启用自动重定向

  • quarkus.rest-client.follow-redirects 用于为所有 REST 客户端启用重定向。

  • quarkus.rest-client.<client-prefix>.follow-redirects 用于为特定 REST 客户端启用重定向。

如果此属性为 true,则 REST 客户端将在从 HTTP 服务器收到重定向响应时执行新请求。

此外,我们可以使用属性“max-redirects”限制重定向次数。

一个重要的注意事项是,根据 RFC2616 规范,默认情况下,重定向仅对 GET 或 HEAD 方法发生。但是,在 REST 客户端中,您可以通过使用 @ClientRedirectHandler 注解、CDI 或在以编程方式创建客户端时提供自定义重定向处理程序,以启用 POST 或 PUT 方法的重定向,或遵循更复杂的逻辑。

让我们看一个关于如何使用 @ClientRedirectHandler 注解注册您自己的自定义重定向处理程序的示例

import jakarta.ws.rs.core.Response;

import io.quarkus.rest.client.reactive.ClientRedirectHandler;

@RegisterRestClient(configKey="extensions-api")
public interface ExtensionsService {
    @ClientRedirectHandler
    static URI alwaysRedirect(Response response) {
        if (Response.Status.Family.familyOf(response.getStatus()) == Response.Status.Family.REDIRECTION) {
            return response.getLocation();
        }

        return null;
    }
}

“alwaysRedirect”重定向处理程序仅由指定的 REST 客户端使用,在本例中为“ExtensionsService”客户端。

或者,您也可以通过 CDI 为所有 REST 客户端提供自定义重定向处理程序

import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ContextResolver;
import jakarta.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.client.handlers.RedirectHandler;

@Provider
public class AlwaysRedirectHandler implements ContextResolver<RedirectHandler> {

    @Override
    public RedirectHandler getContext(Class<?> aClass) {
        return response -> {
            if (Response.Status.Family.familyOf(response.getStatus()) == Response.Status.Family.REDIRECTION) {
                return response.getLocation();
            }
            // no redirect
            return null;
        };
    }
}

现在,所有 REST 客户端都将使用您的自定义重定向处理程序。

另一种方法是在以编程方式创建客户端时提供它

@Path("/extension")
public class ExtensionsResource {

    private final ExtensionsService extensionsService;

    public ExtensionsResource() {
        extensionsService = QuarkusRestClientBuilder.newBuilder()
            .baseUri(URI.create("https://stage.code.quarkus.io/api"))
            .register(AlwaysRedirectHandler.class) (1)
            .build(ExtensionsService.class);
    }

    // ...
}
1 如果通过 CDI 提供重定向处理程序,则客户端将使用注册的重定向处理程序。

更新测试

接下来,我们需要更新功能测试以反映对端点所做的更改。编辑 src/test/java/org/acme/rest/client/ExtensionsResourceTest.java 文件并将测试的内容更改为

package org.acme.rest.client;

import io.quarkus.test.junit.QuarkusTest;

import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.greaterThan;

@QuarkusTest
public class ExtensionsResourceTest {

    @Test
    public void testExtensionsIdEndpoint() {
        given()
            .when().get("/extension/id/io.quarkus:quarkus-rest-client")
            .then()
            .statusCode(200)
            .body("$.size()", is(1),
                "[0].id", is("io.quarkus:quarkus-rest-client"),
                "[0].name", is("REST Client"),
                "[0].keywords.size()", greaterThan(1),
                "[0].keywords", hasItem("rest-client"));
    }
}

上面的代码使用了 REST Assuredjson-path 功能。

异步支持

要充分利用客户端的反应式特性,您可以使用 REST 客户端扩展的非阻塞版本,该版本支持 CompletionStageUni。让我们通过在我们的 ExtensionsService REST 接口中添加一个 getByIdAsync 方法来实际体验一下。代码应如下所示

package org.acme.rest.client;

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

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import java.util.Set;
import java.util.concurrent.CompletionStage;

@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);

    @GET
    CompletionStage<Set<Extension>> getByIdAsync(@QueryParam("id") String id);
}

打开 src/main/java/org/acme/rest/client/ExtensionsResource.java 文件并使用以下内容更新它

package org.acme.rest.client;

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

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.Set;
import java.util.concurrent.CompletionStage;

@Path("/extension")
public class ExtensionsResource {

    @RestClient
    ExtensionsService extensionsService;


    @GET
    @Path("/id/{id}")
    public Set<Extension> id(String id) {
        return extensionsService.getById(id);
    }

    @GET
    @Path("/id-async/{id}")
    public CompletionStage<Set<Extension>> idAsync(String id) {
        return extensionsService.getByIdAsync(id);
    }
}

请注意,由于现在调用是非阻塞的,因此 idAsync 方法将在事件循环上调用,即不会卸载到工作池线程,从而减少硬件资源利用率。有关更多详细信息,请参阅 Quarkus REST 执行模型

要测试异步方法,请在 ExtensionsResourceTest 中添加以下测试方法

@Test
public void testExtensionIdAsyncEndpoint() {
    given()
        .when().get("/extension/id-async/io.quarkus:quarkus-rest-client")
        .then()
        .statusCode(200)
        .body("$.size()", is(1),
            "[0].id", is("io.quarkus:quarkus-rest-client"),
            "[0].name", is("REST Client"),
            "[0].keywords.size()", greaterThan(1),
            "[0].keywords", hasItem("rest-client"));
}

Uni 版本非常相似

package org.acme.rest.client;

import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import java.util.Set;

@Path("/extensions")
@RegisterRestClient(configKey = "extensions-api")
public interface ExtensionsService {

    // ...

    @GET
    Uni<Set<Extension>> getByIdAsUni(@QueryParam("id") String id);
}

ExtensionsResource 变为

package org.acme.rest.client;

import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.inject.RestClient;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.Set;

@Path("/extension")
public class ExtensionsResource {

    @RestClient
    ExtensionsService extensionsService;


    // ...

    @GET
    @Path("/id-uni/{id}")
    public Uni<Set<Extension>> idUni(String id) {
        return extensionsService.getByIdAsUni(id);
    }
}
Mutiny

前面的代码段使用了 Mutiny 反应式类型。如果您不熟悉 Mutiny,请查看 Mutiny - 一个直观的反应式编程库

当返回 Uni 时,每次 订阅 都会调用远程服务。这意味着您可以通过重新订阅 Uni 来重新发送请求,或者使用如下所示的 retry

@RestClient ExtensionsService extensionsService;

// ...

extensionsService.getByIdAsUni(id)
    .onFailure().retry().atMost(10);

如果您使用 CompletionStage,则需要调用服务的方法才能重试。这种差异来自 Mutiny 的惰性方面及其订阅协议。有关此的更多详细信息,请参见 Mutiny 文档

服务器发送事件 (SSE) 支持

只需将结果类型声明为 io.smallrye.mutiny.Multi 即可使用 SSE 事件。

最简单的例子是

package org.acme.rest.client;

import io.smallrye.mutiny.Multi;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@Path("/sse")
@RegisterRestClient(configKey = "some-api")
public interface SseClient {
     @GET
     @Produces(MediaType.SERVER_SENT_EVENTS)
     Multi<String> get();
}

流式传输 SSE 结果涉及的所有 IO 都以非阻塞方式完成。

结果不限于字符串 - 例如,当服务器为每个事件返回 JSON 负载时,Quarkus 会自动将其反序列化为 Multi 中使用的泛型类型。

用户还可以使用 org.jboss.resteasy.reactive.client.SseEvent 类型访问整个 SSE 事件。

一个简单的示例,其中事件负载为 Long 值,如下所示

package org.acme.rest.client;

import io.smallrye.mutiny.Uni;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.client.SseEvent;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;

@Path("/sse")
@RegisterRestClient(configKey = "some-api")
public interface SseClient {
     @GET
     @Produces(MediaType.SERVER_SENT_EVENTS)
     Multi<SseEvent<Long>> get();
}

过滤掉事件

有时,SSE 事件流可能包含一些不应由客户端返回的事件 - 例如,让服务器发送心跳事件以保持底层 TCP 连接打开。REST 客户端支持通过提供 @org.jboss.resteasy.reactive.client.SseEventFilter 过滤掉此类事件。

以下是过滤掉心跳事件的示例

package org.acme.rest.client;

import io.smallrye.mutiny.Uni;
import java.util.function.Predicate;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.client.SseEvent;
import org.jboss.resteasy.reactive.client.SseEventFilter;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;

@Path("/sse")
@RegisterRestClient(configKey = "some-api")
public interface SseClient {

     @GET
     @Produces(MediaType.SERVER_SENT_EVENTS)
     @SseEventFilter(HeartbeatFilter.class)
     Multi<SseEvent<Long>> get();


     class HeartbeatFilter implements Predicate<SseEvent<String>> {

        @Override
        public boolean test(SseEvent<String> event) {
            return !"heartbeat".equals(event.id());
        }
     }
}

自定义标头支持

您可以通过以下几种方式为 REST 调用指定自定义标头

  • 通过使用 @RegisterClientHeaders 注解注册 ClientHeadersFactoryReactiveClientHeadersFactory

  • 通过使用 QuarkusRestClientBuilder.clientHeadersFactory(factory) 方法以编程方式注册 ClientHeadersFactoryReactiveClientHeadersFactory

  • 通过使用 @ClientHeaderParam 指定标头的值

  • 通过使用 @HeaderParam 指定标头的值

以下代码演示了如何使用这些技术中的每一种

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.annotation.ClientHeaderParam;
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import java.util.Set;
import io.quarkus.rest.client.reactive.NotBody;

@Path("/extensions")
@RegisterRestClient
@RegisterClientHeaders(RequestUUIDHeaderFactory.class) (1)
@ClientHeaderParam(name = "my-header", value = "constant-header-value") (2)
@ClientHeaderParam(name = "computed-header", value = "{org.acme.rest.client.Util.computeHeader}") (3)
public interface ExtensionsService {

    @GET
    @ClientHeaderParam(name = "header-from-properties", value = "${header.value}") (4)
    @ClientHeaderParam(name = "header-from-method-param", value = "Bearer {token}") (5)
    Set<Extension> getById(@QueryParam("id") String id, @HeaderParam("jaxrs-style-header") String headerValue, @NotBody String token); (6)
}
1 每个类只能有一个 ClientHeadersFactory。使用它,您不仅可以添加自定义标头,还可以转换现有标头。有关工厂的示例,请参见下面的 RequestUUIDHeaderFactory 类。
2 @ClientHeaderParam 可以在客户端接口和方法上使用。它可以指定一个常量标头值……​
3 ……以及应该计算标头值的方法的名称。它可以是此接口中的静态方法或默认方法。该方法可以不带参数、带一个 String 参数或带一个 io.quarkus.rest.client.reactive.ComputedParamContext 参数(对于需要根据方法参数计算标头的代码非常有用,并且自然地补充了 @io.quarkus.rest.client.reactive.NotBody)。
4 ……以及来自您的应用程序配置的值
5 ……或者甚至是逐字文本、方法参数(按名称引用)、配置值(如前所述)和方法调用(如前所述)的任何混合
6 ……或作为普通的 Jakarta REST @HeaderParam 注释参数

当使用 Kotlin 时,如果要利用默认方法,则需要将 Kotlin 编译器配置为使用 Java 的默认接口功能。有关更多详细信息,请参见 this

ClientHeadersFactory 可以如下所示

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.UUID;

@ApplicationScoped
public class RequestUUIDHeaderFactory implements ClientHeadersFactory {

    @Override
    public MultivaluedMap<String, String> update(MultivaluedMap<String, String> incomingHeaders, MultivaluedMap<String, String> clientOutgoingHeaders) {
        MultivaluedMap<String, String> result = new MultivaluedHashMap<>();
        result.add("X-request-uuid", UUID.randomUUID().toString());
        return result;
    }
}

如您在上面的示例中所见,您可以通过使用范围定义注解(例如 @Singleton@ApplicationScoped 等)对其进行注释,从而使您的 ClientHeadersFactory 实现成为 CDI bean。

要指定 ${header.value} 的值,只需将以下内容放在您的 application.properties

header.value=value of the header

此外,还有一种 ClientHeadersFactory 的反应式风格,允许进行阻塞操作。例如

package org.acme.rest.client;

import io.smallrye.mutiny.Uni;

import org.eclipse.microprofile.rest.client.ext.ClientHeadersFactory;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.MultivaluedMap;
import java.util.UUID;

@ApplicationScoped
public class GetTokenReactiveClientHeadersFactory extends ReactiveClientHeadersFactory {

    @Inject
    Service service;

    @Override
    public Uni<MultivaluedMap<String, String>> getHeaders(
            MultivaluedMap<String, String> incomingHeaders,
            MultivaluedMap<String, String> clientOutgoingHeaders) {
        return Uni.createFrom().item(() -> {
            MultivaluedHashMap<String, String> newHeaders = new MultivaluedHashMap<>();
            // perform blocking call
            newHeaders.add(HEADER_NAME, service.getToken());
            return newHeaders;
        });
    }
}

当使用 HTTP Basic Auth 时,@io.quarkus.rest.client.reactive.ClientBasicAuth 注解提供了一种更简单的配置必要 Authorization 标头的方法。

一个非常简单的例子是

@ClientBasicAuth(username = "${service.username}", password = "${service.password}")
public interface SomeClient {

}

其中 service.usernameservice.password 是必须在运行时设置为允许访问被调用服务的用户名和密码的配置属性。

默认标头工厂

也可以使用 @RegisterClientHeaders 注解而不指定任何自定义工厂。在这种情况下,将使用 DefaultClientHeadersFactoryImpl 工厂。如果您从 REST 资源进行 REST 客户端调用,此工厂会将 org.eclipse.microprofile.rest.client.propagateHeaders 配置属性中列出的所有标头从资源请求传播到客户端请求。单个标头名称用逗号分隔。

@Path("/extensions")
@RegisterRestClient
@RegisterClientHeaders
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);

    @GET
    CompletionStage<Set<Extension>> getByIdAsync(@QueryParam("id") String id);
}
org.eclipse.microprofile.rest.client.propagateHeaders=Authorization,Proxy-Authorization

自定义请求

REST 客户端支持通过过滤器进一步自定义要发送到服务器的最终请求。过滤器必须实现接口 ClientRequestFilterResteasyReactiveClientRequestFilter

自定义请求的一个简单示例是添加自定义标头

@Provider
public class TestClientRequestFilter implements ClientRequestFilter {

    @Override
    public void filter(ClientRequestContext requestContext) {
        requestContext.getHeaders().add("my_header", "value");
    }
}

接下来,您可以使用 @RegisterProvider 注解注册您的过滤器

@Path("/extensions")
@RegisterProvider(TestClientRequestFilter.class)
public interface ExtensionsService {

    // ...
}

或以编程方式使用 .register() 方法

QuarkusRestClientBuilder.newBuilder()
    .register(TestClientRequestFilter.class)
    .build(ExtensionsService.class)

在过滤器中注入 jakarta.ws.rs.ext.Providers 实例

当我们需要在当前客户端中查找提供程序实例时,jakarta.ws.rs.ext.Providers 非常有用。

我们可以从请求上下文获取过滤器中的 Providers 实例,如下所示

@Provider
public class TestClientRequestFilter implements ClientRequestFilter {

    @Override
    public void filter(ClientRequestContext requestContext) {
        Providers providers = ((ResteasyReactiveClientRequestContext) requestContext).getProviders();
        // ...
    }
}

或者,您可以实现 ResteasyReactiveClientRequestFilter 接口而不是 ClientRequestFilter 接口,该接口将直接提供 ResteasyReactiveClientRequestContext 上下文

@Provider
public class TestClientRequestFilter implements ResteasyReactiveClientRequestFilter {

    @Override
    public void filter(ResteasyReactiveClientRequestFilter requestContext) {
        Providers providers = requestContext.getProviders();
        // ...
    }
}

在 REST Client Jackson 中自定义 ObjectMapper

REST 客户端支持添加自定义 ObjectMapper,该 ObjectMapper 仅使用注解 @ClientObjectMapper 用于客户端。

一个简单的例子是通过执行以下操作为 REST Client Jackson 扩展提供自定义 ObjectMapper

@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);

    @ClientObjectMapper (1)
    static ObjectMapper objectMapper(ObjectMapper defaultObjectMapper) { (2)
        return defaultObjectMapper.copy() (3)
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .disable(DeserializationFeature.UNWRAP_ROOT_VALUE);
    }
}
1 该方法必须使用 @ClientObjectMapper 注释。
2 它必须是静态方法。此外,参数 defaultObjectMapper 将通过 CDI 解析。如果未找到,它将在运行时引发异常。
3 在此示例中,我们正在创建默认对象映射器的副本。您应该 永远不要 修改默认对象映射器,而是创建一个副本。

异常处理

MicroProfile REST 客户端规范引入了 org.eclipse.microprofile.rest.client.ext.ResponseExceptionMapper,其目的是将 HTTP 响应转换为异常。

为上面讨论的 ExtensionsService 实现此类 ResponseExceptionMapper 的一个简单示例是

public class MyResponseExceptionMapper implements ResponseExceptionMapper<RuntimeException> {

    @Override
    public RuntimeException toThrowable(Response response) {
        if (response.getStatus() == 500) {
            throw new RuntimeException("The remote service responded with HTTP 500");
        }
        return null;
    }
}

ResponseExceptionMapper 还定义了 getPriority 方法,该方法用于确定将调用 ResponseExceptionMapper 实现的优先级(getPriority 的值较低的实现将首先调用)。如果 toThrowable 返回异常,则将引发该异常。如果返回 null,则将调用链中下一个 ResponseExceptionMapper 的实现(如果有)。

上述编写的类不会自动被任何 REST 客户端使用。要使其可用于应用程序的每个 REST 客户端,该类需要使用 @Provider 注释(只要未将 quarkus.rest-client.provider-autodiscovery 设置为 false)。或者,如果异常处理类应仅应用于特定的 REST 客户端接口,您可以注释带有 @RegisterProvider(MyResponseExceptionMapper.class) 的接口,或使用适当的 quarkus.rest-client 配置组的 providers 属性使用配置注册它。

使用 @ClientExceptionMapper

转换 400 或更高的 HTTP 响应代码的一种更简单的方法是使用 @ClientExceptionMapper 注解。

对于上面定义的 ExtensionsService REST 客户端接口,@ClientExceptionMapper 的一个示例用法是

@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {

    @GET
    Set<Extension> getById(@QueryParam("id") String id);

    @GET
    CompletionStage<Set<Extension>> getByIdAsync(@QueryParam("id") String id);

    @ClientExceptionMapper
    static RuntimeException toException(Response response) {
        if (response.getStatus() == 500) {
            return new RuntimeException("The remote service responded with HTTP 500");
        }
        return null;
    }
}

自然,此处理是每个 REST 客户端。如果未设置 priority 属性,则 @ClientExceptionMapper 使用默认优先级,并且适用依次调用所有处理程序的正常规则。

使用 @ClientExceptionMapper 注释的方法还可以采用 java.lang.reflect.Method 参数,如果异常映射代码需要知道被调用并导致异常映射代码参与的 REST 客户端方法,这将非常有用。

在异常映射器中使用 @Blocking 注解

在需要使用 InputStream 作为 REST 客户端方法的返回类型的情况下(例如,当需要读取大量数据时)

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

    @GET
    InputStream get();
}

这将按预期工作,但是如果您尝试在自定义异常映射器中读取此 InputStream 对象,您将收到 BlockingNotAllowedException 异常。这是因为默认情况下,ResponseExceptionMapper 类在事件循环线程执行程序上运行 - 这不允许执行 IO 操作。

要使您的异常映射器阻塞,您可以使用 @Blocking 注解注释异常映射器

@Provider
@Blocking (1)
public class MyResponseExceptionMapper implements ResponseExceptionMapper<RuntimeException> {

    @Override
    public RuntimeException toThrowable(Response response) {
        if (response.getStatus() == 500) {
            response.readEntity(String.class); (2)
            return new RuntimeException("The remote service responded with HTTP 500");
        }
        return null;
    }
}
1 使用 @Blocking 注解,MyResponseExceptionMapper 异常映射器将在工作线程池中执行。
2 现在允许读取实体,因为我们在工作线程池上执行映射器。

请注意,当使用 @ClientExceptionMapper 时,您还可以使用 @Blocking 注解

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

    @GET
    InputStream get();

    @ClientExceptionMapper
    @Blocking
    static RuntimeException toException(Response response) {
        if (response.getStatus() == 500) {
            response.readEntity(String.class);
            return new RuntimeException("The remote service responded with HTTP 500");
        }
        return null;
    }
}

禁用默认映射器

按照 REST 客户端规范的要求,包含一个默认异常映射器,当 HTTP 状态代码高于 400 时会引发异常。虽然当客户端返回常规对象时此行为很好,但是当客户端需要返回 jakarta.ws.rs.core.Response 时,这非常不直观(目的是允许调用方决定如何处理 HTTP 状态代码)。

因此,REST 客户端包含一个名为 disable-default-mapper 的属性,该属性可用于在以声明方式使用 REST 客户端时禁用默认映射器。

例如,对于像这样的客户端

    @Path("foo")
    @RegisterRestClient(configKey = "bar")
    public interface Client {
        @GET
        Response get();
    }

可以通过设置 quarkus.rest-client.bar.disable-default-mapper=true 来禁用默认异常映射器,从而为使用键 bar 配置的 REST 客户端禁用异常映射器。

当使用编程方法创建 REST 客户端时,QuarkusRestClientBuilder 提供了一个名为 disableDefaultMapper 的方法,该方法提供相同的功能。

Multipart Form 支持

发送 Multipart 消息

REST 客户端允许将数据作为 multipart 表单发送。这样,您可以例如高效地发送文件。

要将数据作为 multipart 表单发送,您可以只使用常规的 @RestForm(或 @FormParam)注解

    @POST
    @Path("/binary")
    String sendMultipart(@RestForm File file, @RestForm String otherField);

指定为 FilePathbyte[]BufferFileUpload 的参数将作为文件发送,并且默认为 application/octet-stream MIME 类型。其他 @RestForm 参数默认为 text/plain MIME 类型。您可以使用 @PartType 注解覆盖这些默认值。

当然,您也可以将这些参数分组到一个包含类中

    public static class Parameters {
        @RestForm
        File file;

        @RestForm
        String otherField;
    }

    @POST
    @Path("/binary")
    String sendMultipart(Parameters parameters);

任何类型为 FilePathbyte[]BufferFileUpload@RestForm 参数,以及任何使用 @PartType 注释的参数,都会自动在方法上暗示 @Consumes(MediaType.MULTIPART_FORM_DATA),如果没有 @Consumes 存在。

如果有不是 multipart 隐含的 @RestForm 参数,则暗示 @Consumes(MediaType.APPLICATION_FORM_URLENCODED)

表单数据的编码方式有几种。默认情况下,REST 客户端使用 RFC1738。您可以通过在客户端级别指定模式来覆盖它,方法是将 io.quarkus.rest.client.multipart-post-encoder-mode RestBuilder 属性设置为 HttpPostRequestEncoder.EncoderMode 的选定值,或者通过在 application.properties 中指定 quarkus.rest-client.multipart-post-encoder-mode。请注意,后者仅适用于使用 @RegisterRestClient 注解创建的客户端。所有可用的模式都在 Netty 文档中描述。

您还可以通过指定 @PartType 注解来发送 JSON multipart

    public static class Person {
        public String firstName;
        public String lastName;
    }

    @POST
    @Path("/json")
    String sendMultipart(@RestForm @PartType(MediaType.APPLICATION_JSON) Person person);

以编程方式创建 Multipart 表单

在需要以编程方式构建 multipart 内容的情况下,REST 客户端提供了 ClientMultipartForm,可以在 REST 客户端中使用,如下所示

public interface MultipartService {

  @POST
  @Path("/multipart")
  @Consumes(MediaType.MULTIPART_FORM_DATA)
  @Produces(MediaType.APPLICATION_JSON)
  Map<String, String> multipart(ClientMultipartForm dataParts);
}

有关此类和支持的方法的更多信息,请参见 ClientMultipartForm 的 javadoc。

将接收到的 multipart 对象转换为客户端请求

创建 ClientMultipartForm 的一个很好的例子是从服务器的 MultipartFormDataInput 创建的(它表示 Quarkus REST 接收到的 multipart 请求) - 目的是在允许任意修改的同时向下游传播请求

public ClientMultipartForm buildClientMultipartForm(MultipartFormDataInput inputForm) (1)
    throws IOException {
  ClientMultipartForm multiPartForm = ClientMultipartForm.create(); (2)
  for (Entry<String, Collection<FormValue>> attribute : inputForm.getValues().entrySet()) {
    for (FormValue fv : attribute.getValue()) {
      if (fv.isFileItem()) {
        final FileItem fi = fv.getFileItem();
        String mediaType = Objects.toString(fv.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE),
            MediaType.APPLICATION_OCTET_STREAM);
        if (fi.isInMemory()) {
          multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(),
              Buffer.buffer(IOUtils.toByteArray(fi.getInputStream())), mediaType); (3)
        } else {
          multiPartForm.binaryFileUpload(attribute.getKey(), fv.getFileName(),
              fi.getFile().toString(), mediaType); (4)
        }
      } else {
        multiPartForm.attribute(attribute.getKey(), fv.getValue(), fv.getFileName()); (5)
      }
    }
  }
  return multiPartForm;
}
1 MultipartFormDataInput 是表示接收到的 multipart 请求的 Quarkus REST(服务器)类型。
2 创建了 ClientMultipartForm
3 为表示内存文件属性的请求属性创建了 FileItem 属性
4 为表示保存在文件系统上的文件属性的请求属性创建了 FileItem 属性
5 如果不是 FileItem,则非文件属性直接添加到 ClientMultipartForm

以类似的方式,如果接收到的服务器 multipart 请求已知并且看起来像

public class Request { (1)

  @RestForm("files")
  @PartType(MediaType.APPLICATION_OCTET_STREAM)
  List<FileUpload> files;

  @RestForm("jsonPayload")
  @PartType(MediaType.TEXT_PLAIN)
  String jsonPayload;
}

可以轻松地创建 ClientMultipartForm,如下所示

public ClientMultipartForm buildClientMultipartForm(Request request) { (1)
  ClientMultipartForm multiPartForm = ClientMultipartForm.create();
  multiPartForm.attribute("jsonPayload", request.getJsonPayload(), "jsonPayload"); (2)
  request.getFiles().forEach(fu -> {
    multiPartForm.fileUpload(fu); (3)
  });
  return multiPartForm;
}
1 表示服务器部件接受的请求的 Request
2 jsonPayload 属性直接添加到 ClientMultipartForm
3 从请求的 FileUpload 创建 fileUpload

当发送使用相同名称的 multipart 数据时,如果客户端和服务器不使用相同的 multipart 编码器模式,则可能会出现问题。默认情况下,REST 客户端使用 RFC1738,但根据具体情况,客户端可能需要配置为 HTML5RFC3986 模式。

可以通过 quarkus.rest-client.multipart-post-encoder-mode 属性实现此配置。

接收 Multipart 消息

REST 客户端还支持接收 multipart 消息。与发送一样,要解析 multipart 响应,您需要创建一个描述响应数据的类,例如

public class FormDto {
    @RestForm (1)
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    public File file;

    @FormParam("otherField") (2)
    @PartType(MediaType.TEXT_PLAIN)
    public String textProperty;
}
1 使用速记 @RestForm 注解将字段作为 multipart 表单的一部分
2 也可以使用标准的 @FormParam。它允许覆盖 multipart 部分的名称。

然后,创建一个与调用对应的接口方法,并使其返回 FormDto

    @GET
    @Produces(MediaType.MULTIPART_FORM_DATA)
    @Path("/get-file")
    FormDto data receiveMultipart();

目前,multipart 响应支持受到以下限制

  • multipart 响应中发送的文件只能解析为 FilePathFileDownload

  • 响应类型的每个字段都必须使用 @PartType 注释 - 没有此注释的字段将被忽略。

REST Client 需要预先知道用作 multipart 返回类型的类。 如果您有一个生成 multipart/form-data 的接口方法,则会自动发现返回类型。 但是,如果您打算使用 ClientBuilder API 将响应解析为 multipart,则需要使用 @MultipartForm 注释您的 DTO 类。

您下载的文件不会自动删除,并且会占用大量磁盘空间。请考虑在完成使用后删除这些文件。

Multipart mixed / OData 用法

应用程序必须使用称为 OData 的特殊协议与企业系统(如 CRM 系统)交互,这并不罕见。 此协议本质上使用自定义 HTTP Content-Type,这需要一些粘合代码才能与 REST Client 一起使用(创建 body 完全取决于应用程序 - REST Client 无法提供太多帮助)。

一个例子如下所示

@Path("/crm")
@RegisterRestClient
public interface CRMService {

    @POST
    @ClientHeaderParam(name = "Content-Type", value = "{calculateContentType}")  (1)
    String performBatch(@HeaderParam("Authorization") String accessToken, @NotBody String batchId, String body); (2)

    default String calculateContentType(ComputedParamContext context) {
        return "multipart/mixed;boundary=batch_" + context.methodParameters().get(1).value(); (3)
    }
}

该代码使用以下部分

1 @ClientHeaderParam(name = "Content-Type", value = "{calculateContentType}"),它确保通过调用接口的 calculateContentType 默认方法来创建 Content-Type 标头。
2 上述参数需要使用 @NotBody 注释,因为它仅用于辅助 HTTP 标头的构造。
3 context.methodParameters().get(1).value(),它允许 calculateContentType 方法获取传递给 REST Client 方法的正确方法参数。

如前所述,body 参数需要由应用程序代码正确地构建,以符合服务的需求。

接收压缩消息

REST Client 还支持接收使用 GZIP 压缩的消息,并且可以通过配置启用。 启用此功能后,如果服务器返回包含标头 Content-Encoding: gzip 的响应,REST Client 将自动解码内容并继续进行消息处理。

一个配置示例可能是

# global configuration is used for all clients
quarkus.rest-client.enable-compression=true

# per-client configuration overrides the global settings for a specific client
quarkus.rest-client.my-client.enable-compression=true

如果没有设置 REST Client 特定的属性,REST Client 将回退到 Quarkus 范围内的 quarkus.http.enable-compression 配置属性(默认为 false)。

代理支持

REST Client 支持通过代理发送请求。 它遵循 JVM 设置,但也允许指定

  • 全局客户端代理设置,使用 quarkus.rest-client.proxy-addressquarkus.rest-client.proxy-userquarkus.rest-client.proxy-passwordquarkus.rest-client.non-proxy-hosts

  • 每个客户端的代理设置,使用 quarkus.rest-client.<my-client>.proxy-address 等。这些仅应用于使用 CDI 注入的客户端,即使用 @RegisterRestClient 创建的客户端。

如果在客户端级别设置了 proxy-address,则客户端使用其特定的代理设置。 没有代理设置从全局配置或 JVM 属性传播。

如果未为客户端设置 proxy-address,但在全局级别设置了,则客户端使用全局设置。 否则,客户端使用 JVM 设置。

设置代理的配置示例

# global proxy configuration is used for all clients
quarkus.rest-client.proxy-address=localhost:8182
quarkus.rest-client.proxy-user=<proxy user name>
quarkus.rest-client.proxy-password=<proxy password>
quarkus.rest-client.non-proxy-hosts=example.com

# per-client configuration overrides the global settings for a specific client
quarkus.rest-client.my-client.proxy-address=localhost:8183
quarkus.rest-client.my-client.proxy-user=<proxy user name>
quarkus.rest-client.my-client.proxy-password=<proxy password>
quarkus.rest-client.my-client.url=...
MicroProfile REST Client 规范不允许设置代理凭据。 为了以编程方式指定代理用户名和代理密码,您需要将 RestClientBuilder 强制转换为 RestClientBuilderImpl

开发模式下的本地代理

当在开发模式下使用 REST Client 时,Quarkus 能够启动一个直通代理,可以用作 Wireshark(或类似工具)的目标,以便捕获来自 REST Client 的所有流量(当 REST Client 用于 HTTPS 服务时,这确实有意义)。

要启用此功能,只需为与需要代理的客户端对应的 configKey 设置 enable-local-proxy 配置选项即可。 例如

quarkus.rest-client.my-client.enable-local-proxy=true

当 REST Client 不使用配置键时(例如,当它通过 QuarkusRestClientBuilder 以编程方式创建时),可以使用类名。 例如

quarkus.rest-client."org.acme.SomeClient".enable-local-proxy=true

可以在启动日志中找到代理正在侦听的端口。 一个示例条目是

Started HTTP proxy server on https://:38227 for REST Client 'org.acme.SomeClient'

打包并运行应用程序

使用以下命令运行应用程序

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

您应该看到一个 JSON 对象,其中包含有关此扩展的一些基本信息。

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

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

记录流量

REST Client 可以记录它发送的请求和接收的响应。 要启用日志记录,请将 quarkus.rest-client.logging.scope 属性添加到您的 application.properties 并将其设置为

  • request-response 以记录请求和响应内容,或

  • all 也可以启用底层库的低级别日志记录。

由于 HTTP 消息可能具有较大的正文,因此我们限制了记录的正文字符数。 默认限制为 100,您可以通过指定 quarkus.rest-client.logging.body-limit 来更改它。

REST Client 使用 DEBUG 级别记录流量,并且不会更改记录器属性。 您可能需要调整您的记录器配置才能使用此功能。

这些配置属性全局适用于由 CDI 注入的所有客户端。 如果您想为特定的声明式客户端配置日志记录,您应该通过指定命名的“client”属性来完成,也称为 quarkus.rest-client."client".logging.* 属性。

一个日志记录配置示例

quarkus.rest-client.logging.scope=request-response
quarkus.rest-client.logging.body-limit=50

quarkus.rest-client.extensions-api.scope=all

quarkus.log.category."org.jboss.resteasy.reactive.client.logging".level=DEBUG
quarkus.log.console.level=DEBUG

REST Client 使用默认的 ClientLogger 实现,该实现可以换成自定义实现。

当使用 QuarkusRestClientBuilder 以编程方式设置客户端时,ClientLogger 通过 clientLogger 方法设置。

对于使用 @RegisterRestClient 的声明式客户端,只需提供一个实现 ClientLogger 的 CDI bean 就足以让该客户端使用该记录器。

为测试模拟客户端

如果您使用使用 @RestClient 注释注入的客户端,您可以轻松地为测试模拟它。 您可以使用 Mockito 的 @InjectMockQuarkusMock 来完成。

本节介绍如何用模拟替换您的客户端。 如果您想更深入地了解模拟在 Quarkus 中的工作方式,请参阅有关 模拟 CDI bean 的博客文章。

当使用 @QuarkusIntegrationTest 时,模拟不起作用。

假设您有以下客户端

package io.quarkus.it.rest.client.main;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

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


@Path("/")
@RegisterRestClient
public interface Client {
    @GET
    String get();
}

使用 InjectMock 模拟

为测试模拟客户端的最简单方法是使用 Mockito 和 @InjectMock

首先,将以下依赖项添加到您的应用程序

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5-mockito</artifactId>
    <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-junit5-mockito")

然后,在您的测试中,您可以简单地使用 @InjectMock 来创建和注入一个模拟

package io.quarkus.it.rest.client.main;

import static org.mockito.Mockito.when;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class InjectMockTest {

    @InjectMock
    @RestClient
    Client mock;

    @BeforeEach
    public void setUp() {
        when(mock.get()).thenReturn("MockAnswer");
    }

    @Test
    void doTest() {
        // ...
    }
}

使用 QuarkusMock 模拟

如果 Mockito 不能满足您的需求,您可以使用 QuarkusMock 以编程方式创建一个模拟,例如

package io.quarkus.it.rest.client.main;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusMock;
import io.quarkus.test.junit.QuarkusTest;

@QuarkusTest
public class QuarkusMockTest {

    @BeforeEach
    public void setUp() {
        Client customMock = new Client() { (1)
            @Override
            public String get() {
                return "MockAnswer";
            }
        };
        QuarkusMock.installMockForType(customMock, Client.class, RestClient.LITERAL); (2)
    }
    @Test
    void doTest() {
        // ...
    }
}
1 这里我们使用手动创建的客户端接口实现来替换实际的 Client
2 请注意,RestClient.LITERAL 必须作为 installMockForType 方法的最后一个参数传递。

为测试使用模拟 HTTP 服务器

设置一个模拟 HTTP 服务器来运行测试是一种常见的测试模式。 此类服务器的示例有 WiremockHoverfly。 在本节中,我们将演示如何利用 Wiremock 来测试上面开发的 ExtensionsService

首先,需要将 Wiremock 添加为测试依赖项。 对于 Maven 项目,这将如下进行

pom.xml
<dependency>
    <groupId>org.wiremock</groupId>
    <artifactId>wiremock</artifactId>
    <scope>test</scope>
    <version>${wiremock.version}</version> (1)
</dependency>
1 使用正确的 Wiremock 版本。所有可用版本都可以在 这里 找到。
build.gradle
testImplementation("org.wiremock:wiremock:$wiremockVersion") (1)
1 使用正确的 Wiremock 版本。所有可用版本都可以在 这里 找到。

在 Quarkus 测试中,当需要在运行 Quarkus 测试之前启动某些服务时,我们使用 @io.quarkus.test.common.QuarkusTestResource 注释来指定一个 io.quarkus.test.common.QuarkusTestResourceLifecycleManager,它可以启动服务并提供 Quarkus 将使用的配置值。

有关 @QuarkusTestResource 的更多详细信息,请参阅 文档的这一部分

让我们创建一个名为 WiremockExtensionsQuarkusTestResourceLifecycleManager 实现,如下所示

package org.acme.rest.client;

import java.util.Map;

import com.github.tomakehurst.wiremock.WireMockServer;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

import static com.github.tomakehurst.wiremock.client.WireMock.*; (1)

public class WireMockExtensions implements QuarkusTestResourceLifecycleManager {  (2)

    private WireMockServer wireMockServer;

    @Override
    public Map<String, String> start() {
        wireMockServer = new WireMockServer();
        wireMockServer.start(); (3)

        wireMockServer.stubFor(get(urlEqualTo("/extensions?id=io.quarkus:quarkus-rest-client"))   (4)
                .willReturn(aResponse()
                        .withHeader("Content-Type", "application/json")
                        .withBody(
                            "[{" +
                            "\"id\": \"io.quarkus:quarkus-rest-client\"," +
                            "\"name\": \"REST Client\"" +
                            "}]"
                        )));

        wireMockServer.stubFor(get(urlMatching(".*")).atPriority(10).willReturn(aResponse().proxiedFrom("https://stage.code.quarkus.io/api")));   (5)

        return Map.of("quarkus.rest-client.\"org.acme.rest.client.ExtensionsService\".url", wireMockServer.baseUrl()); (6)
    }

    @Override
    public void stop() {
        if (null != wireMockServer) {
            wireMockServer.stop();  (7)
        }
    }
}
1 静态导入 Wiremock 包中的方法可以更容易地读取测试。
2 start 方法由 Quarkus 在任何测试运行之前调用,并返回一个适用于测试执行期间的配置属性的 Map
3 启动 Wiremock。
4 通过返回特定的 canned 响应来配置 Wiremock 以 stub 对 /extensions?id=io.quarkus:quarkus-rest-client 的调用。
5 所有未 stub 的 HTTP 调用都通过调用实际服务来处理。 这是为了演示目的而完成的,因为这在实际测试中通常不会发生。
6 由于 start 方法返回适用于测试的配置,因此我们将控制 ExtensionsService 实现使用的基本 URL 的 rest-client 属性设置为 Wiremock 正在侦听传入请求的基本 URL。
7 当所有测试完成后,关闭 Wiremock。

ExtensionsResourceTest 测试类需要像这样注释

@QuarkusTest
@QuarkusTestResource(WireMockExtensions.class)
public class ExtensionsResourceTest {

}

@QuarkusTestResource 适用于所有测试,而不仅仅是 ExtensionsResourceTest

已知限制

虽然 REST Client 扩展旨在作为 RESTEasy Client 扩展的直接替代品,但仍存在一些差异和限制

  • 新扩展的客户端的默认范围是 @ApplicationScoped,而 quarkus-resteasy-client 默认为 @Dependent。要更改此行为,请将 quarkus.rest-client.scope 属性设置为完全限定的范围名称。

  • 无法设置 SSLContext

  • 一些对于非阻塞实现没有意义的事情,例如设置 ExecutorService,不起作用

配置参考

构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖

配置属性

类型

默认

默认情况下,RESTEasy Reactive 对 String 值使用 text/plain 内容类型,对其他所有内容使用 application/json。

MicroProfile Rest Client 规范要求实现始终默认为 application/json。 此构建项目禁用 RESTEasy Reactive 的“智能”行为以符合规范

环境变量: QUARKUS_REST_CLIENT_DISABLE_SMART_PRODUCES

显示更多

布尔值

false

是否应自动为应用程序中的所有客户端注册使用 jakarta.ws.rs.ext.Provider 注释的提供程序(过滤器等)。

环境变量: QUARKUS_REST_CLIENT_PROVIDER_AUTODISCOVERY

显示更多

布尔值

true

构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖

配置属性

类型

默认

如果为 true,则扩展将自动删除路径中任何尾部的斜杠。 此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_REMOVES_TRAILING_SLASH

显示更多

布尔值

true

表单数据的编码模式。 可能的值为 HTML5RFC1738RFC3986。 这些模式在 Netty 文档中进行了描述

默认情况下,Rest Client Reactive 使用 RFC1738。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_MULTIPART_POST_ENCODER_MODE

显示更多

字符串

一个字符串值,格式为 :,用于指定客户端要使用的 HTTP 代理服务器主机名(或 IP 地址)和端口。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_PROXY_ADDRESS

显示更多

字符串

代理用户名,等效于 http.proxy 或 https.proxy JVM 设置。

可以被特定于客户端的设置覆盖。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_PROXY_USER

显示更多

字符串

代理密码,等效于 http.proxyPassword 或 https.proxyPassword JVM 设置。

可以被特定于客户端的设置覆盖。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_PROXY_PASSWORD

显示更多

字符串

要访问而不使用代理的主机,类似于 http.nonProxyHosts 或 https.nonProxyHosts JVM 设置。 请注意,与 JVM 设置不同,此属性默认情况下为空。

可以被特定于客户端的设置覆盖。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_NON_PROXY_HOSTS

显示更多

字符串

REST 客户端应等待连接到远程端点的超时时间(以毫秒为单位)。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_CONNECT_TIMEOUT

显示更多

long

15000

REST 客户端应等待来自远程端点响应的超时时间(以毫秒为单位)。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_READ_TIMEOUT

显示更多

long

30000

如果为 true,则在客户端调用期间发生异常时,REST 客户端将不会提供额外的上下文信息(例如 REST 客户端类和方法名称)。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_DISABLE_CONTEXTUAL_ERROR_MESSAGES

显示更多

布尔值

false

用于所有 REST 客户端的 HTTP user-agent 标头的默认配置。

可以被特定于客户端的设置覆盖。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_USER_AGENT

显示更多

字符串

应应用于 rest 客户端所有请求的 HTTP 标头。

环境变量: QUARKUS_REST_CLIENT_HEADERS__HEADER_NAME_

显示更多

Map<String,String>

主机名验证器的类名。 该类必须具有一个公共的无参数构造函数。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_HOSTNAME_VERIFIER

显示更多

字符串

连接在连接池中保持未使用状态的时间(以毫秒为单位),然后被驱逐和关闭。 超时 0 表示没有超时。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_CONNECTION_TTL

显示更多

整数

此客户端的连接池的大小。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_CONNECTION_POOL_SIZE

显示更多

整数

50

如果设置为 false,则完全禁用 keep alive。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_KEEP_ALIVE_ENABLED

显示更多

布尔值

true

请求可以遵循的最大重定向次数。

可以被特定于客户端的设置覆盖。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_MAX_REDIRECTS

显示更多

整数

一个布尔值,用于确定客户端是否应遵循 HTTP 重定向响应。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_FOLLOW_REDIRECTS

显示更多

布尔值

要包含在客户端中的完全限定的提供程序类名。 等效于 @RegisterProvider 注释。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_PROVIDERS

显示更多

字符串

用于注入 REST 客户端实例的 CDI 范围。 值可以是 CDI 范围注释的完全限定类名(例如“jakarta.enterprise.context.ApplicationScoped”)或其简单名称(例如“ApplicationScoped”)。

rest-client 扩展的默认范围是“Dependent”(这是符合规范的行为)。

rest-client-reactive 扩展的默认范围是“ApplicationScoped”。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_SCOPE

显示更多

字符串

一个枚举类型字符串值,可能的值为“MULTI_PAIRS”(默认)、“COMMA_SEPARATED”或“ARRAY_PAIRS”,用于指定使用相同查询参数的多个值的格式。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_QUERY_PARAM_STYLE

显示更多

multi-pairs, comma-separated, array-pairs

设置是否启用主机名验证。 默认已启用。 不应在生产环境中禁用此设置,因为它会使客户端容易受到 MITM 攻击。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_VERIFY_HOST

显示更多

布尔值

信任存储位置。 可以指向类路径资源或文件。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_TRUST_STORE

显示更多

字符串

信任存储密码。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_TRUST_STORE_PASSWORD

显示更多

字符串

信任存储的类型。 默认为“JKS”。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_TRUST_STORE_TYPE

显示更多

字符串

密钥存储位置。 可以指向类路径资源或文件。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_KEY_STORE

显示更多

字符串

密钥存储密码。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_KEY_STORE_PASSWORD

显示更多

字符串

密钥存储的类型。 默认为“JKS”。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_KEY_STORE_TYPE

显示更多

字符串

要使用的 TLS 配置的名称。

如果配置了名称,则它使用来自 quarkus.tls.<name>.* 的配置 如果配置了名称,但未找到具有该名称的 TLS 配置,则会抛出错误。 默认的 TLS 配置将被忽略。

如果未设置命名的 TLS 配置,则将使用 key-store、trust-store 等属性。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_TLS_CONFIGURATION_NAME

显示更多

字符串

如果为 true,则将启用 HTTP/2。

环境变量: QUARKUS_REST_CLIENT_HTTP2

显示更多

布尔值

false

配置 HTTP/2 升级聚合内容的最大长度(以字节为单位)。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_HTTP2_UPGRADE_MAX_CONTENT_LENGTH

显示更多

MemorySize 

64K

配置两件不同的事情

  • 最大 HTTP 块大小,最多 Integer.MAX_VALUE 个字节。

  • 使用 InputStream 作为输入时要读取的块的大小

可以被特定于客户端的设置覆盖。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT_MAX_CHUNK_SIZE

显示更多

MemorySize 

8k

支持接收使用 GZIP 压缩的消息。 启用此功能后,如果服务器返回包含标头 Content-Encoding: gzip 的响应,REST Client 将自动解码内容并继续进行消息处理。

此属性不适用于 RESTEasy Client。

可以被特定于客户端的设置覆盖。

环境变量: QUARKUS_REST_CLIENT_ENABLE_COMPRESSION

显示更多

布尔值

如果启用了应用程序层协议协商,则客户端将协商使用服务器公开的协议中的哪个协议。 默认情况下,它将首先尝试使用 HTTP/2,如果未启用,则将使用 HTTP/1.1。 当属性 http2 启用时,此标志将自动启用。

环境变量: QUARKUS_REST_CLIENT_ALPN

显示更多

布尔值

如果为 true,则会捕获 REST Client 方法调用的堆栈跟踪。 如果调用引发异常,则将使用此堆栈跟踪

环境变量: QUARKUS_REST_CLIENT_CAPTURE_STACKTRACE

显示更多

布尔值

false

客户端的日志记录范围。
警告:注意记录敏感数据
可能的值为

  • request-response - 启用记录请求和响应,包括重定向响应

  • all - 启用记录请求和响应以及较低级别的日志记录

  • none - 无其他日志记录

此属性仅适用于响应式 REST 客户端。

环境变量: QUARKUS_REST_CLIENT_LOGGING_SCOPE

显示更多

字符串

应记录正文的多少个字符。 消息正文可能很大,并且很容易污染日志。

默认情况下,设置为 100。

此属性仅适用于响应式 REST 客户端。

环境变量: QUARKUS_REST_CLIENT_LOGGING_BODY_LIMIT

显示更多

整数

100

用于注入的 CDI 范围。 此属性可以包含 CDI 范围注释的完全限定类名(例如“jakarta.enterprise.context.ApplicationScoped”)或其简单名称(例如“ApplicationScoped”)。 默认情况下,未设置此属性,这意味着该接口未注册为 bean,除非它使用 RegisterRestClient 注释。 如果接口未使用 RegisterRestClient 注释并且设置了此属性,则 Quarkus 会将该接口设置为配置范围的 bean。

环境变量: QUARKUS_REST_CLIENT__CLIENTS__SCOPE

显示更多

字符串

如果设置为 true,则 Quarkus 将确保来自 REST 客户端的所有调用都通过本地代理服务器(由 Quarkus 管理)。 这对于捕获到使用 HTTPS 服务的网络流量非常有用。

此属性不适用于 RESTEasy Client,仅适用于 Quarkus REST 客户端(以前的 RESTEasy Reactive 客户端)。

此属性仅适用于开发和测试模式。

环境变量: QUARKUS_REST_CLIENT__CLIENTS__ENABLE_LOCAL_PROXY

显示更多

布尔值

false

如果存在多个代理提供程序,则使用此设置来选择要使用的代理提供程序。 它仅在 enable-local-proxy 为 true 时适用。

在多个提供程序之间进行选择的算法如下

  • 如果只有默认提供程序,则使用它(其名称为 default

  • 如果除了默认提供程序之外只有一个,则使用它

  • 如果有多个,则失败

环境变量: QUARKUS_REST_CLIENT__CLIENTS__LOCAL_PROXY_PROVIDER

显示更多

字符串

如果为 true,则扩展将自动删除路径中任何尾部的斜杠。 此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENTS__REMOVES_TRAILING_SLASH

显示更多

布尔值

true

用于此服务的基本 URL。 除非在 @RegisterRestClient 注释中配置了 baseUri 属性,否则此属性或 uri 属性被认为是必需的。

环境变量: QUARKUS_REST_CLIENT__CLIENT__URL

显示更多

字符串

用于此服务的基本 URI。 除非在 @RegisterRestClient 注释中配置了 baseUri 属性,否则此属性或 url 属性被认为是必需的。

环境变量: QUARKUS_REST_CLIENT__CLIENT__URI

显示更多

字符串

此属性仅用于高级配置设置,以覆盖为 uri 或 url 设置的任何值。 覆盖是使用 REST Client 类名配置语法完成的。

此属性不适用于 RESTEasy Client,仅适用于 Quarkus Rest 客户端(以前的 RESTEasy Reactive 客户端)。

环境变量: QUARKUS_REST_CLIENT__CLIENT__OVERRIDE_URI

显示更多

字符串

Map,其中键是要包含在客户端中的完全限定的提供程序类名,值是它们的整数优先级。 等效于 @RegisterProvider 注释。

环境变量: QUARKUS_REST_CLIENT__CLIENT__PROVIDERS

显示更多

字符串

等待连接到远程端点的超时时间(以毫秒为单位)。

环境变量: QUARKUS_REST_CLIENT__CLIENT__CONNECT_TIMEOUT

显示更多

long

等待来自远程端点响应的超时时间(以毫秒为单位)。

环境变量: QUARKUS_REST_CLIENT__CLIENT__READ_TIMEOUT

显示更多

long

一个布尔值,用于确定客户端是否应遵循 HTTP 重定向响应。

环境变量: QUARKUS_REST_CLIENT__CLIENT__FOLLOW_REDIRECTS

显示更多

布尔值

表单数据的编码模式。 可能的值为 HTML5RFC1738RFC3986。 这些模式在 Netty 文档中进行了描述

默认情况下,Rest Client Reactive 使用 RFC1738。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__MULTIPART_POST_ENCODER_MODE

显示更多

字符串

一个字符串值,格式为 :,用于指定此客户端要使用的 HTTP 代理服务器主机名(或 IP 地址)和端口。

使用 none 禁用代理

环境变量: QUARKUS_REST_CLIENT__CLIENT__PROXY_ADDRESS

显示更多

字符串

代理用户名。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__PROXY_USER

显示更多

字符串

代理密码。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__PROXY_PASSWORD

显示更多

字符串

要访问而不使用代理的主机

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__NON_PROXY_HOSTS

显示更多

字符串

一个枚举类型字符串值,可能的值为“MULTI_PAIRS”(默认)、“COMMA_SEPARATED”或“ARRAY_PAIRS”,用于指定使用相同查询参数的多个值的格式。

环境变量: QUARKUS_REST_CLIENT__CLIENT__QUERY_PARAM_STYLE

显示更多

multi-pairs, comma-separated, array-pairs

设置是否启用主机名验证。 默认已启用。 不应在生产环境中禁用此设置,因为它会使客户端容易受到 MITM 攻击。

环境变量: QUARKUS_REST_CLIENT__CLIENT__VERIFY_HOST

显示更多

布尔值

信任存储位置。 可以指向类路径资源或文件。

环境变量: QUARKUS_REST_CLIENT__CLIENT__TRUST_STORE

显示更多

字符串

信任存储密码。

环境变量: QUARKUS_REST_CLIENT__CLIENT__TRUST_STORE_PASSWORD

显示更多

字符串

信任存储的类型。 默认为“JKS”。

环境变量: QUARKUS_REST_CLIENT__CLIENT__TRUST_STORE_TYPE

显示更多

字符串

密钥存储位置。 可以指向类路径资源或文件。

环境变量: QUARKUS_REST_CLIENT__CLIENT__KEY_STORE

显示更多

字符串

密钥存储密码。

环境变量: QUARKUS_REST_CLIENT__CLIENT__KEY_STORE_PASSWORD

显示更多

字符串

密钥存储的类型。 默认为“JKS”。

环境变量: QUARKUS_REST_CLIENT__CLIENT__KEY_STORE_TYPE

显示更多

字符串

主机名验证器的类名。 该类必须具有一个公共的无参数构造函数。

环境变量: QUARKUS_REST_CLIENT__CLIENT__HOSTNAME_VERIFIER

显示更多

字符串

要使用的 TLS 配置的名称。

如果配置了名称,则它使用来自 quarkus.tls.<name>.* 的配置 如果配置了名称,但未找到具有该名称的 TLS 配置,则会抛出错误。 默认的 TLS 配置将被忽略。

如果未设置命名的 TLS 配置,则将使用 key-store、trust-store 等属性。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__TLS_CONFIGURATION_NAME

显示更多

字符串

连接在连接池中保持未使用状态的时间(以毫秒为单位),然后被驱逐和关闭。 超时 0 表示没有超时。

环境变量: QUARKUS_REST_CLIENT__CLIENT__CONNECTION_TTL

显示更多

整数

此客户端的连接池的大小。

环境变量: QUARKUS_REST_CLIENT__CLIENT__CONNECTION_POOL_SIZE

显示更多

整数

50

如果设置为 false,则完全禁用 keep alive。

环境变量: QUARKUS_REST_CLIENT__CLIENT__KEEP_ALIVE_ENABLED

显示更多

布尔值

请求可以遵循的最大重定向次数。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__MAX_REDIRECTS

显示更多

整数

应应用于 rest 客户端所有请求的 HTTP 标头。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__HEADERS__HEADER_NAME_

显示更多

Map<String,String>

设置为 true 以在 REST 客户端之间共享 HTTP 客户端。可以有多个共享客户端,通过名称来区分,当没有设置特定名称时,使用名称 __vertx.DEFAULT

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__SHARED

显示更多

布尔值

设置 HTTP 客户端名称,当客户端被共享时使用,否则忽略。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__NAME

显示更多

字符串

配置要使用的 HTTP user-agent 标头。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__USER_AGENT

显示更多

字符串

如果为 true,则将启用 HTTP/2。

环境变量: QUARKUS_REST_CLIENT__CLIENT__HTTP2

显示更多

布尔值

配置 HTTP/2 升级聚合内容的最大长度(以字节为单位)。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__HTTP2_UPGRADE_MAX_CONTENT_LENGTH

显示更多

MemorySize 

64K

配置两件不同的事情

  • 最大 HTTP 块大小,最多 Integer.MAX_VALUE 个字节。

  • 读取 InputStream 并发送到服务器时要读取的块的大小

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__MAX_CHUNK_SIZE

显示更多

MemorySize 

8K

支持接收使用 GZIP 压缩的消息。 启用此功能后,如果服务器返回包含标头 Content-Encoding: gzip 的响应,REST Client 将自动解码内容并继续进行消息处理。

此属性不适用于 RESTEasy Client。

环境变量: QUARKUS_REST_CLIENT__CLIENT__ENABLE_COMPRESSION

显示更多

布尔值

如果启用了应用程序层协议协商,则客户端将协商使用服务器公开的协议中的哪个协议。 默认情况下,它将首先尝试使用 HTTP/2,如果未启用,则将使用 HTTP/1.1。 当属性 http2 启用时,此标志将自动启用。

环境变量: QUARKUS_REST_CLIENT__CLIENT__ALPN

显示更多

布尔值

如果为 true,则会捕获 REST Client 方法调用的堆栈跟踪。 如果调用引发异常,则将使用此堆栈跟踪

环境变量: QUARKUS_REST_CLIENT__CLIENT__CAPTURE_STACKTRACE

显示更多

布尔值

如果设置为 true,则此 REST 客户端将不会使用默认异常映射器,如果 HTTP 响应代码 >= 400,该映射器始终抛出异常。此属性不适用于 RESTEasy 客户端。

环境变量: QUARKUS_REST_CLIENT__CLIENT__DISABLE_DEFAULT_MAPPER

显示更多

布尔值

${microprofile.rest.client.disable.default.mapper:false}

客户端的日志记录范围。
警告:注意记录敏感数据
可能的值为

  • request-response - 启用记录请求和响应,包括重定向响应

  • all - 启用记录请求和响应以及较低级别的日志记录

  • none - 无其他日志记录

此属性仅适用于响应式 REST 客户端。

环境变量: QUARKUS_REST_CLIENT__CLIENT__LOGGING_SCOPE

显示更多

字符串

应记录正文的多少个字符。 消息正文可能很大,并且很容易污染日志。

默认情况下,设置为 100。

此属性仅适用于响应式 REST 客户端。

环境变量: QUARKUS_REST_CLIENT__CLIENT__LOGGING_BODY_LIMIT

显示更多

整数

100

关于 MemorySize 格式

大小配置选项识别以下格式的字符串(显示为正则表达式):[0-9]+[KkMmGgTtPpEeZzYy]?

如果未给出后缀,则假定为字节。

相关内容