Извлечение значений ответа WebClient GET в фильтре Spring Cloud Gateway

Моя конечная цель — реализовать способ выполнения составных вызовов API в теле фильтра маршрута шлюза. У меня есть очень простое демонстрационное приложение, работающее на порту 9000и раскрывая несколько конечных точек. Вот контроллер REST:

      @RestController
@RequestMapping("/composite")
public class CompositeCallController {

    @GetMapping("/test/one")
    public Map<String, Object> first() {
        Map<String, Object> output = new HashMap<>();
        output.put("response-1-1", "FIRST 1");
        output.put("response-1-2", "FIRST 2");
        output.put("response-1-3", "FIRST 3");
        return output;
    }

    @GetMapping("/test/two")
    public Map<String, Object> second() {
        Map<String, Object> output = new HashMap<>();
        output.put("response-2-1", "SECOND 1");
        output.put("response-2-2", "SECOND 2");
        output.put("response-2-3", "SECOND 3");
        return output;
    }

    @GetMapping
    public Map<String, Object> init() {
        return new HashMap<>();
    }
}

Оба контроллера возвращают простую карту с несколькими записями внутри. У меня есть приложение Spring Cloud Gateway, работающее на отдельном порту, и я настроил через YML маршрут, который ведет к localhost:9000/compositeконечная точка, которая возвращает пустую карту. Тогда у меня есть ModifyResponseBodyGatewayFilterFactoryfilter, который срабатывает и создает два совершенно новых запроса к двум другим конечным точкам в моем демонстрационном приложении.

Я хочу объединить эти два ответа в один, передав их в новую карту, которую я возвращаю в цепочку фильтров. Вот как выглядит мой фильтр:

          public GatewayFilter apply(final Config config) {
        final ModifyResponseBodyGatewayFilterFactory.Config modifyResponseBodyFilterFactoryConfig = new ModifyResponseBodyGatewayFilterFactory.Config();
        modifyResponseBodyFilterFactoryConfig.setRewriteFunction(Map.class, Map.class, (exchange, body) -> {
            WebClient client = WebClient.create();

            Mono<Map<String, Object>> firstCallMono = client.get()
                    .uri(FIRST_SERVICE_URL)
                    .retrieve()
                    .bodyToMono(json);

            Mono<Map<String, Object>> secondCallMono = client.get()
                    .uri(SECOND_SERVICE_URL)
                    .retrieve()
                    .bodyToMono(json);

            Map<String, Object> output = new HashMap<>();
            Mono.zip(firstCallMono, secondCallMono)
                    .log()
                    .subscribe(v -> {
                        System.out.println("FIRST VALUE = " + v.getT1());
                        System.out.println("SECOND VALUE = " + v.getT2());
                        output.put("1", v.getT1());
                        output.put("2", v.getT2());
                    });

            System.out.println("OUTPUT VALUE 1 = " + output.get("1"));
            System.out.println("OUTPUT VALUE 2 = " + output.get("2"));

            return Mono.just(output);
        });
        return modifyResponseBodyFilterFactory.apply(modifyResponseBodyFilterFactoryConfig);
    }

В jsonтип определяется как private final ParameterizedTypeReference<Map<String, Object>> json = new ParameterizedTypeReference<>() {};

URI следующие:

      public static final String FIRST_SERVICE_URL = "http://localhost:9000/composite/test/one";
public static final String SECOND_SERVICE_URL = "http://localhost:9000/composite/test/two";

И вот моя конфигурация шлюза для справки:

      logging:
  level:
    reactor:
      netty: INFO
    org:
      springframework:
        cloud:
          gateway: TRACE

spring:
  codec:
    max-in-memory-size: 20MB
  cloud:
    gateway:
      httpclient:
        wiretap: true
      httpserver:
        wiretap: true
      routes:
        - id: composite-call-test
          uri: http://localhost:9000
          predicates:
            - Path=/composite/**
          filters:
            - CompositeApiCallFilter

Чтобы объединить моно, я использую Mono.zip()поскольку кажется, что он служит именно этой цели. Я намеренно поставил два System.out.println()s внутри тела, чтобы убедиться, что ответы на два вышеупомянутых запроса WebClient действительно верны, и это определенно выглядит так:

      FIRST VALUE = {response-1-2=FIRST 2, response-1-3=FIRST 3, response-1-1=FIRST 1}
SECOND VALUE = {response-2-3=SECOND 3, response-2-1=SECOND 1, response-2-2=SECOND 2}

Тем не менее, я также поместил два отпечатка консоли после zip()чтобы проверить, есть ли что-то на карте, и она по какой-то причине совершенно пуста:

      OUTPUT VALUE 1 = null
OUTPUT VALUE 2 = null

Вот полный вывод консоли из запроса на справку:

      2022-05-13 14:53:22.087  INFO 72992 --- [ctor-http-nio-3] reactor.Mono.Zip.1                       : onSubscribe([Fuseable] MonoZip.ZipCoordinator)
2022-05-13 14:53:22.090  INFO 72992 --- [ctor-http-nio-3] reactor.Mono.Zip.1                       : request(unbounded)
OUTPUT VALUE 1 = null
OUTPUT VALUE 2 = null
2022-05-13 14:53:22.139  INFO 72992 --- [ctor-http-nio-3] reactor.Mono.Zip.1                       : onNext([{response-1-2=FIRST 2, response-1-3=FIRST 3, response-1-1=FIRST 1},{response-2-3=SECOND 3, response-2-1=SECOND 1, response-2-2=SECOND 2}])
FIRST VALUE = {response-1-2=FIRST 2, response-1-3=FIRST 3, response-1-1=FIRST 1}
SECOND VALUE = {response-2-3=SECOND 3, response-2-1=SECOND 1, response-2-2=SECOND 2}
2022-05-13 14:53:22.140  INFO 72992 --- [ctor-http-nio-3] reactor.Mono.Zip.1                       : onComplete()

Я попробовал кучу других способов сделать это, например, объединить два s в a с помощью firstCallMono.mergeWith(secondCallMono)а затем подписаться на полученный Fluxобъект и заполнение карты, но результат идентичен.

Я также пытался поставить два Monoв Pairобъект и извлечение значений следующим образом:

      Pair<Mono<Map<String, Object>>, Mono<Map<String, Object>>> pair = new Pair(firstCall, secondCallDTOMono);
pair.getValue0().log().subscribe(v -> output.put("1", v));
pair.getValue1().log().subscribe(v -> output.put("2", v));

Но опять же, outputкарта в конце пуста, и я не понимаю, почему. Кажется, что все возвращается из WebClient .get()вызов типа MonoFlapMap.FlatMapMainи я подозреваю, что проблема связана с распаковкой значений этого типа в мой обычный HashMap, но я не знаю, как решить эту проблему. я пытался использовать .map()а также .flatMap()но ни один не работал.

Может кто-нибудь, пожалуйста, дайте мне знать, как извлечь эти значения?

0 ответов

Другие вопросы по тегам