使用 OpenFeign 发起 PATCH 请求

1、概览

通过 REST API 更新资源时,可以使用 PATCH 方法。该方法专门用于“更新部分字段”的场景。当需要完全更改现有资源时(全量替换),可以使用 PUT 方法。

在本教程中,我们将学习如何在 OpenFeign 中设置 HTTP PATCH 方法。我们还将演示在 Feign client 测试 PATCH 方法时出现的异常情况,以及解决方案。

2、Spring Boot 中的应用示例

假设我们需要构建一个简单的微服务,调用下游服务进行部分更新。

2.1、Maven 依赖

首先,我们要添加 spring-boot-starter-webspring-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

现在,让我们使用 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

现在,让我们通过模拟 HTTP 调用为 UserClient 实现一个测试用例。

3.1、设置 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 使用的 hostport 与 Feign client 配置的相同。

3.2、PATCH 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 请求

测试时,我们将把 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 错误的原因

上述异常信息表明所请求的 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 异常

为修复该错误,我们将添加一个受支持的 HTTP Client 依赖。另外,我们还需要通过配置属性来覆盖默认的 HTTP 客户端。

4.1、添加 OkHttpClient 依赖

添加 feign-okhttp 依赖:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

除了 Okhttp 外,任何其他受支持的 HTTP client(如 ApacheHttpClient)也可以。

4.2、启用 OkHttpClient

OkHttpClient 类将 PATCH 视为有效的 HTTP 方法,不会抛出任何异常。

让我们使用下面的配置来启用 OkHttpClient 类:

feign.okhttp.enabled=true

最后,我们重新运行测试,验证 PATCH 方法是否有效。输出的日志如下,一切OK, Feign client 未抛出任何异常。

UserClientUnitTest.givenUserExistsAndIsValid_whenUpdateUserCalled_thenReturnSuccess: 1 total, 1 passed

5、总结

在本文中,我们学习了如何使用 OpenFeign 发起 PATCH 请求,以及如何使用 OkHttpClient 来处理 “Invalid HTTP method: PATCH” 异常。


参考:https://www.baeldung.com/openfeign-http-patch-request