mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-19 15:09:15 +00:00
@@ -85,5 +85,6 @@ tasks {
|
|||||||
relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp")
|
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.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.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
|
||||||
|
relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ tasks {
|
|||||||
relocate("io.netty.handler.codec.rtsp", "net.momirealms.craftengine.libraries.netty.handler.codec.rtsp")
|
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.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.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
|
||||||
|
relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -195,9 +195,13 @@ resource-pack:
|
|||||||
protocol: "http"
|
protocol: "http"
|
||||||
deny-non-minecraft-request: true
|
deny-non-minecraft-request: true
|
||||||
one-time-token: true
|
one-time-token: true
|
||||||
rate-limit:
|
rate-limitation-per-ip:
|
||||||
|
enable: true
|
||||||
max-requests: 10
|
max-requests: 10
|
||||||
reset-interval: 30
|
reset-interval: 30
|
||||||
|
token-bucket:
|
||||||
|
enable: true
|
||||||
|
qps: 1000
|
||||||
|
|
||||||
item:
|
item:
|
||||||
# [Premium Exclusive]
|
# [Premium Exclusive]
|
||||||
|
|||||||
@@ -35,3 +35,4 @@ amazon-sdk-s3=${amazon_awssdk_version}
|
|||||||
amazon-sdk-eventstream=${amazon_awssdk_eventstream_version}
|
amazon-sdk-eventstream=${amazon_awssdk_eventstream_version}
|
||||||
evalex=${evalex_version}
|
evalex=${evalex_version}
|
||||||
jimfs=${jimfs_version}
|
jimfs=${jimfs_version}
|
||||||
|
bucket4j=${bucket4j_version}
|
||||||
@@ -80,6 +80,7 @@ warning.config.yaml.duplicated_key: "<red>Issue found in file <arg:0> - Found du
|
|||||||
warning.config.yaml.inconsistent_value_type: "<red>Issue found in file <arg:0> - Found duplicated key '<arg:1>' at line <arg:2> with different value types, this might cause unexpected results.</red>"
|
warning.config.yaml.inconsistent_value_type: "<red>Issue found in file <arg:0> - Found duplicated key '<arg:1>' at line <arg:2> with different value types, this might cause unexpected results.</red>"
|
||||||
warning.config.type.int: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to integer type for option '<arg:3>'.</yellow>"
|
warning.config.type.int: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to integer type for option '<arg:3>'.</yellow>"
|
||||||
warning.config.type.boolean: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to boolean type for option '<arg:3>'.</yellow>"
|
warning.config.type.boolean: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to boolean type for option '<arg:3>'.</yellow>"
|
||||||
|
warning.config.type.long: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to long type for option '<arg:3>'.</yellow>"
|
||||||
warning.config.type.float: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to float type for option '<arg:3>'.</yellow>"
|
warning.config.type.float: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to float type for option '<arg:3>'.</yellow>"
|
||||||
warning.config.type.double: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to double type for option '<arg:3>'.</yellow>"
|
warning.config.type.double: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to double type for option '<arg:3>'.</yellow>"
|
||||||
warning.config.type.quaternionf: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Quaternionf type for option '<arg:3>'.</yellow>"
|
warning.config.type.quaternionf: "<yellow>Issue found in file <arg:0> - Failed to load '<arg:1>': Cannot cast '<arg:2>' to Quaternionf type for option '<arg:3>'.</yellow>"
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ warning.config.yaml.duplicated_key: "<red>在文件 <arg:0> 发现问题 - 在
|
|||||||
warning.config.yaml.inconsistent_value_type: "<red>在文件 <arg:0> 发现问题 - 在第<arg:2>行发现重复且值类型不同的键 '<arg:1>', 这可能会导致一些意料之外的问题</red>"
|
warning.config.yaml.inconsistent_value_type: "<red>在文件 <arg:0> 发现问题 - 在第<arg:2>行发现重复且值类型不同的键 '<arg:1>', 这可能会导致一些意料之外的问题</red>"
|
||||||
warning.config.type.int: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为整数类型 (选项 '<arg:3>')</yellow>"
|
warning.config.type.int: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为整数类型 (选项 '<arg:3>')</yellow>"
|
||||||
warning.config.type.boolean: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为布尔类型 (选项 '<arg:3>')</yellow>"
|
warning.config.type.boolean: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为布尔类型 (选项 '<arg:3>')</yellow>"
|
||||||
|
warning.config.type.long: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为长整型类型 (选项 '<arg:3>')</yellow>"
|
||||||
warning.config.type.float: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为浮点数类型 (选项 '<arg:3>')</yellow>"
|
warning.config.type.float: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为浮点数类型 (选项 '<arg:3>')</yellow>"
|
||||||
warning.config.type.double: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为双精度类型 (选项 '<arg:3>')</yellow>"
|
warning.config.type.double: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为双精度类型 (选项 '<arg:3>')</yellow>"
|
||||||
warning.config.type.quaternionf: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为四元数类型 (选项 '<arg:3>')</yellow>"
|
warning.config.type.quaternionf: "<yellow>在文件 <arg:0> 发现问题 - 无法加载 '<arg:1>': 无法将 '<arg:2>' 转换为四元数类型 (选项 '<arg:3>')</yellow>"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ dependencies {
|
|||||||
implementation("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}")
|
implementation("net.momirealms:sparrow-nbt-codec:${rootProject.properties["sparrow_nbt_version"]}")
|
||||||
implementation("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}")
|
implementation("net.momirealms:sparrow-nbt-legacy-codec:${rootProject.properties["sparrow_nbt_version"]}")
|
||||||
// S3
|
// S3
|
||||||
implementation("net.momirealms:craft-engine-s3:0.8")
|
implementation("net.momirealms:craft-engine-s3:0.9")
|
||||||
// Util
|
// Util
|
||||||
compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}")
|
compileOnly("net.momirealms:sparrow-util:${rootProject.properties["sparrow_util_version"]}")
|
||||||
// Adventure
|
// Adventure
|
||||||
@@ -69,6 +69,8 @@ dependencies {
|
|||||||
compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}")
|
compileOnly("com.mojang:authlib:${rootProject.properties["authlib_version"]}")
|
||||||
// concurrentutil
|
// concurrentutil
|
||||||
compileOnly("ca.spottedleaf:concurrentutil:${rootProject.properties["concurrent_util_version"]}")
|
compileOnly("ca.spottedleaf:concurrentutil:${rootProject.properties["concurrent_util_version"]}")
|
||||||
|
// bucket4j
|
||||||
|
compileOnly("com.bucket4j:bucket4j_jdk17-core:${rootProject.properties["bucket4j_version"]}")
|
||||||
}
|
}
|
||||||
|
|
||||||
java {
|
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.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.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.netty.handler.codec.http2", "net.momirealms.craftengine.libraries.netty.handler.codec.http2")
|
||||||
|
relocate("io.github.bucket4j", "net.momirealms.craftengine.libraries.bucket4j") // bucket4j
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ public abstract class AbstractFurnitureManager implements FurnitureManager {
|
|||||||
.item(Key.of(ResourceConfigUtils.requireNonEmptyStringOrThrow(element.get("item"), "warning.config.furniture.element.missing_item")))
|
.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"))
|
.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))
|
.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(ResourceConfigUtils.get(element, "transform", "display-transform"), o -> ItemDisplayContext.valueOf(o.toString().toUpperCase(Locale.ENGLISH)), ItemDisplayContext.NONE))
|
||||||
.scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale"))
|
.scale(ResourceConfigUtils.getAsVector3f(element.getOrDefault("scale", "1"), "scale"))
|
||||||
.position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position"))
|
.position(ResourceConfigUtils.getAsVector3f(element.getOrDefault("position", "0"), "position"))
|
||||||
.translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation"))
|
.translation(ResourceConfigUtils.getAsVector3f(element.getOrDefault("translation", "0"), "translation"))
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package net.momirealms.craftengine.core.pack.host.impl;
|
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.ResourcePackDownloadData;
|
||||||
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
|
import net.momirealms.craftengine.core.pack.host.ResourcePackHost;
|
||||||
import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory;
|
import net.momirealms.craftengine.core.pack.host.ResourcePackHostFactory;
|
||||||
@@ -12,10 +14,12 @@ import net.momirealms.craftengine.core.util.MiscUtils;
|
|||||||
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
|
import net.momirealms.craftengine.core.util.ResourceConfigUtils;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class SelfHost implements ResourcePackHost {
|
public class SelfHost implements ResourcePackHost {
|
||||||
public static final Factory FACTORY = new Factory();
|
public static final Factory FACTORY = new Factory();
|
||||||
@@ -58,6 +62,7 @@ public class SelfHost implements ResourcePackHost {
|
|||||||
|
|
||||||
public static class Factory implements ResourcePackHostFactory {
|
public static class Factory implements ResourcePackHostFactory {
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
@Override
|
@Override
|
||||||
public ResourcePackHost create(Map<String, Object> arguments) {
|
public ResourcePackHost create(Map<String, Object> arguments) {
|
||||||
SelfHostHttpServer selfHostHttpServer = SelfHostHttpServer.instance();
|
SelfHostHttpServer selfHostHttpServer = SelfHostHttpServer.instance();
|
||||||
@@ -76,14 +81,30 @@ public class SelfHost implements ResourcePackHost {
|
|||||||
boolean oneTimeToken = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("one-time-token", true), "one-time-token");
|
boolean oneTimeToken = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("one-time-token", true), "one-time-token");
|
||||||
String protocol = arguments.getOrDefault("protocol", "http").toString();
|
String protocol = arguments.getOrDefault("protocol", "http").toString();
|
||||||
boolean denyNonMinecraftRequest = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("deny-non-minecraft-request", true), "deny-non-minecraft-request");
|
boolean denyNonMinecraftRequest = ResourceConfigUtils.getAsBoolean(arguments.getOrDefault("deny-non-minecraft-request", true), "deny-non-minecraft-request");
|
||||||
Map<String, Object> rateMap = MiscUtils.castToMap(arguments.get("rate-map"), true);
|
Map<String, Object> rateLimitPerIp = MiscUtils.castToMap(arguments.get("rate-limitation-per-ip"), true);
|
||||||
int maxRequests = 5;
|
boolean enabledLimitPerIp = false;
|
||||||
int resetInterval = 20_000;
|
Bandwidth limit = null;
|
||||||
if (rateMap != null) {
|
out:
|
||||||
maxRequests = ResourceConfigUtils.getAsInt(rateMap.getOrDefault("max-requests", 5), "max-requests");
|
if (rateLimitPerIp != null) {
|
||||||
resetInterval = ResourceConfigUtils.getAsInt(rateMap.getOrDefault("reset-interval", 20), "reset-interval") * 1000;
|
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();
|
||||||
}
|
}
|
||||||
selfHostHttpServer.updateProperties(ip, port, url, denyNonMinecraftRequest, protocol, maxRequests, resetInterval, oneTimeToken);
|
Map<String, Object> 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;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package net.momirealms.craftengine.core.pack.host.impl;
|
|||||||
import com.github.benmanes.caffeine.cache.Cache;
|
import com.github.benmanes.caffeine.cache.Cache;
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import com.github.benmanes.caffeine.cache.Scheduler;
|
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;
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.*;
|
import io.netty.channel.*;
|
||||||
@@ -23,10 +26,12 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
public class SelfHostHttpServer {
|
public class SelfHostHttpServer {
|
||||||
private static SelfHostHttpServer instance;
|
private static SelfHostHttpServer instance;
|
||||||
private final Cache<String, Boolean> oneTimePackUrls = Caffeine.newBuilder()
|
private final Cache<String, Boolean> oneTimePackUrls = Caffeine.newBuilder()
|
||||||
@@ -34,17 +39,23 @@ public class SelfHostHttpServer {
|
|||||||
.scheduler(Scheduler.systemScheduler())
|
.scheduler(Scheduler.systemScheduler())
|
||||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||||
.build();
|
.build();
|
||||||
private final Cache<String, IpAccessRecord> ipAccessCache = Caffeine.newBuilder()
|
private final Cache<String, Bucket> ipRateLimiters = Caffeine.newBuilder()
|
||||||
.maximumSize(256)
|
.maximumSize(256)
|
||||||
.scheduler(Scheduler.systemScheduler())
|
.scheduler(Scheduler.systemScheduler())
|
||||||
.expireAfterWrite(10, TimeUnit.MINUTES)
|
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private final AtomicLong totalRequests = new AtomicLong();
|
private final AtomicLong totalRequests = new AtomicLong();
|
||||||
private final AtomicLong blockedRequests = new AtomicLong();
|
private final AtomicLong blockedRequests = new AtomicLong();
|
||||||
|
|
||||||
private int rateLimit = 1;
|
private Bandwidth limitPerIp = Bandwidth.builder()
|
||||||
private long rateLimitInterval = 1000;
|
.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 String ip = "localhost";
|
||||||
private int port = -1;
|
private int port = -1;
|
||||||
private String protocol = "http";
|
private String protocol = "http";
|
||||||
@@ -72,15 +83,19 @@ public class SelfHostHttpServer {
|
|||||||
String url,
|
String url,
|
||||||
boolean denyNonMinecraft,
|
boolean denyNonMinecraft,
|
||||||
String protocol,
|
String protocol,
|
||||||
int maxRequests,
|
Bandwidth limitPerIp,
|
||||||
int resetInterval,
|
boolean enabledLimitPerIp,
|
||||||
|
boolean enabledGlobalLimit,
|
||||||
|
RateLimiter globalLimiter,
|
||||||
boolean token) {
|
boolean token) {
|
||||||
this.ip = ip;
|
this.ip = ip;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.denyNonMinecraft = denyNonMinecraft;
|
this.denyNonMinecraft = denyNonMinecraft;
|
||||||
this.protocol = protocol;
|
this.protocol = protocol;
|
||||||
this.rateLimit = maxRequests;
|
this.limitPerIp = limitPerIp;
|
||||||
this.rateLimitInterval = resetInterval;
|
this.enabledLimitPerIp = enabledLimitPerIp;
|
||||||
|
this.enabledGlobalLimit = enabledGlobalLimit;
|
||||||
|
this.globalLimiter = globalLimiter;
|
||||||
this.useToken = token;
|
this.useToken = token;
|
||||||
|
|
||||||
if (port <= 0 || port > 65535) {
|
if (port <= 0 || port > 65535) {
|
||||||
@@ -136,7 +151,13 @@ public class SelfHostHttpServer {
|
|||||||
String clientIp = ((InetSocketAddress) ctx.channel().remoteAddress())
|
String clientIp = ((InetSocketAddress) ctx.channel().remoteAddress())
|
||||||
.getAddress().getHostAddress();
|
.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");
|
sendError(ctx, HttpResponseStatus.TOO_MANY_REQUESTS, "Rate limit exceeded");
|
||||||
blockedRequests.incrementAndGet();
|
blockedRequests.incrementAndGet();
|
||||||
return;
|
return;
|
||||||
@@ -213,23 +234,12 @@ public class SelfHostHttpServer {
|
|||||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkRateLimit(String clientIp) {
|
private boolean checkIpRateLimit(String clientIp) {
|
||||||
IpAccessRecord record = ipAccessCache.getIfPresent(clientIp);
|
Bucket rateLimiter = ipRateLimiters.get(clientIp, k ->
|
||||||
long now = System.currentTimeMillis();
|
Bucket.builder().addLimit(limitPerIp).build()
|
||||||
|
);
|
||||||
if (record == null) {
|
assert rateLimiter != null;
|
||||||
record = new IpAccessRecord(now, 1);
|
return rateLimiter.tryConsume(1);
|
||||||
ipAccessCache.put(clientIp, record);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now - record.lastAccessTime > rateLimitInterval) {
|
|
||||||
record.lastAccessTime = now;
|
|
||||||
record.accessCount = 1;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ++record.accessCount > rateLimit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean validateToken(String token) {
|
private boolean validateToken(String token) {
|
||||||
@@ -312,14 +322,4 @@ public class SelfHostHttpServer {
|
|||||||
CraftEngine.instance().logger().severe("SHA-1 algorithm not available", e);
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -419,7 +419,8 @@ public abstract class CraftEngine implements Plugin {
|
|||||||
Dependencies.LZ4,
|
Dependencies.LZ4,
|
||||||
Dependencies.EVALEX,
|
Dependencies.EVALEX,
|
||||||
Dependencies.NETTY_HTTP,
|
Dependencies.NETTY_HTTP,
|
||||||
Dependencies.JIMFS
|
Dependencies.JIMFS,
|
||||||
|
Dependencies.BUCKET_4_J
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -372,6 +372,13 @@ public class Dependencies {
|
|||||||
List.of(Relocation.of("jimfs", "com{}google{}common{}jimfs"))
|
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(
|
public static final Dependency NETTY_HTTP = new Dependency(
|
||||||
"netty-codec-http",
|
"netty-codec-http",
|
||||||
"io{}netty",
|
"io{}netty",
|
||||||
|
|||||||
@@ -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")
|
@SuppressWarnings("unchecked")
|
||||||
public static Map<String, Object> getAsMap(Object obj, String option) {
|
public static Map<String, Object> getAsMap(Object obj, String option) {
|
||||||
if (obj instanceof Map<?, ?> map) {
|
if (obj instanceof Map<?, ?> map) {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ org.gradle.jvmargs=-Xmx1G
|
|||||||
|
|
||||||
# Project settings
|
# Project settings
|
||||||
project_version=0.0.65.11
|
project_version=0.0.65.11
|
||||||
config_version=57
|
config_version=58
|
||||||
lang_version=39
|
lang_version=40
|
||||||
project_group=net.momirealms
|
project_group=net.momirealms
|
||||||
latest_supported_version=1.21.10
|
latest_supported_version=1.21.10
|
||||||
|
|
||||||
@@ -56,6 +56,7 @@ amazon_awssdk_eventstream_version=1.0.1
|
|||||||
jimfs_version=1.3.1
|
jimfs_version=1.3.1
|
||||||
authlib_version=7.0.60
|
authlib_version=7.0.60
|
||||||
concurrent_util_version=0.0.3
|
concurrent_util_version=0.0.3
|
||||||
|
bucket4j_version=8.15.0
|
||||||
|
|
||||||
# Proxy settings
|
# Proxy settings
|
||||||
#systemProp.socks.proxyHost=127.0.0.1
|
#systemProp.socks.proxyHost=127.0.0.1
|
||||||
|
|||||||
Reference in New Issue
Block a user