diff --git a/src/main/java/net/ripe/rpki/rsyncit/RsyncitApplication.java b/src/main/java/net/ripe/rpki/rsyncit/RsyncitApplication.java index 0813d32..e28a9a0 100644 --- a/src/main/java/net/ripe/rpki/rsyncit/RsyncitApplication.java +++ b/src/main/java/net/ripe/rpki/rsyncit/RsyncitApplication.java @@ -1,14 +1,26 @@ package net.ripe.rpki.rsyncit; +import io.micrometer.common.KeyValues; +import io.netty.channel.ChannelOption; import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import io.netty.resolver.ResolvedAddressTypes; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.ClientRequestObservationContext; +import org.springframework.web.reactive.function.client.ClientRequestObservationConvention; +import org.springframework.web.reactive.function.client.DefaultClientRequestObservationConvention; import org.springframework.web.reactive.function.client.WebClient; -import net.ripe.rpki.rsyncit.util.http.WebClientBuilderFactory; import net.ripe.rpki.rsyncit.config.AppConfig; +import reactor.netty.http.client.HttpClient; +import java.time.Duration; import java.util.Properties; +import java.util.concurrent.TimeUnit; @SpringBootApplication public class RsyncitApplication { @@ -23,11 +35,40 @@ public static void main(String[] args) { } @Bean - public WebClientBuilderFactory webclientConfigurer(WebClient.Builder baseBuilder, AppConfig appConfig) { - // Explicit event loop is required for custom DnsNameResolverBuilder - NioEventLoopGroup group = new NioEventLoopGroup(1); + public WebClient webclientConfiguration(WebClient.Builder baseBuilder, AppConfig appConfig) { + final var userAgent = "rsyncit %s".formatted(appConfig.getInfo().gitCommitId()); - return new WebClientBuilderFactory(group, baseBuilder, "rsyncit %s".formatted(appConfig.getInfo().gitCommitId())); + var httpClient = HttpClient.create() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) + .responseTimeout(Duration.ofMillis(5000)) + // remember: read and write timeouts are per read, not for a request. + .doOnConnected(conn -> + conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS)) + .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)) + ).resolver(spec -> + spec.resolvedAddressTypes(ResolvedAddressTypes.IPV6_PREFERRED) + .completeOncePreferredResolved(false)); + + + return baseBuilder + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .defaultHeader(HttpHeaders.USER_AGENT, userAgent) + .build(); } + /** + * Return an observation customiser that only differs in that it omits the URL. + * The hostname for the request is the clientName. + * + * The full URL is in a high cardinality value (which would be used by observability tools) + */ + @Bean + public ClientRequestObservationConvention nonUriClientRequestObservationConvention() { + return new DefaultClientRequestObservationConvention() { + @Override + public KeyValues getLowCardinalityKeyValues(ClientRequestObservationContext context) { + return KeyValues.of(method(context), status(context), clientName(context), exception(context), outcome(context)); + } + }; + } } diff --git a/src/main/java/net/ripe/rpki/rsyncit/service/SyncService.java b/src/main/java/net/ripe/rpki/rsyncit/service/SyncService.java index df31fdb..c295ca5 100644 --- a/src/main/java/net/ripe/rpki/rsyncit/service/SyncService.java +++ b/src/main/java/net/ripe/rpki/rsyncit/service/SyncService.java @@ -9,9 +9,9 @@ import net.ripe.rpki.rsyncit.rrdp.State; import net.ripe.rpki.rsyncit.rsync.RsyncWriter; import net.ripe.rpki.rsyncit.util.Time; -import net.ripe.rpki.rsyncit.util.http.WebClientBuilderFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; import java.time.Instant; import java.time.temporal.ChronoUnit; @@ -21,24 +21,24 @@ @Getter public class SyncService { - private final WebClientBuilderFactory webClientFactory; + private final WebClient webClient; private final AppConfig appConfig; private final State state; private final RRDPFetcherMetrics metrics; @Autowired - public SyncService(WebClientBuilderFactory webClientFactory, + public SyncService(WebClient webClient, AppConfig appConfig, MeterRegistry meterRegistry) { - this.webClientFactory = webClientFactory; this.appConfig = appConfig; + this.webClient = webClient; this.metrics = new RRDPFetcherMetrics(meterRegistry); this.state = new State(); } public void sync() { var config = appConfig.getConfig(); - var rrdpFetcher = new RrdpFetcher(config, webClientFactory.builder().build(), state); + var rrdpFetcher = new RrdpFetcher(config, webClient, state); var t = Time.timed(rrdpFetcher::fetchObjects); final RrdpFetcher.FetchResult fetchResult = t.getResult(); diff --git a/src/main/java/net/ripe/rpki/rsyncit/util/http/WebClientBuilderFactory.java b/src/main/java/net/ripe/rpki/rsyncit/util/http/WebClientBuilderFactory.java deleted file mode 100644 index fea64c5..0000000 --- a/src/main/java/net/ripe/rpki/rsyncit/util/http/WebClientBuilderFactory.java +++ /dev/null @@ -1,55 +0,0 @@ -package net.ripe.rpki.rsyncit.util.http; - -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.socket.nio.NioDatagramChannel; -import io.netty.handler.timeout.ReadTimeoutHandler; -import io.netty.handler.timeout.WriteTimeoutHandler; -import io.netty.resolver.ResolvedAddressTypes; -import io.netty.resolver.dns.DnsNameResolverBuilder; -import org.springframework.http.HttpHeaders; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.netty.http.client.HttpClient; - -import java.time.Duration; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -/** - * Customise a spring-configured WebClient.Builder with rpki-monitoring specific settings. - * - * @param eventLoopGroup to run in - * @param baseBuilder to customise - * @param userAgent to set in headers - */ -public record WebClientBuilderFactory(EventLoopGroup eventLoopGroup, WebClient.Builder baseBuilder, String userAgent) { - - private HttpClient initialHttpClientConfig() { - return HttpClient.create() - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) - .responseTimeout(Duration.ofMillis(5000)) - // remember: read and write timeouts are per read, not for a request. - .doOnConnected(conn -> - conn.addHandlerLast(new ReadTimeoutHandler(5000, TimeUnit.MILLISECONDS)) - .addHandlerLast(new WriteTimeoutHandler(5000, TimeUnit.MILLISECONDS)) - ); - } - - private WebClient.Builder forHttpClient(HttpClient httpClient) { - return baseBuilder - .clientConnector(new ReactorClientHttpConnector(httpClient)) - .defaultHeader(HttpHeaders.USER_AGENT, userAgent); - - } - - public WebClient.Builder builder() { - return forHttpClient( - initialHttpClientConfig() - .resolver(spec -> - spec.resolvedAddressTypes(ResolvedAddressTypes.IPV6_PREFERRED) - .completeOncePreferredResolved(false) - ) - ); - } -}