diff --git a/bukkit/loader/build.gradle.kts b/bukkit/loader/build.gradle.kts index 2465542bb..901f5444a 100644 --- a/bukkit/loader/build.gradle.kts +++ b/bukkit/loader/build.gradle.kts @@ -85,5 +85,6 @@ tasks { relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy") relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2") + relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j") } } diff --git a/bukkit/paper-loader/build.gradle.kts b/bukkit/paper-loader/build.gradle.kts index 17432d821..ce8513840 100644 --- a/bukkit/paper-loader/build.gradle.kts +++ b/bukkit/paper-loader/build.gradle.kts @@ -172,6 +172,7 @@ tasks { relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy") relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2") + relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j") } } diff --git a/common-files/src/main/resources/craft-engine.properties b/common-files/src/main/resources/craft-engine.properties index 23c13b14f..1b396c4fe 100644 --- a/common-files/src/main/resources/craft-engine.properties +++ b/common-files/src/main/resources/craft-engine.properties @@ -34,4 +34,5 @@ reactive-streams=${reactive_streams_version} amazon-sdk-s3=${amazon_awssdk_version} amazon-sdk-eventstream=${amazon_awssdk_eventstream_version} evalex=${evalex_version} -jimfs=${jimfs_version} \ No newline at end of file +jimfs=${jimfs_version} +bucket4j=${bucket4j_version} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 9ad35c52d..be15d9371 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -21,7 +21,7 @@ dependencies { implementation("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}") implementation("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}") // S3 - implementation("net.momirealms:craft-engine-s3:0.8") + implementation("net.momirealms:craft-engine-s3:0.9") // Util compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}") // Adventure @@ -69,6 +69,8 @@ dependencies { compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}") // concurrentutil compileOnly("ca.spottedleaf:concurrentutil:${rootProject.properties["concurrent_util_version"]}") + // bucket4j + compileOnly("com.bucket4j:bucket4j_jdk17-core:${rootProject.properties["bucket4j_version"]}") } java { @@ -107,6 +109,7 @@ tasks { relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp") relocate("io.netty.handler.codec.spdy", "net.momirealms.craftengine.libraries.netty.handler.codec.spdy") relocate("io.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2") + relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j") // bucket4j } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java index 4f93508c7..812caaef6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHost.java @@ -1,5 +1,6 @@ package net.momirealms.craftengine.core.pack.host.impl; +import io.github.bucket4j.Bandwidth; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory; @@ -12,6 +13,7 @@ import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.util.ResourceConfigUtils; import java.nio.file.Path; +import java.time.Duration; import java.util.List; import java.util.Map; import java.util.UUID; @@ -76,14 +78,19 @@ public class SelfHost implements ResourcePackHost { boolean oneTimeToken = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("one-time-token", true), "one-time-token"); String protocol = arguments.getOrDefault("protocol", "http").toString(); boolean denyNonMinecraftRequest = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("deny-non-minecraft-request", true), "deny-non-minecraft-request"); - Map rateMap = MiscUtils.castToMap(arguments.get("rate-map"), true); + Map rateMap = MiscUtils.castToMap(arguments.getOrDefault("rate-map", arguments.get("rate-limit")), true); int maxRequests = 5; - int resetInterval = 20_000; + int resetInterval = 20; if (rateMap != null) { - maxRequests = ResourceConfigUtils.getAsInt(rateMap.getOrDefault("max-requests", 5), "max-requests"); - resetInterval = ResourceConfigUtils.getAsInt(rateMap.getOrDefault("reset-interval", 20), "reset-interval") * 1000; + maxRequests = Math.max(ResourceConfigUtils.getAsInt(rateMap.getOrDefault("max-requests", 5), "max-requests"), 1); + resetInterval = Math.max(ResourceConfigUtils.getAsInt(rateMap.getOrDefault("reset-interval", 20), "reset-interval"), 1); } - selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, maxRequests, resetInterval, oneTimeToken); + Bandwidth limit = Bandwidth.builder() + .capacity(maxRequests) + .refillGreedy(maxRequests, Duration.ofSeconds(resetInterval)) + .initialTokens(maxRequests / 2) // 修正首次可以直接突破限制请求 maxRequests * 2 次 + .build(); + selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, limit, oneTimeToken); return INSTANCE; } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java index c320f80df..00d7c8f6d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/pack/host/impl/SelfHostHttpServer.java @@ -3,6 +3,8 @@ package net.momirealms.craftengine.core.pack.host.impl; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Scheduler; +import io.github.bucket4j.Bandwidth; +import io.github.bucket4j.Bucket; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.Unpooled; import io.netty.channel.*; @@ -23,6 +25,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.time.Duration; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; @@ -34,17 +37,20 @@ public class SelfHostHttpServer { .scheduler(Scheduler.systemScheduler()) .expireAfterWrite(1, TimeUnit.MINUTES) .build(); - private final Cache ipAccessCache = Caffeine.newBuilder() + private final Cache ipRateLimiters = Caffeine.newBuilder() .maximumSize(256) .scheduler(Scheduler.systemScheduler()) - .expireAfterWrite(10, TimeUnit.MINUTES) + .expireAfterAccess(10, TimeUnit.MINUTES) .build(); private final AtomicLong totalRequests = new AtomicLong(); private final AtomicLong blockedRequests = new AtomicLong(); - private int rateLimit = 1; - private long rateLimitInterval = 1000; + private Bandwidth limit = Bandwidth.builder() + .capacity(1) + .refillGreedy(1, Duration.ofSeconds(1)) + .initialTokens(1) + .build(); private String ip = "localhost"; private int port = -1; private String protocol = "http"; @@ -72,15 +78,13 @@ public class SelfHostHttpServer { String url, boolean denyNonMinecraft, String protocol, - int maxRequests, - int resetInterval, + Bandwidth limit, boolean token) { this.ip = ip; this.url = url; this.denyNonMinecraft = denyNonMinecraft; this.protocol = protocol; - this.rateLimit = maxRequests; - this.rateLimitInterval = resetInterval; + this.limit = limit; this.useToken = token; if (port <= 0 || port > 65535) { @@ -214,22 +218,12 @@ public class SelfHostHttpServer { } private boolean checkRateLimit(String clientIp) { - IpAccessRecord record = ipAccessCache.getIfPresent(clientIp); - long now = System.currentTimeMillis(); - - if (record == null) { - record = new IpAccessRecord(now, 1); - ipAccessCache.put(clientIp, record); - return false; + Bucket rateLimiter = ipRateLimiters.get(clientIp, k -> Bucket.builder().addLimit(limit).build()); + if (rateLimiter == null) { // 怎么可能null? + rateLimiter = Bucket.builder().addLimit(limit).build(); + ipRateLimiters.put(clientIp, rateLimiter); } - - if (now - record.lastAccessTime > rateLimitInterval) { - record.lastAccessTime = now; - record.accessCount = 1; - return false; - } - - return ++record.accessCount > rateLimit; + return !rateLimiter.tryConsume(1); } private boolean validateToken(String token) { @@ -312,14 +306,4 @@ public class SelfHostHttpServer { CraftEngine.instance().logger().severe("SHA-1 algorithm not available", e); } } - - private static class IpAccessRecord { - long lastAccessTime; - int accessCount; - - IpAccessRecord(long lastAccessTime, int accessCount) { - this.lastAccessTime = lastAccessTime; - this.accessCount = accessCount; - } - } } \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 8bb7f61d3..8bbfb01c0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -419,7 +419,8 @@ public abstract class CraftEngine implements Plugin { Dependencies.LZ4, Dependencies.EVALEX, Dependencies.NETTY_HTTP, - Dependencies.JIMFS + Dependencies.JIMFS, + Dependencies.BUCKET_4_J ); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java index 0d44161bb..bd66b18cb 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/dependency/Dependencies.java @@ -372,6 +372,13 @@ public class Dependencies { List.of(Relocation.of("jimfs", "com{}google{}common{}jimfs")) ); + public static final Dependency BUCKET_4_J = new Dependency( + "bucket4j", + "com{}bucket4j", + "bucket4j_jdk17-core", + List.of(Relocation.of("bucket4j", "io{}github{}bucket4j")) + ); + public static final Dependency NETTY_HTTP = new Dependency( "netty-codec-http", "io{}netty", diff --git a/gradle.properties b/gradle.properties index 5ec8f0c20..b5935d3e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -56,6 +56,7 @@ amazon_awssdk_eventstream_version=1.0.1 jimfs_version=1.3.1 authlib_version=7.0.60 concurrent_util_version=0.0.3 +bucket4j_version=8.15.0 # Proxy settings #systemProp.socks.proxyHost=127.0.0.1