使用 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 项目
首先,我们需要一个新项目。使用以下命令创建一个新项目
对于 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
扩展添加到您的项目中
quarkus extension add rest-client-jackson
./mvnw quarkus:add-extension -Dextensions='rest-client-jackson'
./gradlew addExtension --extensions='rest-client-jackson'
这会将以下内容添加到您的构建文件中
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId>
</dependency>
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 注解,用于定义如何访问服务
安装 如果您不依赖 JSON 默认值,强烈建议您使用 |
上面的 |
查询参数
指定查询参数的最简单方法是使用 @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 查询参数外,还会添加值为 other 的 some-other-param 。 |
3 | 当调用 getFromMethod 时,除了 my-param 查询参数外,还会添加值为 test 的 param-from-method (因为这是当使用 param-from-method 调用 withParam 方法时返回的值)。 |
请注意,如果接口方法包含使用 |
有关此注解的更多信息,请参见 @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 调用 ExtensionsService 的 getById 方法将导致向 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 客户端支持使用 |
信任所有证书并禁用 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);
}
}
|
使用自定义 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 Assured 的 json-path 功能。
异步支持
要充分利用客户端的反应式特性,您可以使用 REST 客户端扩展的非阻塞版本,该版本支持 CompletionStage
和 Uni
。让我们通过在我们的 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
中使用的泛型类型。
用户还可以使用 一个简单的示例,其中事件负载为
|
过滤掉事件
有时,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
注解注册ClientHeadersFactory
或ReactiveClientHeadersFactory
-
通过使用
QuarkusRestClientBuilder.clientHeadersFactory(factory)
方法以编程方式注册ClientHeadersFactory
或ReactiveClientHeadersFactory
-
通过使用
@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 时, 一个非常简单的例子是
其中 |
默认标头工厂
也可以使用 @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 客户端支持通过过滤器进一步自定义要发送到服务器的最终请求。过滤器必须实现接口 ClientRequestFilter
或 ResteasyReactiveClientRequestFilter
。
自定义请求的一个简单示例是添加自定义标头
@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 客户端时, |
Multipart Form 支持
发送 Multipart 消息
REST 客户端允许将数据作为 multipart 表单发送。这样,您可以例如高效地发送文件。
要将数据作为 multipart 表单发送,您可以只使用常规的 @RestForm
(或 @FormParam
)注解
@POST
@Path("/binary")
String sendMultipart(@RestForm File file, @RestForm String otherField);
指定为 File
、Path
、byte[]
、Buffer
或 FileUpload
的参数将作为文件发送,并且默认为 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);
任何类型为 File
、Path
、byte[]
、Buffer
或 FileUpload
的 @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 客户端使用 可以通过 |
接收 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 响应中发送的文件只能解析为
File
、Path
和FileDownload
。 -
响应类型的每个字段都必须使用
@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-address
、quarkus.rest-client.proxy-user
、quarkus.rest-client.proxy-password
、quarkus.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'
打包并运行应用程序
使用以下命令运行应用程序
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
您应该看到一个 JSON 对象,其中包含有关此扩展的一些基本信息。
与往常一样,可以使用以下命令打包应用程序
quarkus build
./mvnw install
./gradlew build
并使用 java -jar target/quarkus-app/quarkus-run.jar
执行。
您还可以使用以下命令生成原生可执行文件
quarkus build --native
./mvnw install -Dnative
./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 使用默认的 当使用 对于使用 |
为测试模拟客户端
如果您使用使用 @RestClient
注释注入的客户端,您可以轻松地为测试模拟它。 您可以使用 Mockito 的 @InjectMock
或 QuarkusMock
来完成。
本节介绍如何用模拟替换您的客户端。 如果您想更深入地了解模拟在 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
。
首先,将以下依赖项添加到您的应用程序
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-mockito</artifactId>
<scope>test</scope>
</dependency>
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 服务器来运行测试是一种常见的测试模式。 此类服务器的示例有 Wiremock 和 Hoverfly。 在本节中,我们将演示如何利用 Wiremock 来测试上面开发的 ExtensionsService
。
首先,需要将 Wiremock 添加为测试依赖项。 对于 Maven 项目,这将如下进行
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
<version>${wiremock.version}</version> (1)
</dependency>
1 | 使用正确的 Wiremock 版本。所有可用版本都可以在 这里 找到。 |
testImplementation("org.wiremock:wiremock:$wiremockVersion") (1)
1 | 使用正确的 Wiremock 版本。所有可用版本都可以在 这里 找到。 |
在 Quarkus 测试中,当需要在运行 Quarkus 测试之前启动某些服务时,我们使用 @io.quarkus.test.common.QuarkusTestResource
注释来指定一个 io.quarkus.test.common.QuarkusTestResourceLifecycleManager
,它可以启动服务并提供 Quarkus 将使用的配置值。
有关 |
让我们创建一个名为 WiremockExtensions
的 QuarkusTestResourceLifecycleManager
实现,如下所示
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 {
}
|
已知限制
虽然 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 的“智能”行为以符合规范 环境变量: 显示更多 |
布尔值 |
|
是否应自动为应用程序中的所有客户端注册使用 环境变量: 显示更多 |
布尔值 |
|
构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖
配置属性 |
类型 |
默认 |
---|---|---|
如果为 true,则扩展将自动删除路径中任何尾部的斜杠。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
布尔值 |
|
表单数据的编码模式。 可能的值为 默认情况下,Rest Client Reactive 使用 RFC1738。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
一个字符串值,格式为 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
字符串 |
|
代理用户名,等效于 http.proxy 或 https.proxy JVM 设置。 可以被特定于客户端的设置覆盖。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
代理密码,等效于 http.proxyPassword 或 https.proxyPassword JVM 设置。 可以被特定于客户端的设置覆盖。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
要访问而不使用代理的主机,类似于 http.nonProxyHosts 或 https.nonProxyHosts JVM 设置。 请注意,与 JVM 设置不同,此属性默认情况下为空。 可以被特定于客户端的设置覆盖。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
REST 客户端应等待连接到远程端点的超时时间(以毫秒为单位)。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
long |
|
REST 客户端应等待来自远程端点响应的超时时间(以毫秒为单位)。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
long |
|
如果为 true,则在客户端调用期间发生异常时,REST 客户端将不会提供额外的上下文信息(例如 REST 客户端类和方法名称)。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
布尔值 |
|
用于所有 REST 客户端的 HTTP user-agent 标头的默认配置。 可以被特定于客户端的设置覆盖。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
应应用于 rest 客户端所有请求的 HTTP 标头。 环境变量: 显示更多 |
Map<String,String> |
|
主机名验证器的类名。 该类必须具有一个公共的无参数构造函数。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
字符串 |
|
连接在连接池中保持未使用状态的时间(以毫秒为单位),然后被驱逐和关闭。 超时 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
整数 |
|
此客户端的连接池的大小。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
整数 |
|
如果设置为 false,则完全禁用 keep alive。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
布尔值 |
|
请求可以遵循的最大重定向次数。 可以被特定于客户端的设置覆盖。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
整数 |
|
一个布尔值,用于确定客户端是否应遵循 HTTP 重定向响应。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
布尔值 |
|
要包含在客户端中的完全限定的提供程序类名。 等效于 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
字符串 |
|
用于注入 REST 客户端实例的 CDI 范围。 值可以是 CDI 范围注释的完全限定类名(例如“jakarta.enterprise.context.ApplicationScoped”)或其简单名称(例如“ApplicationScoped”)。 rest-client 扩展的默认范围是“Dependent”(这是符合规范的行为)。 rest-client-reactive 扩展的默认范围是“ApplicationScoped”。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
字符串 |
|
一个枚举类型字符串值,可能的值为“MULTI_PAIRS”(默认)、“COMMA_SEPARATED”或“ARRAY_PAIRS”,用于指定使用相同查询参数的多个值的格式。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
|
|
设置是否启用主机名验证。 默认已启用。 不应在生产环境中禁用此设置,因为它会使客户端容易受到 MITM 攻击。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
布尔值 |
|
信任存储位置。 可以指向类路径资源或文件。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
字符串 |
|
信任存储密码。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
字符串 |
|
信任存储的类型。 默认为“JKS”。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
字符串 |
|
密钥存储位置。 可以指向类路径资源或文件。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
字符串 |
|
密钥存储密码。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
字符串 |
|
密钥存储的类型。 默认为“JKS”。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
字符串 |
|
要使用的 TLS 配置的名称。 如果配置了名称,则它使用来自 如果未设置命名的 TLS 配置,则将使用 key-store、trust-store 等属性。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
布尔值 |
|
|
配置 HTTP/2 升级聚合内容的最大长度(以字节为单位)。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
|
|
配置两件不同的事情
可以被特定于客户端的设置覆盖。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
|
|
支持接收使用 GZIP 压缩的消息。 启用此功能后,如果服务器返回包含标头 此属性不适用于 RESTEasy Client。 可以被特定于客户端的设置覆盖。 环境变量: 显示更多 |
布尔值 |
|
如果启用了应用程序层协议协商,则客户端将协商使用服务器公开的协议中的哪个协议。 默认情况下,它将首先尝试使用 HTTP/2,如果未启用,则将使用 HTTP/1.1。 当属性 环境变量: 显示更多 |
布尔值 |
|
如果为 环境变量: 显示更多 |
布尔值 |
|
客户端的日志记录范围。
此属性仅适用于响应式 REST 客户端。 环境变量: 显示更多 |
字符串 |
|
应记录正文的多少个字符。 消息正文可能很大,并且很容易污染日志。 默认情况下,设置为 100。 此属性仅适用于响应式 REST 客户端。 环境变量: 显示更多 |
整数 |
|
用于注入的 CDI 范围。 此属性可以包含 CDI 范围注释的完全限定类名(例如“jakarta.enterprise.context.ApplicationScoped”)或其简单名称(例如“ApplicationScoped”)。 默认情况下,未设置此属性,这意味着该接口未注册为 bean,除非它使用 环境变量: 显示更多 |
字符串 |
|
如果设置为 true,则 Quarkus 将确保来自 REST 客户端的所有调用都通过本地代理服务器(由 Quarkus 管理)。 这对于捕获到使用 HTTPS 服务的网络流量非常有用。 此属性不适用于 RESTEasy Client,仅适用于 Quarkus REST 客户端(以前的 RESTEasy Reactive 客户端)。 此属性仅适用于开发和测试模式。 环境变量: 显示更多 |
布尔值 |
|
如果存在多个代理提供程序,则使用此设置来选择要使用的代理提供程序。 它仅在 在多个提供程序之间进行选择的算法如下
环境变量: 显示更多 |
字符串 |
|
如果为 true,则扩展将自动删除路径中任何尾部的斜杠。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
布尔值 |
|
用于此服务的基本 URL。 除非在 环境变量: 显示更多 |
字符串 |
|
用于此服务的基本 URI。 除非在 环境变量: 显示更多 |
字符串 |
|
此属性仅用于高级配置设置,以覆盖为 uri 或 url 设置的任何值。 覆盖是使用 REST Client 类名配置语法完成的。 此属性不适用于 RESTEasy Client,仅适用于 Quarkus Rest 客户端(以前的 RESTEasy Reactive 客户端)。 环境变量: 显示更多 |
字符串 |
|
Map,其中键是要包含在客户端中的完全限定的提供程序类名,值是它们的整数优先级。 等效于 环境变量: 显示更多 |
字符串 |
|
等待连接到远程端点的超时时间(以毫秒为单位)。 环境变量: 显示更多 |
long |
|
等待来自远程端点响应的超时时间(以毫秒为单位)。 环境变量: 显示更多 |
long |
|
一个布尔值,用于确定客户端是否应遵循 HTTP 重定向响应。 环境变量: 显示更多 |
布尔值 |
|
表单数据的编码模式。 可能的值为 默认情况下,Rest Client Reactive 使用 RFC1738。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
一个字符串值,格式为 使用 环境变量: 显示更多 |
字符串 |
|
代理用户名。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
代理密码。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
要访问而不使用代理的主机 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
一个枚举类型字符串值,可能的值为“MULTI_PAIRS”(默认)、“COMMA_SEPARATED”或“ARRAY_PAIRS”,用于指定使用相同查询参数的多个值的格式。 环境变量: 显示更多 |
|
|
设置是否启用主机名验证。 默认已启用。 不应在生产环境中禁用此设置,因为它会使客户端容易受到 MITM 攻击。 环境变量: 显示更多 |
布尔值 |
|
信任存储位置。 可以指向类路径资源或文件。 环境变量: 显示更多 |
字符串 |
|
信任存储密码。 环境变量: 显示更多 |
字符串 |
|
信任存储的类型。 默认为“JKS”。 环境变量: 显示更多 |
字符串 |
|
密钥存储位置。 可以指向类路径资源或文件。 环境变量: 显示更多 |
字符串 |
|
密钥存储密码。 环境变量: 显示更多 |
字符串 |
|
密钥存储的类型。 默认为“JKS”。 环境变量: 显示更多 |
字符串 |
|
主机名验证器的类名。 该类必须具有一个公共的无参数构造函数。 环境变量: 显示更多 |
字符串 |
|
要使用的 TLS 配置的名称。 如果配置了名称,则它使用来自 如果未设置命名的 TLS 配置,则将使用 key-store、trust-store 等属性。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
连接在连接池中保持未使用状态的时间(以毫秒为单位),然后被驱逐和关闭。 超时 环境变量: 显示更多 |
整数 |
|
此客户端的连接池的大小。 环境变量: 显示更多 |
整数 |
|
如果设置为 false,则完全禁用 keep alive。 环境变量: 显示更多 |
布尔值 |
|
请求可以遵循的最大重定向次数。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
整数 |
|
应应用于 rest 客户端所有请求的 HTTP 标头。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
Map<String,String> |
|
设置为 true 以在 REST 客户端之间共享 HTTP 客户端。可以有多个共享客户端,通过名称来区分,当没有设置特定名称时,使用名称 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
布尔值 |
|
设置 HTTP 客户端名称,当客户端被共享时使用,否则忽略。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
配置要使用的 HTTP user-agent 标头。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
字符串 |
|
如果为 true,则将启用 HTTP/2。 环境变量: 显示更多 |
布尔值 |
|
配置 HTTP/2 升级聚合内容的最大长度(以字节为单位)。 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
|
|
配置两件不同的事情
此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
|
|
支持接收使用 GZIP 压缩的消息。 启用此功能后,如果服务器返回包含标头 此属性不适用于 RESTEasy Client。 环境变量: 显示更多 |
布尔值 |
|
如果启用了应用程序层协议协商,则客户端将协商使用服务器公开的协议中的哪个协议。 默认情况下,它将首先尝试使用 HTTP/2,如果未启用,则将使用 HTTP/1.1。 当属性 环境变量: 显示更多 |
布尔值 |
|
如果为 环境变量: 显示更多 |
布尔值 |
|
如果设置为 环境变量: 显示更多 |
布尔值 |
|
客户端的日志记录范围。
此属性仅适用于响应式 REST 客户端。 环境变量: 显示更多 |
字符串 |
|
应记录正文的多少个字符。 消息正文可能很大,并且很容易污染日志。 默认情况下,设置为 100。 此属性仅适用于响应式 REST 客户端。 环境变量: 显示更多 |
整数 |
|
关于 MemorySize 格式
大小配置选项识别以下格式的字符串(显示为正则表达式): 如果未给出后缀,则假定为字节。 |