Извлечение значений ответа 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
конечная точка, которая возвращает пустую карту. Тогда у меня есть
ModifyResponseBodyGatewayFilterFactory
filter, который срабатывает и создает два совершенно новых запроса к двум другим конечным точкам в моем демонстрационном приложении.
Я хочу объединить эти два ответа в один, передав их в новую карту, которую я возвращаю в цепочку фильтров. Вот как выглядит мой фильтр:
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()
но ни один не работал.
Может кто-нибудь, пожалуйста, дайте мне знать, как извлечь эти значения?