|
4 | 4 | import com.google.common.collect.ImmutableMap; |
5 | 5 | import io.github.resilience4j.circuitbreaker.CircuitBreaker; |
6 | 6 | import io.github.resilience4j.reactor.circuitbreaker.operator.CircuitBreakerOperator; |
| 7 | +import io.netty.channel.ChannelOption; |
7 | 8 | import lombok.extern.slf4j.Slf4j; |
8 | 9 | import org.apache.commons.lang3.StringUtils; |
9 | 10 | import org.apache.commons.lang3.exception.ExceptionUtils; |
|
30 | 31 | import org.springframework.http.HttpHeaders; |
31 | 32 | import org.springframework.http.HttpStatus; |
32 | 33 | import org.springframework.http.MediaType; |
| 34 | +import org.springframework.http.client.reactive.ReactorClientHttpConnector; |
33 | 35 | import org.springframework.stereotype.Component; |
34 | 36 | import org.springframework.web.reactive.function.BodyExtractors; |
35 | 37 | import org.springframework.web.reactive.function.client.WebClient; |
36 | 38 | import org.springframework.web.reactive.function.server.ServerRequest; |
37 | 39 | import org.springframework.web.reactive.function.server.ServerResponse; |
38 | 40 | import reactor.core.publisher.Flux; |
39 | 41 | import reactor.core.publisher.Mono; |
| 42 | +import reactor.core.publisher.ParallelFlux; |
40 | 43 | import reactor.core.publisher.SynchronousSink; |
41 | 44 | import reactor.core.scheduler.Schedulers; |
| 45 | +import reactor.netty.http.client.HttpClient; |
42 | 46 |
|
43 | 47 | import java.io.IOException; |
44 | | -import java.util.ArrayList; |
| 48 | +import java.time.Duration; |
45 | 49 | import java.util.HashMap; |
46 | 50 | import java.util.List; |
47 | 51 | import java.util.Map; |
@@ -80,7 +84,15 @@ public PostCacheHandler(final ReactiveRepository<PayloadWrapper, String> reposit |
80 | 84 | this.repository = repository; |
81 | 85 | this.config = config; |
82 | 86 | if (config.getSecondaryUris() != null) { |
83 | | - config.getSecondaryUris().forEach(ip -> webClients.put(ip, WebClient.create(ip))); |
| 87 | + config.getSecondaryUris().forEach(url -> { |
| 88 | + HttpClient httpClient = HttpClient.create() |
| 89 | + .responseTimeout(Duration.ofMillis(config.getSecondaryCacheTimeoutMs())) |
| 90 | + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getSecondaryCacheTimeoutMs()); |
| 91 | + webClients.put(url, WebClient.builder() |
| 92 | + .baseUrl(url) |
| 93 | + .clientConnector(new ReactorClientHttpConnector(httpClient)) |
| 94 | + .build()); |
| 95 | + }); |
84 | 96 | } |
85 | 97 | this.builder = builder; |
86 | 98 | this.metricTagPrefix = "write"; |
@@ -202,33 +214,46 @@ private long adjustExpiry(Long expiry) { |
202 | 214 | } |
203 | 215 |
|
204 | 216 | private void sendRequestToSecondaryPrebidCacheHosts(List<PayloadWrapper> payloadWrappers, String secondaryCache) { |
205 | | - if (!"yes".equals(secondaryCache) && webClients.size() != 0) { |
206 | | - final List<PayloadTransfer> payloadTransfers = new ArrayList<>(); |
207 | | - for (PayloadWrapper payloadWrapper : payloadWrappers) { |
208 | | - payloadTransfers.add(wrapperToTransfer(payloadWrapper)); |
209 | | - } |
| 217 | + if (!"yes".equals(secondaryCache) && !webClients.isEmpty()) { |
| 218 | + Flux.fromIterable(payloadWrappers) |
| 219 | + .map(this::wrapperToTransfer) |
| 220 | + .collectList() |
| 221 | + .flatMapMany(this::createSecondaryCacheRequests) |
| 222 | + .subscribe(); |
| 223 | + } |
| 224 | + } |
| 225 | + |
| 226 | + private ParallelFlux<Void> createSecondaryCacheRequests(List<PayloadTransfer> payloadTransfers) { |
| 227 | + return Flux.fromIterable(webClients.entrySet()) |
| 228 | + .parallel() |
| 229 | + .runOn(Schedulers.parallel()) |
| 230 | + .flatMap(entry -> sendRequestToSecondaryCache(entry.getValue(), entry.getKey(), payloadTransfers)); |
| 231 | + } |
210 | 232 |
|
211 | | - webClients.forEach((ip, webClient) -> webClient.post() |
212 | | - .uri(uriBuilder -> uriBuilder.path(config.getSecondaryCachePath()) |
213 | | - .queryParam("secondaryCache", "yes").build()) |
214 | | - .contentType(MediaType.APPLICATION_JSON) |
215 | | - .headers(enrichWithSecurityHeader()) |
216 | | - .bodyValue(RequestObject.of(payloadTransfers)) |
217 | | - .exchange() |
218 | | - .transform(CircuitBreakerOperator.of(circuitBreaker)) |
219 | | - .doOnError(throwable -> { |
| 233 | + private Mono<Void> sendRequestToSecondaryCache(WebClient webClient, |
| 234 | + String url, |
| 235 | + List<PayloadTransfer> payloadTransfers) { |
| 236 | + return webClient.post() |
| 237 | + .uri(uriBuilder -> uriBuilder.path(config.getSecondaryCachePath()) |
| 238 | + .queryParam("secondaryCache", "yes").build()) |
| 239 | + .contentType(MediaType.APPLICATION_JSON) |
| 240 | + .headers(enrichWithSecurityHeader()) |
| 241 | + .bodyValue(RequestObject.of(payloadTransfers)) |
| 242 | + .exchangeToMono(clientResponse -> { |
| 243 | + if (clientResponse.statusCode() != HttpStatus.OK) { |
220 | 244 | metricsRecorder.getSecondaryCacheWriteError().increment(); |
221 | | - log.info("Failed to send request: '{}', cause: '{}'", |
222 | | - ExceptionUtils.getMessage(throwable), ExceptionUtils.getMessage(throwable)); |
223 | | - }) |
224 | | - .subscribe(clientResponse -> { |
225 | | - if (clientResponse.statusCode() != HttpStatus.OK) { |
226 | | - metricsRecorder.getSecondaryCacheWriteError().increment(); |
227 | | - log.debug(clientResponse.statusCode().toString()); |
228 | | - log.info("Failed to write to remote address : {}", ip); |
229 | | - } |
230 | | - })); |
231 | | - } |
| 245 | + log.debug(clientResponse.statusCode().toString()); |
| 246 | + log.error("Failed to write to remote address: {}", url); |
| 247 | + } |
| 248 | + return clientResponse.releaseBody(); |
| 249 | + }) |
| 250 | + .transform(CircuitBreakerOperator.of(circuitBreaker)) |
| 251 | + .doOnError(throwable -> { |
| 252 | + metricsRecorder.getSecondaryCacheWriteError().increment(); |
| 253 | + log.error("Failed to send request: '{}', cause: '{}'", |
| 254 | + ExceptionUtils.getMessage(throwable), ExceptionUtils.getMessage(throwable)); |
| 255 | + }) |
| 256 | + .then(); |
232 | 257 | } |
233 | 258 |
|
234 | 259 | private Consumer<HttpHeaders> enrichWithSecurityHeader() { |
|
0 commit comments