1、概览 {#1概览}
通过 REST API 更新资源时,可以使用 PATCH
方法。该方法专门用于"更新部分字段"的场景。当需要完全更改现有资源时(全量替换),可以使用 PUT
方法。
在本教程中,我们将学习如何在 OpenFeign 中设置 HTTP PATCH 方法。我们还将演示在 Feign client 测试 PATCH
方法时出现的异常情况,以及解决方案。
2、Spring Boot 中的应用示例 {#2spring-boot-中的应用示例}
假设我们需要构建一个简单的微服务,调用下游服务进行部分更新。
2.1、Maven 依赖 {#21maven-依赖}
首先,我们要添加 spring-boot-starter-web 和 spring-cloud-starter-openfeign 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.2、实现 Feign Client {#22实现-feign-client}
现在,让我们使用 Spring Web 注解在 Feign 中实现 PATCH
方法。
首先,让我们建立一个 User
model,它有几个简单的属性。
public class User {
private String userId;
private String userName;
private String email;
}
接下来,我们将使用 updateUser
方法来实现一个 UserClient
接口:
@FeignClient(name = "user-client", url = "http://localhost:8082/api/user")
public interface UserClient {
@RequestMapping(value = "{userId}", method = RequestMethod.PATCH)
User updateUser(@PathVariable(value = "userId") String userId, @RequestBody User user);
}
在上述 PATCH
方法中,我们传递的 User
对象只包含需要更新的字段和 userId
字段。这样做比发送完整的资源对象更省事,节省了一些网络带宽,并避免了同一对象在不同字段上进行多次更新时的冲突。
相比之下,如果我们使用 PUT
请求,就必须传递完整的资源对象表示来替换现有资源。
3、测试 Feign Client {#3测试-feign-client}
现在,让我们通过模拟 HTTP 调用为 UserClient
实现一个测试用例。
3.1、设置 WireMock Server {#31设置-wiremock-server}
为了进行实验,我们需要使用模拟框架来模拟我们正在调用的服务。
首先,让我们加入 WireMockServer Maven 依赖:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.35.0</version>
<scope>test</scope>
</dependency>
然后,配置并启动 WireMockServer
:
WireMockServer wireMockServer = new WireMockServer(8082);
configureFor("localhost", 8082);
wireMockServer.start();
WireMockServer
使用的 host
和 port
与 Feign client 配置的相同。
3.2、PATCH API {#32patch-api}
我们将模拟 PATCH
方法来测试更新 User
的 API:
String updatedUserResponse = "{\n" +
"\"userId\": 100001,\n" +
"\"userName\": \"name\",\n" +
"\"email\": \"updated-email@mail.in\"\n" +
"}";
stubFor(patch(urlEqualTo("/api/user/".concat(USER_ID)))
.willReturn(aResponse().withStatus(HttpStatus.OK.value())
.withHeader("Content-Type", "application/json")
.withBody(updatedUserResponse)));
3.3、测试 PATCH 请求 {#33测试-patch-请求}
测试时,我们将把 User
对象连同需要更新的字段一起传递给 UserClient
。
现在,让我们执行测试并验证更新功能:
User user = new User();
user.setUserId("100001");
user.setEmail("updated-email@mail.in");
User updatedUser = userClient.updateUser("100001", user);
assertEquals(user.getUserId(), updatedUser.getUserId());
assertEquals(user.getEmail(), updatedUser.getEmail());
上述测试代码看起来没有任何问题,但执行会抛出异常。
feign.RetryableException: Invalid HTTP method: PATCH executing PATCH http://localhost:8082/api/user/100001
at feign.FeignException.errorExecuting(FeignException.java:268)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:131)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:91)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
at jdk.proxy2/jdk.proxy2.$Proxy80.updateUser(Unknown Source)
at com.baeldung.cloud.openfeign.patcherror.client.UserClientUnitTest.givenUserExistsAndIsValid_whenUpdateUserCalled_thenReturnSuccess(UserClientUnitTest.java:64)
...
接下来,让我们详细研究一下这个异常。
3.4、出现 Invalid HTTP Method 错误的原因 {#34出现-invalid-http-method-错误的原因}
上述异常信息表明所请求的 HTTP 方法无效。不过,根据 HTTP 标准,PATCH 方法是有效的。
从异常堆栈中看到,异常类型是 ProtocolException
,由 HttpURLConnection
类抛出:
Caused by: java.net.ProtocolException: Invalid HTTP method: PATCH
at java.base/java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:489)
at java.base/sun.net.www.protocol.http.HttpURLConnection.setRequestMethod(HttpURLConnection.java:598)
at feign.Client$Default.convertAndSend(Client.java:170)
at feign.Client$Default.execute(Client.java:104)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:119)
原来,默认 HTTP 客户端使用 HttpURLConnection
类来建立 HTTP 连接。HttpURLConnection
有一个 setRequestMethod
方法,用于设置请求方法。
遗憾的是,HttpURLConnection
类不支持 PATCH
方法。
4、处理 PATCH Method 异常 {#4处理-patch-method-异常}
为修复该错误,我们将添加一个受支持的 HTTP Client 依赖。另外,我们还需要通过配置属性来覆盖默认的 HTTP 客户端。
4.1、添加 OkHttpClient
依赖 {#41添加-okhttpclient-依赖}
添加 feign-okhttp 依赖:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
除了 Okhttp 外,任何其他受支持的 HTTP client(如 ApacheHttpClient
)也可以。
4.2、启用 OkHttpClient
{#42启用-okhttpclient}
OkHttpClient
类将 PATCH
视为有效的 HTTP 方法,不会抛出任何异常。
让我们使用下面的配置来启用 OkHttpClient
类:
feign.okhttp.enabled=true
最后,我们重新运行测试,验证 PATCH
方法是否有效。输出的日志如下,一切OK, Feign client 未抛出任何异常。
UserClientUnitTest.givenUserExistsAndIsValid_whenUpdateUserCalled_thenReturnSuccess: 1 total, 1 passed
5、总结 {#5总结}
在本文中,我们学习了如何使用 OpenFeign
发起 PATCH
请求,以及如何使用 OkHttpClient
来处理 "Invalid HTTP method: PATCH" 异常。
参考:https://www.baeldung.com/openfeign-http-patch-request