From 33da29d6bfeef0a78ea0b948baf282b5a187331b Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 21 Nov 2025 10:29:08 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E6=94=B9=E7=94=A8=E6=BB=91=E5=8A=A8?= =?UTF-8?q?=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bukkit/loader/build.gradle.kts | 1 + bukkit/paper-loader/build.gradle.kts | 1 + .../main/resources/craft-engine.properties | 3 +- core/build.gradle.kts | 5 +- .../core/pack/host/impl/SelfHost.java | 17 +++++-- .../pack/host/impl/SelfHostHttpServer.java | 50 +++++++------------ .../craftengine/core/plugin/CraftEngine.java | 3 +- .../core/plugin/dependency/Dependencies.java | 7 +++ gradle.properties | 1 + 9 files changed, 47 insertions(+), 41 deletions(-) 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 From 512dec030b9608cad723d4fc580b238a016591b7 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 21 Nov 2025 11:04:17 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E5=B0=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/entity/furniture/AbstractFurnitureManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index ab16f7a96..4e9e89dd0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -132,7 +132,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { .item(Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(element.get("item"), "warning.config.furniture.element.missing_item"))) .applyDyedColor(ResourceConfigUtils.getAsBoolean(element.getOrDefault("apply-dyed-color", true), "apply-dyed-color")) .billboard(ResourceConfigUtils.getOrDefault(element.get("billboard"), o -> Billboard.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), Billboard.FIXED)) - .transform(ResourceConfigUtils.getOrDefault(element.get("transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE)) + .transform(ResourceConfigUtils.getOrDefault(element.getOrDefault("transform", element.get("display-transform")), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE)) .scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale")) .position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position")) .translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation")) From bd08be20eef1f7d13e4bcddbafd31a27684db912 Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 21 Nov 2025 14:39:07 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E6=94=B9=E8=BF=9B=E5=86=99=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/entity/furniture/AbstractFurnitureManager.java | 2 +- .../momirealms/craftengine/core/pack/host/impl/SelfHost.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java index 4e9e89dd0..52ea8252d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/furniture/AbstractFurnitureManager.java @@ -132,7 +132,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager { .item(Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(element.get("item"), "warning.config.furniture.element.missing_item"))) .applyDyedColor(ResourceConfigUtils.getAsBoolean(element.getOrDefault("apply-dyed-color", true), "apply-dyed-color")) .billboard(ResourceConfigUtils.getOrDefault(element.get("billboard"), o -> Billboard.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), Billboard.FIXED)) - .transform(ResourceConfigUtils.getOrDefault(element.getOrDefault("transform", element.get("display-transform")), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE)) + .transform(ResourceConfigUtils.getOrDefault(ResourceConfigUtils.get(element, "transform", "display-transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE)) .scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale")) .position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position")) .translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation")) 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 812caaef6..767e416c3 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 @@ -78,7 +78,7 @@ 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.getOrDefault("rate-map", arguments.get("rate-limit")), true); + Map rateMap = MiscUtils.castToMap(ResourceConfigUtils.get(arguments, "rate-map", "rate-limit"), true); int maxRequests = 5; int resetInterval = 20; if (rateMap != null) { From 0d01e637c5ea46558fa0334898cd486ac19cbb5c Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 21 Nov 2025 18:34:24 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E9=99=90=E9=80=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/translations/en.yml | 1 + .../src/main/resources/translations/zh_cn.yml | 1 + .../core/pack/host/impl/SelfHost.java | 38 +++++++++++++------ .../pack/host/impl/SelfHostHttpServer.java | 38 +++++++++++++------ .../core/util/ResourceConfigUtils.java | 24 ++++++++++++ 5 files changed, 79 insertions(+), 23 deletions(-) diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index 899854b70..761e46f38 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -80,6 +80,7 @@ warning.config.yaml.duplicated_key: "Issue found in file - Found du warning.config.yaml.inconsistent_value_type: "Issue found in file - Found duplicated key '' at line with different value types, this might cause unexpected results." warning.config.type.int: "Issue found in file - Failed to load '': Cannot cast '' to integer type for option ''." warning.config.type.boolean: "Issue found in file - Failed to load '': Cannot cast '' to boolean type for option ''." +warning.config.type.long: "Issue found in file - Failed to load '': Cannot cast '' to long type for option ''." warning.config.type.float: "Issue found in file - Failed to load '': Cannot cast '' to float type for option ''." warning.config.type.double: "Issue found in file - Failed to load '': Cannot cast '' to double type for option ''." warning.config.type.quaternionf: "Issue found in file - Failed to load '': Cannot cast '' to Quaternionf type for option ''." diff --git a/common-files/src/main/resources/translations/zh_cn.yml b/common-files/src/main/resources/translations/zh_cn.yml index 038b7d04d..3de669d54 100644 --- a/common-files/src/main/resources/translations/zh_cn.yml +++ b/common-files/src/main/resources/translations/zh_cn.yml @@ -80,6 +80,7 @@ warning.config.yaml.duplicated_key: "在文件 发现问题 - 在 warning.config.yaml.inconsistent_value_type: "在文件 发现问题 - 在第行发现重复且值类型不同的键 '', 这可能会导致一些意料之外的问题" warning.config.type.int: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为整数类型 (选项 '')" warning.config.type.boolean: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为布尔类型 (选项 '')" +warning.config.type.long: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为长整型类型 (选项 '')" warning.config.type.float: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为浮点数类型 (选项 '')" warning.config.type.double: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为双精度类型 (选项 '')" warning.config.type.quaternionf: "在文件 发现问题 - 无法加载 '': 无法将 '' 转换为四元数类型 (选项 '')" 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 767e416c3..c0958173d 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 com.google.common.util.concurrent.RateLimiter; import io.github.bucket4j.Bandwidth; import net.momirealms.craftengine.core.pack.host.ResourcePackDownloadData; import net.momirealms.craftengine.core.pack.host.ResourcePackHost; @@ -18,6 +19,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; public class SelfHost implements ResourcePackHost { public static final Factory FACTORY = new Factory(); @@ -60,6 +62,7 @@ public class SelfHost implements ResourcePackHost { public static class Factory implements ResourcePackHostFactory { + @SuppressWarnings("UnstableApiUsage") @Override public ResourcePackHost create(Map arguments) { SelfHostHttpServer selfHostHttpServer = SelfHostHttpServer.instance(); @@ -78,19 +81,30 @@ 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(ResourceConfigUtils.get(arguments, "rate-map", "rate-limit"), true); - int maxRequests = 5; - int resetInterval = 20; - if (rateMap != null) { - 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); + Map rateLimitPerIp = MiscUtils.castToMap(arguments.get("rate-limitation-per-ip"), true); + boolean enabledLimitPerIp = false; + Bandwidth limit = null; + out: + if (rateLimitPerIp != null) { + enabledLimitPerIp = ResourceConfigUtils.getAsBoolean(rateLimitPerIp.getOrDefault("enable", false), "enable"); + if (!enabledLimitPerIp) break out; + int maxRequests = Math.max(ResourceConfigUtils.getAsInt(rateLimitPerIp.getOrDefault("max-requests", 5), "max-requests"), 1); + int resetInterval = Math.max(ResourceConfigUtils.getAsInt(rateLimitPerIp.getOrDefault("reset-interval", 20), "reset-interval"), 1); + limit = Bandwidth.builder() + .capacity(maxRequests) + .refillGreedy(maxRequests, Duration.ofSeconds(resetInterval)) + .build(); } - 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); + Map tokenBucket = MiscUtils.castToMap(arguments.get("token-bucket"), true); + boolean enabledTokenBucket = false; + RateLimiter globalLimiter = null; + out: + if (tokenBucket != null) { + enabledTokenBucket = ResourceConfigUtils.getAsBoolean(tokenBucket.getOrDefault("enable", false), "enable"); + if (!enabledTokenBucket) break out; + globalLimiter = RateLimiter.create(ResourceConfigUtils.getAsDouble(tokenBucket.getOrDefault("qps", 1000), "qps")); + } + selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, limit, enabledLimitPerIp, enabledTokenBucket, globalLimiter, 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 00d7c8f6d..7004f90fa 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,7 @@ 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 com.google.common.util.concurrent.RateLimiter; import io.github.bucket4j.Bandwidth; import io.github.bucket4j.Bucket; import io.netty.bootstrap.ServerBootstrap; @@ -30,6 +31,7 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +@SuppressWarnings("UnstableApiUsage") public class SelfHostHttpServer { private static SelfHostHttpServer instance; private final Cache oneTimePackUrls = Caffeine.newBuilder() @@ -46,11 +48,14 @@ public class SelfHostHttpServer { private final AtomicLong totalRequests = new AtomicLong(); private final AtomicLong blockedRequests = new AtomicLong(); - private Bandwidth limit = Bandwidth.builder() + private Bandwidth limitPerIp = Bandwidth.builder() .capacity(1) .refillGreedy(1, Duration.ofSeconds(1)) .initialTokens(1) .build(); + private RateLimiter globalLimiter = RateLimiter.create(1); + private boolean enabledLimitPerIp = false; + private boolean enabledGlobalLimit = false; private String ip = "localhost"; private int port = -1; private String protocol = "http"; @@ -78,13 +83,19 @@ public class SelfHostHttpServer { String url, boolean denyNonMinecraft, String protocol, - Bandwidth limit, + Bandwidth limitPerIp, + boolean enabledLimitPerIp, + boolean enabledGlobalLimit, + RateLimiter globalLimiter, boolean token) { this.ip = ip; this.url = url; this.denyNonMinecraft = denyNonMinecraft; this.protocol = protocol; - this.limit = limit; + this.limitPerIp = limitPerIp; + this.enabledLimitPerIp = enabledLimitPerIp; + this.enabledGlobalLimit = enabledGlobalLimit; + this.globalLimiter = globalLimiter; this.useToken = token; if (port <= 0 || port > 65535) { @@ -140,7 +151,13 @@ public class SelfHostHttpServer { String clientIp = ((InetSocketAddress) ctx.channel().remoteAddress()) .getAddress().getHostAddress(); - if (checkRateLimit(clientIp)) { + if (enabledGlobalLimit && !globalLimiter.tryAcquire()) { + sendError(ctx, HttpResponseStatus.TOO_MANY_REQUESTS, "Rate limit exceeded"); + blockedRequests.incrementAndGet(); + return; + } + + if (enabledLimitPerIp && !checkIpRateLimit(clientIp)) { sendError(ctx, HttpResponseStatus.TOO_MANY_REQUESTS, "Rate limit exceeded"); blockedRequests.incrementAndGet(); return; @@ -217,13 +234,12 @@ public class SelfHostHttpServer { ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } - private boolean checkRateLimit(String clientIp) { - 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); - } - return !rateLimiter.tryConsume(1); + private boolean checkIpRateLimit(String clientIp) { + Bucket rateLimiter = ipRateLimiters.get(clientIp, k -> + Bucket.builder().addLimit(limitPerIp).build() + ); + assert rateLimiter != null; + return rateLimiter.tryConsume(1); } private boolean validateToken(String token) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index 3c21da4ce..c399e7b65 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -218,6 +218,30 @@ public final class ResourceConfigUtils { } } + public static long getAsLong(Object o, String option) { + switch (o) { + case null -> { + return 0; + } + case Long l -> { + return l; + } + case Number number -> { + return number.longValue(); + } + case String s -> { + try { + return Long.parseLong(s); + } catch (NumberFormatException e) { + throw new LocalizedResourceConfigException("warning.config.type.long", e, s, option); + } + } + default -> { + throw new LocalizedResourceConfigException("warning.config.type.long", o.toString(), option); + } + } + } + @SuppressWarnings("unchecked") public static Map getAsMap(Object obj, String option) { if (obj instanceof Map map) { From 41fb2a79c2618887e365da6452f485bfd74ed57e Mon Sep 17 00:00:00 2001 From: jhqwqmc Date: Fri, 21 Nov 2025 18:35:42 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common-files/src/main/resources/config.yml | 6 +++++- gradle.properties | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index bd8b101a0..44c64fa80 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -195,9 +195,13 @@ resource-pack: protocol: "http" deny-non-minecraft-request: true one-time-token: true - rate-limit: + rate-limitation-per-ip: + enable: true max-requests: 10 reset-interval: 30 + token-bucket: + enable: true + qps: 1000 item: # [Premium Exclusive] diff --git a/gradle.properties b/gradle.properties index b5935d3e0..68950dd75 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx1G # Project settings project_version=0.0.65.11 -config_version=57 -lang_version=39 +config_version=58 +lang_version=40 project_group=net.momirealms latest_supported_version=1.21.10