【问题标题】:Spring Cloud Gateway modifyResponseBody of FluxSpring Cloud Gateway 修改 Flux 的ResponseBody
【发布时间】:2020-10-12 15:55:47
【问题描述】:

我在 Spring Cloud Gateway 中修改 Flux 响应时遇到了一些麻烦。我的设置(简化)如下: 我有 2 个域对象:

@Data
@AllArgsConstructor
public class PersonV10 {
    @NotNull
    private String name;
}


@Data
@AllArgsConstructor
public class PersonV20 {
    @NotNull
    private String firstName;

    @NotNull
    private String lastName;
}

现在我的服务正在返回 PersonV20 的实例并使用 Spring Cloud Gateway 我想将响应修改为 PersonV10。在 /person 我得到一个 Flux 作为响应,在 /person/{id} 我得到一个 Mono 作为响应。

我的路线如下:

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
            .route("rewrite_response_v20", r -> r.path("/person/**")
                    .filters(f -> f.modifyResponseBody(PersonV20.class, PersonV10.class,
                            (exchange, p) -> Mono.just(new PersonV10(p.getFirstName() + " " + p.getLastName()))))
                    .uri("https://localhost:8444"))
                    .build();
}

当调用 /person/{id} 端点时,它工作得很好,我的响应很好地修改为 PersonV10。但是,当我现在调用 /person 端点时,我得到了一个 Flux,我在 Spring Cloud Gateway 中得到了这个异常:

org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize instance of `com.example.gateway.mutate_response_filter.PersonV20` out of START_ARRAY token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.example.gateway.mutate_response_filter.PersonV20` out of START_ARRAY token
 at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1]
    at org.springframework.http.codec.json.AbstractJackson2Decoder.processException(AbstractJackson2Decoder.java:215) ~[spring-web-5.2.6.RELEASE.jar:5.2.6.RELEASE]

现在我想这是有道理的,因为我得到了 Flux 作为响应,而在 modifyResponseBody 中我使用的是 Mono。但是我不清楚在这种情况下如何使用 Flux。有人可以指出我正确的方向吗?谢谢!

【问题讨论】:

    标签: java spring-cloud-gateway


    【解决方案1】:

    从 spring cloud gateway 源代码中,您会找到一个 TODO,上面写着“TODO:flux or mono”。这意味着最近只支持单声道类型。

    // TODO: flux or mono
    Mono modifiedBody = extractBody(exchange, clientResponse, inClass)
                        .flatMap(originalBody -> config.getRewriteFunction().apply(exchange,
                                originalBody))
                        .switchIfEmpty(Mono.defer(() -> (Mono) config.getRewriteFunction()
                                .apply(exchange, null)));
    

    如果你想使用默认的 ModifyResponseBodyFilter,也许你可以把类放在一个包装类中,例如

    class PersonV2ListWrapper{
        public List<PersonV2> persons;
    }
    

    否则,实现 ModifyResponseBodyFilter 的功能并支持 Flux 的自过滤器也可能会有所帮助。

    【讨论】:

    • 谢谢!包装器建议非常有用。它没有开箱即用,但你确实让我走上了正确的轨道。事实证明,如果我将类型设置为 Person 对象数组,那么它确实有效!我将在单独的答案中发布我的解决方案的更完整答案。
    • 希望你的新帖子。好像我误解了源代码的那一部分。正如你所说,我也会试试这个。
    • 我已经发布了工作代码。对您的观点非常感兴趣,为什么在没有额外谓词的情况下无法正确选择路径。
    • 到@JonckvanderKogel,你可以测试两种方法:r -> r.path(ROOT_PATTERN).order(a big value)。要么。 r -> r.path(ROOT_PATTERN, false)
    【解决方案2】:

    感谢@eric 的提示,我得以完成这项工作。一些警告:现在将集合包装在 Mono 中,当然我现在正在消除反应流的一些好处,因此更好的方法当然是编写支持 Flux 的 ModifyResponseBodyFilter 的实现。另一个项目与原始问题的主题略有不同,因此我将在本文结尾处介绍。

    最终为我工作的解决方案:

    private static final String ROOT_PATTERN = "/person";
    private static final String INDIVIDUAL_PATTERN = "/person/*";
    private AntPathMatcher pathMatcher = new AntPathMatcher();
    
    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("rewrite_response_v20_flux", r -> r.path(ROOT_PATTERN)
                        .filters(f -> f.modifyResponseBody(PersonV20[].class, PersonV10[].class,
                                (exchange, s) -> Mono.just(
                                        Stream.of(s)
                                                .map(v20 -> new PersonV10(v20.getId(), v20.getFirstName() + " " + v20.getLastName()))
                                                .toArray(PersonV10[]::new)
                                )))
                        .uri("https://localhost:8444")
                        .predicate(e -> pathMatcher.match(ROOT_PATTERN, e.getRequest().getPath().value()))
                )
                .route("rewrite_response_v20_mono", r -> r.path(INDIVIDUAL_PATTERN)
                        .filters(f -> f.rewritePath("/person/(?<ID>.*)", "/person/${ID}")
                                .modifyResponseBody(PersonV20.class, PersonV10.class,
                                        (exchange, s) -> Mono.just(new PersonV10(s.getId(), s.getFirstName() + " " + s.getLastName()))))
                        .uri("https://localhost:8444")
                        .predicate(e -> pathMatcher.match(INDIVIDUAL_PATTERN, e.getRequest().getPath().value()))
                )
                .build();
    }
    

    现在回到我上面提到的警告:让我感到奇怪的是,目前这两种路径模式不足以区分两个不同的端点。我编写了一个测试,证明 AntPathMatcher(如果您检查 JavaDoc,则在后台使用)能够使用我提供的模式区分 /person 和 /person/1。以下是测试:

    public class PathMatcherTests {
        private AntPathMatcher pathMatcher = new AntPathMatcher();
    
        private static final String ROOT_ENDPOINT = "/person";
        private static final String INDIVIDUAL_ENDPOINT = "/person/1";
    
        @Test
        public void shouldMatchRootButNotIndividual() {
            String rootMatchPattern = "/person";
    
            assertTrue(pathMatcher.match(rootMatchPattern, ROOT_ENDPOINT));
            assertFalse(pathMatcher.match(rootMatchPattern, INDIVIDUAL_ENDPOINT));
        }
    
        @Test
        public void shouldMatchIndividualButNotRoot() {
            String individualMatchPattern = "/person/*";
    
            assertFalse(pathMatcher.match(individualMatchPattern, ROOT_ENDPOINT));
            assertTrue(pathMatcher.match(individualMatchPattern, INDIVIDUAL_ENDPOINT));
        }
    }
    

    但是,即使这个测试成功,如果我没有在测试请求路径的末尾添加额外的谓词,它对我也不起作用。如果我将 rewrite_response_v20_flux 路由放在首位,则个人请求将失败,并出现 DecodingException ,就像我在原始帖子中一样,如果我将 rewrite_response_v20_mono 路由放在首位,则人员请求的完整列表将失败。在我看来,额外的谓词不应该是必要的,但为了让它工作,我必须添加这些。

    非常欢迎有关此主题的任何其他 cmets/帮助,因为即使这“有效”,解决方案仍然不理想。

    希望这至少对某人有所帮助。

    【讨论】:

      猜你喜欢
      • 2020-01-21
      • 2020-07-19
      • 1970-01-01
      • 2018-07-06
      • 2021-07-23
      • 1970-01-01
      • 1970-01-01
      • 2021-10-20
      • 1970-01-01
      相关资源
      最近更新 更多