From fbec257f9433cb3740cf92aa8bdbb0d036f8f4e0 Mon Sep 17 00:00:00 2001 From: lukas Date: Sun, 10 May 2026 13:28:43 +0200 Subject: [PATCH] feat(cache): add configurable api result cache --- .../common/NetworkFilterCommon.java | 65 ++++++++----------- .../common/cache/CacheFactory.java | 12 ++++ .../networkfilter/common/config/Config.java | 4 ++ .../config/cache/types/ApiCacheSettings.java | 17 +++++ common/src/main/resources/config.yml | 7 ++ 5 files changed, 66 insertions(+), 39 deletions(-) create mode 100644 common/src/main/java/ls/ni/networkfilter/common/config/cache/types/ApiCacheSettings.java diff --git a/common/src/main/java/ls/ni/networkfilter/common/NetworkFilterCommon.java b/common/src/main/java/ls/ni/networkfilter/common/NetworkFilterCommon.java index 8b128e3..fdc3543 100644 --- a/common/src/main/java/ls/ni/networkfilter/common/NetworkFilterCommon.java +++ b/common/src/main/java/ls/ni/networkfilter/common/NetworkFilterCommon.java @@ -21,7 +21,6 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.text.MessageFormat; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -116,22 +115,36 @@ public void debug(String pattern, Object... arguments) { private @NotNull NetworkFilterResult check(@NotNull String ip) { long startTime = System.nanoTime(); - // cache - Optional cached = Optional.ofNullable(this.filterCache.getIfPresent(ip)); - if (cached.isPresent()) { - this.debug("[{0}] Result is cached: {1}", ip, cached.get()); + FilterResult cached = this.filterCache.getIfPresent(ip); + if (cached != null) { + this.debug("[{0}] Result is cached: {1}", ip, cached); return new NetworkFilterResult( - cached.get().block(), - cached.get().asn(), - cached.get().org(), + cached.block(), + cached.asn(), + cached.org(), ip, true, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) ); } - // ignore + FilterResult filterResult = this.filterCache.get(ip, this::loadFilterResult); + + this.debug("[{0}] Requested: {1}", ip, filterResult); + + return new NetworkFilterResult( + filterResult.block(), + filterResult.asn(), + filterResult.org(), + ip, + false, + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + ); + } + + private @NotNull FilterResult loadFilterResult(@NotNull String ip) { + try { for (String network : this.configManager.getConfig().getIgnore().getNetworks()) { if (!network.contains("/")) { @@ -142,54 +155,28 @@ public void debug(String pattern, Object... arguments) { SubnetUtils subnetUtils = new SubnetUtils(network); if (subnetUtils.getInfo().isInRange(ip)) { - FilterResult filterResult = new FilterResult(false, null, null); - - this.filterCache.put(ip, filterResult); - this.debug("[{0}] IP is in ignored range: {1}", ip, network); - return new NetworkFilterResult( - false, - -1, - "Ignored Network", - ip, - false, - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) - ); + return new FilterResult(false, -1, "Ignored Network"); } } } catch (Throwable t) { this.logger.log(Level.SEVERE, "Error while checking inetAddress for ignored", t); } - // check - FilterResult filterResult; try { - filterResult = this.filterService.check(ip); + return this.filterService.check(ip); } catch (FilterException e) { this.logger.log(Level.SEVERE, "Could not check ip " + ip + " (status: " + e.getCode() + ", body: " + e.getBody().toString() + ")", e); // TODO: make configurable (something like "blockOnFilterServiceError") - should apply on rate limit? - filterResult = new FilterResult(false, null, null); + return new FilterResult(false, null, null); } catch (Throwable t) { this.logger.log(Level.SEVERE, "Could not check ip " + ip, t); // TODO: make configurable (something like "blockOnUnexpectedError") - filterResult = new FilterResult(false, null, null); + return new FilterResult(false, null, null); } - - this.filterCache.put(ip, filterResult); - - this.debug("[{0}] Requested: {1}", ip, filterResult); - - return new NetworkFilterResult( - filterResult.block(), - filterResult.asn(), - filterResult.org(), - ip, - false, - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) - ); } public void sendNotify(NetworkFilterResult result, String name, UUID uuid) { diff --git a/common/src/main/java/ls/ni/networkfilter/common/cache/CacheFactory.java b/common/src/main/java/ls/ni/networkfilter/common/cache/CacheFactory.java index 48cf0f9..96214f0 100644 --- a/common/src/main/java/ls/ni/networkfilter/common/cache/CacheFactory.java +++ b/common/src/main/java/ls/ni/networkfilter/common/cache/CacheFactory.java @@ -4,6 +4,7 @@ import ls.ni.networkfilter.common.cache.types.NoopCache; import ls.ni.networkfilter.common.cache.types.RedisCache; import ls.ni.networkfilter.common.config.Config; +import ls.ni.networkfilter.common.config.cache.types.ApiCacheSettings; import org.jetbrains.annotations.NotNull; import java.time.Duration; @@ -11,6 +12,17 @@ public class CacheFactory { public static Cache create(@NotNull Config config) { + ApiCacheSettings apiCache = config.getApiCache(); + if (apiCache != null && Boolean.TRUE.equals(apiCache.getEnabled())) { + long maximumSize = apiCache.getMaximumSize() != null ? apiCache.getMaximumSize() : 1000L; + long cacheTimeMinutes = apiCache.getCacheTimeMinutes() != null ? apiCache.getCacheTimeMinutes() : 60L; + + return new CaffeineCache( + maximumSize, + Duration.ofMinutes(cacheTimeMinutes) + ); + } + return switch (config.getCache()) { case DISABLED -> new NoopCache(); case LOCAL -> { diff --git a/common/src/main/java/ls/ni/networkfilter/common/config/Config.java b/common/src/main/java/ls/ni/networkfilter/common/config/Config.java index 8944aa4..7a5bbbf 100644 --- a/common/src/main/java/ls/ni/networkfilter/common/config/Config.java +++ b/common/src/main/java/ls/ni/networkfilter/common/config/Config.java @@ -7,6 +7,7 @@ import lombok.NoArgsConstructor; import ls.ni.networkfilter.common.config.cache.CacheSettings; import ls.ni.networkfilter.common.config.cache.CacheType; +import ls.ni.networkfilter.common.config.cache.types.ApiCacheSettings; import ls.ni.networkfilter.common.config.consequence.ConsequenceSettings; import ls.ni.networkfilter.common.config.ignore.IgnoreSettings; import ls.ni.networkfilter.common.config.notify.NotifySettings; @@ -37,6 +38,9 @@ public class Config { @NotNull private CacheSettings caches; + @Valid + private ApiCacheSettings apiCache; + @Valid @NotNull private ServiceSettings services; diff --git a/common/src/main/java/ls/ni/networkfilter/common/config/cache/types/ApiCacheSettings.java b/common/src/main/java/ls/ni/networkfilter/common/config/cache/types/ApiCacheSettings.java new file mode 100644 index 0000000..a15e54a --- /dev/null +++ b/common/src/main/java/ls/ni/networkfilter/common/config/cache/types/ApiCacheSettings.java @@ -0,0 +1,17 @@ +package ls.ni.networkfilter.common.config.cache.types; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ApiCacheSettings { + + private Boolean enabled; + + private Long maximumSize; + + private Long cacheTimeMinutes; +} diff --git a/common/src/main/resources/config.yml b/common/src/main/resources/config.yml index 761c1cb..115f35d 100644 --- a/common/src/main/resources/config.yml +++ b/common/src/main/resources/config.yml @@ -17,6 +17,13 @@ caches: uri: "redis://user:password@localhost:6379" cacheTimeMinutes: 15 +# Local in-memory cache for API/filter results. +# If enabled, this is checked before every API request. +apiCache: + enabled: true + maximumSize: 1000 + cacheTimeMinutes: 60 + services: # https://nf.ni.ls networkfilter: