Using the legacy REST Client
本指南是关于与 RESTEasy Classic 兼容的 REST 客户端的,直到 Quarkus 2.8,它一直是默认的 Jakarta REST(以前称为 JAX-RS)实现。 现在建议使用 Quarkus REST(以前称为 RESTEasy Reactive),它同样支持传统的阻塞工作负载和反应式工作负载。有关 Quarkus REST 的更多信息,请参阅 REST Client guide,对于服务器端,请参阅 introductory REST JSON guide 或更详细的 Quarkus REST guide。
本指南解释了如何使用 RESTEasy REST 客户端与 REST API 进行交互,而几乎不需要任何工作。
如果您需要编写服务器 JSON REST APIs,还有其他指南。 |
- Prerequisites
- Solution
- Creating the Maven project
- Setting up the model
- Create the interface
- Create the configuration
- Create the Jakarta REST resource
- Update the test
- Redirection
- Async Support
- Custom headers support
- Package and run the application
- REST Client and RESTEasy interactions
- Using a Mock HTTP Server for tests
- Further reading
Prerequisites
如要完成本指南,您需要:
-
Roughly 15 minutes
-
An IDE
-
安装了 JDK 17+,已正确配置
JAVA_HOME
-
Apache Maven ${proposed-maven-version}
-
如果你想使用 Quarkus CLI, 则可以选择使用
-
如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately
Solution
我们建议您遵循接下来的部分中的说明,按部就班地创建应用程序。然而,您可以直接跳到完成的示例。
克隆 Git 存储库: git clone $${quickstarts-base-url}.git
,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。
解决方案位于 resteasy-client-quickstart
directory。
Creating the Maven project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
--no-code
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 --gradle
或 --gradle-kotlin-dsl
选项。
有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
-DprojectGroupId={create-app-group-id} \
-DprojectArtifactId={create-app-artifact-id} \
-DnoCode
cd {create-app-artifact-id}
要创建一个 Gradle 项目,添加 -DbuildTool=gradle
或 -DbuildTool=gradle-kotlin-dsl
选项。
适用于 Windows 用户:
-
如果使用 cmd,(不要使用反斜杠
\
,并将所有内容放在同一行上) -
如果使用 Powershell,将
-D
参数用双引号引起来,例如"-DprojectArtifactId={create-app-artifact-id}"
此命令生成具有 REST 端点的 Maven 项目,并导入:
-
用于 REST 服务器支持的
resteasy
和resteasy-jackson
扩展; -
用于 REST 客户端支持的
resteasy-client
和resteasy-client-jackson
扩展。
如果您已配置 Quarkus 项目,您可以通过在项目根目录中运行以下命令将 resteasy-client
和 resteasy-client-jackson
扩展添加到您的项目:
quarkus extension add {add-extension-extensions}
./mvnw quarkus:add-extension -Dextensions='{add-extension-extensions}'
./gradlew addExtension --extensions='{add-extension-extensions}'
这会将以下内容添加到您的 pom.xml
:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-client-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-resteasy-client")
implementation("io.quarkus:quarkus-resteasy-client-jackson")
Setting up the model
在本指南中,我们将演示如何使用 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;
}
以上模型只是该服务所提供的字段的一个子集,但足以满足本指南的目的。
Create the interface
使用 RESTEasy 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 org.jboss.resteasy.annotations.jaxrs.QueryParam;
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 String id);
}
getById
方法为我们的代码提供从 Code Quarkus API 根据 id 获取扩展的能力。客户端将处理所有网络连接和封送,使我们的代码不包含此类技术细节。
上述代码中注释的目的如下:
-
@RegisterRestClient
允许 Quarkus 知道此接口旨在作为 REST 客户端用于 CDI 注入 -
@Path
、@GET
和@QueryParam
是用于定义如何访问服务的标准 Jakarta REST 注释
当安装了 JSON 扩展(例如 |
Path Parameters
如果 GET 请求需要路径参数,你可以利用 @PathParam("parameter-name")
注释,而非(或附加) @QueryParam
。路径和查询参数可以根据需要组合,如下面的模拟示例所示。
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.Set;
@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {
@GET
@Path("/stream/{stream}")
Set<Extension> getByStream(@PathParam String stream, @QueryParam("id") String id);
}
Create the configuration
要确定发起 REST 调用时的基本 URL,REST 客户端使用 application.properties
的配置。属性的名称需要遵循特定的约定,这在下面的代码中展示得最好:
# Your configuration properties
quarkus.rest-client."org.acme.rest.client.ExtensionsService".url=https://stage.code.quarkus.io/api # (1)
quarkus.rest-client."org.acme.rest.client.ExtensionsService".scope=jakarta.inject.Singleton # (2)
1 | 使用此配置意味着使用 ExtensionsService 执行的所有请求都将使用 https://stage.code.quarkus.io 作为基本 URL。使用上述配置,用 io.quarkus:quarkus-resteasy-client 的值调用 ExtensionsService 的 getById 方法将导致对 https://stage.code.quarkus.io/api/extensions?id=io.quarkus:quarkus-rest-client 发起 HTTP GET 请求。 |
2 | 使用此配置意味着 ExtensionsService 的默认作用域将是 @Singleton 。受支持的作用域值分别是 @Singleton 、@Dependent 、@ApplicationScoped 和 @RequestScoped 。默认作用域是 @Dependent 。还可以在界面上定义默认作用域。 |
请注意,org.acme.rest.client.ExtensionsService
must 匹配我们之前部分中创建的 ExtensionsService
界面中的完整限定名。
也可以使用标准的 MicroProfile Rest 客户端属性表示法配置客户端:
如果通过 Quarkus 表示法和 MicroProfile 表示法指定一个属性,Quarkus 表示法具有优先权。 |
为了便于配置,你可以使用 @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
Disabling Hostname Verification
要为特定 REST 客户端禁用 SSL 主机名验证,请将以下属性添加到你的配置中:
quarkus.rest-client.extensions-api.verify-host=false
此设置不应在生产环境中使用,因为它会禁用 SSL 主机名验证。
此外,你可以将 REST 客户端配置为使用自定义主机名验证策略,只需提供实现 javax.net.ssl.HostnameVerifier
界面的类,并将以下属性添加到你的配置中:
quarkus.rest-client.extensions-api.hostname-verifier=<full qualified custom hostname verifier class name>
Quarkus REST 客户端提供了一个嵌入式主机名验证器策略,以禁用名为 |
Create the Jakarta REST resource
使用以下内容创建 src/main/java/org/acme/rest/client/ExtensionsResource.java
文件:
package org.acme.rest.client;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.Set;
@Path("/extension")
public class ExtensionsResource {
@Inject
@RestClient
ExtensionsService extensionsService;
@GET
@Path("/id/{id}")
public Set<Extension> id(@PathParam String id) {
return extensionsService.getById(id);
}
}
请注意除了标准 CDI @Inject`注解以外,还需要使用 MicroProfile `@RestClient`注解来注入 `ExtensionsService
。
Update the test
还需要更新功能测试来反映端点所做的更改。编辑 `src/test/java/org/acme/rest/client/ExtensionsResourceTest.java`文件并将 `testExtensionIdEndpoint`方法的内容更改为:
package org.acme.rest.client;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.greaterThan;
import org.acme.rest.client.resources.WireMockExtensionsResource;
import org.junit.jupiter.api.Test;
import io.quarkus.test.common.WithTestResource;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
@WithTestResource(WireMockExtensionsResource.class)
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 Classic"),
"[0].keywords.size()", greaterThan(1),
"[0].keywords", hasItem("rest-client"));
}
}
上面的代码使用的是 REST Assured的 json-path功能。
Redirection
HTTP 服务器可以通过发送一个以“3”开头的状态码和一个包含待重定向到的 URL 的 HTTP 标头“Location”的响应来重定向响应到另一个位置。当 REST 客户端从 HTTP 服务器接收到重定向响应时,它不会自动对新位置执行另一个请求。但是,可以通过启用“follow-redirects”属性来启用自动重定向:
-
`quarkus.rest-client.follow-redirects`可为所有 REST 客户端启用重定向。
-
`quarkus.rest-client.<client-prefix>.follow-redirects`可为特定 REST 客户端启用重定向。
如果该属性为 true,则 REST 客户端将执行它从 HTTP 服务器接收到重定向响应后执行的新请求。
此外,我们可以使用“max-redirects”属性限制重定向次数。
需要注意的重要一点是,根据 RFC2616规范,默认情况下重定向只会发生在 GET 或 HEAD 方法中。
Async Support
Rest 客户端支持异步 rest 调用。异步支持有 2 种类型:可以返回 CompletionStage`或 `Uni
(需要 `quarkus-resteasy-client-mutiny`扩展)。让我们通过在 `ExtensionsService`REST 接口中添加一个 `getByIdAsync`方法来对其实现进行操作。代码应如下所示:
package org.acme.rest.client;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;
@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam String id);
@GET
CompletionStage<Set<Extension>> getByIdAsync(@QueryParam String id);
}
打开 `src/main/java/org/acme/rest/client/ExtensionsResource.java`文件并用以下内容更新它:
package org.acme.rest.client;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
@Path("/extension")
public class ExtensionsResource {
@Inject
@RestClient
ExtensionsService extensionsService;
@GET
@Path("/id/{id}")
public Set<Extension> id(@PathParam String id) {
return extensionsService.getById(id);
}
@GET
@Path("/id-async/{id}")
public CompletionStage<Set<Extension>> idAsync(@PathParam String id) {
return extensionsService.getByIdAsync(id);
}
}
要测试异步方法,请在 `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 Classic"),
"[0].keywords.size()", greaterThan(1),
"[0].keywords", hasItem("rest-client"));
}
`Uni`版本非常相似:
package org.acme.rest.client;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;
import io.smallrye.mutiny.Uni;
@Path("/extensions")
@RegisterRestClient
public interface ExtensionsService {
// ...
@GET
Uni<Set<Extension>> getByIdAsUni(@QueryParam String id);
}
`ExtensionsResource`变为:
package org.acme.rest.client;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;
import io.smallrye.mutiny.Uni;
@Path("/extension")
public class ExtensionsResource {
@Inject
@RestClient
ExtensionsService extensionsService;
// ...
@GET
@Path("/id-uni/{id}")
public Uni<Set<Extension>> idMutiny(@PathParam String id) {
return extensionsService.getByIdAsUni(id);
}
}
Mutiny
前面的片段使用了 Mutiny reactive 类型。如果不熟悉 Mutiny,请参阅 Mutiny - an intuitive reactive programming library。 |
在返回 Uni`时,每个 _subscription_都会调用远程服务。这意味着可以通过重新订阅 `Uni`重新发送请求,或按如下方式使用 `retry
:
@Inject @RestClient ExtensionsService extensionsService;
// ...
extensionsService.getByIdAsUni(id)
.onFailure().retry().atMost(10);
如果使用 CompletionStage
,则需要调用服务的方法来重试。这种差异源自 Mutiny 的惰性方面及其订阅协议。有关这方面的更多详细信息,请参阅 the Mutiny documentation。
Custom headers support
MicroProfile REST 客户端允许通过使用具有 `@RegisterClientHeaders`注解的 `ClientHeadersFactory`来修改请求头。
让我们通过在 `ExtensionsService`REST 接口中添加一个指向 `RequestUUIDHeaderFactory`类的 `@RegisterClientHeaders`注解来对其进行操作:
package org.acme.rest.client;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.annotation.RegisterClientHeaders;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.QueryParam;
import io.smallrye.mutiny.Uni;
@Path("/extensions")
@RegisterRestClient
@RegisterClientHeaders(RequestUUIDHeaderFactory.class)
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam String id);
@GET
CompletionStage<Set<Extension>> getByIdAsync(@QueryParam String id);
@GET
Uni<Set<Extension>> getByIdAsUni(@QueryParam String id);
}
而 RequestUUIDHeaderFactory
将类似如下所示:
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。
Default header factory
你还可以使用没有指定任何自定义工厂的 @RegisterClientHeaders
声明。在这种情况下,将使用 DefaultClientHeadersFactoryImpl
工厂,并将修改 org.eclipse.microprofile.rest.client.propagateHeaders
配置属性中列出的所有头文件。各个头文件名称以逗号分隔。
@Path("/extensions")
@RegisterRestClient
@RegisterClientHeaders
public interface ExtensionsService {
@GET
Set<Extension> getById(@QueryParam String id);
@GET
CompletionStage<Set<Extension>> getByIdAsync(@QueryParam String id);
@GET
Uni<Set<Extension>> getByIdAsUni(@QueryParam String id);
}
org.eclipse.microprofile.rest.client.propagateHeaders=Authorization,Proxy-Authorization
Package and run the application
使用以下内容运行应用程序:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
使用 [role="bare"][role="bare"]http://localhost:8080/extension/id/io.quarkus:quarkus-rest-client 打开你的浏览器。
你应该会看到一个 JSON 对象,其中包含有关 REST 客户端扩展的一些基本信息。
和往常一样,可以使用以下命令打包应用程序:
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 and RESTEasy interactions
在 Quarkus 中,REST 客户端扩展和 the RESTEasy extension 共享相同的架构。这一考虑的一个重要后果是它们共享相同的提供程序列表(在 Jakarta REST 的术语意义上)。
例如,如果你声明一个 WriterInterceptor
,它将默认拦截服务器调用和客户端调用,但这可能不是期望的行为。
但是,您可以更改此默认行为,并将一个提供程序约束为:
-
仅通过向你的提供程序添加
@ConstrainedTo(RuntimeType.CLIENT)
声明来考虑 client 调用; -
仅通过向你的提供程序添加
@ConstrainedTo(RuntimeType.SERVER)
声明来考虑 server 调用。
Using a Mock HTTP Server for tests
在某些情况下,你可能希望模拟远程端点(HTTP 服务器),而不是模拟客户端本身。这对于原生测试或程序化创建的客户端可能特别有用。
你可以使用 Wiremock 轻松模拟 HTTP 服务器。Wiremock section of the Quarkus - Using the REST Client 详细介绍了如何设置该服务器。