From 8873de19fcee8b021b532d6b01db2cb4b39ce927 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Wed, 17 Sep 2025 14:30:04 +0300 Subject: [PATCH 01/78] Depend on monkeylib (and thus jankson too) --- build.gradle | 11 +++++++++++ common/build.gradle | 9 +++++++++ fabric/build.gradle | 12 +++++++++++- fabric/gradle.properties | 7 +++++-- gradle.properties | 3 +++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 3fc62e3..6abc8fc 100644 --- a/build.gradle +++ b/build.gradle @@ -23,6 +23,17 @@ subprojects { archivesBaseName = "mesh-lib-${project.nameSuffix}" version = "${rootProject.mod_version}+${rootProject.minecraft_version}" + repositories { + maven { + name = "OffsetMods538" + url = "https://maven.offsetmonkey538.top/releases" + content { + includeGroup "top.offsetmonkey538.monkeylib538" + includeGroup "top.offsetmonkey538.offsetconfig538" + } + } + } + tasks.named("javadoc", Javadoc) { options.addFileOption('-add-stylesheet', rootProject.file("javadoc-stylesheet.css")) } diff --git a/common/build.gradle b/common/build.gradle index 84516a6..cc51555 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -5,10 +5,19 @@ plugins { repositories { mavenCentral() + maven { + name = "Mojang Libraries" + url = "https://libraries.minecraft.net" + content { + includeGroup "com.mojang" + } + } } dependencies { // Netty api "io.netty:netty-codec-http:${project.netty_version}" + + shadow compileOnlyApi("top.offsetmonkey538.monkeylib538:monkeylib538-common:${project.monkeylib538_version}") } tasks.build.dependsOn(shadowJar) diff --git a/fabric/build.gradle b/fabric/build.gradle index 50d687a..116f27c 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -11,6 +11,7 @@ outlet { mcVersionRange = rootProject.supported_minecraft_versions allowedReleaseTypes = Set.of(ReleaseType.RELEASE) propertiesData = [ + 'fapi_version': outlet.fapiVersion(project.minecraft_version), 'yarn_version': outlet.yarnVersion(project.minecraft_version), 'loader_version': outlet.loaderVersion() ] @@ -68,7 +69,16 @@ dependencies { common project(":common") // Uncomment for including a module of fabric api - // includeModImplementation fabricApi.module("fabric-api-base", project.fapi_version) + //includeModImplementation fabricApi.module("fabric-api-base", project.fapi_version) + modRuntimeOnly fabricApi.module("fabric-command-api-v2", project.fapi_version) + + modRuntimeOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-1.20.5:${project.monkeylib538_version}+1.20.5") { + exclude(group: "net.fabricmc.fabric-api") + exclude(group: "top.offsetmonkey538.monkeylib538:monkeylib538-fabric") + } + modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${project.monkeylib538_version}") { + exclude(group: "net.fabricmc.fabric-api") + } } processResources { diff --git a/fabric/gradle.properties b/fabric/gradle.properties index 11075b5..cf0f596 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -2,12 +2,15 @@ # Check at https://fabricmc.net/develop # These should be automatically updated, unless the environment # variable "DISABLE_PROPERTIES_UPDATE" is set. -yarn_version = 1.21.4+build.4 -loader_version = 0.16.9 +yarn_version = 1.21.4+build.8 +loader_version = 0.17.2 # Dependencies ## DevAuth, check at https://github.com/DJtheRedstoner/DevAuth devauth_version = 1.2.1 +## Fabric API, only used at runtime for monkeylib command +fapi_version = 0.119.4+1.21.4 + nameSuffix = fabric diff --git a/gradle.properties b/gradle.properties index de17bd0..0b5f4f5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,9 @@ minecraft_version = 1.21.4 ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.82.Final +# MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 +monkeylib538_version = 3.0.0-6345c7b + # Mod Properties mod_version = 1.0.5 supported_minecraft_versions = >=1.19 From 5a564a97fd4bb7714611305d2931ac60af0cc571 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Wed, 17 Sep 2025 21:18:21 +0300 Subject: [PATCH 02/78] refactorizationing --- .../top/offsetmonkey538/meshlib/MESHLib.java | 20 ++++++++-- .../meshlib/example/ExampleMain.java | 7 ++-- .../meshlib/platform/PlatformMain.java | 31 +++++++++++++++ .../platform/fabric/FabricPlatformMain.java | 32 +++++++++++++++ .../fabric}/mixin/ServerNetworkIoMixin.java | 2 +- ...setmonkey538.meshlib.platform.PlatformMain | 1 + fabric/src/main/resources/fabric.mod.json | 2 +- .../src/main/resources/mesh-lib.mixins.json | 4 +- paper/build.gradle | 2 +- .../top/offsetmonkey538/meshlib/MeshLib.java | 15 ------- .../meshlib/MeshLibPlugin.java | 14 ------- .../meshlib/platform/paper/MeshLibPlugin.java | 13 +++++++ .../platform/paper/PaperPlatformMain.java | 39 +++++++++++++++++++ ...setmonkey538.meshlib.platform.PlatformMain | 1 + .../{plugin.yml => paper-plugin.yml} | 2 +- 15 files changed, 144 insertions(+), 41 deletions(-) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java create mode 100644 fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java rename fabric/src/main/java/top/offsetmonkey538/meshlib/{ => platform/fabric}/mixin/ServerNetworkIoMixin.java (93%) create mode 100644 fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain delete mode 100644 paper/src/main/java/top/offsetmonkey538/meshlib/MeshLib.java delete mode 100644 paper/src/main/java/top/offsetmonkey538/meshlib/MeshLibPlugin.java create mode 100644 paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java create mode 100644 paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java create mode 100644 paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain rename paper/src/main/resources/{plugin.yml => paper-plugin.yml} (80%) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index 0316b26..01cd6c5 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -2,10 +2,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import top.offsetmonkey538.meshlib.example.ExampleMain; +import top.offsetmonkey538.meshlib.platform.PlatformMain; + +import java.util.ServiceLoader; -/** - * Just used to store some constants - */ public final class MESHLib { /** * Private constructor as this class shouldn't be instanced @@ -20,4 +21,17 @@ private MESHLib() {} * Logger instance used by this mod */ public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + + + public static void initialize() { + PlatformMain.enableVanillaHandler(); + ExampleMain.onInitialize(); + } + + + public static T load(Class clazz) { + return ServiceLoader.load(clazz, clazz.getClassLoader()) + .findFirst() + .orElseThrow(() -> new RuntimeException("Failed to load service for " + clazz.getName())); + } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index b4e4316..faeb85d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -6,8 +6,6 @@ /** * Initializer for the example handlers - *

- * Called from either the plugin initializer {@code MeshLibPlugin} or defined as an entrypoint in the {@code fabric.mod.json} file */ public final class ExampleMain { private ExampleMain() { @@ -21,7 +19,10 @@ private ExampleMain() { */ public static void onInitialize() { // Ignore if "meshEnableExamples" isn't set - if (System.getProperty("meshEnableExamples", "").isEmpty()) return; + // FIXME: figure something out... + // Multi-version project doesn't generate launch configs and I can't seem to pass properties through the gradle tasks + // I guess env variables could work? + if (false && !Boolean.getBoolean("meshEnableExamples")) return; LOGGER.warn("MESH examples enabled!"); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java new file mode 100644 index 0000000..68432a4 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java @@ -0,0 +1,31 @@ +package top.offsetmonkey538.meshlib.platform; + +import org.jetbrains.annotations.ApiStatus; + +import java.nio.file.Path; + +import static top.offsetmonkey538.meshlib.MESHLib.load; + +@ApiStatus.Internal +public interface PlatformMain { + + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated // Only for use in this class + PlatformMain INSTANCE = load(PlatformMain.class); + + static void enableVanillaHandler() { + INSTANCE.enableVanillaHandlerImpl(); + } + + static void disableVanillaHandler() { + INSTANCE.disableVanillaHandlerImpl(); + } + + static Path getConfigDir() { + return INSTANCE.getConfigDirImpl(); + } + + void enableVanillaHandlerImpl(); + void disableVanillaHandlerImpl(); + Path getConfigDirImpl(); +} diff --git a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java new file mode 100644 index 0000000..8b5329d --- /dev/null +++ b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java @@ -0,0 +1,32 @@ +package top.offsetmonkey538.meshlib.platform.fabric; + +import net.fabricmc.api.DedicatedServerModInitializer; +import net.fabricmc.loader.api.FabricLoader; +import top.offsetmonkey538.meshlib.MESHLib; +import top.offsetmonkey538.meshlib.platform.PlatformMain; + +import java.nio.file.Path; + +import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; + +public final class FabricPlatformMain implements PlatformMain, DedicatedServerModInitializer { + @Override + public void enableVanillaHandlerImpl() { + // TODO + } + + @Override + public void disableVanillaHandlerImpl() { + // TODO + } + + @Override + public Path getConfigDirImpl() { + return FabricLoader.getInstance().getConfigDir().resolve(MOD_ID); + } + + @Override + public void onInitializeServer() { + MESHLib.initialize(); + } +} diff --git a/fabric/src/main/java/top/offsetmonkey538/meshlib/mixin/ServerNetworkIoMixin.java b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java similarity index 93% rename from fabric/src/main/java/top/offsetmonkey538/meshlib/mixin/ServerNetworkIoMixin.java rename to fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java index 688bed2..a5b6525 100644 --- a/fabric/src/main/java/top/offsetmonkey538/meshlib/mixin/ServerNetworkIoMixin.java +++ b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.mixin; +package top.offsetmonkey538.meshlib.platform.fabric.mixin; import io.netty.channel.Channel; import org.spongepowered.asm.mixin.Mixin; diff --git a/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain b/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain new file mode 100644 index 0000000..ad7a267 --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain @@ -0,0 +1 @@ +top.offsetmonkey538.meshlib.platform.fabric.FabricPlatformMain \ No newline at end of file diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 4d3ac56..822365c 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -17,7 +17,7 @@ "environment": "server", "entrypoints": { "server": [ - "top.offsetmonkey538.meshlib.example.ExampleMain::onInitialize" + "top.offsetmonkey538.meshlib.platform.fabric.FabricPlatformMain" ] }, "mixins": [ diff --git a/fabric/src/main/resources/mesh-lib.mixins.json b/fabric/src/main/resources/mesh-lib.mixins.json index c577538..51aa7c6 100644 --- a/fabric/src/main/resources/mesh-lib.mixins.json +++ b/fabric/src/main/resources/mesh-lib.mixins.json @@ -1,9 +1,9 @@ { "required": true, - "package": "top.offsetmonkey538.meshlib.mixin", + "package": "top.offsetmonkey538.meshlib.platform.fabric.mixin", "compatibilityLevel": "JAVA_17", "server": [ - "ServerNetworkIoMixin" + "ServerNetworkIoMixin" ], "injectors": { "defaultRequire": 1 diff --git a/paper/build.gradle b/paper/build.gradle index 30958f2..6887e9b 100644 --- a/paper/build.gradle +++ b/paper/build.gradle @@ -45,7 +45,7 @@ processResources { inputs.properties(properties) - filesMatching("plugin.yml") { + filesMatching("paper-plugin.yml") { expand(properties) } } diff --git a/paper/src/main/java/top/offsetmonkey538/meshlib/MeshLib.java b/paper/src/main/java/top/offsetmonkey538/meshlib/MeshLib.java deleted file mode 100644 index 7821767..0000000 --- a/paper/src/main/java/top/offsetmonkey538/meshlib/MeshLib.java +++ /dev/null @@ -1,15 +0,0 @@ -package top.offsetmonkey538.meshlib; - -import io.papermc.paper.network.ChannelInitializeListenerHolder; -import net.kyori.adventure.key.Key; -import top.offsetmonkey538.meshlib.impl.ProtocolHandler; - -public final class MeshLib { - private MeshLib() { - - } - - public static void initialize() { - ChannelInitializeListenerHolder.addListener(Key.key("meshlib", "meshlib"), channel -> channel.pipeline().addFirst(MESHLib.MOD_ID, new ProtocolHandler())); - } -} diff --git a/paper/src/main/java/top/offsetmonkey538/meshlib/MeshLibPlugin.java b/paper/src/main/java/top/offsetmonkey538/meshlib/MeshLibPlugin.java deleted file mode 100644 index 19000fd..0000000 --- a/paper/src/main/java/top/offsetmonkey538/meshlib/MeshLibPlugin.java +++ /dev/null @@ -1,14 +0,0 @@ -package top.offsetmonkey538.meshlib; - -import org.bukkit.plugin.java.JavaPlugin; -import top.offsetmonkey538.meshlib.example.ExampleMain; - -public class MeshLibPlugin extends JavaPlugin { - - @Override - public void onEnable() { - MeshLib.initialize(); - - ExampleMain.onInitialize(); - } -} diff --git a/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java b/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java new file mode 100644 index 0000000..6d3dbf0 --- /dev/null +++ b/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java @@ -0,0 +1,13 @@ +package top.offsetmonkey538.meshlib.platform.paper; + +import org.bukkit.plugin.java.JavaPlugin; +import top.offsetmonkey538.meshlib.MESHLib; + +public final class MeshLibPlugin extends JavaPlugin { + + @Override + public void onEnable() { + PaperPlatformMain.setPlugin(this); + MESHLib.initialize(); + } +} diff --git a/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java b/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java new file mode 100644 index 0000000..39788e7 --- /dev/null +++ b/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java @@ -0,0 +1,39 @@ +package top.offsetmonkey538.meshlib.platform.paper; + +import io.papermc.paper.network.ChannelInitializeListenerHolder; +import net.kyori.adventure.key.Key; +import top.offsetmonkey538.meshlib.MESHLib; +import top.offsetmonkey538.meshlib.impl.ProtocolHandler; +import top.offsetmonkey538.meshlib.platform.PlatformMain; + +import java.nio.file.Path; + +public final class PaperPlatformMain implements PlatformMain { + private static final Key HANDLER_KEY = Key.key("meshlib", "meshlib_vanilla_handler"); + + private static MeshLibPlugin plugin; + + @Override + public void enableVanillaHandlerImpl() { + if (ChannelInitializeListenerHolder.hasListener(HANDLER_KEY)) return; + ChannelInitializeListenerHolder.addListener(HANDLER_KEY, channel -> channel.pipeline().addFirst(MESHLib.MOD_ID, new ProtocolHandler())); + } + + @Override + public void disableVanillaHandlerImpl() { + ChannelInitializeListenerHolder.removeListener(HANDLER_KEY); + } + + @Override + public Path getConfigDirImpl() { + return getPlugin().getDataPath(); + } + + public static void setPlugin(MeshLibPlugin plugin) { + PaperPlatformMain.plugin = plugin; + } + + public static MeshLibPlugin getPlugin() { + return plugin; + } +} diff --git a/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain b/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain new file mode 100644 index 0000000..7d900a1 --- /dev/null +++ b/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain @@ -0,0 +1 @@ +top.offsetmonkey538.meshlib.platform.paper.PaperPlatformMain \ No newline at end of file diff --git a/paper/src/main/resources/plugin.yml b/paper/src/main/resources/paper-plugin.yml similarity index 80% rename from paper/src/main/resources/plugin.yml rename to paper/src/main/resources/paper-plugin.yml index 227404d..4cfa555 100644 --- a/paper/src/main/resources/plugin.yml +++ b/paper/src/main/resources/paper-plugin.yml @@ -1,6 +1,6 @@ name: MESH-Lib version: '${modVersion}' -main: top.offsetmonkey538.meshlib.MeshLibPlugin +main: top.offsetmonkey538.meshlib.platform.paper.MeshLibPlugin description: Easy to use library for hosting an HTTP server on the Minecraft server's port author: OffsetMonkey538 website: https://github.com/OffsetMods538/MESH-Lib From a6d4e6363c8f048f7b6b9450aeeb2f9e7e1fc539 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Wed, 17 Sep 2025 21:24:46 +0300 Subject: [PATCH 03/78] make loom generate run configuration oh my fucking god how did i not know this was possible this is so useful for like idk some other projects as well oh my fucking goddddddd --- .../top/offsetmonkey538/meshlib/example/ExampleMain.java | 5 +---- fabric/build.gradle | 5 +++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index faeb85d..1722c2f 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -19,10 +19,7 @@ private ExampleMain() { */ public static void onInitialize() { // Ignore if "meshEnableExamples" isn't set - // FIXME: figure something out... - // Multi-version project doesn't generate launch configs and I can't seem to pass properties through the gradle tasks - // I guess env variables could work? - if (false && !Boolean.getBoolean("meshEnableExamples")) return; + if (!Boolean.getBoolean("meshEnableExamples")) return; LOGGER.warn("MESH examples enabled!"); diff --git a/fabric/build.gradle b/fabric/build.gradle index 116f27c..edac216 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -27,6 +27,11 @@ loom { runDir "run/server" } } + + runConfigs.configureEach { + ideConfigGenerated(true) + vmArg "-DmeshEnableExamples=true" + } } // https://gist.github.com/maityyy/3dbcd558d58a6412c3a2a38c72706e8e From 0480cd6ed9388ee81e11e3f5649e2d4c218a90e9 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Wed, 17 Sep 2025 21:48:39 +0300 Subject: [PATCH 04/78] this is supposedly kind of a thing that maybe makes sense? --- .../top/offsetmonkey538/meshlib/MESHLib.java | 9 ++ .../meshlib/config/TestConfig.java | 134 ++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index 01cd6c5..9f6fdb3 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -2,8 +2,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import top.offsetmonkey538.meshlib.config.TestConfig; import top.offsetmonkey538.meshlib.example.ExampleMain; import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; +import top.offsetmonkey538.offsetconfig538.api.config.ConfigHolder; +import top.offsetmonkey538.offsetconfig538.api.config.ConfigManager; import java.util.ServiceLoader; @@ -26,6 +30,11 @@ private MESHLib() {} public static void initialize() { PlatformMain.enableVanillaHandler(); ExampleMain.onInitialize(); + + + final ConfigHolder config = ConfigManager.INSTANCE.init(ConfigHolder.create(TestConfig::new, LOGGER::error)); + System.out.println(config.get().thingy); + ConfigCommandApi.registerConfigCommand(config, () -> System.out.println(config.get().thingy), "test"); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java new file mode 100644 index 0000000..ecbea91 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java @@ -0,0 +1,134 @@ +package top.offsetmonkey538.meshlib.config; + +import blue.endless.jankson.Jankson; +import blue.endless.jankson.JsonObject; +import blue.endless.jankson.JsonPrimitive; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.offsetconfig538.api.config.Config; + +import java.nio.file.Path; +import java.util.Map; +import java.util.function.Function; + +public class TestConfig implements Config { + + public AbstractThing thingy = new Type1(new Type1.Data("defaultValue")); + + + public interface AbstractThing { + String getType(); + T getData(); + } + private static class Type1 implements AbstractThing { + private final String value; + + public Type1(Data data) { + this.value = data.value; + } + + @Override + public String getType() { + return "type1"; + } + + @Override + public Data getData() { + return new Data(value); + } + + @Override + public String toString() { + return "value: " + value; + } + + public static final class Data { + @SuppressWarnings("FieldMayBeFinal") // Think it needs to be non-final cause jankson + public String value; + + @SuppressWarnings("unused") // Public no-args used by jankson i think + public Data() { + + } + + private Data(String value) { + this.value = value; + } + } + } + private static class Type2 implements AbstractThing { + private final double differentValue; + + public Type2(Data data) { + this.differentValue = data.differentValue; + } + + @Override + public String getType() { + return "type2"; + } + + @Override + public Data getData() { + return new Data(differentValue); + } + + @Override + public String toString() { + return "differentValue: " + differentValue; + } + + public static final class Data { + @SuppressWarnings("FieldMayBeFinal") // Think it needs to be non-final cause jankson + public double differentValue; + + @SuppressWarnings("unused") // Public no-args used by jankson i think + public Data() { + + } + + private Data(double value) { + this.differentValue = value; + } + } + } + + private static record ThingyDefinition(Class dataType, Function> thingyInitializer) { + + } + + @Override + public void configureJankson(Jankson.@NotNull Builder builder) { + // type to value holder type todo: actually have a registry type of thing for this + final Map> testMap = Map.of( + "type1", new ThingyDefinition<>(Type1.Data.class, Type1::new), + "type2", new ThingyDefinition<>(Type2.Data.class, Type2::new) + ); + + builder.registerSerializer(AbstractThing.class, (abstractThing, marshaller) -> { + final JsonObject result = (JsonObject) marshaller.serialize(abstractThing.getData()); + result.put("type", JsonPrimitive.of(abstractThing.getType())); + return result; + }); + builder.registerDeserializer(JsonObject.class, AbstractThing.class, (jsonObject, marshaller) -> { + final String type = jsonObject.get(String.class, "type"); + @SuppressWarnings("unchecked") // It's proooobably a subclass of Object... + ThingyDefinition thingyDefinition = (ThingyDefinition) testMap.get(type); + final JsonObject dummyParent = new JsonObject(); + dummyParent.put("data", jsonObject); + Object data = dummyParent.get(thingyDefinition.dataType, "data"); + + return thingyDefinition.thingyInitializer.apply(data); + }); + } + + @Override + public @NotNull Path getFilePath() { + return PlatformMain.getConfigDir().resolve(getId() + ".json"); + } + + @Override + public @NotNull String getId() { + return "test"; + } +} From 753b74e3695fd6960a3d81dff6dc1c5eb892dd6b Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 21 Sep 2025 09:53:08 +0300 Subject: [PATCH 05/78] Update Gradle to v9.1.0 --- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.jar | Bin 43583 -> 45457 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 9 +++------ gradlew.bat | 3 +-- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 6abc8fc..ab9a4f9 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ subprojects { apply plugin: "java" apply plugin: "java-library" - archivesBaseName = "mesh-lib-${project.nameSuffix}" + base.archivesName = "mesh-lib-${project.nameSuffix}" version = "${rootProject.mod_version}+${rootProject.minecraft_version}" repositories { @@ -70,7 +70,7 @@ subprojects { } publications { maven(MavenPublication) { - artifactId = project.archivesBaseName + artifactId = base.archivesName.get() from(components["java"]) } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index a4b76b9530d66f5e68d973ea569d8e19de379189..8bdaf60c75ab801e22807dde59e12a8735a34077 100644 GIT binary patch delta 37330 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1b837a..6bf6239 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=7a00d51fb93147819aab76024feece20b6b84e420694101f276be952e08bef03 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionSha256Sum=b84e04fa845fecba48551f425957641074fcc00a88a84d2aae5808743b35fc85 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f3b75f3..adff685 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -205,15 +203,14 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 00ed2f8c3c955f8432a72c62d41ce1052daf7c6c Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 21 Sep 2025 09:59:14 +0300 Subject: [PATCH 06/78] Update Loom to v1.11 --- build.gradle | 2 +- fabric/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index ab9a4f9..45980a8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ import dex.plugins.outlet.v2.util.ReleaseType plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' apply false + id 'fabric-loom' version '1.11-SNAPSHOT' apply false id 'io.github.dexman545.outlet' version '1.6.1' apply false id 'com.modrinth.minotaur' version '2.+' apply false id 'maven-publish' diff --git a/fabric/build.gradle b/fabric/build.gradle index edac216..2caf67b 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -1,7 +1,7 @@ import dex.plugins.outlet.v2.util.ReleaseType plugins { - id 'fabric-loom' version '1.9-SNAPSHOT' + id 'fabric-loom' version '1.11-SNAPSHOT' id 'io.github.dexman545.outlet' version '1.6.1' id 'maven-publish' } From afff12f8928980f637d171b72120e73805e4902e Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 21 Sep 2025 11:29:39 +0300 Subject: [PATCH 07/78] Refactor build system --- .github/workflows/build_artifacts.yml | 10 +- .github/workflows/publish.yml | 8 +- build.gradle | 340 ++++++++++++++++++---- common/build.gradle | 23 -- common/gradle.properties | 7 +- fabric/build.gradle | 108 ------- fabric/gradle.properties | 12 +- fabric/src/main/resources/fabric.mod.json | 3 +- gradle.properties | 6 - paper/build.gradle | 61 ---- paper/gradle.properties | 8 +- 11 files changed, 321 insertions(+), 265 deletions(-) delete mode 100644 common/build.gradle delete mode 100644 fabric/build.gradle delete mode 100644 paper/build.gradle diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index 3752e3d..ffe939e 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -24,10 +24,14 @@ jobs: with: cache-read-only: false + - name: Store short commit hash + run: echo "short_commit_hash=$(git rev-parse --short "$GITHUB_SHA")" >> "$GITHUB_ENV" + - name: Build with Gradle run: ./gradlew build env: - DISABLE_PROPERTIES_UPDATE: true + IS_PRODUCTION_BUILD: true + CUSTOM_VERSION: ${{ env.short_commit_hash }} - name: Delete common libs run: rm -r ./common/build/libs @@ -36,4 +40,6 @@ jobs: uses: actions/upload-artifact@v4 with: name: Artifacts - path: ./*/build/libs/ + path: | + ./*/build/libs/ + ./*/*/build/libs/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 90e8b34..f23009b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,11 +29,12 @@ jobs: - name: Build with Gradle run: ./gradlew build env: - DISABLE_PROPERTIES_UPDATE: true + IS_PRODUCTION_BUILD: true - name: Upload to Modrinth run: ./gradlew modrinth env: + IS_PRODUCTION_BUILD: true MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} VERSION_NAME: ${{ github.event.release.name }} VERSION_IS_PRERELEASE: ${{ github.event.release.prerelease }} @@ -42,6 +43,7 @@ jobs: - name: Publish to Maven run: ./gradlew publishMavenPublicationToOffsetMonkey538Repository env: + IS_PRODUCTION_BUILD: true MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} @@ -51,4 +53,6 @@ jobs: - name: Upload to GitHub uses: softprops/action-gh-release@v2 with: - files: ./*/build/libs/*.jar + files: | + ./*/build/libs/*.jar + ./*/*/build/libs/*.jar diff --git a/build.gradle b/build.gradle index 45980a8..bd8a438 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,27 @@ import dex.plugins.outlet.v2.util.ReleaseType plugins { - id 'fabric-loom' version '1.11-SNAPSHOT' apply false - id 'io.github.dexman545.outlet' version '1.6.1' apply false - id 'com.modrinth.minotaur' version '2.+' apply false - id 'maven-publish' + id 'fabric-loom' version '1.11-SNAPSHOT' apply false + id 'io.github.dexman545.outlet' version '1.6.1' apply false + id 'io.papermc.paperweight.userdev' version '2.0.0-beta.8' apply false + id 'com.gradleup.shadow' version '9.0.0-beta4' apply false + id 'com.modrinth.minotaur' version '2.+' apply false + id 'xyz.jpenilla.run-paper' version '2.3.1' apply false } ext { - debugVersion = System.currentTimeMillis() + versionPrefix = rootProject.mod_version + + if (!Boolean.getBoolean("IS_PRODUCTION_BUILD")) { + versionPrefix = "${versionPrefix}-${System.currentTimeMillis()}" + } + + final String versionSuffix = System.getenv("VERSION_SUFFIX") + if (versionSuffix != null && !versionSuffix.isEmpty()) { + versionPrefix = "${versionPrefix}-${versionSuffix}" + } + + println "Version Prefix: ${versionPrefix}" } allprojects { @@ -16,14 +29,17 @@ allprojects { } subprojects { - apply plugin: "maven-publish" - apply plugin: "java" - apply plugin: "java-library" + apply plugin: "java" + apply plugin: "java-library" + apply plugin: "maven-publish" - base.archivesName = "mesh-lib-${project.nameSuffix}" - version = "${rootProject.mod_version}+${rootProject.minecraft_version}" + base.archivesName = "mesh-lib-${project.project_name}" + version = "${rootProject.versionPrefix}" + if (project.hasProperty("minecraft_version")) version = "${version}+${project.property("minecraft_version")}" repositories { + mavenCentral() + mavenLocal() maven { name = "OffsetMods538" url = "https://maven.offsetmonkey538.top/releases" @@ -34,25 +50,31 @@ subprojects { } } - tasks.named("javadoc", Javadoc) { - options.addFileOption('-add-stylesheet', rootProject.file("javadoc-stylesheet.css")) - } + dependencies { + compileOnly "org.jetbrains:annotations:24.0.0" + } + + tasks.withType(JavaCompile).configureEach { + it.options.release = 21 + } java { withSourcesJar() withJavadocJar() - } - jar { - from("${rootProject.projectDir}/LICENSE") { - rename { "${it}" } - } + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } - dependencies { - compileOnly "org.jetbrains:annotations:24.0.0" - compileOnly "org.slf4j:slf4j-api:2.0.16" - } + tasks.named("javadoc", Javadoc) { + options.addFileOption('-add-stylesheet', rootProject.file("javadoc-stylesheet.css")) + } + + jar { + from("${rootProject.projectDir}/LICENSE") { + rename { "${it}" } + } + } publishing { repositories { @@ -72,41 +94,259 @@ subprojects { maven(MavenPublication) { artifactId = base.archivesName.get() - from(components["java"]) + from components.java } } } - tasks.publishMavenPublicationToMavenLocal.doLast { - if (System.getenv("IS_DEBUG") == "true") System.out.println("Version: " + version) - } } -configure(subprojects.findAll { it.name != "common" }) { - apply plugin: 'com.modrinth.minotaur' - apply plugin: 'io.github.dexman545.outlet' +configure(subprojects.findAll { it.name == "common" }) { + apply plugin: "com.gradleup.shadow" - if (System.getenv("IS_DEBUG") == "true") version = "${version}-${rootProject.debugVersion}" + repositories { + maven { + name = "Mojang Libraries" + url = "https://libraries.minecraft.net" + content { + includeGroup "com.mojang" + } + } + } - outlet { - mcVersionRange = rootProject.supported_minecraft_versions - allowedReleaseTypes = Set.of(ReleaseType.RELEASE) - } + dependencies { + api "io.netty:netty-codec-http:${project.netty_version}" - modrinth { - token = System.getenv("MODRINTH_TOKEN") - projectId = "mesh-lib" - def customVersionName = System.getenv("VERSION_NAME") - if (customVersionName != null) versionName = customVersionName - versionNumber = "${project.version}" - versionType = "alpha" - def isPreRelease = System.getenv("VERSION_IS_PRERELEASE") - versionType = !"false".equals(isPreRelease) ? "beta" : "release" - additionalFiles = [sourcesJar.archiveFile] - gameVersions = outlet.mcVersions() - syncBodyFrom = rootProject.file("README.md").text - def changelogEnv = System.getenv("VERSION_CHANGELOG") - if (changelogEnv != null) changelog = changelogEnv - } + shadow compileOnlyApi("top.offsetmonkey538.monkeylib538:monkeylib538-common:${rootProject.monkeylib538_version}") + } + tasks.build.dependsOn(shadowJar) +} + +configure(subprojects.findAll { it.name == "fabric" }) { + apply plugin: "fabric-loom" + apply plugin: "io.github.dexman545.outlet" + apply plugin: "com.modrinth.minotaur" + + outlet { + maintainPropertiesFile = !Boolean.getBoolean("IS_PRODUCTION_BUILD") + mcVersionRange = project.supported_minecraft_versions + allowedReleaseTypes = [ReleaseType.RELEASE] + propertiesData = [ + 'fapi_version': outlet.fapiVersion(project.minecraft_version), + 'yarn_version': outlet.yarnVersion(project.minecraft_version), + 'loader_version': outlet.loaderVersion() + ] + } + + loom { + runs { + client { + runDir "run/client" + } + server { + runDir "run/server" + } + } + + runConfigs.configureEach { + ideConfigGenerated(true) + vmArg "-DmeshEnableExamples=true" + if (!Boolean.getBoolean("IS_PRODUCTION_BUILD")) vmArg "-Ddevauth.enabled=true" + } + } + + repositories { + maven { + name = "DevAuth" + url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" + content { + includeGroup "me.djtheredstoner" + } + } + } + + configurations { + common { + canBeResolved = true + canBeConsumed = false + } + api.extendsFrom common + } + + dependencies { + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_version}:v2" + modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" + + // DevAuth + modLocalRuntime "me.djtheredstoner:DevAuth-fabric:${project.devauth_version}" + + include project(path: ":common", configuration: "shadow") + common project(":common") + + // Only need the command api at runtime for the monkeylib config command + modRuntimeOnly fabricApi.module("fabric-command-api-v2", project.fapi_version) + + // Whatever the heck this is for monkeylib + modRuntimeOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-1.20.5:${rootProject.monkeylib538_version}+1.20.5") { + exclude(group: "net.fabricmc.fabric-api") + exclude(group: "top.offsetmonkey538.monkeylib538:monkeylib538-fabric") + } + modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${rootProject.monkeylib538_version}") { + exclude(group: "net.fabricmc.fabric-api") + } + } + + processResources { + final Map properties = [ + "commonVersion": rootProject.versionPrefix, + "modVersion": project.version, + "supportedMinecraftVersions": project.supported_minecraft_versions, + "currentMonkeylibVersion": project.monkeylib538_version + ] + inputs.properties(properties) + filesMatching("fabric.mod.json") { + expand(properties) + } + + exclude ".cache/**" + } + + modrinth { + // Main properties + token = System.getenv("MODRINTH_TOKEN") + projectId = "mesh-lib" + gameVersions = outlet.mcVersions() + loaders = ["fabric"] + + // Version stuff + def customVersionName = System.getenv("VERSION_NAME") + if (customVersionName != null) versionName = customVersionName + + versionNumber = "${project.version}" + + versionType = Boolean.getBoolean("VERSION_IS_PRERELEASE") ? "beta" : "release" + + if (rootProject.mod_version.contains("beta")) versionType = "beta" + if (rootProject.mod_version.contains("alpha")) versionType = "alpha" + + // Files + uploadFile = remapJar.archiveFile + additionalFiles = [sourcesJar.archiveFile] + + + // Project info + syncBodyFrom = rootProject.file("README.md").text + def changelogEnv = System.getenv("VERSION_CHANGELOG") + if (changelogEnv != null) changelog = changelogEnv + + dependencies { + required.project "monkeylib538" + } + } + tasks.modrinth.dependsOn(tasks.modrinthSyncBody) +} + +configure(subprojects.findAll { it.name == "paper" }) { + apply plugin: "com.gradleup.shadow" + apply plugin: "com.modrinth.minotaur" + apply plugin: "io.github.dexman545.outlet" + apply plugin: "io.papermc.paperweight.userdev" + apply plugin: "xyz.jpenilla.run-paper" + + outlet { + maintainPropertiesFile = false + mcVersionRange = project.supported_minecraft_versions + allowedReleaseTypes = [ReleaseType.RELEASE] + } + + paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.getMOJANG_PRODUCTION() + + repositories { + maven { + name = 'PaperMC' + url = 'https://repo.papermc.io/repository/maven-public/' + content { + includeGroup "io.papermc.paper" + } + } + } + + configurations { + common { + canBeResolved = true + canBeConsumed = false + } + api.extendsFrom common + } + + dependencies { + compileOnly "io.papermc.paper:paper-api:${project.paper_version}" + + paperweight.paperDevBundle project.paper_version + + common project(path: ":common", configuration: "shadow") + + /* todo: monke not have paper yetttttttt + // Whatever the heck this is for monkeylib + modRuntimeOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-1.20.5:${rootProject.monkeylib538_version}+1.20.5") { + exclude(group: "io.papermc.paper") + exclude(group: "top.offsetmonkey538.monkeylib538:monkeylib538-fabric") + } + modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${rootProject.monkeylib538_version}") { + exclude(group: "net.fabricmc.fabric-api") + } + */ + } + tasks.build.dependsOn(shadowJar) + + processResources { + final Map properties = [ + "modVersion": rootProject.mod_version, + "currentMonkeylibVersion": project.monkeylib538_version, + "lowestMinecraftVersion": outlet.mcVersions().first() // here's hoping outlet does this in order + ] + inputs.properties(properties) + filesMatching("paper-plugin.yml") { + expand(properties) + } + } + + tasks.runServer { + minecraftVersion(project.minecraft_version) + jvmArgs "-DmeshEnableExamples=true" + } + + modrinth { + // Main properties + token = System.getenv("MODRINTH_TOKEN") + projectId = "mesh-lib" + gameVersions = outlet.mcVersions() + loaders = ["paper"] + + // Version stuff + def customVersionName = System.getenv("VERSION_NAME") + if (customVersionName != null) versionName = customVersionName + + versionNumber = "${project.version}" + + versionType = Boolean.getBoolean("VERSION_IS_PRERELEASE") ? "beta" : "release" + + if (rootProject.mod_version.contains("beta")) versionType = "beta" + if (rootProject.mod_version.contains("alpha")) versionType = "alpha" + + // Files + uploadFile = shadowJar + additionalFiles = [sourcesJar.archiveFile] + + + // Project info + syncBodyFrom = rootProject.file("README.md").text + def changelogEnv = System.getenv("VERSION_CHANGELOG") + if (changelogEnv != null) changelog = changelogEnv - tasks.modrinth.dependsOn(tasks.modrinthSyncBody) + dependencies { + required.project "monkeylib538" + } + } + tasks.modrinth.dependsOn(tasks.modrinthSyncBody) } diff --git a/common/build.gradle b/common/build.gradle deleted file mode 100644 index cc51555..0000000 --- a/common/build.gradle +++ /dev/null @@ -1,23 +0,0 @@ -plugins { - id 'com.gradleup.shadow' version '9.0.0-beta4' - id 'maven-publish' -} - -repositories { - mavenCentral() - maven { - name = "Mojang Libraries" - url = "https://libraries.minecraft.net" - content { - includeGroup "com.mojang" - } - } -} - -dependencies { - // Netty - api "io.netty:netty-codec-http:${project.netty_version}" - - shadow compileOnlyApi("top.offsetmonkey538.monkeylib538:monkeylib538-common:${project.monkeylib538_version}") -} -tasks.build.dependsOn(shadowJar) diff --git a/common/gradle.properties b/common/gradle.properties index 9e33a89..b33f612 100644 --- a/common/gradle.properties +++ b/common/gradle.properties @@ -1,5 +1,4 @@ -## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty -netty_version = 4.1.82.Final - +project_name = api -nameSuffix = api +## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty +netty_version = 4.1.82.Final diff --git a/fabric/build.gradle b/fabric/build.gradle deleted file mode 100644 index 2caf67b..0000000 --- a/fabric/build.gradle +++ /dev/null @@ -1,108 +0,0 @@ -import dex.plugins.outlet.v2.util.ReleaseType - -plugins { - id 'fabric-loom' version '1.11-SNAPSHOT' - id 'io.github.dexman545.outlet' version '1.6.1' - id 'maven-publish' -} - -outlet { - maintainPropertiesFile = System.getenv("DISABLE_PROPERTIES_UPDATE") == null - mcVersionRange = rootProject.supported_minecraft_versions - allowedReleaseTypes = Set.of(ReleaseType.RELEASE) - propertiesData = [ - 'fapi_version': outlet.fapiVersion(project.minecraft_version), - 'yarn_version': outlet.yarnVersion(project.minecraft_version), - 'loader_version': outlet.loaderVersion() - ] -} - - -loom { - runs { - client { - runDir "run/client" - } - server { - runDir "run/server" - } - } - - runConfigs.configureEach { - ideConfigGenerated(true) - vmArg "-DmeshEnableExamples=true" - } -} - -// https://gist.github.com/maityyy/3dbcd558d58a6412c3a2a38c72706e8e -afterEvaluate { - loom.runs.configureEach { - vmArg "-javaagent:${project.configurations.compileClasspath.find{ it.name.contains("sponge-mixin") }}" - if (System.getenv("DISABLE_PROPERTIES_UPDATE") == null) vmArg "-Ddevauth.enabled=true" - } -} - -repositories { - mavenCentral() - mavenLocal() - maven { - name = "DevAuth" - url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" - content { - includeGroup "me.djtheredstoner" - } - } -} - -configurations { - common { - canBeResolved = true - canBeConsumed = false - } - api.extendsFrom common -} - -dependencies { - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings "net.fabricmc:yarn:${project.yarn_version}:v2" - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // DevAuth - modLocalRuntime "me.djtheredstoner:DevAuth-fabric:${devauth_version}" - - include project(path: ":common", configuration: "shadow") - common project(":common") - - // Uncomment for including a module of fabric api - //includeModImplementation fabricApi.module("fabric-api-base", project.fapi_version) - modRuntimeOnly fabricApi.module("fabric-command-api-v2", project.fapi_version) - - modRuntimeOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-1.20.5:${project.monkeylib538_version}+1.20.5") { - exclude(group: "net.fabricmc.fabric-api") - exclude(group: "top.offsetmonkey538.monkeylib538:monkeylib538-fabric") - } - modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${project.monkeylib538_version}") { - exclude(group: "net.fabricmc.fabric-api") - } -} - -processResources { - final Map properties = Map.of( - "modVersion", rootProject.mod_version, - "supportedMinecraftVersions", rootProject.supported_minecraft_versions - ) - - inputs.properties(properties) - - filesMatching("fabric.mod.json") { - expand(properties) - } - - exclude ".cache/**" -} - - -modrinth { - loaders = ["fabric"] - uploadFile = remapJar.archiveFile -} diff --git a/fabric/gradle.properties b/fabric/gradle.properties index cf0f596..a7142fe 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -1,7 +1,10 @@ +project_name = fabric + # Fabric -# Check at https://fabricmc.net/develop -# These should be automatically updated, unless the environment -# variable "DISABLE_PROPERTIES_UPDATE" is set. +## Check at https://fabricmc.net/develop +## These should be automatically updated, unless the environment +## variable "IS_PRODUCTION_BUILD" is set. +minecraft_version = 1.21.4 yarn_version = 1.21.4+build.8 loader_version = 0.17.2 @@ -13,4 +16,5 @@ devauth_version = 1.2.1 fapi_version = 0.119.4+1.21.4 -nameSuffix = fabric +# Mod Properties TODO +supported_minecraft_versions = * \ No newline at end of file diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 822365c..d166a8f 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -24,6 +24,7 @@ "mesh-lib.mixins.json" ], "depends": { - "minecraft": "${supportedMinecraftVersions}" + "minecraft": "${supportedMinecraftVersions}", + "monkeylib538": ">=${currentMonkeylibVersion}" } } diff --git a/gradle.properties b/gradle.properties index 0b5f4f5..d07a743 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,14 +2,8 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true -minecraft_version = 1.21.4 - -## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty -netty_version = 4.1.82.Final - # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 monkeylib538_version = 3.0.0-6345c7b # Mod Properties mod_version = 1.0.5 -supported_minecraft_versions = >=1.19 diff --git a/paper/build.gradle b/paper/build.gradle deleted file mode 100644 index 6887e9b..0000000 --- a/paper/build.gradle +++ /dev/null @@ -1,61 +0,0 @@ -import io.papermc.paperweight.userdev.ReobfArtifactConfiguration - -plugins { - id 'com.gradleup.shadow' version '9.0.0-beta4' - id 'io.papermc.paperweight.userdev' version '2.0.0-beta.8' - id 'xyz.jpenilla.run-paper' version '2.3.1' - id 'maven-publish' -} - -paperweight.reobfArtifactConfiguration = ReobfArtifactConfiguration.getMOJANG_PRODUCTION() - -repositories { - mavenCentral() - maven { - name = 'papermc' - url = 'https://repo.papermc.io/repository/maven-public/' - } -} - -configurations { - common { - canBeResolved = true - canBeConsumed = false - } - api.extendsFrom common -} - -dependencies { - // Paper - compileOnly "io.papermc.paper:paper-api:${project.paper_version}" - - // Userdev - paperweight.paperDevBundle(project.paper_version) - - // Common - common project(path: ":common", configuration: "shadow") -} -tasks.build.dependsOn(shadowJar) - -processResources { - final Map properties = Map.of( - "modVersion", rootProject.mod_version, - "lowestMinecraftVersion", outlet.mcVersions().first() // Hopefully outlet always does stuff in this order - ) - - inputs.properties(properties) - - filesMatching("paper-plugin.yml") { - expand(properties) - } -} - -tasks.runServer { - minecraftVersion("1.21.4") - jvmArgs "-DmeshEnableExamples=true" -} - -modrinth { - loaders = ["paper"] - uploadFile = shadowJar -} diff --git a/paper/gradle.properties b/paper/gradle.properties index ea66afc..6cca3f3 100644 --- a/paper/gradle.properties +++ b/paper/gradle.properties @@ -1,8 +1,8 @@ -## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty -netty_version = 4.1.82.Final +project_name = paper ## no idea where to get this +minecraft_version = 1.21.4 paper_version = 1.21.4-R0.1-SNAPSHOT - -nameSuffix = paper \ No newline at end of file +# Mod Properties TODO +supported_minecraft_versions = * \ No newline at end of file From 14847812ebd854a0ae215c737736a26c6ffa576f Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 21 Sep 2025 11:33:07 +0300 Subject: [PATCH 08/78] Fix build errors (use monkeylib logger) --- .../src/main/java/top/offsetmonkey538/meshlib/MESHLib.java | 5 ++--- .../top/offsetmonkey538/meshlib/impl/ProtocolHandler.java | 4 ++-- fabric/gradle.properties | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index 9f6fdb3..d5fac96 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -1,11 +1,10 @@ package top.offsetmonkey538.meshlib; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import top.offsetmonkey538.meshlib.config.TestConfig; import top.offsetmonkey538.meshlib.example.ExampleMain; import top.offsetmonkey538.meshlib.platform.PlatformMain; import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; +import top.offsetmonkey538.monkeylib538.api.log.MonkeyLibLogger; import top.offsetmonkey538.offsetconfig538.api.config.ConfigHolder; import top.offsetmonkey538.offsetconfig538.api.config.ConfigManager; @@ -24,7 +23,7 @@ private MESHLib() {} /** * Logger instance used by this mod */ - public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + public static final MonkeyLibLogger LOGGER = MonkeyLibLogger.create(MOD_ID); public static void initialize() { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java index 3fa6286..3042e59 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java @@ -20,7 +20,7 @@ public class ProtocolHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object request) throws Exception { if (!(request instanceof ByteBuf buf)) { - LOGGER.warn("Received request '{}' that wasn't a ByteBuf", request); + LOGGER.warn("Received request '%s' that wasn't a ByteBuf", request); return; } @@ -46,7 +46,7 @@ public void channelRead(ChannelHandlerContext ctx, Object request) throws Except final String handlerId = uri.split("/")[1]; if (!HttpHandlerRegistry.INSTANCE.has(handlerId)) { - LOGGER.debug("Handler with id '{}' not registered! Passing on...", handlerId); + LOGGER.debug("Handler with id '%s' not registered! Passing on...", handlerId); forward(ctx, request); return; } diff --git a/fabric/gradle.properties b/fabric/gradle.properties index a7142fe..9043e7a 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -17,4 +17,4 @@ fapi_version = 0.119.4+1.21.4 # Mod Properties TODO -supported_minecraft_versions = * \ No newline at end of file +supported_minecraft_versions = * From 5999f0dfa2195e71d5cb2c1e5df411cb6268dea2 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 21 Sep 2025 11:37:51 +0300 Subject: [PATCH 09/78] Update github workflow --- .github/workflows/build_artifacts.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index ffe939e..b7a4185 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -31,7 +31,15 @@ jobs: run: ./gradlew build env: IS_PRODUCTION_BUILD: true - CUSTOM_VERSION: ${{ env.short_commit_hash }} + VERSION_SUFFIX: ${{ env.short_commit_hash }} + + - name: Publish to Maven + run: ./gradlew publishMavenPublicationToOffsetMonkey538Repository + env: + DISABLE_PROPERTIES_UPDATE: true + VERSION_SUFFIX: ${{ env.short_commit_hash }} + MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} - name: Delete common libs run: rm -r ./common/build/libs From cb050a63630f0d36bc45ed3df89090843f963803 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 21 Sep 2025 11:55:28 +0300 Subject: [PATCH 10/78] fix env vars not being used --- .github/workflows/build_artifacts.yml | 1 - .github/workflows/publish.yml | 1 - build.gradle | 10 +++++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index b7a4185..cc65959 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -50,4 +50,3 @@ jobs: name: Artifacts path: | ./*/build/libs/ - ./*/*/build/libs/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f23009b..4aedf35 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -55,4 +55,3 @@ jobs: with: files: | ./*/build/libs/*.jar - ./*/*/build/libs/*.jar diff --git a/build.gradle b/build.gradle index bd8a438..0d5ceb5 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ plugins { ext { versionPrefix = rootProject.mod_version - if (!Boolean.getBoolean("IS_PRODUCTION_BUILD")) { + if (!Boolean.parseBoolean(System.getenv("IS_PRODUCTION_BUILD"))) { versionPrefix = "${versionPrefix}-${System.currentTimeMillis()}" } @@ -127,7 +127,7 @@ configure(subprojects.findAll { it.name == "fabric" }) { apply plugin: "com.modrinth.minotaur" outlet { - maintainPropertiesFile = !Boolean.getBoolean("IS_PRODUCTION_BUILD") + maintainPropertiesFile = !Boolean.parseBoolean(System.getenv("IS_PRODUCTION_BUILD")) mcVersionRange = project.supported_minecraft_versions allowedReleaseTypes = [ReleaseType.RELEASE] propertiesData = [ @@ -150,7 +150,7 @@ configure(subprojects.findAll { it.name == "fabric" }) { runConfigs.configureEach { ideConfigGenerated(true) vmArg "-DmeshEnableExamples=true" - if (!Boolean.getBoolean("IS_PRODUCTION_BUILD")) vmArg "-Ddevauth.enabled=true" + if (!Boolean.parseBoolean(System.getenv("IS_PRODUCTION_BUILD"))) vmArg "-Ddevauth.enabled=true" } } @@ -224,7 +224,7 @@ configure(subprojects.findAll { it.name == "fabric" }) { versionNumber = "${project.version}" - versionType = Boolean.getBoolean("VERSION_IS_PRERELEASE") ? "beta" : "release" + versionType = Boolean.parseBoolean(System.getenv("VERSION_IS_PRERELEASE")) ? "beta" : "release" if (rootProject.mod_version.contains("beta")) versionType = "beta" if (rootProject.mod_version.contains("alpha")) versionType = "alpha" @@ -329,7 +329,7 @@ configure(subprojects.findAll { it.name == "paper" }) { versionNumber = "${project.version}" - versionType = Boolean.getBoolean("VERSION_IS_PRERELEASE") ? "beta" : "release" + versionType = Boolean.parseBoolean(System.getenv("VERSION_IS_PRERELEASE")) ? "beta" : "release" if (rootProject.mod_version.contains("beta")) versionType = "beta" if (rootProject.mod_version.contains("alpha")) versionType = "alpha" From d75252c0bc70ce154c497fdd70c614e9532c7620 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 21 Sep 2025 11:55:49 +0300 Subject: [PATCH 11/78] imprt --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0d5ceb5..c62261a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,5 @@ import dex.plugins.outlet.v2.util.ReleaseType +import io.papermc.paperweight.userdev.ReobfArtifactConfiguration plugins { id 'fabric-loom' version '1.11-SNAPSHOT' apply false @@ -259,7 +260,7 @@ configure(subprojects.findAll { it.name == "paper" }) { allowedReleaseTypes = [ReleaseType.RELEASE] } - paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.getMOJANG_PRODUCTION() + paperweight.reobfArtifactConfiguration = ReobfArtifactConfiguration.getMOJANG_PRODUCTION() repositories { maven { From c4d5d7791dc6879f32d529a7419d64be7f0d3065 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 21 Sep 2025 12:00:32 +0300 Subject: [PATCH 12/78] fix build workflow one maybe last time --- .github/workflows/build_artifacts.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index cc65959..dbda0e7 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -36,6 +36,7 @@ jobs: - name: Publish to Maven run: ./gradlew publishMavenPublicationToOffsetMonkey538Repository env: + IS_PRODUCTION_BUILD: true DISABLE_PROPERTIES_UPDATE: true VERSION_SUFFIX: ${{ env.short_commit_hash }} MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} From 3952f49e06812a616105637cf42eb6af7faf2773 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Tue, 23 Sep 2025 21:39:13 +0300 Subject: [PATCH 13/78] I really shouldn't be committing code that's in this state... --- .../top/offsetmonkey538/meshlib/MESHLib.java | 66 +++++++++- .../meshlib/api/HttpHandler.java | 10 +- .../meshlib/api/HttpHandlerRegistry.java | 49 -------- .../meshlib/api/HttpHandlerTypeRegistry.java | 30 +++++ .../meshlib/api/router/HttpRouter.java | 9 ++ .../api/router/HttpRouterRegistry.java | 41 ++++++ .../meshlib/api/router/rule/HttpRule.java | 16 +++ .../api/router/rule/HttpRuleTypeRegistry.java | 24 ++++ .../meshlib/api/router/target/HttpTarget.java | 4 + .../meshlib/config/TestConfig.java | 118 +----------------- .../meshlib/example/ExampleMain.java | 14 ++- .../meshlib/example/SimpleHttpHandler.java | 2 +- .../meshlib/impl/HttpHandlerRegistryImpl.java | 34 ----- .../meshlib/impl/HttpRouterRegistryImpl.java | 30 +++++ .../meshlib/impl/MainHttpHandler.java | 69 +++++++++- .../meshlib/impl/ProtocolHandler.java | 31 ++--- .../router/HttpHandlerTypeRegistryImpl.java | 41 ++++++ .../impl/router/rule/DomainHttpRule.java | 49 ++++++++ .../router/rule/HttpRuleTypeRegistryImpl.java | 29 +++++ .../router/target/WebserverHttpTarget.java | 17 +++ 20 files changed, 455 insertions(+), 228 deletions(-) delete mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerRegistry.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerTypeRegistry.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRule.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRuleTypeRegistry.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/router/target/HttpTarget.java delete mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpHandlerRegistryImpl.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/DomainHttpRule.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index d5fac96..711dc28 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -1,12 +1,21 @@ package top.offsetmonkey538.meshlib; +import blue.endless.jankson.JsonObject; +import blue.endless.jankson.JsonPrimitive; +import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.api.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.config.TestConfig; import top.offsetmonkey538.meshlib.example.ExampleMain; +import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; +import top.offsetmonkey538.meshlib.impl.router.rule.DomainHttpRule; import top.offsetmonkey538.meshlib.platform.PlatformMain; import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; import top.offsetmonkey538.monkeylib538.api.log.MonkeyLibLogger; import top.offsetmonkey538.offsetconfig538.api.config.ConfigHolder; import top.offsetmonkey538.offsetconfig538.api.config.ConfigManager; +import top.offsetmonkey538.offsetconfig538.api.event.OffsetConfig538Events; import java.util.ServiceLoader; @@ -31,9 +40,62 @@ public static void initialize() { ExampleMain.onInitialize(); + OffsetConfig538Events.JANKSON_CONFIGURATION_EVENT.listen(builder -> { + builder.registerSerializer(HttpRule.class, (httpRule, marshaller) -> { + final JsonObject result = (JsonObject) marshaller.serialize(httpRule.getData()); + result.put("type", JsonPrimitive.of(httpRule.getType())); + return result; + }); + + builder.registerDeserializer(JsonObject.class, HttpRule.class, (jsonObject, marshaller) -> { + final String type = jsonObject.get(String.class, "type"); + + @SuppressWarnings("unchecked") // It's proooobably a subclass of Object... + HttpRule.HttpRuleDefinition ruleDefinition = (HttpRule.HttpRuleDefinition) HttpRuleTypeRegistry.get(type); + + final JsonObject dummyParent = new JsonObject(); + dummyParent.put("dataHolder", jsonObject); + final Object dataHolder = dummyParent.get(ruleDefinition.dataType(), "dataHolder"); + + return ruleDefinition.ruleInitializer().apply(dataHolder); + }); + + + builder.registerSerializer(HttpHandler.class, (httpHandler, marshaller) -> { + HttpHandler.HttpHandlerDefinition handlerDefinition = HttpHandlerTypeRegistry.get(httpHandler.getClass()); + + final JsonObject result = (JsonObject) marshaller.serialize(handlerDefinition.handlerToData().apply(httpHandler)); + result.put("type", JsonPrimitive.of(handlerDefinition.type())); + return result; + }); + + builder.registerDeserializer(JsonObject.class, HttpHandler.class, (jsonObject, marshaller) -> { + final String type = jsonObject.get(String.class, "type"); + + @SuppressWarnings("unchecked") // It's proooobably a subclass of Object... + HttpHandler.HttpHandlerDefinition handlerDefinition = (HttpHandler.HttpHandlerDefinition) HttpHandlerTypeRegistry.get(type); + + final JsonObject dummyParent = new JsonObject(); + dummyParent.put("dataHolder", jsonObject); + final Object dataHolder = dummyParent.get(handlerDefinition.dataType(), "dataHolder"); + + return handlerDefinition.handlerInitializer().apply(dataHolder); + }); + }); + + HttpHandlerTypeRegistry.register(SimpleHttpHandler.class, new HttpHandler.HttpHandlerDefinition<>("simple-http", testDatType.class, testDatType -> new SimpleHttpHandler(), httpHandler -> new testDatType("a"))); + HttpRuleTypeRegistry.register("domain", new HttpRule.HttpRuleDefinition<>(DomainHttpRule.Data.class, DomainHttpRule::new)); + final ConfigHolder config = ConfigManager.INSTANCE.init(ConfigHolder.create(TestConfig::new, LOGGER::error)); - System.out.println(config.get().thingy); - ConfigCommandApi.registerConfigCommand(config, () -> System.out.println(config.get().thingy), "test"); + System.out.println("HELLO!"); + ConfigCommandApi.registerConfigCommand(config, () -> { + System.out.println(config); + System.out.println("Hello!"); + }, "test"); + } + + public record testDatType(String hi) { + } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java index c6aa42e..e26f5a3 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java @@ -9,10 +9,14 @@ import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.CharsetUtil; +import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.function.Function; + import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; @@ -26,7 +30,7 @@ *

* Look at {@link SimpleHttpHandler SimpleHttpHandler} for an example * - * @see HttpHandlerRegistry + * @see HttpRouterRegistry */ @FunctionalInterface public interface HttpHandler { @@ -72,4 +76,8 @@ static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseS response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } + + record HttpHandlerDefinition(String type, Class dataType, Function handlerInitializer, Function handlerToData) { + + } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerRegistry.java deleted file mode 100644 index 7c05d3c..0000000 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerRegistry.java +++ /dev/null @@ -1,49 +0,0 @@ -package top.offsetmonkey538.meshlib.api; - -import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.impl.HttpHandlerRegistryImpl; - -/** - * Registry for the handlers - *

- * Each handler will only be able to listen to requests on *its* sub-path. - *
- * If your handler's id is {@code testmod}, then your handler will only receive requests on {@code server.com/testmod/} - * @see HttpHandler - */ -public interface HttpHandlerRegistry { - /** - * Instance - */ - HttpHandlerRegistry INSTANCE = new HttpHandlerRegistryImpl(); - - /** - * Method for registering a {@link HttpHandler} - *
- * THE ID SHOULD NOT BE EMPTY - * - * @param id your handler or mod's id - * @param handler the {@link HttpHandler} to be registered - * @throws IllegalArgumentException when the provided id is empty or a handler with this id is already registered - * @see HttpHandler - */ - void register(@NotNull String id, @NotNull HttpHandler handler) throws IllegalArgumentException; - - /** - * Method for getting a registered {@link HttpHandler} - * - * @param id the handler's id - * @return the {@link HttpHandler} for the provided id - * @throws IllegalStateException when there is no {@link HttpHandler} registered for the provided id - */ - @NotNull - HttpHandler get(@NotNull String id) throws IllegalStateException; - - /** - * Returns true if handler with provided id is registered, false otherwise - * - * @param id the id to check - * @return true if handler with provided id is registered, false otherwise - */ - boolean has(@NotNull String id); -} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerTypeRegistry.java new file mode 100644 index 0000000..e06b3f6 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerTypeRegistry.java @@ -0,0 +1,30 @@ +package top.offsetmonkey538.meshlib.api; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; +import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; + +public interface HttpHandlerTypeRegistry { + /** + * Instance + */ + @ApiStatus.Internal + HttpHandlerTypeRegistry INSTANCE = new HttpHandlerTypeRegistryImpl(); + + static void register(@NotNull final Class type, @NotNull final HttpHandler.HttpHandlerDefinition handler) { + INSTANCE.registerImpl(type, handler); + } + + static HttpHandler.HttpHandlerDefinition get(final String type) throws IllegalStateException { + return INSTANCE.getImpl(type); + } + static HttpHandler.HttpHandlerDefinition get(final Class type) throws IllegalStateException { + return INSTANCE.getImpl(type); + } + + void registerImpl(@NotNull final Class type, @NotNull final HttpHandler.HttpHandlerDefinition handler); + HttpHandler.HttpHandlerDefinition getImpl(final String type) throws IllegalStateException; + HttpHandler.HttpHandlerDefinition getImpl(final Class type) throws IllegalStateException; +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java new file mode 100644 index 0000000..3c7c0c4 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java @@ -0,0 +1,9 @@ +package top.offsetmonkey538.meshlib.api.router; + +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.router.target.HttpTarget; + +public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java new file mode 100644 index 0000000..518f39d --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java @@ -0,0 +1,41 @@ +package top.offsetmonkey538.meshlib.api.router; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.impl.HttpRouterRegistryImpl; + +import java.util.Map; + +/** + * Registry for routers + * todo;K commebnt + *

+ * Each handler will only be able to listen to requests on *its* sub-path. + *
+ * If your handler's id is {@code testmod}, then your handler will only receive requests on {@code server.com/testmod/} + * @see HttpHandler + */ +public interface HttpRouterRegistry { + /** + * Instance + */ + @ApiStatus.Internal + HttpRouterRegistry INSTANCE = new HttpRouterRegistryImpl(); + + /** + * todo;: actual comment + * Method for registering a {@link HttpHandler} + *
+ * THE ID SHOULD NOT BE EMPTY + * + * @param id your handler or mod's id + * @param handler the {@link HttpHandler} to be registered + * @throws IllegalArgumentException when the provided id is empty or a handler with this id is already registered + * @see HttpHandler + */ + void register(@NotNull String id, @NotNull HttpRouter router) throws IllegalArgumentException; + + @NotNull + Iterable> iterable(); +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRule.java new file mode 100644 index 0000000..e0eeec2 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRule.java @@ -0,0 +1,16 @@ +package top.offsetmonkey538.meshlib.api.router.rule; + +import io.netty.handler.codec.http.FullHttpRequest; + +import java.util.function.Function; + +public interface HttpRule { + String getType(); + T getData(); + + boolean matches(final FullHttpRequest httpRequest); + + record HttpRuleDefinition(Class dataType, Function> ruleInitializer) { + + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRuleTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRuleTypeRegistry.java new file mode 100644 index 0000000..c804bb3 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRuleTypeRegistry.java @@ -0,0 +1,24 @@ +package top.offsetmonkey538.meshlib.api.router.rule; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; + +public interface HttpRuleTypeRegistry { + /** + * Instance + */ + @ApiStatus.Internal + HttpRuleTypeRegistry INSTANCE = new HttpRuleTypeRegistryImpl(); + + static void register(final String type, @NotNull final HttpRule.HttpRuleDefinition rule) { + INSTANCE.registerImpl(type, rule); + } + + static @NotNull HttpRule.HttpRuleDefinition get(final String type) throws IllegalStateException { + return INSTANCE.getImpl(type); + } + + void registerImpl(final String type, @NotNull final HttpRule.HttpRuleDefinition rule); + @NotNull HttpRule.HttpRuleDefinition getImpl(final String type) throws IllegalStateException; +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/target/HttpTarget.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/target/HttpTarget.java new file mode 100644 index 0000000..588f610 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/target/HttpTarget.java @@ -0,0 +1,4 @@ +package top.offsetmonkey538.meshlib.api.router.target; + +public interface HttpTarget { +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java index ecbea91..5c40803 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java @@ -1,127 +1,21 @@ package top.offsetmonkey538.meshlib.config; -import blue.endless.jankson.Jankson; -import blue.endless.jankson.JsonObject; -import blue.endless.jankson.JsonPrimitive; import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; +import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; +import top.offsetmonkey538.meshlib.impl.router.rule.DomainHttpRule; import top.offsetmonkey538.meshlib.platform.PlatformMain; import top.offsetmonkey538.offsetconfig538.api.config.Config; import java.nio.file.Path; -import java.util.Map; -import java.util.function.Function; public class TestConfig implements Config { - public AbstractThing thingy = new Type1(new Type1.Data("defaultValue")); + public HttpRule rule = new DomainHttpRule(new DomainHttpRule.Data("map.example.com")); + public HttpHandler target = new SimpleHttpHandler(); - public interface AbstractThing { - String getType(); - T getData(); - } - private static class Type1 implements AbstractThing { - private final String value; - - public Type1(Data data) { - this.value = data.value; - } - - @Override - public String getType() { - return "type1"; - } - - @Override - public Data getData() { - return new Data(value); - } - - @Override - public String toString() { - return "value: " + value; - } - - public static final class Data { - @SuppressWarnings("FieldMayBeFinal") // Think it needs to be non-final cause jankson - public String value; - - @SuppressWarnings("unused") // Public no-args used by jankson i think - public Data() { - - } - - private Data(String value) { - this.value = value; - } - } - } - private static class Type2 implements AbstractThing { - private final double differentValue; - - public Type2(Data data) { - this.differentValue = data.differentValue; - } - - @Override - public String getType() { - return "type2"; - } - - @Override - public Data getData() { - return new Data(differentValue); - } - - @Override - public String toString() { - return "differentValue: " + differentValue; - } - - public static final class Data { - @SuppressWarnings("FieldMayBeFinal") // Think it needs to be non-final cause jankson - public double differentValue; - - @SuppressWarnings("unused") // Public no-args used by jankson i think - public Data() { - - } - - private Data(double value) { - this.differentValue = value; - } - } - } - - private static record ThingyDefinition(Class dataType, Function> thingyInitializer) { - - } - - @Override - public void configureJankson(Jankson.@NotNull Builder builder) { - // type to value holder type todo: actually have a registry type of thing for this - final Map> testMap = Map.of( - "type1", new ThingyDefinition<>(Type1.Data.class, Type1::new), - "type2", new ThingyDefinition<>(Type2.Data.class, Type2::new) - ); - - builder.registerSerializer(AbstractThing.class, (abstractThing, marshaller) -> { - final JsonObject result = (JsonObject) marshaller.serialize(abstractThing.getData()); - result.put("type", JsonPrimitive.of(abstractThing.getType())); - return result; - }); - builder.registerDeserializer(JsonObject.class, AbstractThing.class, (jsonObject, marshaller) -> { - final String type = jsonObject.get(String.class, "type"); - @SuppressWarnings("unchecked") // It's proooobably a subclass of Object... - ThingyDefinition thingyDefinition = (ThingyDefinition) testMap.get(type); - final JsonObject dummyParent = new JsonObject(); - dummyParent.put("data", jsonObject); - Object data = dummyParent.get(thingyDefinition.dataType, "data"); - - return thingyDefinition.thingyInitializer.apply(data); - }); - } - @Override public @NotNull Path getFilePath() { return PlatformMain.getConfigDir().resolve(getId() + ".json"); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index 1722c2f..7d3000b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -1,6 +1,13 @@ package top.offsetmonkey538.meshlib.example; -import top.offsetmonkey538.meshlib.api.HttpHandlerRegistry; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.router.HttpRouter; +import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.api.router.target.HttpTarget; +import top.offsetmonkey538.meshlib.impl.router.rule.DomainHttpRule; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; @@ -25,6 +32,9 @@ public static void onInitialize() { LOGGER.warn("MESH examples enabled!"); // Register - HttpHandlerRegistry.INSTANCE.register("simple-server", new SimpleHttpHandler()); + HttpRouterRegistry.INSTANCE.register("simple-server", new HttpRouter( + new DomainHttpRule(new DomainHttpRule.Data("localhost")), + new SimpleHttpHandler() + )); } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java index 1e25980..f9ad724 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java @@ -31,7 +31,7 @@ public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpR // Set the "CONTENT_TYPE" header to tell the browser that this is plain text encoded in UTF-8 response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); - + System.out.println("hi"); // Send the response and close the connection ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpHandlerRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpHandlerRegistryImpl.java deleted file mode 100644 index 14670cc..0000000 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpHandlerRegistryImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -package top.offsetmonkey538.meshlib.impl; - -import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.HttpHandler; -import top.offsetmonkey538.meshlib.api.HttpHandlerRegistry; - -import java.util.HashMap; -import java.util.Map; - -/** - * Implementation of {@link HttpHandlerRegistry} - */ -public class HttpHandlerRegistryImpl implements HttpHandlerRegistry { - private final Map handlers = new HashMap<>(); - - @Override - public void register(@NotNull String id, @NotNull HttpHandler handler) throws IllegalArgumentException { - if (id.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); - if (handlers.containsKey(id)) throw new IllegalArgumentException("Handler with id '" + id + "' already registered!"); - - handlers.put(id, handler); - } - - @Override - public @NotNull HttpHandler get(@NotNull String id) throws IllegalStateException { - if (handlers.containsKey(id)) return handlers.get(id); - throw new IllegalStateException("Handler with id '" + id + "' not registered!"); - } - - @Override - public boolean has(@NotNull String id) { - return handlers.containsKey(id); - } -} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java new file mode 100644 index 0000000..4e3bf31 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java @@ -0,0 +1,30 @@ +package top.offsetmonkey538.meshlib.impl; + +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.router.HttpRouter; +import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Implementation of {@link HttpRouterRegistry} + */ +public class HttpRouterRegistryImpl implements HttpRouterRegistry { + private final Map handlers = new HashMap<>(); + + @Override + public void register(@NotNull String id, @NotNull HttpRouter router) throws IllegalArgumentException { + if (id.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); + if (handlers.containsKey(id)) throw new IllegalArgumentException("Handler with id '" + id + "' already registered!"); + + handlers.put(id, router); + } + + @Override + public @NotNull Iterable> iterable() { + return this.handlers.entrySet(); + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java index d82da67..abb3962 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java @@ -2,19 +2,26 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.*; +import io.netty.util.ReferenceCountUtil; import top.offsetmonkey538.meshlib.api.HttpHandler; -import top.offsetmonkey538.meshlib.api.HttpHandlerRegistry; +import top.offsetmonkey538.meshlib.api.router.HttpRouter; +import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; import static top.offsetmonkey538.meshlib.api.HttpHandler.sendError; /** * Main HTTP handler for MESH. *

- * Forwards the requests to {@link HttpHandler HttpHandler}s registered in {@link HttpHandlerRegistry} + * Forwards the requests to {@link HttpHandler HttpHandler}s registered in {@link HttpRouterRegistry} */ public class MainHttpHandler extends SimpleChannelInboundHandler { @@ -25,16 +32,66 @@ protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) return; } - final String handlerId = request.uri().split("/")[1]; + HttpRouter router = null; + List matchedRouterIDs = new ArrayList<>(0); + for (Map.Entry possibleRouter : HttpRouterRegistry.INSTANCE.iterable()) { + if (!possibleRouter.getValue().rule().matches(request)) continue; + + matchedRouterIDs.add(possibleRouter.getKey()); + if (router == null) router = possibleRouter.getValue(); + } + if (router == null) { + LOGGER.warn("No routers matched request! Ignoring..."); + forward(ctx, request); + return; + } + + if (matchedRouterIDs.size() > 1) { + LOGGER.error("More than one router matched request! Ignoring..."); + final StringBuilder builder = new StringBuilder("Matched routers: ").append(matchedRouterIDs.getFirst()); + for (int i = 1; i < matchedRouterIDs.size(); i++) { + builder.append(", "); + builder.append(matchedRouterIDs.get(i)); + } + LOGGER.error(builder.toString()); + forward(ctx, request); + return; + } + + final HttpHandler handler = router.handler(); + ctx.pipeline().addAfter(MOD_ID + "/handler", MOD_ID + "/" + matchedRouterIDs.getFirst(), new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { + handler.handleRequest(ctx, request); + } - HttpHandlerRegistry.INSTANCE.get(handlerId).handleRequest(ctx, request); + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + exceptionCaughtImpl(ctx, cause); + } + }); + forward(ctx, request); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + exceptionCaughtImpl(ctx, cause); + } + + private static void exceptionCaughtImpl(ChannelHandlerContext ctx, Throwable cause) { LOGGER.error("Failed to handle request", cause); if (!ctx.channel().isActive()) return; sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, cause.getMessage()); } + + private static void forward(ChannelHandlerContext ctx, FullHttpRequest request) { + // These handlers can be removed from this context now + // actually these probably can't!! otherwise the stupid requests can't like yknow do http stuff ctx.pipeline().remove(MOD_ID + "/codec"); + // actually these probably can't!! otherwise the stupid requests can't like yknow do http stuff ctx.pipeline().remove(MOD_ID + "/aggregator"); + ctx.pipeline().remove(MOD_ID + "/handler"); + + // Forward to the next handler. + ctx.fireChannelRead(ReferenceCountUtil.retain(request)); + } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java index 3042e59..95ab592 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java @@ -6,7 +6,7 @@ import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; -import top.offsetmonkey538.meshlib.api.HttpHandlerRegistry; +import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; @@ -35,28 +35,17 @@ public void channelRead(ChannelHandlerContext ctx, Object request) throws Except final boolean isHttp = firstLine.toString().contains("HTTP"); - // If it's an http request, add the correct handlers - if (isHttp) { - final String uri = firstLine.toString().split(" ")[1]; - if (uri.equals("/")) { - LOGGER.debug("Request was made to root domain! Passing on..."); - forward(ctx, request); - return; - } - - final String handlerId = uri.split("/")[1]; - if (!HttpHandlerRegistry.INSTANCE.has(handlerId)) { - LOGGER.debug("Handler with id '%s' not registered! Passing on...", handlerId); - forward(ctx, request); - return; - } - - final ChannelPipeline pipeline = ctx.pipeline(); - pipeline.addAfter(MOD_ID, MOD_ID + "/codec", new HttpServerCodec()); - pipeline.addAfter(MOD_ID + "/codec", MOD_ID + "/aggregator", new HttpObjectAggregator(65536)); - pipeline.addAfter(MOD_ID + "/aggregator", MOD_ID + "/handler", new MainHttpHandler()); + if (!isHttp) { + forward(ctx, request); + return; } + // If it's an http request, add the correct handlers + final ChannelPipeline pipeline = ctx.pipeline(); + pipeline.addAfter(MOD_ID, MOD_ID + "/codec", new HttpServerCodec()); + pipeline.addAfter(MOD_ID + "/codec", MOD_ID + "/aggregator", new HttpObjectAggregator(65536)); + pipeline.addAfter(MOD_ID + "/aggregator", MOD_ID + "/handler", new MainHttpHandler()); + forward(ctx, request); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java new file mode 100644 index 0000000..90af059 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java @@ -0,0 +1,41 @@ +package top.offsetmonkey538.meshlib.impl.router; + +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRuleTypeRegistry; + +import java.util.HashMap; +import java.util.Map; + +/** + * Implementation of {@link HttpRuleTypeRegistry} + */ +public class HttpHandlerTypeRegistryImpl implements HttpHandlerTypeRegistry { + private final Map> handlersById = new HashMap<>(); + private final Map, HttpHandler.HttpHandlerDefinition> handlersByType = new HashMap<>(); + + @Override + public void registerImpl(@NotNull final Class type, @NotNull final HttpHandler.HttpHandlerDefinition handler) { + final String id = handler.type(); + if (id.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); + if (handlersById.containsKey(id)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!"); + if (handlersByType.containsKey(type)) throw new IllegalArgumentException("Handler type for type '" + type + "' already registered!"); + + handlersById.put(id, handler); + handlersByType.put(type, handler); + } + + @Override + public HttpHandler.HttpHandlerDefinition getImpl(final String type) throws IllegalStateException { + if (handlersById.containsKey(type)) return handlersById.get(type); + throw new IllegalStateException("Http rule with type '" + type + "' not registered!"); + } + + @Override + public HttpHandler.HttpHandlerDefinition getImpl(final Class type) throws IllegalStateException { + if (handlersByType.containsKey(type)) return handlersByType.get(type); + throw new IllegalStateException("Http rule with type '" + type + "' not registered!"); + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/DomainHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/DomainHttpRule.java new file mode 100644 index 0000000..964914f --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/DomainHttpRule.java @@ -0,0 +1,49 @@ +package top.offsetmonkey538.meshlib.impl.router.rule; + +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; +import top.offsetmonkey538.meshlib.config.TestConfig; + +public class DomainHttpRule implements HttpRule { + private final String value; // i.e. map.example.com + + public DomainHttpRule(Data data) { + this.value = data.value; + } + + @Override + public String getType() { + return "domain"; + } + + @Override + public Data getData() { + return new Data(value); + } + + @Override + public boolean matches(FullHttpRequest httpRequest) { + String host = httpRequest.headers().get(HttpHeaderNames.HOST); + if (host == null) return false; + + final int portIndex = host.indexOf(':'); + if (portIndex != -1) host = host.substring(0, portIndex); + + return host.equals(value); + } + + public static final class Data { + @SuppressWarnings("FieldMayBeFinal") // Think it needs to be non-final cause jankson + public String value; + + @SuppressWarnings("unused") // Public no-args used by jankson i think + public Data() { + + } + + public Data(String value) { + this.value = value; + } + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java new file mode 100644 index 0000000..b078268 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java @@ -0,0 +1,29 @@ +package top.offsetmonkey538.meshlib.impl.router.rule; + +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.router.rule.HttpRuleTypeRegistry; + +import java.util.HashMap; +import java.util.Map; + +/** + * Implementation of {@link HttpRuleTypeRegistry} + */ +public class HttpRuleTypeRegistryImpl implements HttpRuleTypeRegistry { + private final Map> handlers = new HashMap<>(); + + @Override + public void registerImpl(String type, HttpRule.@NotNull HttpRuleDefinition rule) { + if (type.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); + if (handlers.containsKey(type)) throw new IllegalArgumentException("Handler with id '" + type + "' already registered!"); + + handlers.put(type, rule); + } + + @Override + public @NotNull HttpRule.HttpRuleDefinition getImpl(String type) throws IllegalStateException { + if (handlers.containsKey(type)) return handlers.get(type); + throw new IllegalStateException("Http rule with type '" + type + "' not registered!"); + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java new file mode 100644 index 0000000..e00dbd1 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java @@ -0,0 +1,17 @@ +package top.offsetmonkey538.meshlib.impl.router.target; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.router.target.HttpTarget; +import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; +import top.offsetmonkey538.meshlib.impl.router.rule.DomainHttpRule; + +public class WebserverHttpTarget implements HttpHandler { + + @Override + public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception { + + } +} From cd2184660502e33ddf1028df3686771109647608 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 5 Oct 2025 10:59:44 +0300 Subject: [PATCH 14/78] debug logs --- .../src/main/java/top/offsetmonkey538/meshlib/MESHLib.java | 7 ------- .../offsetmonkey538/meshlib/example/SimpleHttpHandler.java | 1 - 2 files changed, 8 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index 711dc28..c86e26b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -85,13 +85,6 @@ public static void initialize() { HttpHandlerTypeRegistry.register(SimpleHttpHandler.class, new HttpHandler.HttpHandlerDefinition<>("simple-http", testDatType.class, testDatType -> new SimpleHttpHandler(), httpHandler -> new testDatType("a"))); HttpRuleTypeRegistry.register("domain", new HttpRule.HttpRuleDefinition<>(DomainHttpRule.Data.class, DomainHttpRule::new)); - - final ConfigHolder config = ConfigManager.INSTANCE.init(ConfigHolder.create(TestConfig::new, LOGGER::error)); - System.out.println("HELLO!"); - ConfigCommandApi.registerConfigCommand(config, () -> { - System.out.println(config); - System.out.println("Hello!"); - }, "test"); } public record testDatType(String hi) { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java index f9ad724..a099e0e 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java @@ -31,7 +31,6 @@ public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpR // Set the "CONTENT_TYPE" header to tell the browser that this is plain text encoded in UTF-8 response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); - System.out.println("hi"); // Send the response and close the connection ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } From 9e71477f87778d85a202d68af7ed2cc101f52bc8 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 5 Oct 2025 11:29:11 +0300 Subject: [PATCH 15/78] will it work? nooooobodyyy knowssssss! --- .../top/offsetmonkey538/meshlib/MESHLib.java | 7 +--- .../meshlib/api/HttpHandler.java | 19 +++++++---- .../meshlib/config/TestConfig.java | 2 +- .../meshlib/example/ExampleMain.java | 6 +++- .../meshlib/example/SimpleHttpHandler.java | 14 ++++++-- .../router/target/WebserverHttpTarget.java | 32 ++++++++++++++++++- 6 files changed, 62 insertions(+), 18 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index c86e26b..af7d33c 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -64,7 +64,7 @@ public static void initialize() { builder.registerSerializer(HttpHandler.class, (httpHandler, marshaller) -> { HttpHandler.HttpHandlerDefinition handlerDefinition = HttpHandlerTypeRegistry.get(httpHandler.getClass()); - final JsonObject result = (JsonObject) marshaller.serialize(handlerDefinition.handlerToData().apply(httpHandler)); + final JsonObject result = (JsonObject) marshaller.serialize(httpHandler.getData()); result.put("type", JsonPrimitive.of(handlerDefinition.type())); return result; }); @@ -83,14 +83,9 @@ public static void initialize() { }); }); - HttpHandlerTypeRegistry.register(SimpleHttpHandler.class, new HttpHandler.HttpHandlerDefinition<>("simple-http", testDatType.class, testDatType -> new SimpleHttpHandler(), httpHandler -> new testDatType("a"))); HttpRuleTypeRegistry.register("domain", new HttpRule.HttpRuleDefinition<>(DomainHttpRule.Data.class, DomainHttpRule::new)); } - public record testDatType(String hi) { - - } - public static T load(Class clazz) { return ServiceLoader.load(clazz, clazz.getClassLoader()) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java index e26f5a3..97b7b40 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java @@ -32,8 +32,15 @@ * * @see HttpRouterRegistry */ -@FunctionalInterface -public interface HttpHandler { +public abstract class HttpHandler { + protected final T data; + + public HttpHandler(T data) { + this.data = data; + } + public T getData() { + return data; + } /** * This is called when an HTTP request is received for this handler. @@ -44,7 +51,7 @@ public interface HttpHandler { * @param request the received request * @throws Exception when anything goes wrong */ - void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception; + public abstract void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception; /** * Sends the requester an error code. @@ -53,7 +60,7 @@ public interface HttpHandler { * @param status the received request * @see #sendError(ChannelHandlerContext, HttpResponseStatus, String) */ - static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status) { + public static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status) { sendError(ctx, status, null); } @@ -65,7 +72,7 @@ static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseS * @param reason reason to display for the error, may be null or empty * @see #sendError(ChannelHandlerContext, HttpResponseStatus) */ - static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @Nullable String reason) { + public static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @Nullable String reason) { final String message = String.format("Failure: %s\r\n%s",status, (reason == null || reason.isBlank() ? "" : "Reason: " + reason + "\r\n")); LOGGER.error(message); @@ -77,7 +84,7 @@ static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseS ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } - record HttpHandlerDefinition(String type, Class dataType, Function handlerInitializer, Function handlerToData) { + public record HttpHandlerDefinition(String type, Class dataType, Function> handlerInitializer) { } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java index 5c40803..d0337ea 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java @@ -13,7 +13,7 @@ public class TestConfig implements Config { public HttpRule rule = new DomainHttpRule(new DomainHttpRule.Data("map.example.com")); - public HttpHandler target = new SimpleHttpHandler(); + public HttpHandler target = new SimpleHttpHandler(new SimpleHttpHandler.Data("Goodbye, World!")); @Override diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index 7d3000b..cba3f0b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -4,6 +4,7 @@ import io.netty.handler.codec.http.FullHttpRequest; import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.api.router.HttpRouter; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import top.offsetmonkey538.meshlib.api.router.target.HttpTarget; @@ -25,6 +26,9 @@ private ExampleMain() { * Checks if the {@code meshEnableExamples} system property is enabled and registers the example handlers if so */ public static void onInitialize() { + HttpHandlerTypeRegistry.register(SimpleHttpHandler.class, new HttpHandler.HttpHandlerDefinition<>("simple-http", SimpleHttpHandler.Data.class, SimpleHttpHandler::new)); + + // Ignore if "meshEnableExamples" isn't set if (!Boolean.getBoolean("meshEnableExamples")) return; @@ -34,7 +38,7 @@ public static void onInitialize() { // Register HttpRouterRegistry.INSTANCE.register("simple-server", new HttpRouter( new DomainHttpRule(new DomainHttpRule.Data("localhost")), - new SimpleHttpHandler() + new SimpleHttpHandler(new SimpleHttpHandler.Data("Yellow!")) )); } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java index a099e0e..a190071 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java @@ -19,12 +19,20 @@ /** * An example {@link HttpHandler} implementation to learn from */ -public class SimpleHttpHandler implements HttpHandler { +public class SimpleHttpHandler extends HttpHandler { + + public SimpleHttpHandler(Data data) { + super(data); + } + + public record Data(String content) { + + } @Override public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception { - // Write "Hello, World!" to a buffer, encoded in UTF-8 - final ByteBuf content = Unpooled.copiedBuffer("Hello, World!", StandardCharsets.UTF_8); + // Write the provided content to a buffer, encoded in UTF-8 + final ByteBuf content = Unpooled.copiedBuffer(data.content(), StandardCharsets.UTF_8); // Create a response with said buffer final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java index e00dbd1..d352d70 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java @@ -1,17 +1,47 @@ package top.offsetmonkey538.meshlib.impl.router.target; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.api.HttpHandler; import top.offsetmonkey538.meshlib.api.router.target.HttpTarget; import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; import top.offsetmonkey538.meshlib.impl.router.rule.DomainHttpRule; -public class WebserverHttpTarget implements HttpHandler { +import java.nio.charset.StandardCharsets; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +//TODO +public class WebserverHttpTarget extends HttpHandler { + + public WebserverHttpTarget(Data data) { + super(data); + } + + public record Data(String directory) { + + } @Override public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception { + // Write the provided content to a buffer, encoded in UTF-8 + final ByteBuf content = Unpooled.copiedBuffer("TODO: work on WebserverHttpTarget", StandardCharsets.UTF_8); + // Create a response with said buffer + final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content); + // Set the "CONTENT_TYPE" header to tell the browser that this is plain text encoded in UTF-8 + response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); + + // Send the response and close the connection + ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } + } From bb5ddaa949a74353a30743dc40932340ee3cda0c Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:58:42 +0300 Subject: [PATCH 16/78] fix javadoc error so github actions can actually compile this crap --- common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java | 3 +++ .../offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index af7d33c..f6d7ffc 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -19,6 +19,9 @@ import java.util.ServiceLoader; +// TODO: once I start working on allowing the netty server to run separately from minecraft, I should make the default exposed port value null to avoid 2 things: +// - The port from server.properties being wrong: The network being behind something (like a firewall or Docker for example) can allow the exposed port to be different from what minecraft binds to. +// - Tell people to use a separate port if at all possible. Proxies that some like running minecraft through (especially DDOS blockers like TcpShield) may block http traffic. public final class MESHLib { /** * Private constructor as this class shouldn't be instanced diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java index 518f39d..3540293 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java @@ -30,7 +30,6 @@ public interface HttpRouterRegistry { * THE ID SHOULD NOT BE EMPTY * * @param id your handler or mod's id - * @param handler the {@link HttpHandler} to be registered * @throws IllegalArgumentException when the provided id is empty or a handler with this id is already registered * @see HttpHandler */ From b4939074e3c532bce208039aeeca696e9afb0c4f Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:54:39 +0300 Subject: [PATCH 17/78] neoforge version? --- build.gradle | 129 +++++++++++++++++- common/gradle.properties | 3 - .../resources/META-INF/neoforge.mods.toml | 14 ++ gradle.properties | 6 +- neoforge/gradle.properties | 15 ++ .../neoforge/NeoforgeInitializer.java | 20 +++ .../neoforge/NeoforgePlatformMain.java | 39 ++++++ .../mixin/ServerConnectionListenerMixin.java | 25 ++++ .../resources/META-INF/neoforge.mods.toml | 32 +++++ ...setmonkey538.meshlib.platform.PlatformMain | 1 + .../src/main/resources/mesh-lib.mixins.json | 11 ++ settings.gradle | 1 + 12 files changed, 289 insertions(+), 7 deletions(-) create mode 100644 common/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 neoforge/gradle.properties create mode 100644 neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java create mode 100644 neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java create mode 100644 neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java create mode 100644 neoforge/src/main/resources/META-INF/neoforge.mods.toml create mode 100644 neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain create mode 100644 neoforge/src/main/resources/mesh-lib.mixins.json diff --git a/build.gradle b/build.gradle index c62261a..3ea90bf 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ import io.papermc.paperweight.userdev.ReobfArtifactConfiguration plugins { id 'fabric-loom' version '1.11-SNAPSHOT' apply false + id 'net.neoforged.moddev' version '2.0.112' apply false id 'io.github.dexman545.outlet' version '1.6.1' apply false id 'io.papermc.paperweight.userdev' version '2.0.0-beta.8' apply false id 'com.gradleup.shadow' version '9.0.0-beta4' apply false @@ -115,11 +116,21 @@ configure(subprojects.findAll { it.name == "common" }) { } dependencies { - api "io.netty:netty-codec-http:${project.netty_version}" + api "io.netty:netty-codec-http:${rootProject.netty_version}" shadow compileOnlyApi("top.offsetmonkey538.monkeylib538:monkeylib538-common:${rootProject.monkeylib538_version}") } tasks.build.dependsOn(shadowJar) + + processResources { + final Map properties = [ + "modVersion": rootProject.versionPrefix + ] + inputs.properties(properties) + filesMatching("META-INF/neoforge.mods.toml") { + expand(properties) + } + } } configure(subprojects.findAll { it.name == "fabric" }) { @@ -184,8 +195,8 @@ configure(subprojects.findAll { it.name == "fabric" }) { include project(path: ":common", configuration: "shadow") common project(":common") - // Only need the command api at runtime for the monkeylib config command - modRuntimeOnly fabricApi.module("fabric-command-api-v2", project.fapi_version) + // Only need fapi at runtime for the monkeylib shtuff + modRuntimeOnly "net.fabricmc.fabric-api:fabric-api:${project.fapi_version}" // Whatever the heck this is for monkeylib modRuntimeOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-1.20.5:${rootProject.monkeylib538_version}+1.20.5") { @@ -247,6 +258,118 @@ configure(subprojects.findAll { it.name == "fabric" }) { tasks.modrinth.dependsOn(tasks.modrinthSyncBody) } +configure(subprojects.findAll { it.name == "neoforge" }) { + apply plugin: "net.neoforged.moddev" + apply plugin: "io.github.dexman545.outlet" + apply plugin: "com.modrinth.minotaur" + + outlet { + mcVersionRange = project.supported_minecraft_versions + allowedReleaseTypes = [ReleaseType.RELEASE] + } + + neoForge { + version = project.neoforge_version + + parchment { + mappingsVersion = project.parchment_mappings_version + minecraftVersion = project.minecraft_version + } + + mods { + "${project.name}" { + sourceSet(sourceSets.main) + } + } + + runs { + server { + server() + } + + configureEach { + systemProperty "forge.logging.markers", "REGISTRIES" + jvmArgument "-DmeshEnableExamples=true" + } + } + } + + repositories { + maven { + name = "DevAuth" + url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" + content { + includeGroup "me.djtheredstoner" + } + } + } + + configurations { + runtimeClasspath.extendsFrom localRuntime + } + + dependencies { + // DevAuth + localRuntime "me.djtheredstoner:DevAuth-neoforge:${project.devauth_version}" + + jarJar(api(project(":common"))) + jarJar(additionalRuntimeClasspath("io.netty:netty-codec-http:${rootProject.netty_version}")) + + // Whatever the heck this is for monkeylib + runtimeOnly "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge-1.20.6:${project.monkeylib538_version}+1.20.6" + compileOnly "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge:${project.monkeylib538_version}" + } + + processResources { + final Map properties = [ + "commonVersion": rootProject.versionPrefix, + "modVersion": project.version, + "supportedMinecraftVersions": project.minecraft_version_range, + "currentMonkeylibVersion": project.monkeylib538_version + ] + inputs.properties(properties) + filesMatching("META-INF/neoforge.mods.toml") { + expand(properties) + } + + exclude ".cache/**" + } + + modrinth { + // Main properties + token = System.getenv("MODRINTH_TOKEN") + projectId = "mesh-lib" + gameVersions = outlet.mcVersions() + loaders = ["neoforge"] + + // Version stuff + def customVersionName = System.getenv("VERSION_NAME") + if (customVersionName != null) versionName = customVersionName + + versionNumber = "${project.version}" + + versionType = Boolean.parseBoolean(System.getenv("VERSION_IS_PRERELEASE")) ? "beta" : "release" + + if (rootProject.mod_version.contains("beta")) versionType = "beta" + if (rootProject.mod_version.contains("alpha")) versionType = "alpha" + + // Files + //TODO: uploadFile = remapJar.archiveFile + additionalFiles = [sourcesJar.archiveFile] + + + // Project info + syncBodyFrom = rootProject.file("README.md").text + def changelogEnv = System.getenv("VERSION_CHANGELOG") + if (changelogEnv != null) changelog = changelogEnv + + dependencies { + required.project "monkeylib538" + } + } + tasks.modrinth.dependsOn(tasks.modrinthSyncBody) +} + configure(subprojects.findAll { it.name == "paper" }) { apply plugin: "com.gradleup.shadow" apply plugin: "com.modrinth.minotaur" diff --git a/common/gradle.properties b/common/gradle.properties index b33f612..ff06f8b 100644 --- a/common/gradle.properties +++ b/common/gradle.properties @@ -1,4 +1 @@ project_name = api - -## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty -netty_version = 4.1.82.Final diff --git a/common/src/main/resources/META-INF/neoforge.mods.toml b/common/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..18e4a1e --- /dev/null +++ b/common/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,14 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +license="MIT" + +issueTrackerURL="https://github.com/OffsetMods538/MonkeyLib538/issues" + + +[[mods]] +modId="mesh_lib_common" +displayName="MESH Lib Common" +version="${modVersion}" +displayURL="https://modrinth.com/mod/monkeylib538" +authors="OffsetMonkey538" +description="A library mod for OffsetMonkey538s mods" diff --git a/gradle.properties b/gradle.properties index d07a743..9a845a9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,11 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-6345c7b +monkeylib538_version = 3.0.0-1759842547452 +## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty +netty_version = 4.1.82.Final +## OffsetConfig538, check at https://github.com/OffsetMods538/OffsetConfig538 +offsetconfig538_version = 1.0.0-946f4f7 # Mod Properties mod_version = 1.0.5 diff --git a/neoforge/gradle.properties b/neoforge/gradle.properties new file mode 100644 index 0000000..5f2330c --- /dev/null +++ b/neoforge/gradle.properties @@ -0,0 +1,15 @@ +project_name = neoforge + +# Fabric TODO: this isn't fabric... +## Check at https://fabricmc.net/develop +minecraft_version = 1.21.4 +neoforge_version = 21.4.154 +parchment_mappings_version = 2025.03.23 + +# Dependencies +## DevAuth, check at https://github.com/DJtheRedstoner/DevAuth +devauth_version = 1.2.1 + +#todo: idfk +minecraft_version_range = [1,) +supported_minecraft_versions = * diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java new file mode 100644 index 0000000..d6bb78b --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java @@ -0,0 +1,20 @@ +package top.offsetmonkey538.meshlib.platform.neoforge; + +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.loading.FMLPaths; +import top.offsetmonkey538.meshlib.MESHLib; +import top.offsetmonkey538.meshlib.platform.PlatformMain; + +import java.nio.file.Path; + +import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; + +@Mod("mesh_lib") +public final class NeoforgeInitializer { + + public NeoforgeInitializer(IEventBus modEventBus, ModContainer modContainer) { + MESHLib.initialize(); + } +} diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java new file mode 100644 index 0000000..6ba92bf --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java @@ -0,0 +1,39 @@ +package top.offsetmonkey538.meshlib.platform.neoforge; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandSourceStack; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.fml.ModContainer; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.loading.FMLPaths; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.RegisterCommandsEvent; +import top.offsetmonkey538.meshlib.MESHLib; +import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.monkeylib538.neoforge.impl.command.CommandRegistrationImpl; + +import java.nio.file.Path; + +import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; + +public final class NeoforgePlatformMain implements PlatformMain { + @Override + public void enableVanillaHandlerImpl() { + // TODO + } + + @Override + public void disableVanillaHandlerImpl() { + // TODO + } + + @Override + public Path getConfigDirImpl() { + return FMLPaths.CONFIGDIR.get().resolve(MOD_ID); + } + + // dis on for loading the PlatformMain service + public NeoforgePlatformMain() { + + } +} diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java new file mode 100644 index 0000000..2c0446e --- /dev/null +++ b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java @@ -0,0 +1,25 @@ +package top.offsetmonkey538.meshlib.platform.neoforge.mixin; + +import io.netty.channel.Channel; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import top.offsetmonkey538.meshlib.impl.ProtocolHandler; + +import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; + +/** + * Mixin adding the {@link ProtocolHandler} to the minecraft netty pipeline + */ +@Mixin(targets = "net/minecraft/server/network/ServerConnectionListener$1") +public abstract class ServerConnectionListenerMixin { + + @Inject( + method = "initChannel", + at = @At("TAIL") + ) + private void meshlib$addHttpHandler(Channel channel, CallbackInfo ci) { + channel.pipeline().addFirst(MOD_ID, new ProtocolHandler()); + } +} diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml new file mode 100644 index 0000000..3a6f7ea --- /dev/null +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -0,0 +1,32 @@ +modLoader = "javafml" +loaderVersion = "[1,)" +license="MIT" + +issueTrackerURL="https://github.com/OffsetMods538/MESH-Lib/issues" + + +[[mods]] +modId="mesh_lib" +displayName="MESH Lib" +version="${modVersion}" +displayURL="https://modrinth.com/mod/mesh-lib" +authors="OffsetMonkey538" +description="A library mod for OffsetMonkey538s mods" + +[[mixins]] +config="mesh-lib.mixins.json" + + +[[dependencies.mesh_lib]] +modId="minecraft" +type="required" +versionRange="${supportedMinecraftVersions}" +ordering="NONE" +side="BOTH" + +[[dependencies.mesh_lib]] +modId="monkeylib538_common_neoforge" +type="required" +versionRange="[${currentMonkeylibVersion},)" +ordering="NONE" +side="BOTH" diff --git a/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain b/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain new file mode 100644 index 0000000..db033d3 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain @@ -0,0 +1 @@ +top.offsetmonkey538.meshlib.platform.neoforge.NeoforgePlatformMain \ No newline at end of file diff --git a/neoforge/src/main/resources/mesh-lib.mixins.json b/neoforge/src/main/resources/mesh-lib.mixins.json new file mode 100644 index 0000000..f4cc04f --- /dev/null +++ b/neoforge/src/main/resources/mesh-lib.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "package": "top.offsetmonkey538.meshlib.platform.neoforge.mixin", + "compatibilityLevel": "JAVA_17", + "server": [ + "ServerConnectionListenerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/settings.gradle b/settings.gradle index f44ca15..3df744c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,4 +15,5 @@ rootProject.name = "mesh-lib" include "common" include "fabric" +include "neoforge" include "paper" From 8e52b9d6e99d4acd4cb0d43ce23b994d490a8d62 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:48:34 +0300 Subject: [PATCH 18/78] depend on newer monkeylib --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9a845a9..59d0c3e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-1759842547452 +monkeylib538_version = 3.0.0-66c29d2 ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.82.Final ## OffsetConfig538, check at https://github.com/OffsetMods538/OffsetConfig538 From c33be5499a5e5364239f738f06c1fe845943603d Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Tue, 7 Oct 2025 20:33:43 +0300 Subject: [PATCH 19/78] depend on newer monkeylib --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 59d0c3e..cc5003c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-66c29d2 +monkeylib538_version = 3.0.0-10e70ec ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.82.Final ## OffsetConfig538, check at https://github.com/OffsetMods538/OffsetConfig538 From 1a52522b14aa9966d67b9024bc220b0cd2fd3a9f Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Tue, 7 Oct 2025 20:59:54 +0300 Subject: [PATCH 20/78] depend on newer monkeylib and offsetconfig --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index cc5003c..3eb6e24 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,11 +3,11 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-10e70ec +monkeylib538_version = 3.0.0-3df936b ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.82.Final ## OffsetConfig538, check at https://github.com/OffsetMods538/OffsetConfig538 -offsetconfig538_version = 1.0.0-946f4f7 +offsetconfig538_version = 1.0.1-e59f400 # Mod Properties mod_version = 1.0.5 From bcec8d6eb87bd422386cafa7bb36b0bd359dd35c Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sat, 11 Oct 2025 12:01:23 +0300 Subject: [PATCH 21/78] depend on newer monkeylib --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3eb6e24..3901158 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-3df936b +monkeylib538_version = 3.0.0-alpha.0.1760173173400+2230010 ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.82.Final ## OffsetConfig538, check at https://github.com/OffsetMods538/OffsetConfig538 From 6126daa29fa09085be93f145c7198912a6afa558 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sat, 11 Oct 2025 12:07:48 +0300 Subject: [PATCH 22/78] redo versioning see https://github.com/OffsetMods538/MonkeyLib538/commit/7d04b64b6682b25032762250a91e085c51d4d793 and https://github.com/OffsetMods538/MonkeyLib538/commit/223001036477e9f6f82c135fb929ff8c2276aa02 --- .github/workflows/build_artifacts.yml | 5 +++-- .github/workflows/publish.yml | 9 ++++++--- build.gradle | 20 +++++++++++++++----- gradle.properties | 2 +- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index dbda0e7..223f20e 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -30,13 +30,14 @@ jobs: - name: Build with Gradle run: ./gradlew build env: - IS_PRODUCTION_BUILD: true + PRESERVE_PRERELEASE_VERSION: true + DISABLE_PROPERTIES_UPDATE: true VERSION_SUFFIX: ${{ env.short_commit_hash }} - name: Publish to Maven run: ./gradlew publishMavenPublicationToOffsetMonkey538Repository env: - IS_PRODUCTION_BUILD: true + PRESERVE_PRERELEASE_VERSION: true DISABLE_PROPERTIES_UPDATE: true VERSION_SUFFIX: ${{ env.short_commit_hash }} MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4aedf35..fa7a833 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -29,12 +29,14 @@ jobs: - name: Build with Gradle run: ./gradlew build env: - IS_PRODUCTION_BUILD: true + IS_RELEASE: true + DISABLE_PROPERTIES_UPDATE: true - name: Upload to Modrinth run: ./gradlew modrinth env: - IS_PRODUCTION_BUILD: true + IS_RELEASE: true + DISABLE_PROPERTIES_UPDATE: true MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} VERSION_NAME: ${{ github.event.release.name }} VERSION_IS_PRERELEASE: ${{ github.event.release.prerelease }} @@ -43,7 +45,8 @@ jobs: - name: Publish to Maven run: ./gradlew publishMavenPublicationToOffsetMonkey538Repository env: - IS_PRODUCTION_BUILD: true + IS_RELEASE: true + DISABLE_PROPERTIES_UPDATE: true MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} diff --git a/build.gradle b/build.gradle index 3ea90bf..92c73cb 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,8 @@ import dex.plugins.outlet.v2.util.ReleaseType import io.papermc.paperweight.userdev.ReobfArtifactConfiguration +import java.nio.file.Files + plugins { id 'fabric-loom' version '1.11-SNAPSHOT' apply false id 'net.neoforged.moddev' version '2.0.112' apply false @@ -13,17 +15,25 @@ plugins { ext { versionPrefix = rootProject.mod_version + if (!Boolean.parseBoolean(System.getenv("IS_RELEASE"))) { + String preReleaseVersion = System.currentTimeMillis() - if (!Boolean.parseBoolean(System.getenv("IS_PRODUCTION_BUILD"))) { - versionPrefix = "${versionPrefix}-${System.currentTimeMillis()}" - } + if (Boolean.parseBoolean(System.getenv("PRESERVE_PRERELEASE_VERSION"))) { + var preReleaseVersionFile = file("preReleaseVersion.txt").toPath() + if (Files.exists(preReleaseVersionFile)) preReleaseVersion = Files.readString(preReleaseVersionFile) + Files.writeString(preReleaseVersionFile, preReleaseVersion) + } + var separator = "-" + if (versionPrefix.contains("-")) separator = "." + versionPrefix = "${versionPrefix}${separator}${preReleaseVersion}" + } final String versionSuffix = System.getenv("VERSION_SUFFIX") if (versionSuffix != null && !versionSuffix.isEmpty()) { - versionPrefix = "${versionPrefix}-${versionSuffix}" + versionPrefix = "${versionPrefix}+${versionSuffix}" } - println "Version Prefix: ${versionPrefix}" + System.out.println("Version Prefix: " + versionPrefix) } allprojects { diff --git a/gradle.properties b/gradle.properties index 3901158..af91f13 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,4 +10,4 @@ netty_version = 4.1.82.Final offsetconfig538_version = 1.0.1-e59f400 # Mod Properties -mod_version = 1.0.5 +mod_version = 2.0.0-alpha.0 From a938519400cec98eb4a32d5d93c9461101a1dac9 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Mon, 20 Oct 2025 15:09:30 +0200 Subject: [PATCH 23/78] Allow disabling vanilla handler have not tested if this actually works cause nothing invokes the disable method, but yeah sure --- .../top/offsetmonkey538/meshlib/api/router/HttpRouter.java | 2 +- .../top/offsetmonkey538/meshlib/impl/MainHttpHandler.java | 2 +- fabric/gradle.properties | 2 +- .../meshlib/platform/fabric/FabricPlatformMain.java | 6 ++++-- .../meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java | 3 +++ .../meshlib/platform/neoforge/NeoforgePlatformMain.java | 6 ++++-- .../neoforge/mixin/ServerConnectionListenerMixin.java | 3 +++ 7 files changed, 17 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java index 3c7c0c4..a1873e7 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java @@ -5,5 +5,5 @@ import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; import top.offsetmonkey538.meshlib.api.router.target.HttpTarget; -public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { +public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java index abb3962..f569957 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java @@ -58,7 +58,7 @@ protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) return; } - final HttpHandler handler = router.handler(); + final HttpHandler handler = router.handler(); ctx.pipeline().addAfter(MOD_ID + "/handler", MOD_ID + "/" + matchedRouterIDs.getFirst(), new SimpleChannelInboundHandler() { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { diff --git a/fabric/gradle.properties b/fabric/gradle.properties index 9043e7a..c2f6003 100644 --- a/fabric/gradle.properties +++ b/fabric/gradle.properties @@ -6,7 +6,7 @@ project_name = fabric ## variable "IS_PRODUCTION_BUILD" is set. minecraft_version = 1.21.4 yarn_version = 1.21.4+build.8 -loader_version = 0.17.2 +loader_version = 0.17.3 # Dependencies ## DevAuth, check at https://github.com/DJtheRedstoner/DevAuth diff --git a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java index 8b5329d..b5397dd 100644 --- a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java +++ b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java @@ -10,14 +10,16 @@ import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; public final class FabricPlatformMain implements PlatformMain, DedicatedServerModInitializer { + public static boolean isVanillaHandlerEnabled = false; + @Override public void enableVanillaHandlerImpl() { - // TODO + isVanillaHandlerEnabled = true; } @Override public void disableVanillaHandlerImpl() { - // TODO + isVanillaHandlerEnabled = false; } @Override diff --git a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java index a5b6525..b9f770b 100644 --- a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java +++ b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java @@ -6,6 +6,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import top.offsetmonkey538.meshlib.impl.ProtocolHandler; +import top.offsetmonkey538.meshlib.platform.fabric.FabricPlatformMain; import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; @@ -20,6 +21,8 @@ public abstract class ServerNetworkIoMixin { at = @At("TAIL") ) private void meshlib$addHttpHandler(Channel channel, CallbackInfo ci) { + // This method is executed every time a new connection is started. Thus, I can just not add to the vanilla server when that's disabled + if (!FabricPlatformMain.isVanillaHandlerEnabled) return; channel.pipeline().addFirst(MOD_ID, new ProtocolHandler()); } } diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java index 6ba92bf..55c01bd 100644 --- a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java +++ b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java @@ -17,14 +17,16 @@ import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; public final class NeoforgePlatformMain implements PlatformMain { + public static boolean isVanillaHandlerEnabled = false; + @Override public void enableVanillaHandlerImpl() { - // TODO + isVanillaHandlerEnabled = true; } @Override public void disableVanillaHandlerImpl() { - // TODO + isVanillaHandlerEnabled = false; } @Override diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java index 2c0446e..e114924 100644 --- a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java +++ b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java @@ -6,6 +6,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import top.offsetmonkey538.meshlib.impl.ProtocolHandler; +import top.offsetmonkey538.meshlib.platform.neoforge.NeoforgePlatformMain; import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; @@ -20,6 +21,8 @@ public abstract class ServerConnectionListenerMixin { at = @At("TAIL") ) private void meshlib$addHttpHandler(Channel channel, CallbackInfo ci) { + // This method is executed every time a new connection is started. Thus, I can just not add to the vanilla server when that's disabled + if (!NeoforgePlatformMain.isVanillaHandlerEnabled) return; channel.pipeline().addFirst(MOD_ID, new ProtocolHandler()); } } From e346d4f3cfd8d93a784e331308be898a85802334 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Mon, 20 Oct 2025 17:59:59 +0200 Subject: [PATCH 24/78] Create HttpResponseUtil, add some utils and move sendError methods from HttpHandler to there --- .../meshlib/api/HttpHandler.java | 31 ----- .../meshlib/api/util/HttpResponseUtil.java | 107 ++++++++++++++++++ .../meshlib/impl/MainHttpHandler.java | 4 +- .../meshlib/impl/ProtocolHandler.java | 1 + .../impl/util/HttpResponseUtilImpl.java | 63 +++++++++++ 5 files changed, 173 insertions(+), 33 deletions(-) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java index 97b7b40..9435d8e 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java @@ -53,37 +53,6 @@ public T getData() { */ public abstract void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception; - /** - * Sends the requester an error code. - * - * @param ctx the current channel handler context - * @param status the received request - * @see #sendError(ChannelHandlerContext, HttpResponseStatus, String) - */ - public static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status) { - sendError(ctx, status, null); - } - - /** - * Sends the requester an error code and optionally a reason with it. - * - * @param ctx the current channel handler context - * @param status the received request - * @param reason reason to display for the error, may be null or empty - * @see #sendError(ChannelHandlerContext, HttpResponseStatus) - */ - public static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @Nullable String reason) { - final String message = String.format("Failure: %s\r\n%s",status, (reason == null || reason.isBlank() ? "" : "Reason: " + reason + "\r\n")); - - LOGGER.error(message); - - final ByteBuf byteBuf = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8); - final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, byteBuf); - - response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); - } - public record HttpHandlerDefinition(String type, Class dataType, Function> handlerInitializer) { } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java new file mode 100644 index 0000000..e7ca0f8 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java @@ -0,0 +1,107 @@ +package top.offsetmonkey538.meshlib.api.util; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.offsetmonkey538.meshlib.impl.util.HttpResponseUtilImpl; + +/** + * Provides utils for responding to http requests + */ +public interface HttpResponseUtil { + /** + * Instance + */ + @ApiStatus.Internal + HttpResponseUtil INSTANCE = new HttpResponseUtilImpl(); + + + /** + * Sends the requester a Permanent Redirect (308) response containing the new location. + *

+ * Afaik clients may cache this redirect and automatically go to the new location when this url is requested? If that is not desired, use {@link #sendTemporaryRedirect(ChannelHandlerContext, String)} + * + * @param ctx the current channel handler context + * @param newLocation the new location. + */ + static void sendPermanentRedirect(@NotNull ChannelHandlerContext ctx, @NotNull String newLocation) { + INSTANCE.sendRedirectImpl(ctx, HttpResponseStatus.PERMANENT_REDIRECT, newLocation); + } + + /** + * Sends the requester a Temporary Redirect (307) response containing the new location. + * + * @param ctx the current channel handler context + * @param newLocation the new location. + */ + static void sendTemporaryRedirect(@NotNull ChannelHandlerContext ctx, @NotNull String newLocation) { + INSTANCE.sendRedirectImpl(ctx, HttpResponseStatus.TEMPORARY_REDIRECT, newLocation); + } + + /** + * Sends the requester a plain text 200 response + * + * @param ctx the current channel handler context + * @param content the plain text response to send + */ + static void sendString(@NotNull ChannelHandlerContext ctx, @NotNull String content) { + INSTANCE.sendStringImpl(ctx, content); + } + + /** + * Logs and sends the requester an error code. + * + * @param ctx the current channel handler context + * @param status the status to send + * @see #sendError(ChannelHandlerContext, HttpResponseStatus, Throwable) + * @see #sendError(ChannelHandlerContext, HttpResponseStatus, String) + */ + static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status) { + sendError(ctx, status, (String) null); + } + + /** + * Logs and sends the requester an error code and a reason with it. + * + * @param ctx the current channel handler context + * @param status the status to send + * @param reason reason to display for the error, MUST NOT be null + * @see #sendError(ChannelHandlerContext, HttpResponseStatus) + * @see #sendError(ChannelHandlerContext, HttpResponseStatus, String) + */ + static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @NotNull Throwable reason) { + sendError(ctx, status, reason.getMessage()); + } + + /** + * Logs and sends the requester an error code and optionally a reason with it. + * + * @param ctx the current channel handler context + * @param status the status to send + * @param reason reason to display for the error, MAY be null or empty + * @see #sendError(ChannelHandlerContext, HttpResponseStatus) + * @see #sendError(ChannelHandlerContext, HttpResponseStatus, Throwable) + */ + static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @Nullable String reason) { + INSTANCE.sendErrorImpl(ctx, status, reason); + } + + /** + * Sends the requester the provided {@link FullHttpResponse}, sets {@link io.netty.handler.codec.http.HttpHeaderNames#CONNECTION CONNECTION} to "close" and closes the connection. + * + * @param ctx the current channel handler context + * @param response the response to send + */ + static void sendResponseAndClose(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpResponse response) { + INSTANCE.sendResponseAndCloseImpl(ctx, response); + } + + + void sendRedirectImpl(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @NotNull String newLocation); + void sendStringImpl(@NotNull ChannelHandlerContext ctx, @NotNull String content); + void sendErrorImpl(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @Nullable String reason); + void sendResponseAndCloseImpl(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpResponse response); +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java index f569957..7056600 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java @@ -16,7 +16,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; -import static top.offsetmonkey538.meshlib.api.HttpHandler.sendError; +import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; /** * Main HTTP handler for MESH. @@ -82,7 +82,7 @@ private static void exceptionCaughtImpl(ChannelHandlerContext ctx, Throwable cau LOGGER.error("Failed to handle request", cause); if (!ctx.channel().isActive()) return; - sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, cause.getMessage()); + sendError(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, cause); } private static void forward(ChannelHandlerContext ctx, FullHttpRequest request) { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java index 95ab592..25eccd6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java @@ -24,6 +24,7 @@ public void channelRead(ChannelHandlerContext ctx, Object request) throws Except return; } + // TODO: pretty sure I will not be able to use this same method for https? there does seem to be a SslHandler.isEncrypted(buf) method which I could use in addition to this? // Read the first line to check if it's an http request // todo: maybe there's a better way to check? final StringBuilder firstLine = new StringBuilder(); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java new file mode 100644 index 0000000..fcfec72 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java @@ -0,0 +1,63 @@ +package top.offsetmonkey538.meshlib.impl.util; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.util.CharsetUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; + +import static io.netty.handler.codec.http.HttpHeaderNames.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; + +public final class HttpResponseUtilImpl implements HttpResponseUtil { + + @Override + public void sendRedirectImpl(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @NotNull String newLocation) { + final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.EMPTY_BUFFER); + + response.headers().set(LOCATION, newLocation); + + sendResponseAndCloseImpl(ctx, response); + } + + @Override + public void sendStringImpl(@NotNull ChannelHandlerContext ctx, @NotNull String content) { + final ByteBuf byteBuf = Unpooled.copiedBuffer(content, CharsetUtil.UTF_8); + final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, byteBuf); + + response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); + + sendResponseAndCloseImpl(ctx, response); + } + + @Override + public void sendErrorImpl(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @Nullable String reason) { + final StringBuilder messageBuilder = new StringBuilder("Failure: ").append(status); + if (reason != null && !reason.isBlank()) messageBuilder.append("\nReason: ").append(reason); + + final String message = messageBuilder.toString(); + + + LOGGER.error(message); + + final ByteBuf byteBuf = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8); + final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, byteBuf); + + response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); + + sendResponseAndCloseImpl(ctx, response); + } + + @Override + public void sendResponseAndCloseImpl(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpResponse response) { + response.headers().set(CONNECTION, "close"); + ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); + } +} From a009a850a0fae979230b2d8c6fb1637754616dac Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:10:43 +0200 Subject: [PATCH 25/78] fix javadoc compilation --- .../meshlib/api/HttpHandler.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java index 9435d8e..cfcc04d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java @@ -1,33 +1,16 @@ package top.offsetmonkey538.meshlib.api; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.util.CharsetUtil; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.function.Function; -import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; -import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; -import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; - /** * An http handler for you to implement :D - *

- * This class also provides some utility methods for making your life easier. *
- * For example {@link #sendError(ChannelHandlerContext, HttpResponseStatus)} {@link #sendError(ChannelHandlerContext, HttpResponseStatus, String)} - *

* Look at {@link SimpleHttpHandler SimpleHttpHandler} for an example * * @see HttpRouterRegistry From 13ed83a74a4d53c2e2856f6f726b7b132d632de1 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:17:19 +0200 Subject: [PATCH 26/78] switch to gradle wrapper bin distribution (why tf was I using -all???) --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6bf6239..6a38a8c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=b84e04fa845fecba48551f425957641074fcc00a88a84d2aae5808743b35fc85 -distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip +distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 822463a852ca90499730e16aae556ca918f582fc Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Mon, 20 Oct 2025 18:17:50 +0200 Subject: [PATCH 27/78] register simple-server handler type only when examples are enabled --- .../top/offsetmonkey538/meshlib/example/ExampleMain.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index cba3f0b..3c0643d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -26,9 +26,6 @@ private ExampleMain() { * Checks if the {@code meshEnableExamples} system property is enabled and registers the example handlers if so */ public static void onInitialize() { - HttpHandlerTypeRegistry.register(SimpleHttpHandler.class, new HttpHandler.HttpHandlerDefinition<>("simple-http", SimpleHttpHandler.Data.class, SimpleHttpHandler::new)); - - // Ignore if "meshEnableExamples" isn't set if (!Boolean.getBoolean("meshEnableExamples")) return; @@ -36,6 +33,8 @@ public static void onInitialize() { LOGGER.warn("MESH examples enabled!"); // Register + HttpHandlerTypeRegistry.register(SimpleHttpHandler.class, new HttpHandler.HttpHandlerDefinition<>("simple-http", SimpleHttpHandler.Data.class, SimpleHttpHandler::new)); + HttpRouterRegistry.INSTANCE.register("simple-server", new HttpRouter( new DomainHttpRule(new DomainHttpRule.Data("localhost")), new SimpleHttpHandler(new SimpleHttpHandler.Data("Yellow!")) From 7d490a5b004d47d7ff3c61a223292a7d5052c556 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:26:15 +0200 Subject: [PATCH 28/78] some refactoring, static file handler, static directory handler with nice (optional) directory index page --- .../top/offsetmonkey538/meshlib/MESHLib.java | 33 +- .../meshlib/api/HttpHandlerTypeRegistry.java | 30 -- .../api/{ => handler}/HttpHandler.java | 20 +- .../api/handler/HttpHandlerTypeRegistry.java | 21 ++ .../handlers/StaticDirectoryHandler.java | 284 ++++++++++++++++++ .../handler/handlers/StaticFileHandler.java | 52 ++++ .../meshlib/api/router/HttpRouter.java | 7 +- .../api/router/HttpRouterRegistry.java | 2 +- .../meshlib/api/router/rule/HttpRule.java | 16 - .../meshlib/api/router/target/HttpTarget.java | 4 - .../meshlib/api/rule/HttpRule.java | 29 ++ .../rule/HttpRuleTypeRegistry.java | 2 +- .../rule/rules}/DomainHttpRule.java | 13 +- .../meshlib/api/rule/rules/PathHttpRule.java | 46 +++ .../meshlib/api/util/HttpResponseUtil.java | 18 ++ .../meshlib/config/TestConfig.java | 10 +- .../meshlib/example/ExampleMain.java | 45 ++- .../meshlib/example/SimpleHttpHandler.java | 18 +- .../meshlib/example/package-info.java | 2 +- .../meshlib/impl/MainHttpHandler.java | 25 +- .../meshlib/impl/ProtocolHandler.java | 4 +- .../router/HttpHandlerTypeRegistryImpl.java | 38 +-- .../router/rule/HttpRuleTypeRegistryImpl.java | 4 +- .../router/target/WebserverHttpTarget.java | 47 --- .../impl/util/HttpResponseUtilImpl.java | 55 +++- 25 files changed, 615 insertions(+), 210 deletions(-) delete mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerTypeRegistry.java rename common/src/main/java/top/offsetmonkey538/meshlib/api/{ => handler}/HttpHandler.java (59%) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java delete mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRule.java delete mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/router/target/HttpTarget.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java rename common/src/main/java/top/offsetmonkey538/meshlib/api/{router => }/rule/HttpRuleTypeRegistry.java (93%) rename common/src/main/java/top/offsetmonkey538/meshlib/{impl/router/rule => api/rule/rules}/DomainHttpRule.java (72%) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java delete mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index f6d7ffc..81245b4 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -2,19 +2,18 @@ import blue.endless.jankson.JsonObject; import blue.endless.jankson.JsonPrimitive; -import top.offsetmonkey538.meshlib.api.HttpHandler; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRuleTypeRegistry; -import top.offsetmonkey538.meshlib.api.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.config.TestConfig; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; +import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.example.ExampleMain; -import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; -import top.offsetmonkey538.meshlib.impl.router.rule.DomainHttpRule; +import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; +import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; +import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; import top.offsetmonkey538.meshlib.platform.PlatformMain; -import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; import top.offsetmonkey538.monkeylib538.api.log.MonkeyLibLogger; -import top.offsetmonkey538.offsetconfig538.api.config.ConfigHolder; -import top.offsetmonkey538.offsetconfig538.api.config.ConfigManager; import top.offsetmonkey538.offsetconfig538.api.event.OffsetConfig538Events; import java.util.ServiceLoader; @@ -65,9 +64,10 @@ public static void initialize() { builder.registerSerializer(HttpHandler.class, (httpHandler, marshaller) -> { - HttpHandler.HttpHandlerDefinition handlerDefinition = HttpHandlerTypeRegistry.get(httpHandler.getClass()); + @SuppressWarnings("unchecked") // handler definition of ?,? extends HttpHandler should match ?,HttpHandler, no? + HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpHandler.getClass()); - final JsonObject result = (JsonObject) marshaller.serialize(httpHandler.getData()); + final JsonObject result = (JsonObject) marshaller.serialize(handlerDefinition.handlerToData().apply(httpHandler)); result.put("type", JsonPrimitive.of(handlerDefinition.type())); return result; }); @@ -76,7 +76,7 @@ public static void initialize() { final String type = jsonObject.get(String.class, "type"); @SuppressWarnings("unchecked") // It's proooobably a subclass of Object... - HttpHandler.HttpHandlerDefinition handlerDefinition = (HttpHandler.HttpHandlerDefinition) HttpHandlerTypeRegistry.get(type); + HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(type); final JsonObject dummyParent = new JsonObject(); dummyParent.put("dataHolder", jsonObject); @@ -86,7 +86,12 @@ public static void initialize() { }); }); - HttpRuleTypeRegistry.register("domain", new HttpRule.HttpRuleDefinition<>(DomainHttpRule.Data.class, DomainHttpRule::new)); + HttpRuleTypeRegistry.register("domain", new HttpRule.HttpRuleDefinition<>(DomainHttpRule.Data.class, data -> new DomainHttpRule(data.value))); + HttpRuleTypeRegistry.register("path", new HttpRule.HttpRuleDefinition<>(PathHttpRule.Data.class, data -> new PathHttpRule(data.value))); + + + HttpHandlerTypeRegistry.register("static-file", StaticFileHandler.Data.class, StaticFileHandler.class, handler -> new StaticFileHandler.Data(handler.fileToServe), data -> new StaticFileHandler(data.fileToServe())); + HttpHandlerTypeRegistry.register("static-directory", StaticDirectoryHandler.Data.class, StaticDirectoryHandler.class, handler -> new StaticDirectoryHandler.Data(handler.baseDir, handler.allowDirectoryList), data -> new StaticDirectoryHandler(data.baseDir(), data.allowDirectoryList())); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerTypeRegistry.java deleted file mode 100644 index e06b3f6..0000000 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandlerTypeRegistry.java +++ /dev/null @@ -1,30 +0,0 @@ -package top.offsetmonkey538.meshlib.api; - -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRuleTypeRegistry; -import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; -import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; - -public interface HttpHandlerTypeRegistry { - /** - * Instance - */ - @ApiStatus.Internal - HttpHandlerTypeRegistry INSTANCE = new HttpHandlerTypeRegistryImpl(); - - static void register(@NotNull final Class type, @NotNull final HttpHandler.HttpHandlerDefinition handler) { - INSTANCE.registerImpl(type, handler); - } - - static HttpHandler.HttpHandlerDefinition get(final String type) throws IllegalStateException { - return INSTANCE.getImpl(type); - } - static HttpHandler.HttpHandlerDefinition get(final Class type) throws IllegalStateException { - return INSTANCE.getImpl(type); - } - - void registerImpl(@NotNull final Class type, @NotNull final HttpHandler.HttpHandlerDefinition handler); - HttpHandler.HttpHandlerDefinition getImpl(final String type) throws IllegalStateException; - HttpHandler.HttpHandlerDefinition getImpl(final Class type) throws IllegalStateException; -} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java similarity index 59% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java rename to common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java index cfcc04d..f562745 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java @@ -1,8 +1,9 @@ -package top.offsetmonkey538.meshlib.api; +package top.offsetmonkey538.meshlib.api.handler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; import org.jetbrains.annotations.NotNull; @@ -15,15 +16,7 @@ * * @see HttpRouterRegistry */ -public abstract class HttpHandler { - protected final T data; - - public HttpHandler(T data) { - this.data = data; - } - public T getData() { - return data; - } +public interface HttpHandler { /** * This is called when an HTTP request is received for this handler. @@ -32,11 +25,8 @@ public T getData() { * * @param ctx the current channel handler context * @param request the received request + * @param rule the rule used to match this handler * @throws Exception when anything goes wrong */ - public abstract void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception; - - public record HttpHandlerDefinition(String type, Class dataType, Function> handlerInitializer) { - - } + void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception; } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java new file mode 100644 index 0000000..4e44197 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java @@ -0,0 +1,21 @@ +package top.offsetmonkey538.meshlib.api.handler; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; + +import java.util.function.Function; + +public interface HttpHandlerTypeRegistry { + /** + * Instance + */ + @ApiStatus.Internal + HttpHandlerTypeRegistry INSTANCE = new HttpHandlerTypeRegistryImpl(); + + static void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler) { + INSTANCE.registerImpl(type, dataType, handlerType, handlerToData, dataToHandler); + } + + void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler); +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java new file mode 100644 index 0000000..d97af04 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java @@ -0,0 +1,284 @@ +package top.offsetmonkey538.meshlib.api.handler.handlers; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.*; +import io.netty.util.CharsetUtil; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.text.CharacterIterator; +import java.text.SimpleDateFormat; +import java.text.StringCharacterIterator; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Formatter; +import java.util.Locale; + +import static io.netty.handler.codec.http.HttpHeaderNames.*; +import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; +import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; +import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendPermanentRedirect; + + +public class StaticDirectoryHandler implements HttpHandler { + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"); + + public final Path baseDir; + public final boolean allowDirectoryList; + + public StaticDirectoryHandler(final Path baseDir, final boolean allowDirectoryList) { + this.baseDir = baseDir.normalize().toAbsolutePath(); + this.allowDirectoryList = allowDirectoryList; + } + + @Override + public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + final String rawPath = new URI(rule.normalizeUri(request.uri())).getPath(); + final Path requestedPath; + try { + requestedPath = baseDir.resolve(rawPath.startsWith("/") ? rawPath.substring(1) : rawPath).normalize(); + } catch (InvalidPathException e) { + sendError(ctx, HttpResponseStatus.BAD_REQUEST, e); + return; + } + + if (!requestedPath.startsWith(baseDir)) { + sendError(ctx, HttpResponseStatus.FORBIDDEN); + return; + } + + if (!Files.exists(requestedPath)) { + sendError(ctx, HttpResponseStatus.NOT_FOUND); + return; + } + + if (!Files.isDirectory(requestedPath)) { + HttpResponseUtil.sendFile(ctx, request, requestedPath); + return; + } + + + if (!allowDirectoryList) { + sendError(ctx, HttpResponseStatus.NOT_FOUND); + return; + } + + if (!request.uri().endsWith("/")) { + sendPermanentRedirect(ctx, request.uri() + "/"); + } + + sendDirectoryListing(ctx, request, rawPath, requestedPath); + } + + private static void sendDirectoryListing(ChannelHandlerContext ctx, FullHttpRequest request, String uriPath, Path directory) throws IOException { + final boolean isKeepAlive = HttpUtil.isKeepAlive(request); + + final ByteBuf byteBuf = Unpooled.copiedBuffer(renderDirectoryListing(uriPath, directory), CharsetUtil.UTF_8); + final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, byteBuf); + + response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8"); + response.headers().set(CONTENT_LENGTH, byteBuf.readableBytes()); + response.headers().set(CONNECTION, isKeepAlive ? KEEP_ALIVE : CLOSE); + + + ctx.write(response).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); + + final ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); + if (!isKeepAlive) lastContentFuture.addListener(ChannelFutureListener.CLOSE); + } + + private static StringBuilder renderDirectoryListing(String uriPath, Path directory) throws IOException { + final StringBuilder result = new StringBuilder(); + + + result.append( + """ + + + + """ + ); + result.append("Index of ").append(uriPath).append("\n"); + + result.append( + """ + + + """ + ); + + result.append( + """ + + + + + + + + + + + + """ + ); + + if (!"/".equals(uriPath)) { + result.append(""); + } + + try (DirectoryStream stream = Files.newDirectoryStream(directory)) { + stream.forEach(path -> { + if (!Files.exists(path) || (!Files.isDirectory(path) && !Files.isRegularFile(path))) { + return; + } + + String name = path.getFileName().toString(); + String icon = "file-icon"; + if (Files.isDirectory(path)) { + name += "/"; + icon = "dir-icon"; + } + String modifiedTime = "-"; + try { + modifiedTime = DATE_FORMATTER.format(Files.getLastModifiedTime(path).toInstant().atZone(ZoneId.of("UTC"))) + " UTC"; + } catch (IOException e) { + LOGGER.error("Failed to get modification time for file '%s'!", e, path); + } + + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + }); + } + + result.append( + """ + + + + +
Index of\s + """); + result.append(uriPath); + result.append( + """ +
NameLast ModifiedSize
Parent Directory--
").append(name).append("").append(modifiedTime).append(""); + formatFileSize(result, path); + result.append("
Provided by MESH Lib
+ + + """ + ); + + return result; + } + + private static void formatFileSize(StringBuilder builder, Path path) { + if (Files.isDirectory(path)) { + builder.append("-"); + return; + } + + final long sizeBytes; + try { + sizeBytes = Files.size(path); + } catch (IOException e) { + LOGGER.error("Failed to get size for file '%s'!", e, path); + builder.append("-"); + return; + } + + // https://stackoverflow.com/a/3758880 + long absB = sizeBytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(sizeBytes); + if (absB < 1024) { + builder.append(sizeBytes).append(" B"); + return; + } + + long value = absB; + final CharacterIterator charIterator = new StringCharacterIterator("KMGTPE"); + for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) { + value >>= 10; + charIterator.next(); + } + value *= Long.signum(sizeBytes); + builder.append("%.1f %ciB".formatted(value / 1024.0, charIterator.current())); + } + + // todo: does this need to be mutable for jankson to do its magic? + public record Data(Path baseDir, boolean allowDirectoryList) { + + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java new file mode 100644 index 0000000..39c4a38 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java @@ -0,0 +1,52 @@ +package top.offsetmonkey538.meshlib.api.handler.handlers; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.DefaultFileRegion; +import io.netty.handler.codec.http.*; +import io.netty.handler.stream.ChunkedFile; +import io.netty.handler.stream.ChunkedNioFile; +import io.netty.handler.stream.ChunkedStream; +import io.netty.handler.stream.ChunkedWriteHandler; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; + +import java.io.File; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; +import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; + + +public class StaticFileHandler implements HttpHandler { + public final Path fileToServe; + + public StaticFileHandler(final Path file) { + this.fileToServe = file.normalize().toAbsolutePath(); + } + + @Override + public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + HttpResponseUtil.sendFile(ctx, request, fileToServe); + } + + // todo: does this need to be mutable for jankson to do its magic? + public record Data(Path fileToServe) { + + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java index a1873e7..72510f8 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java @@ -1,9 +1,8 @@ package top.offsetmonkey538.meshlib.api.router; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.HttpHandler; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.router.target.HttpTarget; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; -public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { +public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java index 3540293..da24fc6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java @@ -2,7 +2,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.impl.HttpRouterRegistryImpl; import java.util.Map; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRule.java deleted file mode 100644 index e0eeec2..0000000 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRule.java +++ /dev/null @@ -1,16 +0,0 @@ -package top.offsetmonkey538.meshlib.api.router.rule; - -import io.netty.handler.codec.http.FullHttpRequest; - -import java.util.function.Function; - -public interface HttpRule { - String getType(); - T getData(); - - boolean matches(final FullHttpRequest httpRequest); - - record HttpRuleDefinition(Class dataType, Function> ruleInitializer) { - - } -} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/target/HttpTarget.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/target/HttpTarget.java deleted file mode 100644 index 588f610..0000000 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/target/HttpTarget.java +++ /dev/null @@ -1,4 +0,0 @@ -package top.offsetmonkey538.meshlib.api.router.target; - -public interface HttpTarget { -} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java new file mode 100644 index 0000000..ab5943c --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java @@ -0,0 +1,29 @@ +package top.offsetmonkey538.meshlib.api.rule; + +import io.netty.handler.codec.http.FullHttpRequest; + +import java.util.function.Function; + +public interface HttpRule { + String getType(); + T getData(); + + boolean matches(final FullHttpRequest request); + + /** + * Should return the uri as if this rule's first match is the root + *

+ * {@link top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule PathHttpRule} removes the matched path so {@link top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler StaticDirectoryHandler} can correctly find the files based on the uri + *

+ * + * @param uri the uri to modify + */ + default String normalizeUri(final String uri) { + // no-op + return uri; + } + + record HttpRuleDefinition(Class dataType, Function> ruleInitializer) { + + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRuleTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java similarity index 93% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRuleTypeRegistry.java rename to common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java index c804bb3..7ae066b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/rule/HttpRuleTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.api.router.rule; +package top.offsetmonkey538.meshlib.api.rule; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/DomainHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java similarity index 72% rename from common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/DomainHttpRule.java rename to common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java index 964914f..7e8003e 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/DomainHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java @@ -1,15 +1,14 @@ -package top.offsetmonkey538.meshlib.impl.router.rule; +package top.offsetmonkey538.meshlib.api.rule.rules; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; -import top.offsetmonkey538.meshlib.config.TestConfig; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; public class DomainHttpRule implements HttpRule { private final String value; // i.e. map.example.com - public DomainHttpRule(Data data) { - this.value = data.value; + public DomainHttpRule(String value) { + this.value = value; } @Override @@ -23,8 +22,8 @@ public Data getData() { } @Override - public boolean matches(FullHttpRequest httpRequest) { - String host = httpRequest.headers().get(HttpHeaderNames.HOST); + public boolean matches(FullHttpRequest request) { + String host = request.headers().get(HttpHeaderNames.HOST); if (host == null) return false; final int portIndex = host.indexOf(':'); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java new file mode 100644 index 0000000..ba483a0 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java @@ -0,0 +1,46 @@ +package top.offsetmonkey538.meshlib.api.rule.rules; + +import io.netty.handler.codec.http.FullHttpRequest; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; + +public class PathHttpRule implements HttpRule { + private final String value; // i.e. /map -> example.com/map + + public PathHttpRule(String value) { + this.value = value; + } + + @Override + public String getType() { + return "path"; + } + + @Override + public Data getData() { + return new Data(value); + } + + @Override + public boolean matches(FullHttpRequest request) { + return request.uri().startsWith(value); + } + + @Override + public String normalizeUri(String uri) { + return uri.replaceFirst(value, ""); + } + + public static final class Data { + @SuppressWarnings("FieldMayBeFinal") // Think it needs to be non-final cause jankson + public String value; + + @SuppressWarnings("unused") // Public no-args used by jankson i think + public Data() { + + } + + public Data(String value) { + this.value = value; + } + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java index e7ca0f8..97fee9a 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java @@ -1,6 +1,7 @@ package top.offsetmonkey538.meshlib.api.util; import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; import org.jetbrains.annotations.ApiStatus; @@ -8,9 +9,13 @@ import org.jetbrains.annotations.Nullable; import top.offsetmonkey538.meshlib.impl.util.HttpResponseUtilImpl; +import java.io.IOException; +import java.nio.file.Path; + /** * Provides utils for responding to http requests */ +// TODO: FIXME: respect keep alive public interface HttpResponseUtil { /** * Instance @@ -19,6 +24,18 @@ public interface HttpResponseUtil { HttpResponseUtil INSTANCE = new HttpResponseUtilImpl(); + /** + * Sends the requester the file at the provided file + * + * @param ctx the current channel handler context + * @param request the client request + * @param fileToSend the path to the file to send + * @throws IOException when io go wrong :( + */ + static void sendFile(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull Path fileToSend) throws IOException { + INSTANCE.sendFileImpl(ctx, request, fileToSend); + } + /** * Sends the requester a Permanent Redirect (308) response containing the new location. *

@@ -100,6 +117,7 @@ static void sendResponseAndClose(@NotNull ChannelHandlerContext ctx, @NotNull Fu } + void sendFileImpl(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull Path fileToSend) throws IOException; void sendRedirectImpl(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @NotNull String newLocation); void sendStringImpl(@NotNull ChannelHandlerContext ctx, @NotNull String content); void sendErrorImpl(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @Nullable String reason); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java index d0337ea..2ff203b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java @@ -1,10 +1,10 @@ package top.offsetmonkey538.meshlib.config; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.HttpHandler; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; -import top.offsetmonkey538.meshlib.impl.router.rule.DomainHttpRule; +import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.platform.PlatformMain; import top.offsetmonkey538.offsetconfig538.api.config.Config; @@ -12,8 +12,8 @@ public class TestConfig implements Config { - public HttpRule rule = new DomainHttpRule(new DomainHttpRule.Data("map.example.com")); - public HttpHandler target = new SimpleHttpHandler(new SimpleHttpHandler.Data("Goodbye, World!")); + public HttpRule rule = new DomainHttpRule("map.example.com"); + public HttpHandler target = new SimpleHttpHandler("Goodbye, World!"); @Override diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index 3c0643d..2d6a747 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -1,14 +1,15 @@ package top.offsetmonkey538.meshlib.example; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.FullHttpRequest; -import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.HttpHandler; -import top.offsetmonkey538.meshlib.api.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; +import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; import top.offsetmonkey538.meshlib.api.router.HttpRouter; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; -import top.offsetmonkey538.meshlib.api.router.target.HttpTarget; -import top.offsetmonkey538.meshlib.impl.router.rule.DomainHttpRule; +import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; +import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; + +import java.nio.file.Path; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; @@ -33,11 +34,33 @@ public static void onInitialize() { LOGGER.warn("MESH examples enabled!"); // Register - HttpHandlerTypeRegistry.register(SimpleHttpHandler.class, new HttpHandler.HttpHandlerDefinition<>("simple-http", SimpleHttpHandler.Data.class, SimpleHttpHandler::new)); + record SimpleHttpHandlerData(String content) { + + } + HttpHandlerTypeRegistry.register("simple-http", SimpleHttpHandlerData.class, SimpleHttpHandler.class, handler -> new SimpleHttpHandlerData(handler.content), data -> new SimpleHttpHandler(data.content())); + + //HttpRouterRegistry.INSTANCE.register("simple-server", new HttpRouter( + // new DomainHttpRule("localhost"), + // new SimpleHttpHandler("Yellow!") + //)); + HttpRouterRegistry.INSTANCE.register("simple-server2", new HttpRouter( + new PathHttpRule("/hi"), + new SimpleHttpHandler("Goodbye!") + )); + + HttpRouterRegistry.INSTANCE.register("static-file-test", new HttpRouter( + new PathHttpRule("/static/file"), + new StaticFileHandler(Path.of("usercache.json")) + )); + + HttpRouterRegistry.INSTANCE.register("static-directory-test", new HttpRouter( + new PathHttpRule("/static/directory"), + new StaticDirectoryHandler(Path.of("/home/dave"), true) + )); - HttpRouterRegistry.INSTANCE.register("simple-server", new HttpRouter( - new DomainHttpRule(new DomainHttpRule.Data("localhost")), - new SimpleHttpHandler(new SimpleHttpHandler.Data("Yellow!")) + HttpRouterRegistry.INSTANCE.register("static-directory-test2", new HttpRouter( + new DomainHttpRule("localhost"), + new StaticDirectoryHandler(Path.of("/home/dave"), false) )); } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java index a190071..b6a51f5 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java @@ -8,7 +8,8 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; import java.nio.charset.StandardCharsets; @@ -19,20 +20,17 @@ /** * An example {@link HttpHandler} implementation to learn from */ -public class SimpleHttpHandler extends HttpHandler { - - public SimpleHttpHandler(Data data) { - super(data); - } - - public record Data(String content) { +public class SimpleHttpHandler implements HttpHandler { + public final String content; + public SimpleHttpHandler(String content) { + this.content = content; } @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception { + public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { // Write the provided content to a buffer, encoded in UTF-8 - final ByteBuf content = Unpooled.copiedBuffer(data.content(), StandardCharsets.UTF_8); + final ByteBuf content = Unpooled.copiedBuffer(this.content, StandardCharsets.UTF_8); // Create a response with said buffer final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/package-info.java index 86e3d0e..e092d2d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/package-info.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/package-info.java @@ -1,4 +1,4 @@ /** - * This contains examples for how to implement {@link top.offsetmonkey538.meshlib.api.HttpHandler HttpHandler}s + * This contains examples for how to implement {@link top.offsetmonkey538.meshlib.api.handler.HttpHandler HttpHandler}s */ package top.offsetmonkey538.meshlib.example; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java index 7056600..c61f453 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java @@ -3,15 +3,13 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.*; -import io.netty.util.ReferenceCountUtil; -import top.offsetmonkey538.meshlib.api.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.router.HttpRouter; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; @@ -41,7 +39,7 @@ protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) if (router == null) router = possibleRouter.getValue(); } if (router == null) { - LOGGER.warn("No routers matched request! Ignoring..."); + LOGGER.warn("No routers matched request for '%s%s'! Ignoring...", request.headers().get(HttpHeaderNames.HOST), request.uri()); forward(ctx, request); return; } @@ -58,19 +56,7 @@ protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) return; } - final HttpHandler handler = router.handler(); - ctx.pipeline().addAfter(MOD_ID + "/handler", MOD_ID + "/" + matchedRouterIDs.getFirst(), new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { - handler.handleRequest(ctx, request); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - exceptionCaughtImpl(ctx, cause); - } - }); - forward(ctx, request); + router.handler().handleRequest(ctx, request, router.rule()); } @Override @@ -87,11 +73,10 @@ private static void exceptionCaughtImpl(ChannelHandlerContext ctx, Throwable cau private static void forward(ChannelHandlerContext ctx, FullHttpRequest request) { // These handlers can be removed from this context now - // actually these probably can't!! otherwise the stupid requests can't like yknow do http stuff ctx.pipeline().remove(MOD_ID + "/codec"); - // actually these probably can't!! otherwise the stupid requests can't like yknow do http stuff ctx.pipeline().remove(MOD_ID + "/aggregator"); ctx.pipeline().remove(MOD_ID + "/handler"); // Forward to the next handler. - ctx.fireChannelRead(ReferenceCountUtil.retain(request)); + // TODO: I don't think I need to retain anymore? ctx.fireChannelRead(ReferenceCountUtil.retain(request)); + ctx.fireChannelRead(request); } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java index 25eccd6..19b26a3 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java @@ -6,6 +6,7 @@ import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.stream.ChunkedWriteHandler; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; @@ -45,7 +46,8 @@ public void channelRead(ChannelHandlerContext ctx, Object request) throws Except final ChannelPipeline pipeline = ctx.pipeline(); pipeline.addAfter(MOD_ID, MOD_ID + "/codec", new HttpServerCodec()); pipeline.addAfter(MOD_ID + "/codec", MOD_ID + "/aggregator", new HttpObjectAggregator(65536)); - pipeline.addAfter(MOD_ID + "/aggregator", MOD_ID + "/handler", new MainHttpHandler()); + pipeline.addAfter(MOD_ID + "/aggregator", MOD_ID + "/chunked", new ChunkedWriteHandler()); + pipeline.addAfter(MOD_ID + "/chunked", MOD_ID + "/handler", new MainHttpHandler()); forward(ctx, request); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java index 90af059..81fb560 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java @@ -1,41 +1,43 @@ package top.offsetmonkey538.meshlib.impl.router; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.HttpHandler; -import top.offsetmonkey538.meshlib.api.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; /** * Implementation of {@link HttpRuleTypeRegistry} */ public class HttpHandlerTypeRegistryImpl implements HttpHandlerTypeRegistry { - private final Map> handlersById = new HashMap<>(); - private final Map, HttpHandler.HttpHandlerDefinition> handlersByType = new HashMap<>(); + private final Map> handlersById = new HashMap<>(); + private final Map, HttpHandlerDefinition> handlersByType = new HashMap<>(); @Override - public void registerImpl(@NotNull final Class type, @NotNull final HttpHandler.HttpHandlerDefinition handler) { - final String id = handler.type(); - if (id.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); - if (handlersById.containsKey(id)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!"); - if (handlersByType.containsKey(type)) throw new IllegalArgumentException("Handler type for type '" + type + "' already registered!"); - - handlersById.put(id, handler); - handlersByType.put(type, handler); + public void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler) { + if (type.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); + if (handlersById.containsKey(type)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!"); + if (handlersByType.containsKey(handlerType)) throw new IllegalArgumentException("Handler type for type '" + handlerType + "' already registered!"); + + final HttpHandlerDefinition handler = new HttpHandlerDefinition<>(type, dataType, handlerType, handlerToData, dataToHandler); + handlersById.put(type, handler); + handlersByType.put(handler.handlerType(), handler); } - @Override - public HttpHandler.HttpHandlerDefinition getImpl(final String type) throws IllegalStateException { + public HttpHandlerDefinition get(final String type) throws IllegalStateException { if (handlersById.containsKey(type)) return handlersById.get(type); throw new IllegalStateException("Http rule with type '" + type + "' not registered!"); } - @Override - public HttpHandler.HttpHandlerDefinition getImpl(final Class type) throws IllegalStateException { + public HttpHandlerDefinition get(final Class type) throws IllegalStateException { if (handlersByType.containsKey(type)) return handlersByType.get(type); throw new IllegalStateException("Http rule with type '" + type + "' not registered!"); } + + public record HttpHandlerDefinition(String type, Class dataType, Class handlerType, Function handlerToData, Function handlerInitializer) { + + } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java index b078268..4851c0f 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java @@ -1,8 +1,8 @@ package top.offsetmonkey538.meshlib.impl.router.rule; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.router.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; import java.util.HashMap; import java.util.Map; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java deleted file mode 100644 index d352d70..0000000 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/target/WebserverHttpTarget.java +++ /dev/null @@ -1,47 +0,0 @@ -package top.offsetmonkey538.meshlib.impl.router.target; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.FullHttpResponse; -import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.HttpHandler; -import top.offsetmonkey538.meshlib.api.router.target.HttpTarget; -import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; -import top.offsetmonkey538.meshlib.impl.router.rule.DomainHttpRule; - -import java.nio.charset.StandardCharsets; - -import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; -import static io.netty.handler.codec.http.HttpResponseStatus.OK; -import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; - -//TODO -public class WebserverHttpTarget extends HttpHandler { - - public WebserverHttpTarget(Data data) { - super(data); - } - - public record Data(String directory) { - - } - - @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception { - // Write the provided content to a buffer, encoded in UTF-8 - final ByteBuf content = Unpooled.copiedBuffer("TODO: work on WebserverHttpTarget", StandardCharsets.UTF_8); - // Create a response with said buffer - final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content); - - // Set the "CONTENT_TYPE" header to tell the browser that this is plain text encoded in UTF-8 - response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); - - // Send the response and close the connection - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); - } - -} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java index fcfec72..40d5303 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java @@ -2,22 +2,58 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.*; +import io.netty.handler.stream.ChunkedNioFile; import io.netty.util.CharsetUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + import static io.netty.handler.codec.http.HttpHeaderNames.*; +import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; +import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; public final class HttpResponseUtilImpl implements HttpResponseUtil { + @Override + public void sendFileImpl(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull Path fileToSend) throws IOException { + if (!Files.exists(fileToSend) || !Files.isRegularFile(fileToSend)) { + sendError(ctx, HttpResponseStatus.NOT_FOUND); + return; + } + + final boolean isKeepAlive = HttpUtil.isKeepAlive(request); + final long fileLength = Files.size(fileToSend); + + + final HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); + response.headers().set(CONTENT_LENGTH, fileLength); + response.headers().set(CONTENT_TYPE, getContentType(fileToSend)); + response.headers().set(CONNECTION, isKeepAlive ? KEEP_ALIVE : CLOSE); + ctx.write(response); + + ctx.write( + new ChunkedNioFile(fileToSend.toFile()), + //new DefaultFileRegion(fileToServe.toFile(), 0, fileLength), + ctx.newProgressivePromise() + ).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); + + final ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); + if (isKeepAlive) return; + lastContentFuture.addListener(ChannelFutureListener.CLOSE); + } + @Override public void sendRedirectImpl(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @NotNull String newLocation) { final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.EMPTY_BUFFER); @@ -60,4 +96,17 @@ public void sendResponseAndCloseImpl(@NotNull ChannelHandlerContext ctx, @NotNul response.headers().set(CONNECTION, "close"); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } + + private static String getContentType(final @NotNull Path file) { + final String result; + + try { + result = Files.probeContentType(file); + } catch (IOException e) { + return "text/plain"; + } + + if (result == null) return "text/plain"; + return result; + } } From b34136e2cb3d3594a106b7e28cfcbbb70bdecacf Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:22:04 +0200 Subject: [PATCH 29/78] static directory handler without directory list enabled will try to serve index.html files --- .../handlers/StaticDirectoryHandler.java | 19 ++++++++++--------- .../meshlib/example/ExampleMain.java | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java index d97af04..aedf1a6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java @@ -31,8 +31,7 @@ import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; -import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; -import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendPermanentRedirect; +import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.*; public class StaticDirectoryHandler implements HttpHandler { @@ -68,21 +67,23 @@ public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpR } if (!Files.isDirectory(requestedPath)) { - HttpResponseUtil.sendFile(ctx, request, requestedPath); + sendFile(ctx, request, requestedPath); return; } - - if (!allowDirectoryList) { - sendError(ctx, HttpResponseStatus.NOT_FOUND); + // At this point we know it's a directory and that it exists + if (!request.uri().endsWith("/")) { + sendPermanentRedirect(ctx, request.uri() + "/"); return; } - if (!request.uri().endsWith("/")) { - sendPermanentRedirect(ctx, request.uri() + "/"); + if (allowDirectoryList) { + sendDirectoryListing(ctx, request, rawPath, requestedPath); + return; } - sendDirectoryListing(ctx, request, rawPath, requestedPath); + // Try serving an index.html file + sendFile(ctx, request, requestedPath.resolve("index.html")); } private static void sendDirectoryListing(ChannelHandlerContext ctx, FullHttpRequest request, String uriPath, Path directory) throws IOException { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index 2d6a747..945138d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -60,7 +60,7 @@ record SimpleHttpHandlerData(String content) { HttpRouterRegistry.INSTANCE.register("static-directory-test2", new HttpRouter( new DomainHttpRule("localhost"), - new StaticDirectoryHandler(Path.of("/home/dave"), false) + new StaticDirectoryHandler(Path.of("/home/dave/Dev/Java/Minecraft/Mods/Loot-Table-Modifier/docs/dist/"), false) )); } } From 26dbc1dd1265354742424fd0a1c4d82b16ba62da Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:32:59 +0200 Subject: [PATCH 30/78] minify directory list page html --- .../handlers/StaticDirectoryHandler.java | 113 ++---------------- 1 file changed, 7 insertions(+), 106 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java index aedf1a6..dad216d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java @@ -107,96 +107,11 @@ private static StringBuilder renderDirectoryListing(String uriPath, Path directo final StringBuilder result = new StringBuilder(); - result.append( - """ - - - - """ - ); - result.append("Index of ").append(uriPath).append("\n"); - - result.append( - """ - - - """ - ); - - result.append( - """ - - - -
Index of\s - """); + result.append("Index of ").append(uriPath).append(""); + result.append(""); + result.append(" - - - - - - - - """ - ); + result.append(""); if (!"/".equals(uriPath)) { result.append(""); @@ -221,27 +136,13 @@ private static StringBuilder renderDirectoryListing(String uriPath, Path directo LOGGER.error("Failed to get modification time for file '%s'!", e, path); } - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); + result.append(""); }); } - result.append( - """ - - - - -
Index of "); result.append(uriPath); - result.append( - """ -
NameLast ModifiedSize
NameLast ModifiedSize
Parent Directory--
").append(name).append("").append(modifiedTime).append(""); + result.append("
").append(name).append("").append(modifiedTime).append(""); formatFileSize(result, path); - result.append("
Provided by MESH Lib
- - - """ - ); + result.append("
Provided by MESH Lib
"); return result; } From c8fbd42c62f17e61b3d4df99587f2324e06801d1 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:42:12 +0200 Subject: [PATCH 31/78] less append calls for rendering directory listing --- .../api/handler/handlers/StaticDirectoryHandler.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java index dad216d..1b63197 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java @@ -104,12 +104,7 @@ private static void sendDirectoryListing(ChannelHandlerContext ctx, FullHttpRequ } private static StringBuilder renderDirectoryListing(String uriPath, Path directory) throws IOException { - final StringBuilder result = new StringBuilder(); - - - result.append("Index of ").append(uriPath).append(""); - result.append(""); - result.append("
Index of "); + final StringBuilder result = new StringBuilder("Index of ").append(uriPath).append(""); From 49696d2ae082e9029f728704d89aa5fd63b6378c Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:43:18 +0200 Subject: [PATCH 32/78] refactor rule and handler registries and their (de)serializers --- .../top/offsetmonkey538/meshlib/MESHLib.java | 27 +++++---- .../meshlib/api/handler/HttpHandler.java | 2 +- .../api/handler/HttpHandlerTypeRegistry.java | 4 +- .../handlers/StaticDirectoryHandler.java | 36 ++++++++---- .../handler/handlers/StaticFileHandler.java | 56 +++++++++---------- .../meshlib/api/rule/HttpRule.java | 9 +-- .../api/rule/HttpRuleTypeRegistry.java | 13 ++--- .../api/rule/rules/DomainHttpRule.java | 43 +++++++------- .../meshlib/api/rule/rules/PathHttpRule.java | 45 +++++++-------- .../meshlib/example/ExampleMain.java | 3 +- .../meshlib/example/SimpleHttpHandler.java | 9 +-- .../router/HttpHandlerTypeRegistryImpl.java | 16 +++--- .../router/rule/HttpRuleTypeRegistryImpl.java | 30 +++++++--- 13 files changed, 149 insertions(+), 144 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index 81245b4..450bac6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -12,6 +12,7 @@ import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; +import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; import top.offsetmonkey538.meshlib.platform.PlatformMain; import top.offsetmonkey538.monkeylib538.api.log.MonkeyLibLogger; import top.offsetmonkey538.offsetconfig538.api.event.OffsetConfig538Events; @@ -44,8 +45,11 @@ public static void initialize() { OffsetConfig538Events.JANKSON_CONFIGURATION_EVENT.listen(builder -> { builder.registerSerializer(HttpRule.class, (httpRule, marshaller) -> { - final JsonObject result = (JsonObject) marshaller.serialize(httpRule.getData()); - result.put("type", JsonPrimitive.of(httpRule.getType())); + @SuppressWarnings("unchecked") // rule definition of ?,? extends HttpRule should match ?,HttpHandler, no? + final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpRule.getClass()); + + final JsonObject result = (JsonObject) marshaller.serialize(ruleDefinition.ruleToData().apply(httpRule)); + result.put("type", JsonPrimitive.of(ruleDefinition.type())); return result; }); @@ -53,19 +57,19 @@ public static void initialize() { final String type = jsonObject.get(String.class, "type"); @SuppressWarnings("unchecked") // It's proooobably a subclass of Object... - HttpRule.HttpRuleDefinition ruleDefinition = (HttpRule.HttpRuleDefinition) HttpRuleTypeRegistry.get(type); + final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpRuleTypeRegistry.INSTANCE).get(type); final JsonObject dummyParent = new JsonObject(); dummyParent.put("dataHolder", jsonObject); final Object dataHolder = dummyParent.get(ruleDefinition.dataType(), "dataHolder"); - return ruleDefinition.ruleInitializer().apply(dataHolder); + return ruleDefinition.dataToRule().apply(dataHolder); }); builder.registerSerializer(HttpHandler.class, (httpHandler, marshaller) -> { @SuppressWarnings("unchecked") // handler definition of ?,? extends HttpHandler should match ?,HttpHandler, no? - HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpHandler.getClass()); + final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpHandler.getClass()); final JsonObject result = (JsonObject) marshaller.serialize(handlerDefinition.handlerToData().apply(httpHandler)); result.put("type", JsonPrimitive.of(handlerDefinition.type())); @@ -76,22 +80,21 @@ public static void initialize() { final String type = jsonObject.get(String.class, "type"); @SuppressWarnings("unchecked") // It's proooobably a subclass of Object... - HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(type); + final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(type); final JsonObject dummyParent = new JsonObject(); dummyParent.put("dataHolder", jsonObject); final Object dataHolder = dummyParent.get(handlerDefinition.dataType(), "dataHolder"); - return handlerDefinition.handlerInitializer().apply(dataHolder); + return handlerDefinition.dataToHandler().apply(dataHolder); }); }); - HttpRuleTypeRegistry.register("domain", new HttpRule.HttpRuleDefinition<>(DomainHttpRule.Data.class, data -> new DomainHttpRule(data.value))); - HttpRuleTypeRegistry.register("path", new HttpRule.HttpRuleDefinition<>(PathHttpRule.Data.class, data -> new PathHttpRule(data.value))); - + DomainHttpRule.register(); + PathHttpRule.register(); - HttpHandlerTypeRegistry.register("static-file", StaticFileHandler.Data.class, StaticFileHandler.class, handler -> new StaticFileHandler.Data(handler.fileToServe), data -> new StaticFileHandler(data.fileToServe())); - HttpHandlerTypeRegistry.register("static-directory", StaticDirectoryHandler.Data.class, StaticDirectoryHandler.class, handler -> new StaticDirectoryHandler.Data(handler.baseDir, handler.allowDirectoryList), data -> new StaticDirectoryHandler(data.baseDir(), data.allowDirectoryList())); + StaticFileHandler.register(); + StaticDirectoryHandler.register(); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java index f562745..4cf1c63 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java @@ -28,5 +28,5 @@ public interface HttpHandler { * @param rule the rule used to match this handler * @throws Exception when anything goes wrong */ - void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception; + void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception; } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java index 4e44197..8ffe805 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java @@ -13,9 +13,9 @@ public interface HttpHandlerTypeRegistry { @ApiStatus.Internal HttpHandlerTypeRegistry INSTANCE = new HttpHandlerTypeRegistryImpl(); - static void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler) { + static void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler) { INSTANCE.registerImpl(type, dataType, handlerType, handlerToData, dataToHandler); } - void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler); + void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java index 1b63197..4ddb1f4 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java @@ -7,10 +7,11 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.*; import io.netty.util.CharsetUtil; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; import java.io.IOException; import java.net.URI; @@ -19,12 +20,9 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.text.CharacterIterator; -import java.text.SimpleDateFormat; import java.text.StringCharacterIterator; import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import java.util.Formatter; -import java.util.Locale; import static io.netty.handler.codec.http.HttpHeaderNames.*; import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; @@ -34,19 +32,16 @@ import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.*; -public class StaticDirectoryHandler implements HttpHandler { +public record StaticDirectoryHandler(Path baseDir, boolean allowDirectoryList) implements HttpHandler { private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"); - public final Path baseDir; - public final boolean allowDirectoryList; - public StaticDirectoryHandler(final Path baseDir, final boolean allowDirectoryList) { this.baseDir = baseDir.normalize().toAbsolutePath(); this.allowDirectoryList = allowDirectoryList; } @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { final String rawPath = new URI(rule.normalizeUri(request.uri())).getPath(); final Path requestedPath; try { @@ -174,8 +169,27 @@ private static void formatFileSize(StringBuilder builder, Path path) { builder.append("%.1f %ciB".formatted(value / 1024.0, charIterator.current())); } - // todo: does this need to be mutable for jankson to do its magic? - public record Data(Path baseDir, boolean allowDirectoryList) { + @ApiStatus.Internal + public static void register() { + HttpHandlerTypeRegistry.register("static-directory", Data.class, StaticDirectoryHandler.class, handler -> new Data(handler.baseDir, handler.allowDirectoryList), data -> new StaticDirectoryHandler(data.baseDir, data.allowDirectoryList)); + } + + @ApiStatus.Internal + private static final class Data { + @SuppressWarnings("FieldMayBeFinal") // Pretty sure this needs to be non-final cause jankson wants to modify + private Path baseDir; + @SuppressWarnings("FieldMayBeFinal") // Pretty sure this needs to be non-final cause jankson wants to modify + private boolean allowDirectoryList; + + @SuppressWarnings("unused") + // Pretty sure this public no-args needs to exist cause jankson wants to create instances + public Data() { + } + + public Data(final Path baseDir, final boolean allowDirectoryList) { + this.baseDir = baseDir; + this.allowDirectoryList = allowDirectoryList; + } } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java index 39c4a38..8856a18 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java @@ -1,52 +1,48 @@ package top.offsetmonkey538.meshlib.api.handler.handlers; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.DefaultFileRegion; import io.netty.handler.codec.http.*; -import io.netty.handler.stream.ChunkedFile; -import io.netty.handler.stream.ChunkedNioFile; -import io.netty.handler.stream.ChunkedStream; -import io.netty.handler.stream.ChunkedWriteHandler; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; -import java.io.File; -import java.nio.channels.FileChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; - -import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; -import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; -import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; -import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; -import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; -import static io.netty.handler.codec.http.HttpResponseStatus.OK; -import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; -import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; +import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; -public class StaticFileHandler implements HttpHandler { - public final Path fileToServe; - public StaticFileHandler(final Path file) { - this.fileToServe = file.normalize().toAbsolutePath(); +public record StaticFileHandler(Path fileToServe) implements HttpHandler { + public StaticFileHandler(final Path fileToServe) { + this.fileToServe = fileToServe.normalize().toAbsolutePath(); } @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { HttpResponseUtil.sendFile(ctx, request, fileToServe); } - // todo: does this need to be mutable for jankson to do its magic? - public record Data(Path fileToServe) { + @ApiStatus.Internal + public static void register() { + HttpHandlerTypeRegistry.register("static-file", Data.class, StaticFileHandler.class, handler -> new Data(handler.fileToServe), data -> new StaticFileHandler(data.fileToServe)); + } + + @ApiStatus.Internal + private static final class Data { + @SuppressWarnings("FieldMayBeFinal") // Pretty sure this needs to be non-final cause jankson wants to modify + private Path fileToServe; + + @SuppressWarnings("unused") + // Pretty sure this public no-args needs to exist cause jankson wants to create instances + public Data() { + + } + public Data(final Path fileToServe) { + this.fileToServe = fileToServe; + } } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java index ab5943c..5feb2ba 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java @@ -4,10 +4,7 @@ import java.util.function.Function; -public interface HttpRule { - String getType(); - T getData(); - +public interface HttpRule { boolean matches(final FullHttpRequest request); /** @@ -22,8 +19,4 @@ default String normalizeUri(final String uri) { // no-op return uri; } - - record HttpRuleDefinition(Class dataType, Function> ruleInitializer) { - - } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java index 7ae066b..0f99d3d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java @@ -4,6 +4,8 @@ import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; +import java.util.function.Function; + public interface HttpRuleTypeRegistry { /** * Instance @@ -11,14 +13,9 @@ public interface HttpRuleTypeRegistry { @ApiStatus.Internal HttpRuleTypeRegistry INSTANCE = new HttpRuleTypeRegistryImpl(); - static void register(final String type, @NotNull final HttpRule.HttpRuleDefinition rule) { - INSTANCE.registerImpl(type, rule); - } - - static @NotNull HttpRule.HttpRuleDefinition get(final String type) throws IllegalStateException { - return INSTANCE.getImpl(type); + static void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule) { + INSTANCE.registerImpl(type, dataType, ruleType, ruleToData, dataToRule); } - void registerImpl(final String type, @NotNull final HttpRule.HttpRuleDefinition rule); - @NotNull HttpRule.HttpRuleDefinition getImpl(final String type) throws IllegalStateException; + void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java index 7e8003e..f4b3a45 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java @@ -2,24 +2,14 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; +import org.jetbrains.annotations.ApiStatus; import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; -public class DomainHttpRule implements HttpRule { - private final String value; // i.e. map.example.com - - public DomainHttpRule(String value) { - this.value = value; - } - - @Override - public String getType() { - return "domain"; - } - - @Override - public Data getData() { - return new Data(value); - } +/** + * @param domain i.e. map.example.com + */ +public record DomainHttpRule(String domain) implements HttpRule { @Override public boolean matches(FullHttpRequest request) { @@ -29,20 +19,27 @@ public boolean matches(FullHttpRequest request) { final int portIndex = host.indexOf(':'); if (portIndex != -1) host = host.substring(0, portIndex); - return host.equals(value); + return host.equals(domain); + } + + @ApiStatus.Internal + public static void register() { + HttpRuleTypeRegistry.register("domain", Data.class, DomainHttpRule.class, rule -> new Data(rule.domain), data -> new DomainHttpRule(data.domain)); } - public static final class Data { - @SuppressWarnings("FieldMayBeFinal") // Think it needs to be non-final cause jankson - public String value; + @ApiStatus.Internal + private static final class Data { + @SuppressWarnings("FieldMayBeFinal") // Pretty sure this needs to be non-final cause jankson wants to modify + private String domain; - @SuppressWarnings("unused") // Public no-args used by jankson i think + @SuppressWarnings("unused") + // Pretty sure this public no-args needs to exist cause jankson wants to create instances public Data() { } - public Data(String value) { - this.value = value; + public Data(final String domain) { + this.domain = domain; } } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java index ba483a0..74665cc 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java @@ -1,46 +1,43 @@ package top.offsetmonkey538.meshlib.api.rule.rules; import io.netty.handler.codec.http.FullHttpRequest; +import org.jetbrains.annotations.ApiStatus; import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; -public class PathHttpRule implements HttpRule { - private final String value; // i.e. /map -> example.com/map - - public PathHttpRule(String value) { - this.value = value; - } - - @Override - public String getType() { - return "path"; - } - - @Override - public Data getData() { - return new Data(value); - } +/** + * @param path i.e. /map -> example.com/map + */ +public record PathHttpRule(String path) implements HttpRule { @Override public boolean matches(FullHttpRequest request) { - return request.uri().startsWith(value); + return request.uri().startsWith(path); } @Override public String normalizeUri(String uri) { - return uri.replaceFirst(value, ""); + return uri.replaceFirst(path, ""); + } + + @ApiStatus.Internal + public static void register() { + HttpRuleTypeRegistry.register("path", Data.class, PathHttpRule.class, rule -> new Data(rule.path), data -> new PathHttpRule(data.path)); } - public static final class Data { - @SuppressWarnings("FieldMayBeFinal") // Think it needs to be non-final cause jankson - public String value; + @ApiStatus.Internal + private static final class Data { + @SuppressWarnings("FieldMayBeFinal") // Pretty sure this needs to be non-final cause jankson wants to modify + private String path; - @SuppressWarnings("unused") // Public no-args used by jankson i think + @SuppressWarnings("unused") + // Pretty sure this public no-args needs to exist cause jankson wants to create instances public Data() { } - public Data(String value) { - this.value = value; + public Data(final String path) { + this.path = path; } } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index 945138d..8e25c02 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -1,6 +1,5 @@ package top.offsetmonkey538.meshlib.example; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; @@ -37,7 +36,7 @@ public static void onInitialize() { record SimpleHttpHandlerData(String content) { } - HttpHandlerTypeRegistry.register("simple-http", SimpleHttpHandlerData.class, SimpleHttpHandler.class, handler -> new SimpleHttpHandlerData(handler.content), data -> new SimpleHttpHandler(data.content())); + HttpHandlerTypeRegistry.register("simple-http", SimpleHttpHandlerData.class, SimpleHttpHandler.class, handler -> new SimpleHttpHandlerData(handler.content()), data -> new SimpleHttpHandler(data.content())); //HttpRouterRegistry.INSTANCE.register("simple-server", new HttpRouter( // new DomainHttpRule("localhost"), diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java index b6a51f5..d3a9b7c 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java @@ -20,15 +20,10 @@ /** * An example {@link HttpHandler} implementation to learn from */ -public class SimpleHttpHandler implements HttpHandler { - public final String content; - - public SimpleHttpHandler(String content) { - this.content = content; - } +public record SimpleHttpHandler(String content) implements HttpHandler { @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { // Write the provided content to a buffer, encoded in UTF-8 final ByteBuf content = Unpooled.copiedBuffer(this.content, StandardCharsets.UTF_8); // Create a response with said buffer diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java index 81fb560..963c586 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java @@ -17,27 +17,27 @@ public class HttpHandlerTypeRegistryImpl implements HttpHandlerTypeRegistry { private final Map, HttpHandlerDefinition> handlersByType = new HashMap<>(); @Override - public void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler) { + public void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler) { if (type.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); if (handlersById.containsKey(type)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!"); if (handlersByType.containsKey(handlerType)) throw new IllegalArgumentException("Handler type for type '" + handlerType + "' already registered!"); - final HttpHandlerDefinition handler = new HttpHandlerDefinition<>(type, dataType, handlerType, handlerToData, dataToHandler); + final HttpHandlerDefinition handler = new HttpHandlerDefinition<>(type, dataType, handlerType, handlerToData, dataToHandler); handlersById.put(type, handler); - handlersByType.put(handler.handlerType(), handler); + handlersByType.put(handlerType, handler); } - public HttpHandlerDefinition get(final String type) throws IllegalStateException { + public HttpHandlerDefinition get(final String type) throws IllegalArgumentException { if (handlersById.containsKey(type)) return handlersById.get(type); - throw new IllegalStateException("Http rule with type '" + type + "' not registered!"); + throw new IllegalArgumentException("Http handler with type '" + type + "' not registered!"); } - public HttpHandlerDefinition get(final Class type) throws IllegalStateException { + public HttpHandlerDefinition get(final Class type) throws IllegalArgumentException { if (handlersByType.containsKey(type)) return handlersByType.get(type); - throw new IllegalStateException("Http rule with type '" + type + "' not registered!"); + throw new IllegalArgumentException("Http handler with type '" + type + "' not registered!"); } - public record HttpHandlerDefinition(String type, Class dataType, Class handlerType, Function handlerToData, Function handlerInitializer) { + public record HttpHandlerDefinition(String type, Class dataType, Class handlerType, Function handlerToData, Function dataToHandler) { } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java index 4851c0f..ab4765b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java @@ -1,29 +1,43 @@ package top.offsetmonkey538.meshlib.impl.router.rule; import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.rule.HttpRule; import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; /** * Implementation of {@link HttpRuleTypeRegistry} */ public class HttpRuleTypeRegistryImpl implements HttpRuleTypeRegistry { - private final Map> handlers = new HashMap<>(); + private final Map> rulesById = new HashMap<>(); + private final Map, HttpRuleDefinition> rulesByType = new HashMap<>(); @Override - public void registerImpl(String type, HttpRule.@NotNull HttpRuleDefinition rule) { + public void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule) { if (type.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); - if (handlers.containsKey(type)) throw new IllegalArgumentException("Handler with id '" + type + "' already registered!"); + if (rulesById.containsKey(type)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!") + if (rulesByType.containsKey(ruleType)) throw new IllegalArgumentException("Handler type for type '" + ruleType + "' already registered!"); - handlers.put(type, rule); + final HttpRuleDefinition rule = new HttpRuleDefinition<>(type, dataType, ruleType, ruleToData, dataToRule); + rulesById.put(type, rule); + rulesByType.put(ruleType, rule); } - @Override - public @NotNull HttpRule.HttpRuleDefinition getImpl(String type) throws IllegalStateException { - if (handlers.containsKey(type)) return handlers.get(type); - throw new IllegalStateException("Http rule with type '" + type + "' not registered!"); + public HttpRuleDefinition get(final String type) throws IllegalArgumentException { + if (rulesById.containsKey(type)) return rulesById.get(type); + throw new IllegalArgumentException("Http rule with type '" + type + "' not registered!"); + } + + public HttpRuleDefinition get(final Class type) throws IllegalArgumentException { + if (rulesByType.containsKey(type)) return rulesByType.get(type); + throw new IllegalArgumentException("Http rule with type '" + type + "' not registered!"); + } + + public record HttpRuleDefinition(String type, Class dataType, Class ruleType, Function ruleToData, Function dataToRule) { + } } From 20c46626497efa54de0c90525fd21082f2fb1c5b Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:44:30 +0200 Subject: [PATCH 33/78] missing semicolon :concern: --- .../meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java index ab4765b..dabbe49 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java @@ -19,7 +19,7 @@ public class HttpRuleTypeRegistryImpl implements HttpRuleTypeRegistry { @Override public void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule) { if (type.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); - if (rulesById.containsKey(type)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!") + if (rulesById.containsKey(type)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!"); if (rulesByType.containsKey(ruleType)) throw new IllegalArgumentException("Handler type for type '" + ruleType + "' already registered!"); final HttpRuleDefinition rule = new HttpRuleDefinition<>(type, dataType, ruleType, ruleToData, dataToRule); From 3a91a756ddeed68a11002747301723773fdf9c48 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:46:00 +0200 Subject: [PATCH 34/78] hm, i guess intellij may not think they may be final after all --- .../meshlib/api/handler/handlers/StaticDirectoryHandler.java | 2 -- .../meshlib/api/handler/handlers/StaticFileHandler.java | 1 - .../offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java | 1 - .../offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java | 1 - 4 files changed, 5 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java index 4ddb1f4..e0de373 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java @@ -176,9 +176,7 @@ public static void register() { @ApiStatus.Internal private static final class Data { - @SuppressWarnings("FieldMayBeFinal") // Pretty sure this needs to be non-final cause jankson wants to modify private Path baseDir; - @SuppressWarnings("FieldMayBeFinal") // Pretty sure this needs to be non-final cause jankson wants to modify private boolean allowDirectoryList; @SuppressWarnings("unused") diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java index 8856a18..dc187f8 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java @@ -32,7 +32,6 @@ public static void register() { @ApiStatus.Internal private static final class Data { - @SuppressWarnings("FieldMayBeFinal") // Pretty sure this needs to be non-final cause jankson wants to modify private Path fileToServe; @SuppressWarnings("unused") diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java index f4b3a45..252e024 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java @@ -29,7 +29,6 @@ public static void register() { @ApiStatus.Internal private static final class Data { - @SuppressWarnings("FieldMayBeFinal") // Pretty sure this needs to be non-final cause jankson wants to modify private String domain; @SuppressWarnings("unused") diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java index 74665cc..e7273a3 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java @@ -27,7 +27,6 @@ public static void register() { @ApiStatus.Internal private static final class Data { - @SuppressWarnings("FieldMayBeFinal") // Pretty sure this needs to be non-final cause jankson wants to modify private String path; @SuppressWarnings("unused") From 38e2ac08ec2b13a88b8c617d0bbfcdceeae38194 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:48:08 +0200 Subject: [PATCH 35/78] lots o' unused imports --- .../offsetmonkey538/meshlib/api/handler/HttpHandler.java | 2 -- .../meshlib/api/handler/handlers/StaticFileHandler.java | 3 --- .../top/offsetmonkey538/meshlib/api/rule/HttpRule.java | 2 -- .../meshlib/impl/HttpRouterRegistryImpl.java | 2 -- .../offsetmonkey538/meshlib/impl/ProtocolHandler.java | 1 - .../impl/router/rule/HttpRuleTypeRegistryImpl.java | 1 - .../meshlib/platform/neoforge/NeoforgeInitializer.java | 6 ------ .../meshlib/platform/neoforge/NeoforgePlatformMain.java | 9 --------- 8 files changed, 26 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java index 4cf1c63..d70be1d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java @@ -7,8 +7,6 @@ import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; import org.jetbrains.annotations.NotNull; -import java.util.function.Function; - /** * An http handler for you to implement :D *
diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java index dc187f8..4ea3157 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java @@ -7,13 +7,10 @@ import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; import java.nio.file.Path; -import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; - public record StaticFileHandler(Path fileToServe) implements HttpHandler { public StaticFileHandler(final Path fileToServe) { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java index 5feb2ba..82180e3 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java @@ -2,8 +2,6 @@ import io.netty.handler.codec.http.FullHttpRequest; -import java.util.function.Function; - public interface HttpRule { boolean matches(final FullHttpRequest request); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java index 4e3bf31..c2f2685 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java @@ -5,9 +5,7 @@ import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; -import java.util.stream.Stream; /** * Implementation of {@link HttpRouterRegistry} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java index 19b26a3..333987b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java @@ -7,7 +7,6 @@ import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.stream.ChunkedWriteHandler; -import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java index dabbe49..054aeb9 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java @@ -1,7 +1,6 @@ package top.offsetmonkey538.meshlib.impl.router.rule; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.rule.HttpRule; import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java index d6bb78b..1e4e5f7 100644 --- a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java +++ b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java @@ -3,13 +3,7 @@ import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; -import net.neoforged.fml.loading.FMLPaths; import top.offsetmonkey538.meshlib.MESHLib; -import top.offsetmonkey538.meshlib.platform.PlatformMain; - -import java.nio.file.Path; - -import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; @Mod("mesh_lib") public final class NeoforgeInitializer { diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java index 55c01bd..864a2bc 100644 --- a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java +++ b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java @@ -1,16 +1,7 @@ package top.offsetmonkey538.meshlib.platform.neoforge; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import net.minecraft.commands.CommandSourceStack; -import net.neoforged.bus.api.IEventBus; -import net.neoforged.fml.ModContainer; -import net.neoforged.fml.common.Mod; import net.neoforged.fml.loading.FMLPaths; -import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.event.RegisterCommandsEvent; -import top.offsetmonkey538.meshlib.MESHLib; import top.offsetmonkey538.meshlib.platform.PlatformMain; -import top.offsetmonkey538.monkeylib538.neoforge.impl.command.CommandRegistrationImpl; import java.nio.file.Path; From 6e5157dc2977e9e631774da2e2c50905f9583c35 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:02:06 +0200 Subject: [PATCH 36/78] create events for registration --- .../top/offsetmonkey538/meshlib/MESHLib.java | 24 +++++-- .../api/handler/HttpHandlerTypeRegistry.java | 57 +++++++++++++++- .../handlers/StaticDirectoryHandler.java | 4 +- .../handler/handlers/StaticFileHandler.java | 4 +- .../meshlib/api/router/HttpRouter.java | 2 +- .../api/router/HttpRouterRegistry.java | 68 +++++++++++++------ .../api/rule/HttpRuleTypeRegistry.java | 56 ++++++++++++++- .../api/rule/rules/DomainHttpRule.java | 4 +- .../meshlib/api/rule/rules/PathHttpRule.java | 4 +- .../meshlib/config/TestConfig.java | 2 +- .../meshlib/example/ExampleMain.java | 38 ++++++----- .../meshlib/impl/HttpRouterRegistryImpl.java | 14 ++-- .../meshlib/impl/MainHttpHandler.java | 3 +- .../router/HttpHandlerTypeRegistryImpl.java | 8 ++- .../router/rule/HttpRuleTypeRegistryImpl.java | 8 ++- gradle.properties | 2 +- 16 files changed, 231 insertions(+), 67 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index 450bac6..6974ed2 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -5,6 +5,7 @@ import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; +import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import top.offsetmonkey538.meshlib.api.rule.HttpRule; import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; @@ -14,6 +15,7 @@ import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.monkeylib538.api.lifecycle.ServerLifecycleApi; import top.offsetmonkey538.monkeylib538.api.log.MonkeyLibLogger; import top.offsetmonkey538.offsetconfig538.api.event.OffsetConfig538Events; @@ -90,11 +92,25 @@ public static void initialize() { }); }); - DomainHttpRule.register(); - PathHttpRule.register(); + HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.listen(DomainHttpRule::register); + HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.listen(PathHttpRule::register); - StaticFileHandler.register(); - StaticDirectoryHandler.register(); + HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.listen(StaticFileHandler::register); + HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.listen(StaticDirectoryHandler::register); + + ServerLifecycleApi.runOnServerStarting(MESHLib::reload); + } + + // TODO: move somewhere under api so others can invoke a reload? For example git pack manager after reloading its config cause that's where the rule for it will be stored. + public static void reload() { + HttpHandlerTypeRegistry.clear(); + HttpRuleTypeRegistry.clear(); + HttpRouterRegistry.clear(); + + + HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.getInvoker().invoke(); + HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.getInvoker().invoke(); + HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT_EVENT.getInvoker().invoke(); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java index 8ffe805..64d717a 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java @@ -3,19 +3,70 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; +import top.offsetmonkey538.offsetconfig538.api.event.Event; import java.util.function.Function; +/** + * Registry for {@link HttpHandler}s, use the {@link #HTTP_HANDLER_REGISTRATION_EVENT} event for registering your handlers. + */ public interface HttpHandlerTypeRegistry { + /** * Instance */ @ApiStatus.Internal + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated // Marking it as internal isn't enough cause I also need to prevent usage from other places in my code HttpHandlerTypeRegistry INSTANCE = new HttpHandlerTypeRegistryImpl(); - static void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler) { - INSTANCE.registerImpl(type, dataType, handlerType, handlerToData, dataToHandler); + /** + * Internal method for clearing the registry, no touch! + */ + @ApiStatus.Internal + static void clear() { + INSTANCE.clearImpl(); } - void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler); + void clearImpl(); + void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler); + + + /** + * Event for registering http handlers. + *

+ * The registry is cleared upon reloading, so to make your handlers persist, you need to register them in this event. + *

+ *

+ * Initially called while the server is starting, so make sure to register your handler before that! + *

+ *

+ * Called before the {@link top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry#HTTP_ROUTER_REGISTRATION_EVENT_EVENT HTTP_ROUTER_REGISTRATION_EVENT_EVENT} event. + *

+ */ + Event HTTP_HANDLER_REGISTRATION_EVENT = Event.createEvent(HttpHandlerRegistrationEvent.class, handlers -> registry -> { + for (HttpHandlerRegistrationEvent handler : handlers) handler.register(registry); + }); + + /** + * Handler for {@link #HTTP_HANDLER_REGISTRATION_EVENT}. + */ + @FunctionalInterface + interface HttpHandlerRegistrationEvent { + + /** + * Internal method for invoking the event without providing the registry, no touch! + */ + @ApiStatus.Internal + default void invoke() { + register(INSTANCE); + } + + /** + * Registers {@link HttpHandler}s to the provided registry + * + * @param registry the registry to register to + */ + void register(final @NotNull HttpHandlerTypeRegistry registry); + } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java index e0de373..5174657 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java @@ -170,8 +170,8 @@ private static void formatFileSize(StringBuilder builder, Path path) { } @ApiStatus.Internal - public static void register() { - HttpHandlerTypeRegistry.register("static-directory", Data.class, StaticDirectoryHandler.class, handler -> new Data(handler.baseDir, handler.allowDirectoryList), data -> new StaticDirectoryHandler(data.baseDir, data.allowDirectoryList)); + public static void register(final HttpHandlerTypeRegistry registry) { + registry.register("static-directory", Data.class, StaticDirectoryHandler.class, handler -> new Data(handler.baseDir, handler.allowDirectoryList), data -> new StaticDirectoryHandler(data.baseDir, data.allowDirectoryList)); } @ApiStatus.Internal diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java index 4ea3157..6d3ad01 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java @@ -23,8 +23,8 @@ public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpR } @ApiStatus.Internal - public static void register() { - HttpHandlerTypeRegistry.register("static-file", Data.class, StaticFileHandler.class, handler -> new Data(handler.fileToServe), data -> new StaticFileHandler(data.fileToServe)); + public static void register(final HttpHandlerTypeRegistry registry) { + registry.register("static-file", Data.class, StaticFileHandler.class, handler -> new Data(handler.fileToServe), data -> new StaticFileHandler(data.fileToServe)); } @ApiStatus.Internal diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java index 72510f8..b2a9151 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java @@ -4,5 +4,5 @@ import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.rule.HttpRule; -public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { +public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java index da24fc6..f4d0d15 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java @@ -2,39 +2,65 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.impl.HttpRouterRegistryImpl; - -import java.util.Map; +import top.offsetmonkey538.offsetconfig538.api.event.Event; /** - * Registry for routers - * todo;K commebnt - *

- * Each handler will only be able to listen to requests on *its* sub-path. - *
- * If your handler's id is {@code testmod}, then your handler will only receive requests on {@code server.com/testmod/} - * @see HttpHandler + * Registry for {@link HttpRouter}s, use the {@link #HTTP_ROUTER_REGISTRATION_EVENT_EVENT} event for registering your routers. */ public interface HttpRouterRegistry { /** * Instance */ @ApiStatus.Internal + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated // Marking it as internal isn't enough cause I also need to prevent usage from other places in my code HttpRouterRegistry INSTANCE = new HttpRouterRegistryImpl(); /** - * todo;: actual comment - * Method for registering a {@link HttpHandler} - *
- * THE ID SHOULD NOT BE EMPTY - * - * @param id your handler or mod's id - * @throws IllegalArgumentException when the provided id is empty or a handler with this id is already registered - * @see HttpHandler + * Internal method for clearing the registry, no touch! */ - void register(@NotNull String id, @NotNull HttpRouter router) throws IllegalArgumentException; + @ApiStatus.Internal + static void clear() { + INSTANCE.clearImpl(); + } + + void clearImpl(); + void register(@NotNull final String id, @NotNull final HttpRouter router); + + + /** + * Event for registering http routers. + *

+ * The registry is cleared upon reloading, so to make your routers persist, you need to register them in this event. + *

+ *

+ * Initially called while the server is starting, so make sure to register your handler before that! + *

+ */ + Event HTTP_ROUTER_REGISTRATION_EVENT_EVENT = Event.createEvent(HttpRouterRegistrationEvent.class, handlers -> registry -> { + for (HttpRouterRegistrationEvent handler : handlers) handler.register(registry); + }); + + /** + * Handler for {@link #HTTP_ROUTER_REGISTRATION_EVENT_EVENT}. + */ + @FunctionalInterface + interface HttpRouterRegistrationEvent { + + /** + * Internal method for invoking the event without providing the registry, no touch! + */ + @ApiStatus.Internal + default void invoke() { + register(INSTANCE); + } - @NotNull - Iterable> iterable(); + /** + * Registers {@link HttpRouter}s to the provided registry + * + * @param registry the registry to register to + */ + void register(final @NotNull HttpRouterRegistry registry); + } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java index 0f99d3d..941688a 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java @@ -3,19 +3,69 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; +import top.offsetmonkey538.offsetconfig538.api.event.Event; import java.util.function.Function; +/** + * Registry for {@link HttpRule}s, use the {@link #HTTP_RULE_REGISTRATION_EVENT} event for registering your rules. + */ public interface HttpRuleTypeRegistry { /** * Instance */ @ApiStatus.Internal + @SuppressWarnings("DeprecatedIsStillUsed") + @Deprecated // Marking it as internal isn't enough cause I also need to prevent usage from other places in my code HttpRuleTypeRegistry INSTANCE = new HttpRuleTypeRegistryImpl(); - static void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule) { - INSTANCE.registerImpl(type, dataType, ruleType, ruleToData, dataToRule); + /** + * Internal method for clearing the registry, no touch! + */ + @ApiStatus.Internal + static void clear() { + INSTANCE.clearImpl(); } - void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule); + void clearImpl(); + void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule); + + + /** + * Event for registering http routing rules. + *

+ * The registry is cleared upon reloading, so to make your rules persist, you need to register them in this event. + *

+ *

+ * Initially called while the server is starting, so make sure to register your handler before that! + *

+ *

+ * Called before the {@link top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry#HTTP_ROUTER_REGISTRATION_EVENT_EVENT HTTP_ROUTER_REGISTRATION_EVENT_EVENT} event. + *

+ */ + Event HTTP_RULE_REGISTRATION_EVENT = Event.createEvent(HttpRuleRegistrationEvent.class, handlers -> registry -> { + for (HttpRuleRegistrationEvent handler : handlers) handler.register(registry); + }); + + /** + * Handler for {@link #HTTP_RULE_REGISTRATION_EVENT}. + */ + @FunctionalInterface + interface HttpRuleRegistrationEvent { + + /** + * Internal method for invoking the event without providing the registry, no touch! + */ + @ApiStatus.Internal + default void invoke() { + register(INSTANCE); + } + + /** + * Registers {@link HttpRule}s to the provided registry + * + * @param registry the registry to register to + */ + void register(final @NotNull HttpRuleTypeRegistry registry); + } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java index 252e024..dadd63c 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java @@ -23,8 +23,8 @@ public boolean matches(FullHttpRequest request) { } @ApiStatus.Internal - public static void register() { - HttpRuleTypeRegistry.register("domain", Data.class, DomainHttpRule.class, rule -> new Data(rule.domain), data -> new DomainHttpRule(data.domain)); + public static void register(final HttpRuleTypeRegistry registry) { + registry.register("domain", Data.class, DomainHttpRule.class, rule -> new Data(rule.domain), data -> new DomainHttpRule(data.domain)); } @ApiStatus.Internal diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java index e7273a3..e55183f 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java @@ -21,8 +21,8 @@ public String normalizeUri(String uri) { } @ApiStatus.Internal - public static void register() { - HttpRuleTypeRegistry.register("path", Data.class, PathHttpRule.class, rule -> new Data(rule.path), data -> new PathHttpRule(data.path)); + public static void register(final HttpRuleTypeRegistry registry) { + registry.register("path", Data.class, PathHttpRule.class, rule -> new Data(rule.path), data -> new PathHttpRule(data.path)); } @ApiStatus.Internal diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java index 2ff203b..228271e 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java @@ -12,7 +12,7 @@ public class TestConfig implements Config { - public HttpRule rule = new DomainHttpRule("map.example.com"); + public HttpRule rule = new DomainHttpRule("map.example.com"); public HttpHandler target = new SimpleHttpHandler("Goodbye, World!"); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index 8e25c02..559acfd 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -36,30 +36,34 @@ public static void onInitialize() { record SimpleHttpHandlerData(String content) { } - HttpHandlerTypeRegistry.register("simple-http", SimpleHttpHandlerData.class, SimpleHttpHandler.class, handler -> new SimpleHttpHandlerData(handler.content()), data -> new SimpleHttpHandler(data.content())); + HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.listen(registry -> { + registry.register("simple-http", SimpleHttpHandlerData.class, SimpleHttpHandler.class, handler -> new SimpleHttpHandlerData(handler.content()), data -> new SimpleHttpHandler(data.content())); + }); //HttpRouterRegistry.INSTANCE.register("simple-server", new HttpRouter( // new DomainHttpRule("localhost"), // new SimpleHttpHandler("Yellow!") //)); - HttpRouterRegistry.INSTANCE.register("simple-server2", new HttpRouter( - new PathHttpRule("/hi"), - new SimpleHttpHandler("Goodbye!") - )); + HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT_EVENT.listen(registry -> { + registry.register("simple-server2", new HttpRouter( + new PathHttpRule("/hi"), + new SimpleHttpHandler("Goodbye!") + )); - HttpRouterRegistry.INSTANCE.register("static-file-test", new HttpRouter( - new PathHttpRule("/static/file"), - new StaticFileHandler(Path.of("usercache.json")) - )); + registry.register("static-file-test", new HttpRouter( + new PathHttpRule("/static/file"), + new StaticFileHandler(Path.of("usercache.json")) + )); - HttpRouterRegistry.INSTANCE.register("static-directory-test", new HttpRouter( - new PathHttpRule("/static/directory"), - new StaticDirectoryHandler(Path.of("/home/dave"), true) - )); + registry.register("static-directory-test", new HttpRouter( + new PathHttpRule("/static/directory"), + new StaticDirectoryHandler(Path.of("/home/dave"), true) + )); - HttpRouterRegistry.INSTANCE.register("static-directory-test2", new HttpRouter( - new DomainHttpRule("localhost"), - new StaticDirectoryHandler(Path.of("/home/dave/Dev/Java/Minecraft/Mods/Loot-Table-Modifier/docs/dist/"), false) - )); + registry.register("static-directory-test2", new HttpRouter( + new DomainHttpRule("localhost"), + new StaticDirectoryHandler(Path.of("/home/dave/Dev/Java/Minecraft/Mods/Loot-Table-Modifier/docs/dist/"), false) + )); + }); } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java index c2f2685..911dc20 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java @@ -11,18 +11,22 @@ * Implementation of {@link HttpRouterRegistry} */ public class HttpRouterRegistryImpl implements HttpRouterRegistry { - private final Map handlers = new HashMap<>(); + private final Map routers = new HashMap<>(); @Override - public void register(@NotNull String id, @NotNull HttpRouter router) throws IllegalArgumentException { + public void register(@NotNull String id, @NotNull HttpRouter router) { if (id.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); - if (handlers.containsKey(id)) throw new IllegalArgumentException("Handler with id '" + id + "' already registered!"); + if (routers.containsKey(id)) throw new IllegalArgumentException("Handler with id '" + id + "' already registered!"); - handlers.put(id, router); + routers.put(id, router); } @Override + public void clearImpl() { + routers.clear(); + } + public @NotNull Iterable> iterable() { - return this.handlers.entrySet(); + return this.routers.entrySet(); } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java index c61f453..5ae4c5b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java @@ -32,7 +32,8 @@ protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) HttpRouter router = null; List matchedRouterIDs = new ArrayList<>(0); - for (Map.Entry possibleRouter : HttpRouterRegistry.INSTANCE.iterable()) { + //noinspection deprecation + for (Map.Entry possibleRouter : ((HttpRouterRegistryImpl) HttpRouterRegistry.INSTANCE).iterable()) { if (!possibleRouter.getValue().rule().matches(request)) continue; matchedRouterIDs.add(possibleRouter.getKey()); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java index 963c586..5810182 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java @@ -17,7 +17,13 @@ public class HttpHandlerTypeRegistryImpl implements HttpHandlerTypeRegistry { private final Map, HttpHandlerDefinition> handlersByType = new HashMap<>(); @Override - public void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler) { + public void clearImpl() { + handlersById.clear(); + handlersByType.clear(); + } + + @Override + public void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler) { if (type.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); if (handlersById.containsKey(type)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!"); if (handlersByType.containsKey(handlerType)) throw new IllegalArgumentException("Handler type for type '" + handlerType + "' already registered!"); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java index 054aeb9..a7ec95e 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java @@ -16,7 +16,13 @@ public class HttpRuleTypeRegistryImpl implements HttpRuleTypeRegistry { private final Map, HttpRuleDefinition> rulesByType = new HashMap<>(); @Override - public void registerImpl(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule) { + public void clearImpl() { + rulesById.clear(); + rulesByType.clear(); + } + + @Override + public void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule) { if (type.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); if (rulesById.containsKey(type)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!"); if (rulesByType.containsKey(ruleType)) throw new IllegalArgumentException("Handler type for type '" + ruleType + "' already registered!"); diff --git a/gradle.properties b/gradle.properties index af91f13..1d33f33 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-alpha.0.1760173173400+2230010 +monkeylib538_version = 3.0.0-alpha.0.1761237113556+d3f347d ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.82.Final ## OffsetConfig538, check at https://github.com/OffsetMods538/OffsetConfig538 From 4973e680bd242947c1e9fe4397d3f7a9330fe6fa Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:25:37 +0200 Subject: [PATCH 37/78] fix no border around "provided" text cell in directory list --- .../meshlib/api/handler/handlers/StaticDirectoryHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java index 5174657..82c9e6d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java @@ -99,7 +99,7 @@ private static void sendDirectoryListing(ChannelHandlerContext ctx, FullHttpRequ } private static StringBuilder renderDirectoryListing(String uriPath, Path directory) throws IOException { - final StringBuilder result = new StringBuilder("Index of ").append(uriPath).append("
Index of "); result.append(uriPath); result.append("
NameLast ModifiedSize
Index of "); + final StringBuilder result = new StringBuilder("Index of ").append(uriPath).append(""); From e952b87b3e617de2895a0b8a5dbc3652900f7e24 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:27:00 +0200 Subject: [PATCH 38/78] Suppress some warnings --- .../main/java/top/offsetmonkey538/meshlib/MESHLib.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index 6974ed2..484efe5 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -47,7 +47,7 @@ public static void initialize() { OffsetConfig538Events.JANKSON_CONFIGURATION_EVENT.listen(builder -> { builder.registerSerializer(HttpRule.class, (httpRule, marshaller) -> { - @SuppressWarnings("unchecked") // rule definition of ?,? extends HttpRule should match ?,HttpHandler, no? + @SuppressWarnings({"unchecked", "deprecation"}) // rule definition of ?,? extends HttpRule should match ?,HttpHandler, no? final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpRule.getClass()); final JsonObject result = (JsonObject) marshaller.serialize(ruleDefinition.ruleToData().apply(httpRule)); @@ -58,7 +58,7 @@ public static void initialize() { builder.registerDeserializer(JsonObject.class, HttpRule.class, (jsonObject, marshaller) -> { final String type = jsonObject.get(String.class, "type"); - @SuppressWarnings("unchecked") // It's proooobably a subclass of Object... + @SuppressWarnings({"unchecked", "deprecation"}) // It's proooobably a subclass of Object... final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpRuleTypeRegistry.INSTANCE).get(type); final JsonObject dummyParent = new JsonObject(); @@ -70,7 +70,7 @@ public static void initialize() { builder.registerSerializer(HttpHandler.class, (httpHandler, marshaller) -> { - @SuppressWarnings("unchecked") // handler definition of ?,? extends HttpHandler should match ?,HttpHandler, no? + @SuppressWarnings({"unchecked", "deprecation"}) // handler definition of ?,? extends HttpHandler should match ?,HttpHandler, no? final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpHandler.getClass()); final JsonObject result = (JsonObject) marshaller.serialize(handlerDefinition.handlerToData().apply(httpHandler)); @@ -81,7 +81,7 @@ public static void initialize() { builder.registerDeserializer(JsonObject.class, HttpHandler.class, (jsonObject, marshaller) -> { final String type = jsonObject.get(String.class, "type"); - @SuppressWarnings("unchecked") // It's proooobably a subclass of Object... + @SuppressWarnings({"unchecked", "deprecation"}) // It's proooobably a subclass of Object... final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(type); final JsonObject dummyParent = new JsonObject(); From f5f088d1cc5ed4fb6d415b7e9ef4bd378c2cb216 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:41:51 +0200 Subject: [PATCH 39/78] atoamtically prefix path rule path with slash --- .../offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java index e55183f..2f4a559 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java @@ -10,6 +10,10 @@ */ public record PathHttpRule(String path) implements HttpRule { + public PathHttpRule(final String path) { + this.path = path.startsWith("/") ? path : "/" + path; + } + @Override public boolean matches(FullHttpRequest request) { return request.uri().startsWith(path); From e57198a642e2f2ee2276107c7a89e27a6f7d7652 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:47:56 +0200 Subject: [PATCH 40/78] EVENT_EVENT? yeah that seems correct --- .../src/main/java/top/offsetmonkey538/meshlib/MESHLib.java | 2 +- .../meshlib/api/handler/HttpHandlerTypeRegistry.java | 2 +- .../meshlib/api/router/HttpRouterRegistry.java | 6 +++--- .../meshlib/api/rule/HttpRuleTypeRegistry.java | 2 +- .../top/offsetmonkey538/meshlib/example/ExampleMain.java | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index 484efe5..e188b36 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -110,7 +110,7 @@ public static void reload() { HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.getInvoker().invoke(); HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.getInvoker().invoke(); - HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT_EVENT.getInvoker().invoke(); + HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.getInvoker().invoke(); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java index 64d717a..3b7b2ab 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java @@ -41,7 +41,7 @@ static void clear() { * Initially called while the server is starting, so make sure to register your handler before that! *

*

- * Called before the {@link top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry#HTTP_ROUTER_REGISTRATION_EVENT_EVENT HTTP_ROUTER_REGISTRATION_EVENT_EVENT} event. + * Called before the {@link top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry#HTTP_ROUTER_REGISTRATION_EVENT HTTP_ROUTER_REGISTRATION_EVENT} event. *

*/ Event HTTP_HANDLER_REGISTRATION_EVENT = Event.createEvent(HttpHandlerRegistrationEvent.class, handlers -> registry -> { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java index f4d0d15..d909efe 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java @@ -6,7 +6,7 @@ import top.offsetmonkey538.offsetconfig538.api.event.Event; /** - * Registry for {@link HttpRouter}s, use the {@link #HTTP_ROUTER_REGISTRATION_EVENT_EVENT} event for registering your routers. + * Registry for {@link HttpRouter}s, use the {@link #HTTP_ROUTER_REGISTRATION_EVENT} event for registering your routers. */ public interface HttpRouterRegistry { /** @@ -38,12 +38,12 @@ static void clear() { * Initially called while the server is starting, so make sure to register your handler before that! *

*/ - Event HTTP_ROUTER_REGISTRATION_EVENT_EVENT = Event.createEvent(HttpRouterRegistrationEvent.class, handlers -> registry -> { + Event HTTP_ROUTER_REGISTRATION_EVENT = Event.createEvent(HttpRouterRegistrationEvent.class, handlers -> registry -> { for (HttpRouterRegistrationEvent handler : handlers) handler.register(registry); }); /** - * Handler for {@link #HTTP_ROUTER_REGISTRATION_EVENT_EVENT}. + * Handler for {@link #HTTP_ROUTER_REGISTRATION_EVENT}. */ @FunctionalInterface interface HttpRouterRegistrationEvent { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java index 941688a..566b596 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java @@ -40,7 +40,7 @@ static void clear() { * Initially called while the server is starting, so make sure to register your handler before that! *

*

- * Called before the {@link top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry#HTTP_ROUTER_REGISTRATION_EVENT_EVENT HTTP_ROUTER_REGISTRATION_EVENT_EVENT} event. + * Called before the {@link top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry#HTTP_ROUTER_REGISTRATION_EVENT HTTP_ROUTER_REGISTRATION_EVENT} event. *

*/ Event HTTP_RULE_REGISTRATION_EVENT = Event.createEvent(HttpRuleRegistrationEvent.class, handlers -> registry -> { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index 559acfd..9810b34 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -44,7 +44,7 @@ record SimpleHttpHandlerData(String content) { // new DomainHttpRule("localhost"), // new SimpleHttpHandler("Yellow!") //)); - HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT_EVENT.listen(registry -> { + HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.listen(registry -> { registry.register("simple-server2", new HttpRouter( new PathHttpRule("/hi"), new SimpleHttpHandler("Goodbye!") From f6d13dccfa0cb524fcf8240a040f744ad167afa2 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 21:53:55 +0200 Subject: [PATCH 41/78] =?UTF-8?q?update=20github=20actions,=20please=20tel?= =?UTF-8?q?l=20me=20this=20fixes=20cache=20not=20working=20=F0=9F=99=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build_artifacts.yml | 4 ++-- .github/workflows/publish.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index 223f20e..06a939c 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -14,13 +14,13 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '21' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + uses: gradle/actions/setup-gradle@v5 with: cache-read-only: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index fa7a833..84b7775 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,13 +16,13 @@ jobs: - uses: actions/checkout@v4 - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: '21' distribution: 'temurin' - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3 + uses: gradle/actions/setup-gradle@v5 with: cache-read-only: false From 1901e70932d134195f8edbadcd1afdf502196481 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 23 Oct 2025 22:15:06 +0200 Subject: [PATCH 42/78] dummy config --- .../meshlib/config/MESHLibConfig.java | 27 ++++++++++++++++++ .../meshlib/config/TestConfig.java | 28 ------------------- 2 files changed, 27 insertions(+), 28 deletions(-) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java delete mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java new file mode 100644 index 0000000..559200d --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java @@ -0,0 +1,27 @@ +package top.offsetmonkey538.meshlib.config; + +import blue.endless.jankson.Comment; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.offsetconfig538.api.config.Config; + +import java.nio.file.Path; + +public class MESHLibConfig implements Config { + + @Comment("Used to figure out if mesh lib should inject into vanilla or not. Required as external port may differ from that's defined in server.properties") + public String minecraftServerExternalPort = null; + @Comment("Port the http server will bind to") + public String httpPort = null; + + + @Override + public @NotNull Path getFilePath() { + return PlatformMain.getConfigDir().resolve(getId() + ".json"); + } + + @Override + public @NotNull String getId() { + return "main"; + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java deleted file mode 100644 index 228271e..0000000 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/TestConfig.java +++ /dev/null @@ -1,28 +0,0 @@ -package top.offsetmonkey538.meshlib.config; - -import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; -import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; -import top.offsetmonkey538.meshlib.platform.PlatformMain; -import top.offsetmonkey538.offsetconfig538.api.config.Config; - -import java.nio.file.Path; - -public class TestConfig implements Config { - - public HttpRule rule = new DomainHttpRule("map.example.com"); - public HttpHandler target = new SimpleHttpHandler("Goodbye, World!"); - - - @Override - public @NotNull Path getFilePath() { - return PlatformMain.getConfigDir().resolve(getId() + ".json"); - } - - @Override - public @NotNull String getId() { - return "test"; - } -} From 89c3c64a4bd9564c6a7d6fc091fe7f3e48fd3e55 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Thu, 23 Oct 2025 22:55:33 +0200 Subject: [PATCH 43/78] Add files via upload --- .github/dependabot.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..85b8c92 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +# Set update schedule for GitHub Actions + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every week + interval: "daily" From 1fbcd488ffef86b1b6f3f6943b7c1e333428f91d Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:07:58 +0200 Subject: [PATCH 44/78] Update monkeylib to v3.0.0-alpha.0.1761395863791+bd100da and add config command that allows reloading --- build.gradle | 1 + .../top/offsetmonkey538/meshlib/MESHLib.java | 18 +++++++++++++++--- .../meshlib/config/MESHLibConfig.java | 12 +++++++----- .../meshlib/platform/PlatformMain.java | 7 ------- .../platform/fabric/FabricPlatformMain.java | 5 ----- gradle.properties | 6 +++--- .../neoforge/NeoforgePlatformMain.java | 10 ---------- .../platform/paper/PaperPlatformMain.java | 5 ----- 8 files changed, 26 insertions(+), 38 deletions(-) diff --git a/build.gradle b/build.gradle index 92c73cb..c6dfdb4 100644 --- a/build.gradle +++ b/build.gradle @@ -129,6 +129,7 @@ configure(subprojects.findAll { it.name == "common" }) { api "io.netty:netty-codec-http:${rootProject.netty_version}" shadow compileOnlyApi("top.offsetmonkey538.monkeylib538:monkeylib538-common:${rootProject.monkeylib538_version}") + shadow compileOnly("com.google.guava:guava:${rootProject.guava_version}") } tasks.build.dependsOn(shadowJar) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index e188b36..f00bb8c 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -2,6 +2,7 @@ import blue.endless.jankson.JsonObject; import blue.endless.jankson.JsonPrimitive; +import com.google.common.base.Stopwatch; import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; @@ -9,21 +10,22 @@ import top.offsetmonkey538.meshlib.api.rule.HttpRule; import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.config.MESHLibConfig; import top.offsetmonkey538.meshlib.example.ExampleMain; import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; import top.offsetmonkey538.monkeylib538.api.lifecycle.ServerLifecycleApi; import top.offsetmonkey538.monkeylib538.api.log.MonkeyLibLogger; +import top.offsetmonkey538.offsetconfig538.api.config.ConfigHolder; +import top.offsetmonkey538.offsetconfig538.api.config.ConfigManager; import top.offsetmonkey538.offsetconfig538.api.event.OffsetConfig538Events; import java.util.ServiceLoader; -// TODO: once I start working on allowing the netty server to run separately from minecraft, I should make the default exposed port value null to avoid 2 things: -// - The port from server.properties being wrong: The network being behind something (like a firewall or Docker for example) can allow the exposed port to be different from what minecraft binds to. -// - Tell people to use a separate port if at all possible. Proxies that some like running minecraft through (especially DDOS blockers like TcpShield) may block http traffic. public final class MESHLib { /** * Private constructor as this class shouldn't be instanced @@ -39,11 +41,14 @@ private MESHLib() {} */ public static final MonkeyLibLogger LOGGER = MonkeyLibLogger.create(MOD_ID); + public static final ConfigHolder CONFIG = ConfigManager.init(ConfigHolder.create(MESHLibConfig::new, LOGGER::error)); + public static void initialize() { PlatformMain.enableVanillaHandler(); ExampleMain.onInitialize(); + ConfigCommandApi.registerConfigCommand(CONFIG, MESHLib::reload, MOD_ID, "config"); OffsetConfig538Events.JANKSON_CONFIGURATION_EVENT.listen(builder -> { builder.registerSerializer(HttpRule.class, (httpRule, marshaller) -> { @@ -103,14 +108,21 @@ public static void initialize() { // TODO: move somewhere under api so others can invoke a reload? For example git pack manager after reloading its config cause that's where the rule for it will be stored. public static void reload() { + LOGGER.info("Reloading MESH Lib..."); + final Stopwatch stopwatch = Stopwatch.createStarted(); + HttpHandlerTypeRegistry.clear(); HttpRuleTypeRegistry.clear(); HttpRouterRegistry.clear(); + LOGGER.info("Registries cleared"); HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.getInvoker().invoke(); HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.getInvoker().invoke(); HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.getInvoker().invoke(); + LOGGER.info("Registries repopulated"); + + LOGGER.info("MESH Lib reloaded in %s!", stopwatch.stop()); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java index 559200d..57374cd 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java @@ -2,12 +2,14 @@ import blue.endless.jankson.Comment; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.monkeylib538.api.platform.PlatformUtil; import top.offsetmonkey538.offsetconfig538.api.config.Config; import java.nio.file.Path; -public class MESHLibConfig implements Config { +import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; + +public final class MESHLibConfig implements Config { @Comment("Used to figure out if mesh lib should inject into vanilla or not. Required as external port may differ from that's defined in server.properties") public String minecraftServerExternalPort = null; @@ -16,12 +18,12 @@ public class MESHLibConfig implements Config { @Override - public @NotNull Path getFilePath() { - return PlatformMain.getConfigDir().resolve(getId() + ".json"); + public @NotNull Path getConfigDirPath() { + return PlatformUtil.getConfigDir(); } @Override public @NotNull String getId() { - return "main"; + return MOD_ID + "/main"; } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java index 68432a4..9db25e9 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java @@ -2,8 +2,6 @@ import org.jetbrains.annotations.ApiStatus; -import java.nio.file.Path; - import static top.offsetmonkey538.meshlib.MESHLib.load; @ApiStatus.Internal @@ -21,11 +19,6 @@ static void disableVanillaHandler() { INSTANCE.disableVanillaHandlerImpl(); } - static Path getConfigDir() { - return INSTANCE.getConfigDirImpl(); - } - void enableVanillaHandlerImpl(); void disableVanillaHandlerImpl(); - Path getConfigDirImpl(); } diff --git a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java index b5397dd..f28c1a0 100644 --- a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java +++ b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java @@ -22,11 +22,6 @@ public void disableVanillaHandlerImpl() { isVanillaHandlerEnabled = false; } - @Override - public Path getConfigDirImpl() { - return FabricLoader.getInstance().getConfigDir().resolve(MOD_ID); - } - @Override public void onInitializeServer() { MESHLib.initialize(); diff --git a/gradle.properties b/gradle.properties index 1d33f33..609c8fe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,11 +3,11 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-alpha.0.1761237113556+d3f347d +monkeylib538_version = 3.0.0-alpha.0.1761395863791+bd100da ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.82.Final -## OffsetConfig538, check at https://github.com/OffsetMods538/OffsetConfig538 -offsetconfig538_version = 1.0.1-e59f400 +## Bundeled with Minecraft, common needs to use +guava_version = 33.4.0-jre # Mod Properties mod_version = 2.0.0-alpha.0 diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java index 864a2bc..4cd03f4 100644 --- a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java +++ b/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java @@ -1,12 +1,7 @@ package top.offsetmonkey538.meshlib.platform.neoforge; -import net.neoforged.fml.loading.FMLPaths; import top.offsetmonkey538.meshlib.platform.PlatformMain; -import java.nio.file.Path; - -import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; - public final class NeoforgePlatformMain implements PlatformMain { public static boolean isVanillaHandlerEnabled = false; @@ -20,11 +15,6 @@ public void disableVanillaHandlerImpl() { isVanillaHandlerEnabled = false; } - @Override - public Path getConfigDirImpl() { - return FMLPaths.CONFIGDIR.get().resolve(MOD_ID); - } - // dis on for loading the PlatformMain service public NeoforgePlatformMain() { diff --git a/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java b/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java index 39788e7..6b92abe 100644 --- a/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java +++ b/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java @@ -24,11 +24,6 @@ public void disableVanillaHandlerImpl() { ChannelInitializeListenerHolder.removeListener(HANDLER_KEY); } - @Override - public Path getConfigDirImpl() { - return getPlugin().getDataPath(); - } - public static void setPlugin(MeshLibPlugin plugin) { PaperPlatformMain.plugin = plugin; } From 33a37a61c9e340e804cbeb9ab7a3c910d915b22c Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 2 Nov 2025 13:21:38 +0200 Subject: [PATCH 45/78] separate jankson configuration into method available from api I guess the router record itself might not be the best place, but it shouldn't have any bad effects as it's a static method? idk, I guess separating it into two methods (one for and in HttpRule and the other for/in Httphandler) could also make sense, but yeah idk this'll be fineeeeee I promiseeee --- .../top/offsetmonkey538/meshlib/MESHLib.java | 48 +------------ .../meshlib/api/router/HttpRouter.java | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+), 46 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index f00bb8c..c22436e 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -6,6 +6,7 @@ import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; +import top.offsetmonkey538.meshlib.api.router.HttpRouter; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import top.offsetmonkey538.meshlib.api.rule.HttpRule; import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; @@ -50,52 +51,7 @@ public static void initialize() { ConfigCommandApi.registerConfigCommand(CONFIG, MESHLib::reload, MOD_ID, "config"); - OffsetConfig538Events.JANKSON_CONFIGURATION_EVENT.listen(builder -> { - builder.registerSerializer(HttpRule.class, (httpRule, marshaller) -> { - @SuppressWarnings({"unchecked", "deprecation"}) // rule definition of ?,? extends HttpRule should match ?,HttpHandler, no? - final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpRule.getClass()); - - final JsonObject result = (JsonObject) marshaller.serialize(ruleDefinition.ruleToData().apply(httpRule)); - result.put("type", JsonPrimitive.of(ruleDefinition.type())); - return result; - }); - - builder.registerDeserializer(JsonObject.class, HttpRule.class, (jsonObject, marshaller) -> { - final String type = jsonObject.get(String.class, "type"); - - @SuppressWarnings({"unchecked", "deprecation"}) // It's proooobably a subclass of Object... - final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpRuleTypeRegistry.INSTANCE).get(type); - - final JsonObject dummyParent = new JsonObject(); - dummyParent.put("dataHolder", jsonObject); - final Object dataHolder = dummyParent.get(ruleDefinition.dataType(), "dataHolder"); - - return ruleDefinition.dataToRule().apply(dataHolder); - }); - - - builder.registerSerializer(HttpHandler.class, (httpHandler, marshaller) -> { - @SuppressWarnings({"unchecked", "deprecation"}) // handler definition of ?,? extends HttpHandler should match ?,HttpHandler, no? - final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpHandler.getClass()); - - final JsonObject result = (JsonObject) marshaller.serialize(handlerDefinition.handlerToData().apply(httpHandler)); - result.put("type", JsonPrimitive.of(handlerDefinition.type())); - return result; - }); - - builder.registerDeserializer(JsonObject.class, HttpHandler.class, (jsonObject, marshaller) -> { - final String type = jsonObject.get(String.class, "type"); - - @SuppressWarnings({"unchecked", "deprecation"}) // It's proooobably a subclass of Object... - final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(type); - - final JsonObject dummyParent = new JsonObject(); - dummyParent.put("dataHolder", jsonObject); - final Object dataHolder = dummyParent.get(handlerDefinition.dataType(), "dataHolder"); - - return handlerDefinition.dataToHandler().apply(dataHolder); - }); - }); + OffsetConfig538Events.JANKSON_CONFIGURATION_EVENT.listen(HttpRouter::configureJankson); HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.listen(DomainHttpRule::register); HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.listen(PathHttpRule::register); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java index b2a9151..7617ef7 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java @@ -1,8 +1,77 @@ package top.offsetmonkey538.meshlib.api.router; +import blue.endless.jankson.Jankson; +import blue.endless.jankson.JsonObject; +import blue.endless.jankson.JsonPrimitive; import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; +import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { + + /** + * Configures the provided {@link Jankson.Builder} with serializers and deserializers for {@link HttpRule}s and {@link HttpHandler}s. + *

+ * When using my own config library, OffsetConfig538, its {@link top.offsetmonkey538.offsetconfig538.api.event.OffsetConfig538Events#JANKSON_CONFIGURATION_EVENT JANKSON_CONFIGURATION_EVENT} will have this configurator registered already and there's no need to call this method. + *
+ * For other config libraries... idk try to understand whatever the fuck I'm doing in this method I guess..... + *

+ * + * @param janksonBuilder the builder to configure + * @return the builder instance + */ + public static Jankson.Builder configureJankson(final @NotNull Jankson.Builder janksonBuilder) { + janksonBuilder.registerSerializer(HttpRule.class, (httpRule, marshaller) -> { + @SuppressWarnings({"unchecked", "deprecation"}) + // rule definition of ?,? extends HttpRule should match ?,HttpHandler, no? + final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpRule.getClass()); + + final JsonObject result = (JsonObject) marshaller.serialize(ruleDefinition.ruleToData().apply(httpRule)); + result.put("type", JsonPrimitive.of(ruleDefinition.type())); + return result; + }); + + janksonBuilder.registerDeserializer(JsonObject.class, HttpRule.class, (jsonObject, marshaller) -> { + final String type = jsonObject.get(String.class, "type"); + + @SuppressWarnings({"unchecked", "deprecation"}) // It's proooobably a subclass of Object... + final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpRuleTypeRegistry.INSTANCE).get(type); + + final JsonObject dummyParent = new JsonObject(); + dummyParent.put("dataHolder", jsonObject); + final Object dataHolder = dummyParent.get(ruleDefinition.dataType(), "dataHolder"); + + return ruleDefinition.dataToRule().apply(dataHolder); + }); + + + janksonBuilder.registerSerializer(HttpHandler.class, (httpHandler, marshaller) -> { + @SuppressWarnings({"unchecked", "deprecation"}) + // handler definition of ?,? extends HttpHandler should match ?,HttpHandler, no? + final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpHandler.getClass()); + + final JsonObject result = (JsonObject) marshaller.serialize(handlerDefinition.handlerToData().apply(httpHandler)); + result.put("type", JsonPrimitive.of(handlerDefinition.type())); + return result; + }); + + janksonBuilder.registerDeserializer(JsonObject.class, HttpHandler.class, (jsonObject, marshaller) -> { + final String type = jsonObject.get(String.class, "type"); + + @SuppressWarnings({"unchecked", "deprecation"}) // It's proooobably a subclass of Object... + final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(type); + + final JsonObject dummyParent = new JsonObject(); + dummyParent.put("dataHolder", jsonObject); + final Object dataHolder = dummyParent.get(handlerDefinition.dataType(), "dataHolder"); + + return handlerDefinition.dataToHandler().apply(dataHolder); + }); + + return janksonBuilder; + } } From ebb3215161c7184bf5709c62dc67a24014dbb9dc Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 2 Nov 2025 15:01:33 +0200 Subject: [PATCH 46/78] allow loading routers from disk? todo: move most of the example routers from ExampleMain to files --- build.gradle | 1 + .../top/offsetmonkey538/meshlib/MESHLib.java | 5 + .../handlers/StaticDirectoryHandler.java | 6 +- .../handler/handlers/StaticFileHandler.java | 6 +- .../meshlib/api/router/HttpRouter.java | 8 +- .../meshlib/config/RouterConfigHandler.java | 194 ++++++++++++++++++ gradle.properties | 2 + 7 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java diff --git a/build.gradle b/build.gradle index c6dfdb4..40bb562 100644 --- a/build.gradle +++ b/build.gradle @@ -130,6 +130,7 @@ configure(subprojects.findAll { it.name == "common" }) { shadow compileOnlyApi("top.offsetmonkey538.monkeylib538:monkeylib538-common:${rootProject.monkeylib538_version}") shadow compileOnly("com.google.guava:guava:${rootProject.guava_version}") + shadow compileOnly("com.mojang:brigadier:${rootProject.brigadier_version}") } tasks.build.dependsOn(shadowJar) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index c22436e..d5f300e 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -12,12 +12,14 @@ import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.config.MESHLibConfig; +import top.offsetmonkey538.meshlib.config.RouterConfigHandler; import top.offsetmonkey538.meshlib.example.ExampleMain; import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.monkeylib538.api.command.CommandRegistrationApi; import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; import top.offsetmonkey538.monkeylib538.api.lifecycle.ServerLifecycleApi; import top.offsetmonkey538.monkeylib538.api.log.MonkeyLibLogger; @@ -50,6 +52,7 @@ public static void initialize() { ExampleMain.onInitialize(); ConfigCommandApi.registerConfigCommand(CONFIG, MESHLib::reload, MOD_ID, "config"); + CommandRegistrationApi.registerCommand(RouterConfigHandler.createExampleConfigCommand()); OffsetConfig538Events.JANKSON_CONFIGURATION_EVENT.listen(HttpRouter::configureJankson); @@ -59,6 +62,8 @@ public static void initialize() { HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.listen(StaticFileHandler::register); HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.listen(StaticDirectoryHandler::register); + HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.listen(RouterConfigHandler::init); + ServerLifecycleApi.runOnServerStarting(MESHLib::reload); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java index 82c9e6d..9278b5f 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java @@ -171,12 +171,12 @@ private static void formatFileSize(StringBuilder builder, Path path) { @ApiStatus.Internal public static void register(final HttpHandlerTypeRegistry registry) { - registry.register("static-directory", Data.class, StaticDirectoryHandler.class, handler -> new Data(handler.baseDir, handler.allowDirectoryList), data -> new StaticDirectoryHandler(data.baseDir, data.allowDirectoryList)); + registry.register("static-directory", Data.class, StaticDirectoryHandler.class, handler -> new Data(handler.baseDir, handler.allowDirectoryList), data -> new StaticDirectoryHandler(Path.of(data.baseDir), data.allowDirectoryList)); } @ApiStatus.Internal private static final class Data { - private Path baseDir; + private String baseDir; private boolean allowDirectoryList; @SuppressWarnings("unused") @@ -186,7 +186,7 @@ public Data() { } public Data(final Path baseDir, final boolean allowDirectoryList) { - this.baseDir = baseDir; + this.baseDir = baseDir.toString(); this.allowDirectoryList = allowDirectoryList; } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java index 6d3ad01..7d7e360 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java @@ -24,12 +24,12 @@ public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpR @ApiStatus.Internal public static void register(final HttpHandlerTypeRegistry registry) { - registry.register("static-file", Data.class, StaticFileHandler.class, handler -> new Data(handler.fileToServe), data -> new StaticFileHandler(data.fileToServe)); + registry.register("static-file", Data.class, StaticFileHandler.class, handler -> new Data(handler.fileToServe), data -> new StaticFileHandler(Path.of(data.fileToServe))); } @ApiStatus.Internal private static final class Data { - private Path fileToServe; + private String fileToServe; @SuppressWarnings("unused") // Pretty sure this public no-args needs to exist cause jankson wants to create instances @@ -38,7 +38,7 @@ public Data() { } public Data(final Path fileToServe) { - this.fileToServe = fileToServe; + this.fileToServe = fileToServe.toString(); } } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java index 7617ef7..bd2eb24 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java @@ -1,6 +1,7 @@ package top.offsetmonkey538.meshlib.api.router; import blue.endless.jankson.Jankson; +import blue.endless.jankson.JsonElement; import blue.endless.jankson.JsonObject; import blue.endless.jankson.JsonPrimitive; import org.jetbrains.annotations.NotNull; @@ -11,6 +12,9 @@ import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; +import java.net.URI; +import java.nio.file.Path; + public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { /** @@ -28,7 +32,7 @@ public static Jankson.Builder configureJankson(final @NotNull Jankson.Builder ja janksonBuilder.registerSerializer(HttpRule.class, (httpRule, marshaller) -> { @SuppressWarnings({"unchecked", "deprecation"}) // rule definition of ?,? extends HttpRule should match ?,HttpHandler, no? - final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpRule.getClass()); + final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpRuleTypeRegistry.INSTANCE).get(httpRule.getClass()); final JsonObject result = (JsonObject) marshaller.serialize(ruleDefinition.ruleToData().apply(httpRule)); result.put("type", JsonPrimitive.of(ruleDefinition.type())); @@ -42,6 +46,7 @@ public static Jankson.Builder configureJankson(final @NotNull Jankson.Builder ja final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpRuleTypeRegistry.INSTANCE).get(type); final JsonObject dummyParent = new JsonObject(); + jsonObject.remove("type"); dummyParent.put("dataHolder", jsonObject); final Object dataHolder = dummyParent.get(ruleDefinition.dataType(), "dataHolder"); @@ -66,6 +71,7 @@ public static Jankson.Builder configureJankson(final @NotNull Jankson.Builder ja final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(type); final JsonObject dummyParent = new JsonObject(); + jsonObject.remove("type"); dummyParent.put("dataHolder", jsonObject); final Object dataHolder = dummyParent.get(handlerDefinition.dataType(), "dataHolder"); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java new file mode 100644 index 0000000..baae4fe --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java @@ -0,0 +1,194 @@ +package top.offsetmonkey538.meshlib.config; + +import blue.endless.jankson.Jankson; +import blue.endless.jankson.JsonElement; +import blue.endless.jankson.JsonObject; +import blue.endless.jankson.JsonPrimitive; +import blue.endless.jankson.api.DeserializationException; +import blue.endless.jankson.api.SyntaxError; +import com.google.common.collect.ImmutableMap; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; +import top.offsetmonkey538.meshlib.api.router.HttpRouter; +import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; +import top.offsetmonkey538.monkeylib538.api.command.CommandAbstractionApi; +import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; +import top.offsetmonkey538.monkeylib538.api.platform.PlatformUtil; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; +import static top.offsetmonkey538.monkeylib538.api.command.CommandAbstractionApi.literal; + +public final class RouterConfigHandler { + private RouterConfigHandler() { + + } + + private static final Path ROUTERS_DIR = PlatformUtil.getConfigDir().resolve(MOD_ID).resolve("routers").normalize().toAbsolutePath(); + + public static LiteralArgumentBuilder createExampleConfigCommand() { + final LiteralArgumentBuilder exampleCommand = literal("example").requires(CommandAbstractionApi::isOp); + + for (final Map.Entry exampleRouter : Map.of( + "server-properties.json", new HttpRouter(new PathHttpRule("/server-properties"), new StaticFileHandler(Path.of("server.properties"))) + ).entrySet()) { + final LiteralArgumentBuilder routerCommand = literal(exampleRouter.getKey()); + routerCommand.executes(context -> { + final Path routerPath = ROUTERS_DIR.resolve("example").resolve(exampleRouter.getKey()).normalize().toAbsolutePath(); + + boolean success; + try { + success = save(routerPath, exampleRouter.getValue()); + } catch (Exception e) { + LOGGER.error("Failed to create example config at '%s'!", e); + success = false; + } + + if (!success) { + CommandAbstractionApi.sendError(context, "Failed to create example config at '%s'! See log for more details", routerPath); + return 0; + } + + CommandAbstractionApi.sendMessage(context, "Created example config at '%s'!", routerPath); + return 1; + }); + exampleCommand.then(routerCommand); + } + + return literal("mesh-lib").then(exampleCommand); + } + + private static boolean save(final Path path, final HttpRouter router) { + final String routerId = ROUTERS_DIR.relativize(path).toString(); + final Jankson jankson = configureJankson(); + + // Convert to json + final JsonElement jsonAsElement = jankson.toJson(router); + if (!(jsonAsElement instanceof final JsonObject json)) { + LOGGER.error("Router '%s' could not be serialized to a 'JsonObject', got '%s' instead! Router will not be saved.", routerId, jsonAsElement.getClass().getName()); + return false; + } + + // Convert to string + final String result = json.toJson(false, true); + + // Save + try { + Files.createDirectories(path.getParent()); + Files.writeString(path, result); + } catch (IOException e) { + LOGGER.error("Config file '%s' could not be saved!", e, routerId); + return false; + } + + return true; + } + + public static void init(final HttpRouterRegistry registry) { + if (!Files.exists(ROUTERS_DIR)) try { + Files.createDirectories(ROUTERS_DIR); + } catch (IOException e) { + LOGGER.error("Failed to create routers directory at '%s'!", e, ROUTERS_DIR); + return; + } + + if (!Files.isDirectory(ROUTERS_DIR)) { + LOGGER.error("'%s' is not a directory!", ROUTERS_DIR); + return; + } + + final List configFiles; + try { + configFiles = gatherConfigFiles(); + } catch (IOException e) { + LOGGER.error("Failed to gather config files from '%s'!", e, ROUTERS_DIR); + return; + } + + // Load and register + loadRouters(configFiles, configureJankson()).forEach(registry::register); + } + + private static List gatherConfigFiles() throws IOException { + try (final Stream files = Files.walk(ROUTERS_DIR)) { + return files + .filter(path -> !Files.isDirectory(path)) + .filter(path -> path.getFileName().toString().endsWith(".json")) + .toList(); + } + } + + private static @NotNull Jankson configureJankson() { + return HttpRouter.configureJankson(Jankson.builder()).build(); + } + + private static @Unmodifiable Map loadRouters(final @NotNull List configFiles, final @NotNull Jankson jankson) throws IllegalArgumentException { + final ImmutableMap.Builder resultBuilder = ImmutableMap.builder(); + + for (final Path path : configFiles) { + final String id = ROUTERS_DIR.relativize(path).toString(); + + try { + resultBuilder.put(id, loadRouter(id, path, jankson)); + } catch (IOException e) { + LOGGER.error("Router configuration file '%s' could not be read!!", e, id); + } catch (SyntaxError e) { + LOGGER.error("Router configuration file '%s' is malformed!", e, id); + LOGGER.error(e.getMessage()); + LOGGER.error(e.getLineMessage()); + } catch (Exception e) { + LOGGER.error("Failed to turn deserialized router configuration file '%s' into an HttpRouter!", e, id); + } + } + + try { + return resultBuilder.buildOrThrow(); + } catch (IllegalArgumentException e) { + LOGGER.error("Failed to build map of id to HttpRouter, no routers will be loaded from config dir!", e); + return Map.of(); + } + } + + @SuppressWarnings("DuplicateThrows") + private static HttpRouter loadRouter(final @NotNull String id, final @NotNull Path path, final @NotNull Jankson jankson) throws IOException, SyntaxError, Exception { + final JsonObject json = jankson.load(Files.newInputStream(path)); + + try { + return jankson.fromJsonCarefully(json, JanksonHttpRouter.class).toRouter(); + } catch (DeserializationException e) { + LOGGER.error("Failed to deserialize router configuration file '%s'!", e, id); + return jankson.fromJson(json, JanksonHttpRouter.class).toRouter(); + } + } + + /** + * Exists because jankson requires a no-arg constructor to create an instance and then modify its fields, which wouldn't be possible with the record {@link HttpRouter} + */ + @SuppressWarnings({"unused", "FieldMayBeFinal"}) + private static class JanksonHttpRouter { + private HttpRule rule = null; + private HttpHandler handler = null; + + public JanksonHttpRouter() { + + } + + public HttpRouter toRouter() throws Exception { + if (rule == null) throw new Exception("rule is null"); + if (handler == null) throw new Exception("handler is null"); + return new HttpRouter(rule, handler); + } + } +} diff --git a/gradle.properties b/gradle.properties index 609c8fe..43c16d2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,6 +8,8 @@ monkeylib538_version = 3.0.0-alpha.0.1761395863791+bd100da netty_version = 4.1.82.Final ## Bundeled with Minecraft, common needs to use guava_version = 33.4.0-jre +## Bundeled with Minecraft, common needs to use +brigadier_version = 1.3.10 # Mod Properties mod_version = 2.0.0-alpha.0 From 5f78b10b7d2a3dcdc6bcafd5c53201f8faf22edd Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Sun, 2 Nov 2025 19:46:19 +0200 Subject: [PATCH 47/78] add more example stuff. Not sure if I need to do anything bout this or not, but all the paths will be absolute because that's what the handlers store and that's what jankson saves... --- .../top/offsetmonkey538/meshlib/MESHLib.java | 2 + .../meshlib/api/handler/HttpHandler.java | 4 +- .../handlers/StaticContentHandler.java | 42 +++++++++++ .../meshlib/config/RouterConfigHandler.java | 65 ++++++++++------ .../meshlib/example/ExampleHttpHandler.java | 75 +++++++++++++++++++ .../meshlib/example/ExampleMain.java | 37 +-------- .../meshlib/example/SimpleHttpHandler.java | 38 ---------- 7 files changed, 169 insertions(+), 94 deletions(-) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java delete mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index d5f300e..fb53c37 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -4,6 +4,7 @@ import blue.endless.jankson.JsonPrimitive; import com.google.common.base.Stopwatch; import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.handlers.StaticContentHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; import top.offsetmonkey538.meshlib.api.router.HttpRouter; @@ -59,6 +60,7 @@ public static void initialize() { HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.listen(DomainHttpRule::register); HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.listen(PathHttpRule::register); + HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.listen(StaticContentHandler::register); HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.listen(StaticFileHandler::register); HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.listen(StaticDirectoryHandler::register); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java index d70be1d..1fe8332 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java @@ -4,13 +4,13 @@ import io.netty.handler.codec.http.FullHttpRequest; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.example.SimpleHttpHandler; +import top.offsetmonkey538.meshlib.example.ExampleHttpHandler; import org.jetbrains.annotations.NotNull; /** * An http handler for you to implement :D *
- * Look at {@link SimpleHttpHandler SimpleHttpHandler} for an example + * Look at {@link ExampleHttpHandler ExampleHttpHandler} for an example * * @see HttpRouterRegistry */ diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java new file mode 100644 index 0000000..10f25ec --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java @@ -0,0 +1,42 @@ +package top.offsetmonkey538.meshlib.api.handler.handlers; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.FullHttpRequest; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; + + +public record StaticContentHandler(String content) implements HttpHandler { + public StaticContentHandler(final String content) { + this.content = content; + } + + @Override + public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + HttpResponseUtil.sendString(ctx, content); + } + + @ApiStatus.Internal + public static void register(final HttpHandlerTypeRegistry registry) { + registry.register("static-content", Data.class, StaticContentHandler.class, handler -> new Data(handler.content), data -> new StaticContentHandler(data.content)); + } + + @ApiStatus.Internal + private static final class Data { + private String content; + + @SuppressWarnings("unused") + // Pretty sure this public no-args needs to exist cause jankson wants to create instances + public Data() { + + } + + public Data(final String content) { + this.content = content; + } + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java index baae4fe..9731940 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java @@ -8,13 +8,17 @@ import blue.endless.jankson.api.SyntaxError; import com.google.common.collect.ImmutableMap; import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Unmodifiable; import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.handlers.StaticContentHandler; +import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; import top.offsetmonkey538.meshlib.api.router.HttpRouter; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; import top.offsetmonkey538.monkeylib538.api.command.CommandAbstractionApi; import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; @@ -25,6 +29,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.stream.Stream; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; @@ -40,34 +45,52 @@ private RouterConfigHandler() { public static LiteralArgumentBuilder createExampleConfigCommand() { final LiteralArgumentBuilder exampleCommand = literal("example").requires(CommandAbstractionApi::isOp); + Consumer> allHandler = context -> {}; for (final Map.Entry exampleRouter : Map.of( - "server-properties.json", new HttpRouter(new PathHttpRule("/server-properties"), new StaticFileHandler(Path.of("server.properties"))) + "server-properties.json", new HttpRouter(new PathHttpRule("example/server-properties"), new StaticFileHandler(Path.of("server.properties"))), + "ops.json", new HttpRouter(new PathHttpRule("example/ops"), new StaticFileHandler(Path.of("ops.json"))), + "directory.json", new HttpRouter(new DomainHttpRule("directory.example.com"), new StaticDirectoryHandler(Path.of("."), true)), + "index.json", new HttpRouter(new DomainHttpRule("docs.example.com"), new StaticDirectoryHandler(Path.of("/home/dave/Dev/Java/Minecraft/Mods/Loot-Table-Modifier/docs/dist/"), false)), + "hello.json", new HttpRouter(new PathHttpRule("/example/hello"), new StaticContentHandler(""" + Hello World! + ... + ... + Goodbye! :P + """)) ).entrySet()) { + allHandler = allHandler.andThen(context -> runCommand(exampleRouter, context)); + final LiteralArgumentBuilder routerCommand = literal(exampleRouter.getKey()); - routerCommand.executes(context -> { - final Path routerPath = ROUTERS_DIR.resolve("example").resolve(exampleRouter.getKey()).normalize().toAbsolutePath(); - - boolean success; - try { - success = save(routerPath, exampleRouter.getValue()); - } catch (Exception e) { - LOGGER.error("Failed to create example config at '%s'!", e); - success = false; - } - - if (!success) { - CommandAbstractionApi.sendError(context, "Failed to create example config at '%s'! See log for more details", routerPath); - return 0; - } - - CommandAbstractionApi.sendMessage(context, "Created example config at '%s'!", routerPath); - return 1; - }); + routerCommand.executes(context -> runCommand(exampleRouter, context)); exampleCommand.then(routerCommand); } - return literal("mesh-lib").then(exampleCommand); + final Consumer> finalAllHandler = allHandler; + return literal("mesh-lib").then(exampleCommand).then(literal("all").executes(context -> { + finalAllHandler.accept(context); + return 1; + })); + } + + private static int runCommand(final Map.Entry exampleRouter, final CommandContext context) { + final Path routerPath = ROUTERS_DIR.resolve("example").resolve(exampleRouter.getKey()).normalize().toAbsolutePath(); + + boolean success; + try { + success = save(routerPath, exampleRouter.getValue()); + } catch (Exception e) { + LOGGER.error("Failed to create example config at '%s'!", e); + success = false; + } + + if (!success) { + CommandAbstractionApi.sendError(context, "Failed to create example config at '%s'! See log for more details", routerPath); + return 0; + } + + CommandAbstractionApi.sendMessage(context, "Created example config at '%s'!", routerPath); + return 1; } private static boolean save(final Path path, final HttpRouter router) { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java new file mode 100644 index 0000000..8f91cd1 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java @@ -0,0 +1,75 @@ +package top.offsetmonkey538.meshlib.example; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; +import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +/** + * An example {@link HttpHandler} implementation to learn from + */ +public record ExampleHttpHandler(String baseContent) implements HttpHandler { + + @Override + public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + // Calculate response using super amazing and hard math™ + final String responseText = superCoolMethodForRunningTheHardAndAmazingCalculationForCalculationinatingTheResponseTM(request.uri()); + + // You could also use this magical utility method for sending the response, but doing it manually makes the example longer and thus more betterer :P + // HttpResponseUtil.sendString(ctx, responseText); + + + // Write the responseText to a buffer, encoded in UTF-8 + final ByteBuf content = Unpooled.copiedBuffer(responseText, StandardCharsets.UTF_8); + // Create a response with said buffer + final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content); + + // Set the "CONTENT_TYPE" header to tell the browser that this is plain text encoded in UTF-8 + response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); + + // Send the response and close the connection + ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); + } + + private String superCoolMethodForRunningTheHardAndAmazingCalculationForCalculationinatingTheResponseTM(String requestUri) { + final String requestedPath = requestUri.substring(requestUri.indexOf('/')); + + return this.baseContent + requestedPath; + } + + public static void register(final HttpHandlerTypeRegistry registry) { + registry.register("example-http", Data.class, ExampleHttpHandler.class, handler -> new Data(handler.baseContent), data -> new ExampleHttpHandler(data.content)); + } + + @ApiStatus.Internal + private static final class Data { + private String content; + + @SuppressWarnings("unused") + // Pretty sure this public no-args needs to exist cause jankson wants to create instances + public Data() { + + } + + public Data(final String content) { + this.content = content; + } + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java index 9810b34..1b93c48 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java @@ -1,14 +1,9 @@ package top.offsetmonkey538.meshlib.example; import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; -import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; import top.offsetmonkey538.meshlib.api.router.HttpRouter; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; -import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; - -import java.nio.file.Path; import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; @@ -33,36 +28,12 @@ public static void onInitialize() { LOGGER.warn("MESH examples enabled!"); // Register - record SimpleHttpHandlerData(String content) { - - } - HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.listen(registry -> { - registry.register("simple-http", SimpleHttpHandlerData.class, SimpleHttpHandler.class, handler -> new SimpleHttpHandlerData(handler.content()), data -> new SimpleHttpHandler(data.content())); - }); + HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.listen(ExampleHttpHandler::register); - //HttpRouterRegistry.INSTANCE.register("simple-server", new HttpRouter( - // new DomainHttpRule("localhost"), - // new SimpleHttpHandler("Yellow!") - //)); HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.listen(registry -> { - registry.register("simple-server2", new HttpRouter( - new PathHttpRule("/hi"), - new SimpleHttpHandler("Goodbye!") - )); - - registry.register("static-file-test", new HttpRouter( - new PathHttpRule("/static/file"), - new StaticFileHandler(Path.of("usercache.json")) - )); - - registry.register("static-directory-test", new HttpRouter( - new PathHttpRule("/static/directory"), - new StaticDirectoryHandler(Path.of("/home/dave"), true) - )); - - registry.register("static-directory-test2", new HttpRouter( - new DomainHttpRule("localhost"), - new StaticDirectoryHandler(Path.of("/home/dave/Dev/Java/Minecraft/Mods/Loot-Table-Modifier/docs/dist/"), false) + registry.register("example/example-http-handler", new HttpRouter( + new DomainHttpRule("site.example.com"), + new ExampleHttpHandler("Requested path: ") )); }); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java deleted file mode 100644 index d3a9b7c..0000000 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/SimpleHttpHandler.java +++ /dev/null @@ -1,38 +0,0 @@ -package top.offsetmonkey538.meshlib.example; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFutureListener; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.FullHttpResponse; -import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; - -import java.nio.charset.StandardCharsets; - -import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; -import static io.netty.handler.codec.http.HttpResponseStatus.OK; -import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; - -/** - * An example {@link HttpHandler} implementation to learn from - */ -public record SimpleHttpHandler(String content) implements HttpHandler { - - @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { - // Write the provided content to a buffer, encoded in UTF-8 - final ByteBuf content = Unpooled.copiedBuffer(this.content, StandardCharsets.UTF_8); - // Create a response with said buffer - final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content); - - // Set the "CONTENT_TYPE" header to tell the browser that this is plain text encoded in UTF-8 - response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); - - // Send the response and close the connection - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); - } -} From c50e0d4a4a37e76e71fd2a61b3b2cc6401fae138 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 <71213040+OffsetMonkey538@users.noreply.github.com> Date: Wed, 12 Nov 2025 20:37:00 +0200 Subject: [PATCH 48/78] Respect keep-alive in HttpResponseUtil --- .../handlers/StaticContentHandler.java | 2 +- .../handlers/StaticDirectoryHandler.java | 8 +-- .../meshlib/api/util/HttpResponseUtil.java | 66 ++++++++++--------- .../meshlib/example/ExampleHttpHandler.java | 16 +---- .../meshlib/impl/MainHttpHandler.java | 4 +- .../impl/util/HttpResponseUtilImpl.java | 32 +++++---- fabric/gradle.properties | 2 +- 7 files changed, 65 insertions(+), 65 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java index 10f25ec..e1ad6b2 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java @@ -17,7 +17,7 @@ public StaticContentHandler(final String content) { @Override public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { - HttpResponseUtil.sendString(ctx, content); + HttpResponseUtil.sendString(ctx, request, content); } @ApiStatus.Internal diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java index 9278b5f..a9d3893 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java @@ -47,17 +47,17 @@ public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpR try { requestedPath = baseDir.resolve(rawPath.startsWith("/") ? rawPath.substring(1) : rawPath).normalize(); } catch (InvalidPathException e) { - sendError(ctx, HttpResponseStatus.BAD_REQUEST, e); + sendError(ctx, request, HttpResponseStatus.BAD_REQUEST, e); return; } if (!requestedPath.startsWith(baseDir)) { - sendError(ctx, HttpResponseStatus.FORBIDDEN); + sendError(ctx, request, HttpResponseStatus.FORBIDDEN); return; } if (!Files.exists(requestedPath)) { - sendError(ctx, HttpResponseStatus.NOT_FOUND); + sendError(ctx, request, HttpResponseStatus.NOT_FOUND); return; } @@ -68,7 +68,7 @@ public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpR // At this point we know it's a directory and that it exists if (!request.uri().endsWith("/")) { - sendPermanentRedirect(ctx, request.uri() + "/"); + sendPermanentRedirect(ctx, request, request.uri() + "/"); return; } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java index 97fee9a..09a409b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java @@ -15,7 +15,6 @@ /** * Provides utils for responding to http requests */ -// TODO: FIXME: respect keep alive public interface HttpResponseUtil { /** * Instance @@ -28,98 +27,105 @@ public interface HttpResponseUtil { * Sends the requester the file at the provided file * * @param ctx the current channel handler context - * @param request the client request + * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param fileToSend the path to the file to send * @throws IOException when io go wrong :( */ - static void sendFile(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull Path fileToSend) throws IOException { + static void sendFile(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull Path fileToSend) throws IOException { INSTANCE.sendFileImpl(ctx, request, fileToSend); } /** * Sends the requester a Permanent Redirect (308) response containing the new location. *

- * Afaik clients may cache this redirect and automatically go to the new location when this url is requested? If that is not desired, use {@link #sendTemporaryRedirect(ChannelHandlerContext, String)} + * Clients may cache this and automatically redirect when the url is requested. If that is not desired, use {@link #sendTemporaryRedirect(ChannelHandlerContext, FullHttpRequest, String)} * * @param ctx the current channel handler context + * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param newLocation the new location. */ - static void sendPermanentRedirect(@NotNull ChannelHandlerContext ctx, @NotNull String newLocation) { - INSTANCE.sendRedirectImpl(ctx, HttpResponseStatus.PERMANENT_REDIRECT, newLocation); + static void sendPermanentRedirect(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull String newLocation) { + INSTANCE.sendRedirectImpl(ctx, request, HttpResponseStatus.PERMANENT_REDIRECT, newLocation); } /** * Sends the requester a Temporary Redirect (307) response containing the new location. * * @param ctx the current channel handler context + * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param newLocation the new location. */ - static void sendTemporaryRedirect(@NotNull ChannelHandlerContext ctx, @NotNull String newLocation) { - INSTANCE.sendRedirectImpl(ctx, HttpResponseStatus.TEMPORARY_REDIRECT, newLocation); + static void sendTemporaryRedirect(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull String newLocation) { + INSTANCE.sendRedirectImpl(ctx, request, HttpResponseStatus.TEMPORARY_REDIRECT, newLocation); } /** * Sends the requester a plain text 200 response * * @param ctx the current channel handler context + * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param content the plain text response to send */ - static void sendString(@NotNull ChannelHandlerContext ctx, @NotNull String content) { - INSTANCE.sendStringImpl(ctx, content); + static void sendString(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull String content) { + INSTANCE.sendStringImpl(ctx, request, content); } /** * Logs and sends the requester an error code. * * @param ctx the current channel handler context + * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param status the status to send - * @see #sendError(ChannelHandlerContext, HttpResponseStatus, Throwable) - * @see #sendError(ChannelHandlerContext, HttpResponseStatus, String) + * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus, Throwable) + * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus, String) */ - static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status) { - sendError(ctx, status, (String) null); + static void sendError(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status) { + sendError(ctx, request, status, (String) null); } /** * Logs and sends the requester an error code and a reason with it. * * @param ctx the current channel handler context + * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param status the status to send * @param reason reason to display for the error, MUST NOT be null - * @see #sendError(ChannelHandlerContext, HttpResponseStatus) - * @see #sendError(ChannelHandlerContext, HttpResponseStatus, String) + * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus) + * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus, String) */ - static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @NotNull Throwable reason) { - sendError(ctx, status, reason.getMessage()); + static void sendError(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status, @NotNull Throwable reason) { + sendError(ctx, request, status, reason.getMessage()); } /** * Logs and sends the requester an error code and optionally a reason with it. * * @param ctx the current channel handler context + * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param status the status to send * @param reason reason to display for the error, MAY be null or empty - * @see #sendError(ChannelHandlerContext, HttpResponseStatus) - * @see #sendError(ChannelHandlerContext, HttpResponseStatus, Throwable) + * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus) + * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus, Throwable) */ - static void sendError(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @Nullable String reason) { - INSTANCE.sendErrorImpl(ctx, status, reason); + static void sendError(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status, @Nullable String reason) { + INSTANCE.sendErrorImpl(ctx, request, status, reason); } /** - * Sends the requester the provided {@link FullHttpResponse}, sets {@link io.netty.handler.codec.http.HttpHeaderNames#CONNECTION CONNECTION} to "close" and closes the connection. + * Sends the requester the provided {@link FullHttpResponse}. If request is keep-alive, sets {@link io.netty.handler.codec.http.HttpHeaderNames#CONNECTION CONNECTION} "keep-alive" and returns. Otherwise sets {@link io.netty.handler.codec.http.HttpHeaderNames#CONNECTION CONNECTION} to "close" and closes the connection. * * @param ctx the current channel handler context + * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param response the response to send */ - static void sendResponseAndClose(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpResponse response) { - INSTANCE.sendResponseAndCloseImpl(ctx, response); + static void sendResponse(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull FullHttpResponse response) { + INSTANCE.sendResponseImpl(ctx, request, response); } - void sendFileImpl(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull Path fileToSend) throws IOException; - void sendRedirectImpl(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @NotNull String newLocation); - void sendStringImpl(@NotNull ChannelHandlerContext ctx, @NotNull String content); - void sendErrorImpl(@NotNull ChannelHandlerContext ctx, @NotNull HttpResponseStatus status, @Nullable String reason); - void sendResponseAndCloseImpl(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpResponse response); + void sendFileImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull Path fileToSend) throws IOException; + void sendRedirectImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status, @NotNull String newLocation); + void sendStringImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull String content); + void sendErrorImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status, @Nullable String reason); + void sendResponseImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull FullHttpResponse response); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java index 8f91cd1..eb0eba2 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java @@ -32,20 +32,8 @@ public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpR // Calculate response using super amazing and hard math™ final String responseText = superCoolMethodForRunningTheHardAndAmazingCalculationForCalculationinatingTheResponseTM(request.uri()); - // You could also use this magical utility method for sending the response, but doing it manually makes the example longer and thus more betterer :P - // HttpResponseUtil.sendString(ctx, responseText); - - - // Write the responseText to a buffer, encoded in UTF-8 - final ByteBuf content = Unpooled.copiedBuffer(responseText, StandardCharsets.UTF_8); - // Create a response with said buffer - final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, content); - - // Set the "CONTENT_TYPE" header to tell the browser that this is plain text encoded in UTF-8 - response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); - - // Send the response and close the connection - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); + // Magical utility for sending a plain-text string + HttpResponseUtil.sendString(ctx, request, responseText); } private String superCoolMethodForRunningTheHardAndAmazingCalculationForCalculationinatingTheResponseTM(String requestUri) { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java index 5ae4c5b..cfe90ce 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java @@ -26,7 +26,7 @@ public class MainHttpHandler extends SimpleChannelInboundHandler Date: Wed, 12 Nov 2025 20:38:50 +0200 Subject: [PATCH 49/78] Cleanup --- .../java/top/offsetmonkey538/meshlib/MESHLib.java | 6 ------ .../api/handler/handlers/StaticContentHandler.java | 3 --- .../meshlib/api/router/HttpRouter.java | 4 ---- .../meshlib/config/RouterConfigHandler.java | 2 -- .../meshlib/example/ExampleHttpHandler.java | 13 ------------- .../meshlib/platform/fabric/FabricPlatformMain.java | 5 ----- .../meshlib/platform/paper/PaperPlatformMain.java | 2 -- 7 files changed, 35 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index fb53c37..22f897f 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -1,15 +1,11 @@ package top.offsetmonkey538.meshlib; -import blue.endless.jankson.JsonObject; -import blue.endless.jankson.JsonPrimitive; import com.google.common.base.Stopwatch; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticContentHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; import top.offsetmonkey538.meshlib.api.router.HttpRouter; import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.config.MESHLibConfig; @@ -17,8 +13,6 @@ import top.offsetmonkey538.meshlib.example.ExampleMain; import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; -import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; -import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; import top.offsetmonkey538.meshlib.platform.PlatformMain; import top.offsetmonkey538.monkeylib538.api.command.CommandRegistrationApi; import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java index e1ad6b2..9c06724 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java @@ -11,9 +11,6 @@ public record StaticContentHandler(String content) implements HttpHandler { - public StaticContentHandler(final String content) { - this.content = content; - } @Override public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java index bd2eb24..9977ad6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java @@ -1,7 +1,6 @@ package top.offsetmonkey538.meshlib.api.router; import blue.endless.jankson.Jankson; -import blue.endless.jankson.JsonElement; import blue.endless.jankson.JsonObject; import blue.endless.jankson.JsonPrimitive; import org.jetbrains.annotations.NotNull; @@ -12,9 +11,6 @@ import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; -import java.net.URI; -import java.nio.file.Path; - public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { /** diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java index 9731940..e2b92be 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java @@ -3,7 +3,6 @@ import blue.endless.jankson.Jankson; import blue.endless.jankson.JsonElement; import blue.endless.jankson.JsonObject; -import blue.endless.jankson.JsonPrimitive; import blue.endless.jankson.api.DeserializationException; import blue.endless.jankson.api.SyntaxError; import com.google.common.collect.ImmutableMap; @@ -21,7 +20,6 @@ import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; import top.offsetmonkey538.monkeylib538.api.command.CommandAbstractionApi; -import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; import top.offsetmonkey538.monkeylib538.api.platform.PlatformUtil; import java.io.IOException; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java index eb0eba2..3433c81 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java @@ -1,27 +1,14 @@ package top.offsetmonkey538.meshlib.example; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.FullHttpResponse; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; import top.offsetmonkey538.meshlib.api.rule.HttpRule; import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; - -import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; -import static io.netty.handler.codec.http.HttpResponseStatus.OK; -import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; - /** * An example {@link HttpHandler} implementation to learn from */ diff --git a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java index f28c1a0..62564ed 100644 --- a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java +++ b/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java @@ -1,14 +1,9 @@ package top.offsetmonkey538.meshlib.platform.fabric; import net.fabricmc.api.DedicatedServerModInitializer; -import net.fabricmc.loader.api.FabricLoader; import top.offsetmonkey538.meshlib.MESHLib; import top.offsetmonkey538.meshlib.platform.PlatformMain; -import java.nio.file.Path; - -import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; - public final class FabricPlatformMain implements PlatformMain, DedicatedServerModInitializer { public static boolean isVanillaHandlerEnabled = false; diff --git a/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java b/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java index 6b92abe..d595603 100644 --- a/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java +++ b/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java @@ -6,8 +6,6 @@ import top.offsetmonkey538.meshlib.impl.ProtocolHandler; import top.offsetmonkey538.meshlib.platform.PlatformMain; -import java.nio.file.Path; - public final class PaperPlatformMain implements PlatformMain { private static final Key HANDLER_KEY = Key.key("meshlib", "meshlib_vanilla_handler"); From 6e552a1525e8b4251ba62b19c02f3fae65abad51 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Fri, 23 Jan 2026 17:11:43 +0200 Subject: [PATCH 50/78] Update Gradle to v9.3.0 --- gradle/wrapper/gradle-wrapper.jar | Bin 45457 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8bdaf60c75ab801e22807dde59e12a8735a34077..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch delta 36855 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%0Aonjyf`q#Bg+;H(_UceX22V^ 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 Date: Sun, 25 Jan 2026 17:13:58 +0200 Subject: [PATCH 51/78] Migrate to new buildscripts --- .gitmodules | 3 + build.gradle | 512 ++---------------- buildSrc | 1 + common/build.gradle | 20 + common/gradle.properties | 2 +- .../top/offsetmonkey538/meshlib/MESHLib.java | 10 +- .../meshlib/config/MESHLibConfig.java | 4 +- .../meshlib/config/RouterConfigHandler.java | 8 +- .../resources/META-INF/neoforge.mods.toml | 14 - .../main/resources/assets/mesh-lib/icon.png | Bin fabric/gradle.properties | 20 - fabric/src/main/resources/fabric.mod.json | 30 - gradle.properties | 20 +- jitpack.yml | 4 - loader/fabric/1.21.1/gradle.properties | 14 + loader/fabric/build.gradle | 48 ++ loader/fabric/gradle.properties | 10 + .../platform/fabric/FabricPlatformMain.java | 0 .../fabric/mixin/ServerNetworkIoMixin.java | 0 ...setmonkey538.meshlib.platform.PlatformMain | 0 .../src/main/resources/mesh-lib.mixins.json | 0 loader/neoforge/1.21.1/gradle.properties | 15 + loader/neoforge/build.gradle | 37 ++ loader/neoforge/gradle.properties | 8 + .../neoforge/NeoforgeInitializer.java | 0 .../neoforge/NeoforgePlatformMain.java | 0 .../mixin/ServerConnectionListenerMixin.java | 0 ...setmonkey538.meshlib.platform.PlatformMain | 0 .../src/main/resources/mesh-lib.mixins.json | 0 loader/paper/1.21.1/gradle.properties | 10 + loader/paper/build.gradle | 40 ++ loader/paper/gradle.properties | 7 + .../meshlib/platform/paper/MeshLibPlugin.java | 0 .../platform/paper/PaperPlatformMain.java | 0 ...setmonkey538.meshlib.platform.PlatformMain | 0 modded/1.21.1/gradle.properties | 6 + modded/build.gradle | 15 + modded/gradle.properties | 4 + neoforge/gradle.properties | 15 - .../resources/META-INF/neoforge.mods.toml | 32 -- paper/gradle.properties | 8 - paper/src/main/resources/paper-plugin.yml | 8 - settings.gradle | 14 +- 43 files changed, 329 insertions(+), 610 deletions(-) create mode 100644 .gitmodules create mode 160000 buildSrc create mode 100644 common/build.gradle delete mode 100644 common/src/main/resources/META-INF/neoforge.mods.toml rename {fabric => common}/src/main/resources/assets/mesh-lib/icon.png (100%) delete mode 100644 fabric/gradle.properties delete mode 100644 fabric/src/main/resources/fabric.mod.json delete mode 100644 jitpack.yml create mode 100644 loader/fabric/1.21.1/gradle.properties create mode 100644 loader/fabric/build.gradle create mode 100644 loader/fabric/gradle.properties rename {fabric => loader/fabric}/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java (100%) rename {fabric => loader/fabric}/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java (100%) rename {fabric => loader/fabric}/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain (100%) rename {fabric => loader/fabric}/src/main/resources/mesh-lib.mixins.json (100%) create mode 100644 loader/neoforge/1.21.1/gradle.properties create mode 100644 loader/neoforge/build.gradle create mode 100644 loader/neoforge/gradle.properties rename {neoforge => loader/neoforge}/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java (100%) rename {neoforge => loader/neoforge}/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java (100%) rename {neoforge => loader/neoforge}/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java (100%) rename {neoforge => loader/neoforge}/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain (100%) rename {neoforge => loader/neoforge}/src/main/resources/mesh-lib.mixins.json (100%) create mode 100644 loader/paper/1.21.1/gradle.properties create mode 100644 loader/paper/build.gradle create mode 100644 loader/paper/gradle.properties rename {paper => loader/paper}/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java (100%) rename {paper => loader/paper}/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java (100%) rename {paper => loader/paper}/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain (100%) create mode 100644 modded/1.21.1/gradle.properties create mode 100644 modded/build.gradle create mode 100644 modded/gradle.properties delete mode 100644 neoforge/gradle.properties delete mode 100644 neoforge/src/main/resources/META-INF/neoforge.mods.toml delete mode 100644 paper/gradle.properties delete mode 100644 paper/src/main/resources/paper-plugin.yml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..cc7fe46 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "buildSrc"] + path = buildSrc + url = https://github.com/OffsetMods538/multiversion-buildscripts diff --git a/build.gradle b/build.gradle index 40bb562..c8c0fd6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,488 +1,74 @@ -import dex.plugins.outlet.v2.util.ReleaseType -import io.papermc.paperweight.userdev.ReobfArtifactConfiguration - -import java.nio.file.Files - plugins { - id 'fabric-loom' version '1.11-SNAPSHOT' apply false - id 'net.neoforged.moddev' version '2.0.112' apply false - id 'io.github.dexman545.outlet' version '1.6.1' apply false - id 'io.papermc.paperweight.userdev' version '2.0.0-beta.8' apply false - id 'com.gradleup.shadow' version '9.0.0-beta4' apply false - id 'com.modrinth.minotaur' version '2.+' apply false - id 'xyz.jpenilla.run-paper' version '2.3.1' apply false + id 'multiloader-root' + id 'java' + id 'java-library' + id 'net.neoforged.moddev' version "${neoforged_moddev}" apply false + id 'fabric-loom' version "${fabric_loom}" apply false + id 'io.papermc.paperweight.userdev' version "${papermc_paperweight_userdev}" apply false + id 'xyz.jpenilla.run-paper' version "${jpenilla_run_task}" apply false + id 'com.gradleup.shadow' version "${gradleup_shadow}" apply false + id 'xyz.jpenilla.resource-factory' version "${jpenilla_resource_factory}" apply false + id 'io.github.dexman545.outlet' version "${dexman_outlet}" apply false + id 'com.modrinth.minotaur' version "${modrinth_minotaur}" apply false + id 'hamburg.janove.elevator-music' version "0.1" } -ext { - versionPrefix = rootProject.mod_version - if (!Boolean.parseBoolean(System.getenv("IS_RELEASE"))) { - String preReleaseVersion = System.currentTimeMillis() - - if (Boolean.parseBoolean(System.getenv("PRESERVE_PRERELEASE_VERSION"))) { - var preReleaseVersionFile = file("preReleaseVersion.txt").toPath() - if (Files.exists(preReleaseVersionFile)) preReleaseVersion = Files.readString(preReleaseVersionFile) - Files.writeString(preReleaseVersionFile, preReleaseVersion) - } - - var separator = "-" - if (versionPrefix.contains("-")) separator = "." - versionPrefix = "${versionPrefix}${separator}${preReleaseVersion}" - } - final String versionSuffix = System.getenv("VERSION_SUFFIX") - if (versionSuffix != null && !versionSuffix.isEmpty()) { - versionPrefix = "${versionPrefix}+${versionSuffix}" - } - - System.out.println("Version Prefix: " + versionPrefix) -} - -allprojects { - group = "top.offsetmonkey538.meshlib" -} +//elevatorMusic { +// if (Boolean.parseBoolean(System.getenv("DISABLE_MUSIC"))) return +// waitMusic = file("${rootProject.projectDir}/veryImportant/music.wav") +// successSound = file("${rootProject.projectDir}/veryImportant/done.wav") +//} subprojects { - apply plugin: "java" apply plugin: "java-library" - apply plugin: "maven-publish" - - base.archivesName = "mesh-lib-${project.project_name}" - version = "${rootProject.versionPrefix}" - if (project.hasProperty("minecraft_version")) version = "${version}+${project.property("minecraft_version")}" - repositories { + repositories { mavenCentral() mavenLocal() - maven { - name = "OffsetMods538" - url = "https://maven.offsetmonkey538.top/releases" - content { - includeGroup "top.offsetmonkey538.monkeylib538" - includeGroup "top.offsetmonkey538.offsetconfig538" - } - } - } - - dependencies { - compileOnly "org.jetbrains:annotations:24.0.0" - } - - tasks.withType(JavaCompile).configureEach { - it.options.release = 21 - } - - java { - withSourcesJar() - withJavadocJar() - - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 - } - - tasks.named("javadoc", Javadoc) { - options.addFileOption('-add-stylesheet', rootProject.file("javadoc-stylesheet.css")) - } - - jar { - from("${rootProject.projectDir}/LICENSE") { - rename { "${it}" } - } - } - - publishing { - repositories { - maven { - name = "OffsetMonkey538" - url = "https://maven.offsetmonkey538.top/releases" - credentials { - username = providers.gradleProperty("OffsetMonkey538Username").getOrElse(System.getenv("MAVEN_USERNAME")) - password = providers.gradleProperty("OffsetMonkey538Password").getOrElse(System.getenv("MAVEN_PASSWORD")) - } - authentication { - basic(BasicAuthentication) - } - } - } - publications { - maven(MavenPublication) { - artifactId = base.archivesName.get() - - from components.java - } - } - } -} - -configure(subprojects.findAll { it.name == "common" }) { - apply plugin: "com.gradleup.shadow" - - repositories { - maven { - name = "Mojang Libraries" - url = "https://libraries.minecraft.net" - content { - includeGroup "com.mojang" - } - } - } - - dependencies { - api "io.netty:netty-codec-http:${rootProject.netty_version}" - - shadow compileOnlyApi("top.offsetmonkey538.monkeylib538:monkeylib538-common:${rootProject.monkeylib538_version}") - shadow compileOnly("com.google.guava:guava:${rootProject.guava_version}") - shadow compileOnly("com.mojang:brigadier:${rootProject.brigadier_version}") - } - tasks.build.dependsOn(shadowJar) - - processResources { - final Map properties = [ - "modVersion": rootProject.versionPrefix - ] - inputs.properties(properties) - filesMatching("META-INF/neoforge.mods.toml") { - expand(properties) - } - } -} - -configure(subprojects.findAll { it.name == "fabric" }) { - apply plugin: "fabric-loom" - apply plugin: "io.github.dexman545.outlet" - apply plugin: "com.modrinth.minotaur" - - outlet { - maintainPropertiesFile = !Boolean.parseBoolean(System.getenv("IS_PRODUCTION_BUILD")) - mcVersionRange = project.supported_minecraft_versions - allowedReleaseTypes = [ReleaseType.RELEASE] - propertiesData = [ - 'fapi_version': outlet.fapiVersion(project.minecraft_version), - 'yarn_version': outlet.yarnVersion(project.minecraft_version), - 'loader_version': outlet.loaderVersion() - ] - } - - loom { - runs { - client { - runDir "run/client" + exclusiveContent { + forRepository { + maven { + name = "OffsetMods538" + url = "https://maven.offsetmonkey538.top/releases" + } } - server { - runDir "run/server" + filter { + includeGroupAndSubgroups "top.offsetmonkey538" } } - - runConfigs.configureEach { - ideConfigGenerated(true) - vmArg "-DmeshEnableExamples=true" - if (!Boolean.parseBoolean(System.getenv("IS_PRODUCTION_BUILD"))) vmArg "-Ddevauth.enabled=true" - } - } - - repositories { - maven { - name = "DevAuth" - url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" - content { - includeGroup "me.djtheredstoner" - } - } - } - - configurations { - common { - canBeResolved = true - canBeConsumed = false - } - api.extendsFrom common } dependencies { - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings "net.fabricmc:yarn:${project.yarn_version}:v2" - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // DevAuth - modLocalRuntime "me.djtheredstoner:DevAuth-fabric:${project.devauth_version}" - - include project(path: ":common", configuration: "shadow") - common project(":common") - - // Only need fapi at runtime for the monkeylib shtuff - modRuntimeOnly "net.fabricmc.fabric-api:fabric-api:${project.fapi_version}" - - // Whatever the heck this is for monkeylib - modRuntimeOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-1.20.5:${rootProject.monkeylib538_version}+1.20.5") { - exclude(group: "net.fabricmc.fabric-api") - exclude(group: "top.offsetmonkey538.monkeylib538:monkeylib538-fabric") - } - modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${rootProject.monkeylib538_version}") { - exclude(group: "net.fabricmc.fabric-api") - } - } - - processResources { - final Map properties = [ - "commonVersion": rootProject.versionPrefix, - "modVersion": project.version, - "supportedMinecraftVersions": project.supported_minecraft_versions, - "currentMonkeylibVersion": project.monkeylib538_version - ] - inputs.properties(properties) - filesMatching("fabric.mod.json") { - expand(properties) - } - - exclude ".cache/**" + //TODO: switch to this: compileOnlyApi "org.jspecify:jspecify:${rootProject.jspecify_version}" + compileOnlyApi "org.jetbrains:annotations:24.0.0" } - - modrinth { - // Main properties - token = System.getenv("MODRINTH_TOKEN") - projectId = "mesh-lib" - gameVersions = outlet.mcVersions() - loaders = ["fabric"] - - // Version stuff - def customVersionName = System.getenv("VERSION_NAME") - if (customVersionName != null) versionName = customVersionName - - versionNumber = "${project.version}" - - versionType = Boolean.parseBoolean(System.getenv("VERSION_IS_PRERELEASE")) ? "beta" : "release" - - if (rootProject.mod_version.contains("beta")) versionType = "beta" - if (rootProject.mod_version.contains("alpha")) versionType = "alpha" - - // Files - uploadFile = remapJar.archiveFile - additionalFiles = [sourcesJar.archiveFile] - - - // Project info - syncBodyFrom = rootProject.file("README.md").text - def changelogEnv = System.getenv("VERSION_CHANGELOG") - if (changelogEnv != null) changelog = changelogEnv - - dependencies { - required.project "monkeylib538" - } - } - tasks.modrinth.dependsOn(tasks.modrinthSyncBody) } -configure(subprojects.findAll { it.name == "neoforge" }) { - apply plugin: "net.neoforged.moddev" - apply plugin: "io.github.dexman545.outlet" - apply plugin: "com.modrinth.minotaur" - - outlet { - mcVersionRange = project.supported_minecraft_versions - allowedReleaseTypes = [ReleaseType.RELEASE] - } - - neoForge { - version = project.neoforge_version - - parchment { - mappingsVersion = project.parchment_mappings_version - minecraftVersion = project.minecraft_version - } - - mods { - "${project.name}" { - sourceSet(sourceSets.main) - } - } - - runs { - server { - server() - } +configure(subprojects.findAll { it.hasProperty("project_name") }) { + apply plugin: "maven-publish" - configureEach { - systemProperty "forge.logging.markers", "REGISTRIES" - jvmArgument "-DmeshEnableExamples=true" + publishing { + repositories { + maven { + name = "OffsetMonkey538" + url = "https://maven.offsetmonkey538.top/releases" + credentials { + username = providers.gradleProperty("OffsetMonkey538Username").getOrElse(System.getenv("MAVEN_USERNAME")) + password = providers.gradleProperty("OffsetMonkey538Password").getOrElse(System.getenv("MAVEN_PASSWORD")) + } + authentication { + basic(BasicAuthentication) + } } } - } - - repositories { - maven { - name = "DevAuth" - url = "https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1" - content { - includeGroup "me.djtheredstoner" + publications { + register("maven", MavenPublication) { + afterEvaluate { + artifactId base.archivesName.get() + } + groupId "top.offsetmonkey538.meshlib" + from components.java } } } - - configurations { - runtimeClasspath.extendsFrom localRuntime - } - - dependencies { - // DevAuth - localRuntime "me.djtheredstoner:DevAuth-neoforge:${project.devauth_version}" - - jarJar(api(project(":common"))) - jarJar(additionalRuntimeClasspath("io.netty:netty-codec-http:${rootProject.netty_version}")) - - // Whatever the heck this is for monkeylib - runtimeOnly "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge-1.20.6:${project.monkeylib538_version}+1.20.6" - compileOnly "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge:${project.monkeylib538_version}" - } - - processResources { - final Map properties = [ - "commonVersion": rootProject.versionPrefix, - "modVersion": project.version, - "supportedMinecraftVersions": project.minecraft_version_range, - "currentMonkeylibVersion": project.monkeylib538_version - ] - inputs.properties(properties) - filesMatching("META-INF/neoforge.mods.toml") { - expand(properties) - } - - exclude ".cache/**" - } - - modrinth { - // Main properties - token = System.getenv("MODRINTH_TOKEN") - projectId = "mesh-lib" - gameVersions = outlet.mcVersions() - loaders = ["neoforge"] - - // Version stuff - def customVersionName = System.getenv("VERSION_NAME") - if (customVersionName != null) versionName = customVersionName - - versionNumber = "${project.version}" - - versionType = Boolean.parseBoolean(System.getenv("VERSION_IS_PRERELEASE")) ? "beta" : "release" - - if (rootProject.mod_version.contains("beta")) versionType = "beta" - if (rootProject.mod_version.contains("alpha")) versionType = "alpha" - - // Files - //TODO: uploadFile = remapJar.archiveFile - additionalFiles = [sourcesJar.archiveFile] - - - // Project info - syncBodyFrom = rootProject.file("README.md").text - def changelogEnv = System.getenv("VERSION_CHANGELOG") - if (changelogEnv != null) changelog = changelogEnv - - dependencies { - required.project "monkeylib538" - } - } - tasks.modrinth.dependsOn(tasks.modrinthSyncBody) -} - -configure(subprojects.findAll { it.name == "paper" }) { - apply plugin: "com.gradleup.shadow" - apply plugin: "com.modrinth.minotaur" - apply plugin: "io.github.dexman545.outlet" - apply plugin: "io.papermc.paperweight.userdev" - apply plugin: "xyz.jpenilla.run-paper" - - outlet { - maintainPropertiesFile = false - mcVersionRange = project.supported_minecraft_versions - allowedReleaseTypes = [ReleaseType.RELEASE] - } - - paperweight.reobfArtifactConfiguration = ReobfArtifactConfiguration.getMOJANG_PRODUCTION() - - repositories { - maven { - name = 'PaperMC' - url = 'https://repo.papermc.io/repository/maven-public/' - content { - includeGroup "io.papermc.paper" - } - } - } - - configurations { - common { - canBeResolved = true - canBeConsumed = false - } - api.extendsFrom common - } - - dependencies { - compileOnly "io.papermc.paper:paper-api:${project.paper_version}" - - paperweight.paperDevBundle project.paper_version - - common project(path: ":common", configuration: "shadow") - - /* todo: monke not have paper yetttttttt - // Whatever the heck this is for monkeylib - modRuntimeOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-1.20.5:${rootProject.monkeylib538_version}+1.20.5") { - exclude(group: "io.papermc.paper") - exclude(group: "top.offsetmonkey538.monkeylib538:monkeylib538-fabric") - } - modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${rootProject.monkeylib538_version}") { - exclude(group: "net.fabricmc.fabric-api") - } - */ - } - tasks.build.dependsOn(shadowJar) - - processResources { - final Map properties = [ - "modVersion": rootProject.mod_version, - "currentMonkeylibVersion": project.monkeylib538_version, - "lowestMinecraftVersion": outlet.mcVersions().first() // here's hoping outlet does this in order - ] - inputs.properties(properties) - filesMatching("paper-plugin.yml") { - expand(properties) - } - } - - tasks.runServer { - minecraftVersion(project.minecraft_version) - jvmArgs "-DmeshEnableExamples=true" - } - - modrinth { - // Main properties - token = System.getenv("MODRINTH_TOKEN") - projectId = "mesh-lib" - gameVersions = outlet.mcVersions() - loaders = ["paper"] - - // Version stuff - def customVersionName = System.getenv("VERSION_NAME") - if (customVersionName != null) versionName = customVersionName - - versionNumber = "${project.version}" - - versionType = Boolean.parseBoolean(System.getenv("VERSION_IS_PRERELEASE")) ? "beta" : "release" - - if (rootProject.mod_version.contains("beta")) versionType = "beta" - if (rootProject.mod_version.contains("alpha")) versionType = "alpha" - - // Files - uploadFile = shadowJar - additionalFiles = [sourcesJar.archiveFile] - - - // Project info - syncBodyFrom = rootProject.file("README.md").text - def changelogEnv = System.getenv("VERSION_CHANGELOG") - if (changelogEnv != null) changelog = changelogEnv - - dependencies { - required.project "monkeylib538" - } - } - tasks.modrinth.dependsOn(tasks.modrinthSyncBody) } diff --git a/buildSrc b/buildSrc new file mode 160000 index 0000000..44aa0ff --- /dev/null +++ b/buildSrc @@ -0,0 +1 @@ +Subproject commit 44aa0ffadd024d65f75dd6edfe844d42be91d25f diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000..c2f21a7 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,20 @@ +plugins { + id 'multiloader-base' +} + +repositories { + maven { + name = "Mojang Libraries" + url = "https://libraries.minecraft.net" + content { + includeGroup "com.mojang" + } + } +} + +dependencies { + compileOnlyApi "top.offsetmonkey538.monkeylib538:monkeylib538-common:${rootProject.monkeylib538_version}+common" + compileOnlyApi "io.netty:netty-codec-http:${rootProject.netty_version}" + compileOnly "com.google.guava:guava:${rootProject.guava_version}" + compileOnly "com.mojang:brigadier:${rootProject.brigadier_version}" +} diff --git a/common/gradle.properties b/common/gradle.properties index ff06f8b..e99725d 100644 --- a/common/gradle.properties +++ b/common/gradle.properties @@ -1 +1 @@ -project_name = api +project_name = common diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java index 22f897f..064a458 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java @@ -14,10 +14,10 @@ import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; import top.offsetmonkey538.meshlib.platform.PlatformMain; -import top.offsetmonkey538.monkeylib538.api.command.CommandRegistrationApi; -import top.offsetmonkey538.monkeylib538.api.command.ConfigCommandApi; -import top.offsetmonkey538.monkeylib538.api.lifecycle.ServerLifecycleApi; -import top.offsetmonkey538.monkeylib538.api.log.MonkeyLibLogger; +import top.offsetmonkey538.monkeylib538.common.api.command.CommandRegistrationApi; +import top.offsetmonkey538.monkeylib538.common.api.command.ConfigCommandApi; +import top.offsetmonkey538.monkeylib538.common.api.lifecycle.ServerLifecycleApi; +import top.offsetmonkey538.monkeylib538.common.api.log.MonkeyLibLogger; import top.offsetmonkey538.offsetconfig538.api.config.ConfigHolder; import top.offsetmonkey538.offsetconfig538.api.config.ConfigManager; import top.offsetmonkey538.offsetconfig538.api.event.OffsetConfig538Events; @@ -33,7 +33,7 @@ private MESHLib() {} /** * String modid for this mod */ - public static final String MOD_ID = "mesh-lib"; + public static final String MOD_ID = "meshlib"; /** * Logger instance used by this mod */ diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java index 57374cd..34ff759 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java @@ -2,7 +2,7 @@ import blue.endless.jankson.Comment; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.monkeylib538.api.platform.PlatformUtil; +import top.offsetmonkey538.monkeylib538.common.api.platform.LoaderUtil; import top.offsetmonkey538.offsetconfig538.api.config.Config; import java.nio.file.Path; @@ -19,7 +19,7 @@ public final class MESHLibConfig implements Config { @Override public @NotNull Path getConfigDirPath() { - return PlatformUtil.getConfigDir(); + return LoaderUtil.getConfigDir(); } @Override diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java index e2b92be..56416be 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java @@ -19,8 +19,8 @@ import top.offsetmonkey538.meshlib.api.rule.HttpRule; import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; -import top.offsetmonkey538.monkeylib538.api.command.CommandAbstractionApi; -import top.offsetmonkey538.monkeylib538.api.platform.PlatformUtil; +import top.offsetmonkey538.monkeylib538.common.api.command.CommandAbstractionApi; +import top.offsetmonkey538.monkeylib538.common.api.platform.LoaderUtil; import java.io.IOException; import java.nio.file.Files; @@ -32,14 +32,14 @@ import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; -import static top.offsetmonkey538.monkeylib538.api.command.CommandAbstractionApi.literal; +import static top.offsetmonkey538.monkeylib538.common.api.command.CommandAbstractionApi.literal; public final class RouterConfigHandler { private RouterConfigHandler() { } - private static final Path ROUTERS_DIR = PlatformUtil.getConfigDir().resolve(MOD_ID).resolve("routers").normalize().toAbsolutePath(); + private static final Path ROUTERS_DIR = LoaderUtil.getConfigDir().resolve(MOD_ID).resolve("routers").normalize().toAbsolutePath(); public static LiteralArgumentBuilder createExampleConfigCommand() { final LiteralArgumentBuilder exampleCommand = literal("example").requires(CommandAbstractionApi::isOp); diff --git a/common/src/main/resources/META-INF/neoforge.mods.toml b/common/src/main/resources/META-INF/neoforge.mods.toml deleted file mode 100644 index 18e4a1e..0000000 --- a/common/src/main/resources/META-INF/neoforge.mods.toml +++ /dev/null @@ -1,14 +0,0 @@ -modLoader = "javafml" -loaderVersion = "[1,)" -license="MIT" - -issueTrackerURL="https://github.com/OffsetMods538/MonkeyLib538/issues" - - -[[mods]] -modId="mesh_lib_common" -displayName="MESH Lib Common" -version="${modVersion}" -displayURL="https://modrinth.com/mod/monkeylib538" -authors="OffsetMonkey538" -description="A library mod for OffsetMonkey538s mods" diff --git a/fabric/src/main/resources/assets/mesh-lib/icon.png b/common/src/main/resources/assets/mesh-lib/icon.png similarity index 100% rename from fabric/src/main/resources/assets/mesh-lib/icon.png rename to common/src/main/resources/assets/mesh-lib/icon.png diff --git a/fabric/gradle.properties b/fabric/gradle.properties deleted file mode 100644 index 23ef475..0000000 --- a/fabric/gradle.properties +++ /dev/null @@ -1,20 +0,0 @@ -project_name = fabric - -# Fabric -## Check at https://fabricmc.net/develop -## These should be automatically updated, unless the environment -## variable "IS_PRODUCTION_BUILD" is set. -minecraft_version = 1.21.4 -yarn_version = 1.21.4+build.8 -loader_version = 0.18.0 - -# Dependencies -## DevAuth, check at https://github.com/DJtheRedstoner/DevAuth -devauth_version = 1.2.1 - -## Fabric API, only used at runtime for monkeylib command -fapi_version = 0.119.4+1.21.4 - - -# Mod Properties TODO -supported_minecraft_versions = * diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json deleted file mode 100644 index d166a8f..0000000 --- a/fabric/src/main/resources/fabric.mod.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "schemaVersion": 1, - "id": "mesh-lib", - "version": "${modVersion}", - "name": "MESH Lib", - "description": "Easy to use library for hosting an HTTP server on the Minecraft server's port", - "authors": [ - "OffsetMonkey538" - ], - "contact": { - "sources": "https://github.com/OffsetMods538/MESH-Lib", - "issues": "https://github.com/OffsetMods538/MESH-Lib/issues", - "homepage": "https://github.com/OffsetMods538/MESH-Lib" - }, - "license": "MIT", - "icon": "assets/mesh-lib/icon.png", - "environment": "server", - "entrypoints": { - "server": [ - "top.offsetmonkey538.meshlib.platform.fabric.FabricPlatformMain" - ] - }, - "mixins": [ - "mesh-lib.mixins.json" - ], - "depends": { - "minecraft": "${supportedMinecraftVersions}", - "monkeylib538": ">=${currentMonkeylibVersion}" - } -} diff --git a/gradle.properties b/gradle.properties index 43c16d2..1ff2ebe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,13 +3,31 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-alpha.0.1761395863791+bd100da +monkeylib538_version = 3.0.0-beta.2.1769251134126+b032519 ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.82.Final ## Bundeled with Minecraft, common needs to use guava_version = 33.4.0-jre ## Bundeled with Minecraft, common needs to use brigadier_version = 1.3.10 +## DevAuth, check at https://github.com/DJtheRedstoner/DevAuth +devauth_version = 1.2.2 + +## Gradle plugins +neoforged_moddev = 2.0.+ +fabric_loom = 1.14-SNAPSHOT +papermc_paperweight_userdev = 2.0.0-beta.19 +jpenilla_run_task = 3.0.2 +gradleup_shadow = 9.3.1 +jpenilla_resource_factory = 1.3.1 +dexman_outlet = 1.6.1 +modrinth_minotaur = 2.+ # Mod Properties mod_version = 2.0.0-alpha.0 +mod_name = MESH Lib +mod_id = meshlib +mod_description = Easy to use library for hosting an HTTP server on the Minecraft server's port +mod_website = https://github.com/OffsetMods538/MESH-Lib +mod_issues = https://github.com/OffsetMods538/MESH-Lib/issues +mod_sources = https://github.com/OffsetMods538/MESH-Lib diff --git a/jitpack.yml b/jitpack.yml deleted file mode 100644 index 9c87873..0000000 --- a/jitpack.yml +++ /dev/null @@ -1,4 +0,0 @@ -jdk: - - openjdk21 -env: - DISABLE_PROPERTIES_UPDATE: true \ No newline at end of file diff --git a/loader/fabric/1.21.1/gradle.properties b/loader/fabric/1.21.1/gradle.properties new file mode 100644 index 0000000..b5dde0d --- /dev/null +++ b/loader/fabric/1.21.1/gradle.properties @@ -0,0 +1,14 @@ +project_name = fabric-1.21.1 +commonModdedVersion = 1.21.1 + +monkeylib538_suffix = 1.21 + +# Minecraft version +minecraft_version = 1.21.1 +# TODO: max version? +supported_minecraft_versions = >=1.21.1 + +# These should be automatically updated, unless the environment +# variable "DISABLE_PROPERTIES_UPDATE" is set. +loader_version = 0.18.4 +fapi_version = 0.116.8+1.21.1 diff --git a/loader/fabric/build.gradle b/loader/fabric/build.gradle new file mode 100644 index 0000000..580e6d6 --- /dev/null +++ b/loader/fabric/build.gradle @@ -0,0 +1,48 @@ +import dex.plugins.outlet.v2.util.ReleaseType +import xyz.jpenilla.resourcefactory.fabric.Environment + +plugins { + id 'multiloader-fabric' +} + +allprojects { + fabricModJson { + mitLicense() + author("OffsetMonkey538") + contact { + extra = ["discord": "https://discord.offsetmonkey538.top"] + } + environment = Environment.SERVER + serverEntrypoint("top.offsetmonkey538.meshlib.platform.fabric.FabricPlatformMain") + mixin("mesh-lib.modded.mixins.json") + + depends("fabric-api", "*") + depends("monkeylib538", ">=${rootProject.monkeylib538_version}") + } + + // Gotta have this here cause I can't import it in buildSrc + outlet.allowedReleaseTypes = Set.of(ReleaseType.RELEASE) + + dependencies { + modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fapi_version}" + + modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${rootProject.monkeylib538_version}+fabric") { + exclude(group: "net.fabricmc.fabric-api") + } + } +} + +subprojects { + dependencies { + modImplementation("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+fabric+${project.monkeylib538_suffix}") { + exclude(group: "net.fabricmc.fabric-api") + } + } + + modrinth { + dependencies { + required.project "fabric-api" + required.project "monkeylib538" + } + } +} diff --git a/loader/fabric/gradle.properties b/loader/fabric/gradle.properties new file mode 100644 index 0000000..ed59ba3 --- /dev/null +++ b/loader/fabric/gradle.properties @@ -0,0 +1,10 @@ +project_name = fabric + +# Minecraft version +minecraft_version = 1.21.11 +supported_minecraft_versions = * + +# These should be automatically updated, unless the environment +# variable "DISABLE_PROPERTIES_UPDATE" is set. +loader_version = 0.18.4 +fapi_version = 0.141.2+1.21.11 diff --git a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java similarity index 100% rename from fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java rename to loader/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java diff --git a/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java similarity index 100% rename from fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java rename to loader/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java diff --git a/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain b/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain similarity index 100% rename from fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain rename to loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain diff --git a/fabric/src/main/resources/mesh-lib.mixins.json b/loader/fabric/src/main/resources/mesh-lib.mixins.json similarity index 100% rename from fabric/src/main/resources/mesh-lib.mixins.json rename to loader/fabric/src/main/resources/mesh-lib.mixins.json diff --git a/loader/neoforge/1.21.1/gradle.properties b/loader/neoforge/1.21.1/gradle.properties new file mode 100644 index 0000000..dd0b4f5 --- /dev/null +++ b/loader/neoforge/1.21.1/gradle.properties @@ -0,0 +1,15 @@ +project_name = neoforge-1.21.1 +commonModdedVersion = 1.21.1 + +monkeylib538_suffix = 1.21 + +# Minecraft version +minecraft_version = 1.21.1 +# TODO: max version? +supported_minecraft_versions = >=1.21.1 +# TODO: max version? +minecraft_version_range = [1.21.1,) + +neoforge_version = 21.1.218 +parchment_minecraft_version = 1.21.1 +parchment_mappings_version = 2024.11.17 diff --git a/loader/neoforge/build.gradle b/loader/neoforge/build.gradle new file mode 100644 index 0000000..bf15b21 --- /dev/null +++ b/loader/neoforge/build.gradle @@ -0,0 +1,37 @@ +import dex.plugins.outlet.v2.util.ReleaseType + +plugins { + id 'multiloader-neoforge' +} + +allprojects { + neoForgeModsToml { + mitLicense() + mod(rootProject.neo_mod_id) { + authors = "OffsetMonkey538" + dependencies { + required("monkeylib538", "[${rootProject.monkeylib538_version},)") + } + } + mixin("mesh-lib.modded.mixins.json") + } + + dependencies { + compileOnly "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge:${rootProject.monkeylib538_version}+neoforge" + } +} + +subprojects { + // Gotta have this here cause I can't import it in buildSrc + outlet.allowedReleaseTypes = Set.of(ReleaseType.RELEASE) + + dependencies { + implementation "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+neoforge+${project.monkeylib538_suffix}" + } + + modrinth { + dependencies { + required.project "monkeylib538" + } + } +} diff --git a/loader/neoforge/gradle.properties b/loader/neoforge/gradle.properties new file mode 100644 index 0000000..0881a7e --- /dev/null +++ b/loader/neoforge/gradle.properties @@ -0,0 +1,8 @@ +project_name = neoforge + +# Minecraft version +minecraft_version = 1.21.11 + +neoforge_version = 21.11.37-beta +parchment_minecraft_version = 1.21.11 +parchment_mappings_version = 2025.12.20 diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java similarity index 100% rename from neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java rename to loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java similarity index 100% rename from neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java rename to loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java diff --git a/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java similarity index 100% rename from neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java rename to loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java diff --git a/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain b/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain similarity index 100% rename from neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain rename to loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain diff --git a/neoforge/src/main/resources/mesh-lib.mixins.json b/loader/neoforge/src/main/resources/mesh-lib.mixins.json similarity index 100% rename from neoforge/src/main/resources/mesh-lib.mixins.json rename to loader/neoforge/src/main/resources/mesh-lib.mixins.json diff --git a/loader/paper/1.21.1/gradle.properties b/loader/paper/1.21.1/gradle.properties new file mode 100644 index 0000000..ea42a7c --- /dev/null +++ b/loader/paper/1.21.1/gradle.properties @@ -0,0 +1,10 @@ +project_name = paper-1.21.1 + +monkeylib538_suffix = 1.21 + +# Minecraft version +minecraft_version = 1.21.1 +# TODO: max version? +supported_minecraft_versions = >=1.21.1 + +paper_version = 1.21.1-R0.1-SNAPSHOT diff --git a/loader/paper/build.gradle b/loader/paper/build.gradle new file mode 100644 index 0000000..a73698e --- /dev/null +++ b/loader/paper/build.gradle @@ -0,0 +1,40 @@ +import dex.plugins.outlet.v2.util.ReleaseType +import xyz.jpenilla.resourcefactory.paper.PaperPluginYaml + +plugins { + id 'multiloader-paper' +} + +allprojects { + // Gotta have this here cause I can't import it in buildSrc + outlet.allowedReleaseTypes = Set.of(ReleaseType.RELEASE) + + paperPluginYaml { + main = "top.offsetmonkey538.meshlib.platform.paper.MeshLibPlugin" + authors.add("OffsetMonkey538") + load = "STARTUP" + dependencies { + server("MonkeyLib538", PaperPluginYaml.Load.BEFORE, true, true) + } + } + + dependencies { + compileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-paper:${rootProject.monkeylib538_version}+paper") + } +} + +subprojects { + dependencies { + shadow implementation("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+fabric+${project.monkeylib538_suffix}") + } + + tasks.runServer { + jvmArgs "-DmeshEnableExamples=true" + } + + modrinth { + dependencies { + required.project "monkeylib538" + } + } +} diff --git a/loader/paper/gradle.properties b/loader/paper/gradle.properties new file mode 100644 index 0000000..2f5d4ca --- /dev/null +++ b/loader/paper/gradle.properties @@ -0,0 +1,7 @@ +project_name = paper + +# Minecraft version +minecraft_version = 1.21.11 +supported_minecraft_versions = * + +paper_version = 1.21.11-R0.1-SNAPSHOT diff --git a/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java similarity index 100% rename from paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java rename to loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java diff --git a/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java similarity index 100% rename from paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java rename to loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java diff --git a/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain b/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain similarity index 100% rename from paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain rename to loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain diff --git a/modded/1.21.1/gradle.properties b/modded/1.21.1/gradle.properties new file mode 100644 index 0000000..af5bfc2 --- /dev/null +++ b/modded/1.21.1/gradle.properties @@ -0,0 +1,6 @@ +project_name = modded-1.21.1 + +monkeylib538_suffix = 1.21 + +# Minecraft version +minecraft_version = 1.21.1 diff --git a/modded/build.gradle b/modded/build.gradle new file mode 100644 index 0000000..83d424d --- /dev/null +++ b/modded/build.gradle @@ -0,0 +1,15 @@ +plugins { + id 'multiloader-modded' +} + +allprojects { + dependencies { + modCompileOnly "top.offsetmonkey538.monkeylib538:monkeylib538-modded:${rootProject.monkeylib538_version}+modded" + } +} + +subprojects { + dependencies { + modCompileOnly "top.offsetmonkey538.monkeylib538:monkeylib538-modded-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+modded+${project.monkeylib538_suffix}" + } +} diff --git a/modded/gradle.properties b/modded/gradle.properties new file mode 100644 index 0000000..1df7c91 --- /dev/null +++ b/modded/gradle.properties @@ -0,0 +1,4 @@ +project_name = modded + +# Minecraft version +minecraft_version = 1.21.11 diff --git a/neoforge/gradle.properties b/neoforge/gradle.properties deleted file mode 100644 index 5f2330c..0000000 --- a/neoforge/gradle.properties +++ /dev/null @@ -1,15 +0,0 @@ -project_name = neoforge - -# Fabric TODO: this isn't fabric... -## Check at https://fabricmc.net/develop -minecraft_version = 1.21.4 -neoforge_version = 21.4.154 -parchment_mappings_version = 2025.03.23 - -# Dependencies -## DevAuth, check at https://github.com/DJtheRedstoner/DevAuth -devauth_version = 1.2.1 - -#todo: idfk -minecraft_version_range = [1,) -supported_minecraft_versions = * diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml deleted file mode 100644 index 3a6f7ea..0000000 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ /dev/null @@ -1,32 +0,0 @@ -modLoader = "javafml" -loaderVersion = "[1,)" -license="MIT" - -issueTrackerURL="https://github.com/OffsetMods538/MESH-Lib/issues" - - -[[mods]] -modId="mesh_lib" -displayName="MESH Lib" -version="${modVersion}" -displayURL="https://modrinth.com/mod/mesh-lib" -authors="OffsetMonkey538" -description="A library mod for OffsetMonkey538s mods" - -[[mixins]] -config="mesh-lib.mixins.json" - - -[[dependencies.mesh_lib]] -modId="minecraft" -type="required" -versionRange="${supportedMinecraftVersions}" -ordering="NONE" -side="BOTH" - -[[dependencies.mesh_lib]] -modId="monkeylib538_common_neoforge" -type="required" -versionRange="[${currentMonkeylibVersion},)" -ordering="NONE" -side="BOTH" diff --git a/paper/gradle.properties b/paper/gradle.properties deleted file mode 100644 index 6cca3f3..0000000 --- a/paper/gradle.properties +++ /dev/null @@ -1,8 +0,0 @@ -project_name = paper - -## no idea where to get this -minecraft_version = 1.21.4 -paper_version = 1.21.4-R0.1-SNAPSHOT - -# Mod Properties TODO -supported_minecraft_versions = * \ No newline at end of file diff --git a/paper/src/main/resources/paper-plugin.yml b/paper/src/main/resources/paper-plugin.yml deleted file mode 100644 index 4cfa555..0000000 --- a/paper/src/main/resources/paper-plugin.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: MESH-Lib -version: '${modVersion}' -main: top.offsetmonkey538.meshlib.platform.paper.MeshLibPlugin -description: Easy to use library for hosting an HTTP server on the Minecraft server's port -author: OffsetMonkey538 -website: https://github.com/OffsetMods538/MESH-Lib -api-version: '${lowestMinecraftVersion}' -load: STARTUP diff --git a/settings.gradle b/settings.gradle index 3df744c..2e77994 100644 --- a/settings.gradle +++ b/settings.gradle @@ -14,6 +14,14 @@ rootProject.name = "mesh-lib" include "common" -include "fabric" -include "neoforge" -include "paper" +include "modded" +include "modded:1.21.1" + +include "loader:fabric" +include "loader:fabric:1.21.1" + +include "loader:neoforge" +include "loader:neoforge:1.21.1" + +include "loader:paper" +include "loader:paper:1.21.1" From 0ad4ebe3cf1f1de2e60f9a85c5946fb4cd64b8e7 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Sun, 25 Jan 2026 17:33:20 +0200 Subject: [PATCH 52/78] Refactoring --- .../meshlib/{ => common}/MESHLib.java | 28 +++++++++---------- .../{ => common}/api/handler/HttpHandler.java | 8 +++--- .../api/handler/HttpHandlerTypeRegistry.java | 7 +++-- .../handlers/StaticContentHandler.java | 10 +++---- .../handlers/StaticDirectoryHandler.java | 12 ++++---- .../handler/handlers/StaticFileHandler.java | 10 +++---- .../{ => common}/api/package-info.java | 2 +- .../{ => common}/api/router/HttpRouter.java | 14 +++++----- .../api/router/HttpRouterRegistry.java | 4 +-- .../{ => common}/api/rule/HttpRule.java | 6 ++-- .../api/rule/HttpRuleTypeRegistry.java | 7 +++-- .../api/rule/rules/DomainHttpRule.java | 6 ++-- .../api/rule/rules/PathHttpRule.java | 6 ++-- .../api/util/HttpResponseUtil.java | 4 +-- .../{ => common}/config/MESHLibConfig.java | 4 +-- .../config/RouterConfigHandler.java | 24 ++++++++-------- .../example/ExampleHttpHandler.java | 10 +++---- .../{ => common}/example/ExampleMain.java | 12 ++++---- .../meshlib/common/example/package-info.java | 4 +++ .../impl/HttpRouterRegistryImpl.java | 6 ++-- .../{ => common}/impl/MainHttpHandler.java | 14 +++++----- .../{ => common}/impl/ProtocolHandler.java | 6 ++-- .../router/HttpHandlerTypeRegistryImpl.java | 8 +++--- .../router/rule/HttpRuleTypeRegistryImpl.java | 6 ++-- .../impl/util/HttpResponseUtilImpl.java | 8 +++--- .../{ => common}/platform/PlatformMain.java | 4 +-- .../meshlib/example/package-info.java | 4 --- .../fabric/mixin/ServerNetworkIoMixin.java | 8 +++--- .../platform}/FabricPlatformMain.java | 6 ++-- ...ey538.meshlib.common.platform.PlatformMain | 1 + ...setmonkey538.meshlib.platform.PlatformMain | 1 - .../src/main/resources/mesh-lib.mixins.json | 4 +-- .../neoforge/NeoforgeInitializer.java | 4 +-- .../mixin/ServerConnectionListenerMixin.java | 8 +++--- .../platform}/NeoforgePlatformMain.java | 4 +-- ...ey538.meshlib.common.platform.PlatformMain | 1 + ...setmonkey538.meshlib.platform.PlatformMain | 1 - .../src/main/resources/mesh-lib.mixins.json | 4 +-- loader/paper/build.gradle | 2 +- .../platform}/PaperPlatformMain.java | 19 ++++++++++--- .../meshlib/platform/paper/MeshLibPlugin.java | 13 --------- ...ey538.meshlib.common.platform.PlatformMain | 1 + ...setmonkey538.meshlib.platform.PlatformMain | 1 - 43 files changed, 157 insertions(+), 155 deletions(-) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/MESHLib.java (76%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/handler/HttpHandler.java (75%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/handler/HttpHandlerTypeRegistry.java (87%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/handler/handlers/StaticContentHandler.java (76%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/handler/handlers/StaticDirectoryHandler.java (95%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/handler/handlers/StaticFileHandler.java (79%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/package-info.java (74%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/router/HttpRouter.java (89%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/router/HttpRouterRegistry.java (94%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/rule/HttpRule.java (50%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/rule/HttpRuleTypeRegistry.java (87%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/rule/rules/DomainHttpRule.java (86%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/rule/rules/PathHttpRule.java (85%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/api/util/HttpResponseUtil.java (98%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/config/MESHLibConfig.java (87%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/config/RouterConfigHandler.java (91%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/example/ExampleHttpHandler.java (83%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/example/ExampleMain.java (69%) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/example/package-info.java rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/impl/HttpRouterRegistryImpl.java (81%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/impl/MainHttpHandler.java (86%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/impl/ProtocolHandler.java (93%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/impl/router/HttpHandlerTypeRegistryImpl.java (89%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/impl/router/rule/HttpRuleTypeRegistryImpl.java (91%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/impl/util/HttpResponseUtilImpl.java (94%) rename common/src/main/java/top/offsetmonkey538/meshlib/{ => common}/platform/PlatformMain.java (81%) delete mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/example/package-info.java rename loader/fabric/src/main/java/top/offsetmonkey538/meshlib/{platform => }/fabric/mixin/ServerNetworkIoMixin.java (78%) rename loader/fabric/src/main/java/top/offsetmonkey538/meshlib/{platform/fabric => fabric/platform}/FabricPlatformMain.java (75%) create mode 100644 loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain delete mode 100644 loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain rename loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/{platform => }/neoforge/NeoforgeInitializer.java (75%) rename loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/{platform => }/neoforge/mixin/ServerConnectionListenerMixin.java (78%) rename loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/{platform/neoforge => neoforge/platform}/NeoforgePlatformMain.java (78%) create mode 100644 loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain delete mode 100644 loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain rename loader/paper/src/main/java/top/offsetmonkey538/meshlib/{platform/paper => paper/platform}/PaperPlatformMain.java (64%) delete mode 100644 loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java create mode 100644 loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain delete mode 100644 loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java similarity index 76% rename from common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java index 064a458..021e9ba 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java @@ -1,19 +1,19 @@ -package top.offsetmonkey538.meshlib; +package top.offsetmonkey538.meshlib.common; import com.google.common.base.Stopwatch; -import top.offsetmonkey538.meshlib.api.handler.handlers.StaticContentHandler; -import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; -import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; -import top.offsetmonkey538.meshlib.api.router.HttpRouter; -import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; -import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; -import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.config.MESHLibConfig; -import top.offsetmonkey538.meshlib.config.RouterConfigHandler; -import top.offsetmonkey538.meshlib.example.ExampleMain; -import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; -import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; -import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticContentHandler; +import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticDirectoryHandler; +import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticFileHandler; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouter; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.rules.DomainHttpRule; +import top.offsetmonkey538.meshlib.common.api.rule.rules.PathHttpRule; +import top.offsetmonkey538.meshlib.common.config.MESHLibConfig; +import top.offsetmonkey538.meshlib.common.config.RouterConfigHandler; +import top.offsetmonkey538.meshlib.common.example.ExampleMain; +import top.offsetmonkey538.meshlib.common.platform.PlatformMain; import top.offsetmonkey538.monkeylib538.common.api.command.CommandRegistrationApi; import top.offsetmonkey538.monkeylib538.common.api.command.ConfigCommandApi; import top.offsetmonkey538.monkeylib538.common.api.lifecycle.ServerLifecycleApi; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java similarity index 75% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java index 1fe8332..954ccc6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java @@ -1,10 +1,10 @@ -package top.offsetmonkey538.meshlib.api.handler; +package top.offsetmonkey538.meshlib.common.api.handler; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; -import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.example.ExampleHttpHandler; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.common.example.ExampleHttpHandler; import org.jetbrains.annotations.NotNull; /** diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandlerTypeRegistry.java similarity index 87% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandlerTypeRegistry.java index 3b7b2ab..6296883 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/HttpHandlerTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandlerTypeRegistry.java @@ -1,8 +1,9 @@ -package top.offsetmonkey538.meshlib.api.handler; +package top.offsetmonkey538.meshlib.common.api.handler; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.common.impl.router.HttpHandlerTypeRegistryImpl; import top.offsetmonkey538.offsetconfig538.api.event.Event; import java.util.function.Function; @@ -41,7 +42,7 @@ static void clear() { * Initially called while the server is starting, so make sure to register your handler before that! *

*

- * Called before the {@link top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry#HTTP_ROUTER_REGISTRATION_EVENT HTTP_ROUTER_REGISTRATION_EVENT} event. + * Called before the {@link HttpRouterRegistry#HTTP_ROUTER_REGISTRATION_EVENT HTTP_ROUTER_REGISTRATION_EVENT} event. *

*/ Event HTTP_HANDLER_REGISTRATION_EVENT = Event.createEvent(HttpHandlerRegistrationEvent.class, handlers -> registry -> { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticContentHandler.java similarity index 76% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticContentHandler.java index 9c06724..1a232e9 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticContentHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticContentHandler.java @@ -1,13 +1,13 @@ -package top.offsetmonkey538.meshlib.api.handler.handlers; +package top.offsetmonkey538.meshlib.common.api.handler.handlers; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; -import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil; public record StaticContentHandler(String content) implements HttpHandler { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticDirectoryHandler.java similarity index 95% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticDirectoryHandler.java index a9d3893..ef49899 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticDirectoryHandler.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.api.handler.handlers; +package top.offsetmonkey538.meshlib.common.api.handler.handlers; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -9,9 +9,9 @@ import io.netty.util.CharsetUtil; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; -import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; import java.io.IOException; import java.net.URI; @@ -28,8 +28,8 @@ import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; -import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; -import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.*; +import static top.offsetmonkey538.meshlib.common.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil.*; public record StaticDirectoryHandler(Path baseDir, boolean allowDirectoryList) implements HttpHandler { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticFileHandler.java similarity index 79% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticFileHandler.java index 7d7e360..f27c31a 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/handler/handlers/StaticFileHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticFileHandler.java @@ -1,13 +1,13 @@ -package top.offsetmonkey538.meshlib.api.handler.handlers; +package top.offsetmonkey538.meshlib.common.api.handler.handlers; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.*; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; -import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil; import java.nio.file.Path; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/package-info.java similarity index 74% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/package-info.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/package-info.java index 9e511da..f34abe6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/package-info.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/package-info.java @@ -3,4 +3,4 @@ *
* There should be no need to interact with anything outside of this package */ -package top.offsetmonkey538.meshlib.api; \ No newline at end of file +package top.offsetmonkey538.meshlib.common.api; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouter.java similarity index 89% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouter.java index 9977ad6..622e2b6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouter.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouter.java @@ -1,15 +1,15 @@ -package top.offsetmonkey538.meshlib.api.router; +package top.offsetmonkey538.meshlib.common.api.router; import blue.endless.jankson.Jankson; import blue.endless.jankson.JsonObject; import blue.endless.jankson.JsonPrimitive; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; -import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; -import top.offsetmonkey538.meshlib.impl.router.HttpHandlerTypeRegistryImpl; -import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.common.impl.router.HttpHandlerTypeRegistryImpl; +import top.offsetmonkey538.meshlib.common.impl.router.rule.HttpRuleTypeRegistryImpl; public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouterRegistry.java similarity index 94% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouterRegistry.java index d909efe..d17867d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/router/HttpRouterRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouterRegistry.java @@ -1,8 +1,8 @@ -package top.offsetmonkey538.meshlib.api.router; +package top.offsetmonkey538.meshlib.common.api.router; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.impl.HttpRouterRegistryImpl; +import top.offsetmonkey538.meshlib.common.impl.HttpRouterRegistryImpl; import top.offsetmonkey538.offsetconfig538.api.event.Event; /** diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRule.java similarity index 50% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRule.java index 82180e3..aca429a 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRule.java @@ -1,6 +1,8 @@ -package top.offsetmonkey538.meshlib.api.rule; +package top.offsetmonkey538.meshlib.common.api.rule; import io.netty.handler.codec.http.FullHttpRequest; +import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticDirectoryHandler; +import top.offsetmonkey538.meshlib.common.api.rule.rules.PathHttpRule; public interface HttpRule { boolean matches(final FullHttpRequest request); @@ -8,7 +10,7 @@ public interface HttpRule { /** * Should return the uri as if this rule's first match is the root *

- * {@link top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule PathHttpRule} removes the matched path so {@link top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler StaticDirectoryHandler} can correctly find the files based on the uri + * {@link PathHttpRule PathHttpRule} removes the matched path so {@link StaticDirectoryHandler StaticDirectoryHandler} can correctly find the files based on the uri *

* * @param uri the uri to modify diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRuleTypeRegistry.java similarity index 87% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRuleTypeRegistry.java index 566b596..ac67db6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/HttpRuleTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRuleTypeRegistry.java @@ -1,8 +1,9 @@ -package top.offsetmonkey538.meshlib.api.rule; +package top.offsetmonkey538.meshlib.common.api.rule; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.impl.router.rule.HttpRuleTypeRegistryImpl; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.common.impl.router.rule.HttpRuleTypeRegistryImpl; import top.offsetmonkey538.offsetconfig538.api.event.Event; import java.util.function.Function; @@ -40,7 +41,7 @@ static void clear() { * Initially called while the server is starting, so make sure to register your handler before that! *

*

- * Called before the {@link top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry#HTTP_ROUTER_REGISTRATION_EVENT HTTP_ROUTER_REGISTRATION_EVENT} event. + * Called before the {@link HttpRouterRegistry#HTTP_ROUTER_REGISTRATION_EVENT HTTP_ROUTER_REGISTRATION_EVENT} event. *

*/ Event HTTP_RULE_REGISTRATION_EVENT = Event.createEvent(HttpRuleRegistrationEvent.class, handlers -> registry -> { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/DomainHttpRule.java similarity index 86% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/DomainHttpRule.java index dadd63c..26ade4b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/DomainHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/DomainHttpRule.java @@ -1,10 +1,10 @@ -package top.offsetmonkey538.meshlib.api.rule.rules; +package top.offsetmonkey538.meshlib.common.api.rule.rules; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; import org.jetbrains.annotations.ApiStatus; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; /** * @param domain i.e. map.example.com diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/PathHttpRule.java similarity index 85% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/PathHttpRule.java index 2f4a559..b0b919d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/rule/rules/PathHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/PathHttpRule.java @@ -1,9 +1,9 @@ -package top.offsetmonkey538.meshlib.api.rule.rules; +package top.offsetmonkey538.meshlib.common.api.rule.rules; import io.netty.handler.codec.http.FullHttpRequest; import org.jetbrains.annotations.ApiStatus; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; /** * @param path i.e. /map -> example.com/map diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/HttpResponseUtil.java similarity index 98% rename from common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/HttpResponseUtil.java index 09a409b..2868149 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/api/util/HttpResponseUtil.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/HttpResponseUtil.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.api.util; +package top.offsetmonkey538.meshlib.common.api.util; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; @@ -7,7 +7,7 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import top.offsetmonkey538.meshlib.impl.util.HttpResponseUtilImpl; +import top.offsetmonkey538.meshlib.common.impl.util.HttpResponseUtilImpl; import java.io.IOException; import java.nio.file.Path; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java similarity index 87% rename from common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java index 34ff759..16ab1d9 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/MESHLibConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.config; +package top.offsetmonkey538.meshlib.common.config; import blue.endless.jankson.Comment; import org.jetbrains.annotations.NotNull; @@ -7,7 +7,7 @@ import java.nio.file.Path; -import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; +import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; public final class MESHLibConfig implements Config { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java similarity index 91% rename from common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java index 56416be..b9d109c 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/config/RouterConfigHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.config; +package top.offsetmonkey538.meshlib.common.config; import blue.endless.jankson.Jankson; import blue.endless.jankson.JsonElement; @@ -10,15 +10,15 @@ import com.mojang.brigadier.context.CommandContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Unmodifiable; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; -import top.offsetmonkey538.meshlib.api.handler.handlers.StaticContentHandler; -import top.offsetmonkey538.meshlib.api.handler.handlers.StaticDirectoryHandler; -import top.offsetmonkey538.meshlib.api.handler.handlers.StaticFileHandler; -import top.offsetmonkey538.meshlib.api.router.HttpRouter; -import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; -import top.offsetmonkey538.meshlib.api.rule.rules.PathHttpRule; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticContentHandler; +import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticDirectoryHandler; +import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticFileHandler; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouter; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.common.api.rule.rules.DomainHttpRule; +import top.offsetmonkey538.meshlib.common.api.rule.rules.PathHttpRule; import top.offsetmonkey538.monkeylib538.common.api.command.CommandAbstractionApi; import top.offsetmonkey538.monkeylib538.common.api.platform.LoaderUtil; @@ -30,8 +30,8 @@ import java.util.function.Consumer; import java.util.stream.Stream; -import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; -import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; +import static top.offsetmonkey538.meshlib.common.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; import static top.offsetmonkey538.monkeylib538.common.api.command.CommandAbstractionApi.literal; public final class RouterConfigHandler { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleHttpHandler.java similarity index 83% rename from common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleHttpHandler.java index 3433c81..ee041d6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleHttpHandler.java @@ -1,13 +1,13 @@ -package top.offsetmonkey538.meshlib.example; +package top.offsetmonkey538.meshlib.common.example; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; -import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil; /** * An example {@link HttpHandler} implementation to learn from diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleMain.java similarity index 69% rename from common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleMain.java index 1b93c48..52fbbc4 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleMain.java @@ -1,11 +1,11 @@ -package top.offsetmonkey538.meshlib.example; +package top.offsetmonkey538.meshlib.common.example; -import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.api.router.HttpRouter; -import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; -import top.offsetmonkey538.meshlib.api.rule.rules.DomainHttpRule; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouter; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.rules.DomainHttpRule; -import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.common.MESHLib.LOGGER; /** * Initializer for the example handlers diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/example/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/example/package-info.java new file mode 100644 index 0000000..33c4ae1 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/example/package-info.java @@ -0,0 +1,4 @@ +/** + * This contains examples for how to implement {@link top.offsetmonkey538.meshlib.common.api.handler.HttpHandler HttpHandler}s + */ +package top.offsetmonkey538.meshlib.common.example; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/HttpRouterRegistryImpl.java similarity index 81% rename from common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/impl/HttpRouterRegistryImpl.java index 911dc20..ff61092 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/HttpRouterRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/HttpRouterRegistryImpl.java @@ -1,8 +1,8 @@ -package top.offsetmonkey538.meshlib.impl; +package top.offsetmonkey538.meshlib.common.impl; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.router.HttpRouter; -import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouter; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; import java.util.HashMap; import java.util.Map; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java similarity index 86% rename from common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java index cfe90ce..4e390eb 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/MainHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java @@ -1,20 +1,20 @@ -package top.offsetmonkey538.meshlib.impl; +package top.offsetmonkey538.meshlib.common.impl; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.*; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; -import top.offsetmonkey538.meshlib.api.router.HttpRouter; -import top.offsetmonkey538.meshlib.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouter; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; import java.util.ArrayList; import java.util.List; import java.util.Map; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; -import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; -import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; -import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; +import static top.offsetmonkey538.meshlib.common.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; +import static top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil.sendError; /** * Main HTTP handler for MESH. diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/ProtocolHandler.java similarity index 93% rename from common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/impl/ProtocolHandler.java index 333987b..edb369b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/ProtocolHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/ProtocolHandler.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.impl; +package top.offsetmonkey538.meshlib.common.impl; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; @@ -8,8 +8,8 @@ import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.stream.ChunkedWriteHandler; -import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; -import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; +import static top.offsetmonkey538.meshlib.common.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; /** * Checks if a request is HTTP and either forwards it to {@link MainHttpHandler} if it is an HTTP request diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/HttpHandlerTypeRegistryImpl.java similarity index 89% rename from common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/HttpHandlerTypeRegistryImpl.java index 5810182..7ff38b6 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/HttpHandlerTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/HttpHandlerTypeRegistryImpl.java @@ -1,9 +1,9 @@ -package top.offsetmonkey538.meshlib.impl.router; +package top.offsetmonkey538.meshlib.common.impl.router; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.handler.HttpHandler; -import top.offsetmonkey538.meshlib.api.handler.HttpHandlerTypeRegistry; -import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; import java.util.HashMap; import java.util.Map; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/HttpRuleTypeRegistryImpl.java similarity index 91% rename from common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/HttpRuleTypeRegistryImpl.java index a7ec95e..fa3122c 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/router/rule/HttpRuleTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/HttpRuleTypeRegistryImpl.java @@ -1,8 +1,8 @@ -package top.offsetmonkey538.meshlib.impl.router.rule; +package top.offsetmonkey538.meshlib.common.impl.router.rule; import org.jetbrains.annotations.NotNull; -import top.offsetmonkey538.meshlib.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; import java.util.HashMap; import java.util.Map; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java similarity index 94% rename from common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java index 7e28ce5..648a844 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/impl/util/HttpResponseUtilImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.impl.util; +package top.offsetmonkey538.meshlib.common.impl.util; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -10,7 +10,7 @@ import io.netty.util.CharsetUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import top.offsetmonkey538.meshlib.api.util.HttpResponseUtil; +import top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil; import java.io.IOException; import java.nio.file.Files; @@ -21,8 +21,8 @@ import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; import static io.netty.handler.codec.http.HttpResponseStatus.OK; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; -import static top.offsetmonkey538.meshlib.MESHLib.LOGGER; -import static top.offsetmonkey538.meshlib.api.util.HttpResponseUtil.sendError; +import static top.offsetmonkey538.meshlib.common.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil.sendError; public final class HttpResponseUtilImpl implements HttpResponseUtil { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformMain.java similarity index 81% rename from common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformMain.java index 9db25e9..9d5a72d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/platform/PlatformMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformMain.java @@ -1,8 +1,8 @@ -package top.offsetmonkey538.meshlib.platform; +package top.offsetmonkey538.meshlib.common.platform; import org.jetbrains.annotations.ApiStatus; -import static top.offsetmonkey538.meshlib.MESHLib.load; +import static top.offsetmonkey538.meshlib.common.MESHLib.load; @ApiStatus.Internal public interface PlatformMain { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/example/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/example/package-info.java deleted file mode 100644 index e092d2d..0000000 --- a/common/src/main/java/top/offsetmonkey538/meshlib/example/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This contains examples for how to implement {@link top.offsetmonkey538.meshlib.api.handler.HttpHandler HttpHandler}s - */ -package top.offsetmonkey538.meshlib.example; diff --git a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java similarity index 78% rename from loader/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java rename to loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java index b9f770b..c614d4b 100644 --- a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/mixin/ServerNetworkIoMixin.java +++ b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java @@ -1,14 +1,14 @@ -package top.offsetmonkey538.meshlib.platform.fabric.mixin; +package top.offsetmonkey538.meshlib.fabric.mixin; import io.netty.channel.Channel; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import top.offsetmonkey538.meshlib.impl.ProtocolHandler; -import top.offsetmonkey538.meshlib.platform.fabric.FabricPlatformMain; +import top.offsetmonkey538.meshlib.common.impl.ProtocolHandler; +import top.offsetmonkey538.meshlib.fabric.platform.FabricPlatformMain; -import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; +import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; /** * Mixin adding the {@link ProtocolHandler} to the minecraft netty pipeline diff --git a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/FabricPlatformMain.java similarity index 75% rename from loader/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java rename to loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/FabricPlatformMain.java index 62564ed..95fdd70 100644 --- a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/platform/fabric/FabricPlatformMain.java +++ b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/FabricPlatformMain.java @@ -1,8 +1,8 @@ -package top.offsetmonkey538.meshlib.platform.fabric; +package top.offsetmonkey538.meshlib.fabric.platform; import net.fabricmc.api.DedicatedServerModInitializer; -import top.offsetmonkey538.meshlib.MESHLib; -import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.meshlib.common.MESHLib; +import top.offsetmonkey538.meshlib.common.platform.PlatformMain; public final class FabricPlatformMain implements PlatformMain, DedicatedServerModInitializer { public static boolean isVanillaHandlerEnabled = false; diff --git a/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain b/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain new file mode 100644 index 0000000..42c3b86 --- /dev/null +++ b/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain @@ -0,0 +1 @@ +top.offsetmonkey538.meshlib.fabric.platform.FabricPlatformMain \ No newline at end of file diff --git a/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain b/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain deleted file mode 100644 index ad7a267..0000000 --- a/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain +++ /dev/null @@ -1 +0,0 @@ -top.offsetmonkey538.meshlib.platform.fabric.FabricPlatformMain \ No newline at end of file diff --git a/loader/fabric/src/main/resources/mesh-lib.mixins.json b/loader/fabric/src/main/resources/mesh-lib.mixins.json index 51aa7c6..30bc74d 100644 --- a/loader/fabric/src/main/resources/mesh-lib.mixins.json +++ b/loader/fabric/src/main/resources/mesh-lib.mixins.json @@ -1,7 +1,7 @@ { "required": true, - "package": "top.offsetmonkey538.meshlib.platform.fabric.mixin", - "compatibilityLevel": "JAVA_17", + "package": "top.offsetmonkey538.meshlib.fabric.mixin", + "compatibilityLevel": "JAVA_21", "server": [ "ServerNetworkIoMixin" ], diff --git a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/NeoforgeInitializer.java similarity index 75% rename from loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java rename to loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/NeoforgeInitializer.java index 1e4e5f7..5961ce9 100644 --- a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgeInitializer.java +++ b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/NeoforgeInitializer.java @@ -1,9 +1,9 @@ -package top.offsetmonkey538.meshlib.platform.neoforge; +package top.offsetmonkey538.meshlib.neoforge; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; -import top.offsetmonkey538.meshlib.MESHLib; +import top.offsetmonkey538.meshlib.common.MESHLib; @Mod("mesh_lib") public final class NeoforgeInitializer { diff --git a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/mixin/ServerConnectionListenerMixin.java similarity index 78% rename from loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java rename to loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/mixin/ServerConnectionListenerMixin.java index e114924..e52efc1 100644 --- a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/mixin/ServerConnectionListenerMixin.java +++ b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/mixin/ServerConnectionListenerMixin.java @@ -1,14 +1,14 @@ -package top.offsetmonkey538.meshlib.platform.neoforge.mixin; +package top.offsetmonkey538.meshlib.neoforge.mixin; import io.netty.channel.Channel; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import top.offsetmonkey538.meshlib.impl.ProtocolHandler; -import top.offsetmonkey538.meshlib.platform.neoforge.NeoforgePlatformMain; +import top.offsetmonkey538.meshlib.common.impl.ProtocolHandler; +import top.offsetmonkey538.meshlib.neoforge.platform.NeoforgePlatformMain; -import static top.offsetmonkey538.meshlib.MESHLib.MOD_ID; +import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; /** * Mixin adding the {@link ProtocolHandler} to the minecraft netty pipeline diff --git a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/NeoforgePlatformMain.java similarity index 78% rename from loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java rename to loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/NeoforgePlatformMain.java index 4cd03f4..1569a9e 100644 --- a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/platform/neoforge/NeoforgePlatformMain.java +++ b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/NeoforgePlatformMain.java @@ -1,6 +1,6 @@ -package top.offsetmonkey538.meshlib.platform.neoforge; +package top.offsetmonkey538.meshlib.neoforge.platform; -import top.offsetmonkey538.meshlib.platform.PlatformMain; +import top.offsetmonkey538.meshlib.common.platform.PlatformMain; public final class NeoforgePlatformMain implements PlatformMain { public static boolean isVanillaHandlerEnabled = false; diff --git a/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain b/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain new file mode 100644 index 0000000..ffc6818 --- /dev/null +++ b/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain @@ -0,0 +1 @@ +top.offsetmonkey538.meshlib.neoforge.platform.NeoforgePlatformMain \ No newline at end of file diff --git a/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain b/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain deleted file mode 100644 index db033d3..0000000 --- a/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain +++ /dev/null @@ -1 +0,0 @@ -top.offsetmonkey538.meshlib.platform.neoforge.NeoforgePlatformMain \ No newline at end of file diff --git a/loader/neoforge/src/main/resources/mesh-lib.mixins.json b/loader/neoforge/src/main/resources/mesh-lib.mixins.json index f4cc04f..09cb2df 100644 --- a/loader/neoforge/src/main/resources/mesh-lib.mixins.json +++ b/loader/neoforge/src/main/resources/mesh-lib.mixins.json @@ -1,7 +1,7 @@ { "required": true, - "package": "top.offsetmonkey538.meshlib.platform.neoforge.mixin", - "compatibilityLevel": "JAVA_17", + "package": "top.offsetmonkey538.meshlib.neoforge.mixin", + "compatibilityLevel": "JAVA_21", "server": [ "ServerConnectionListenerMixin" ], diff --git a/loader/paper/build.gradle b/loader/paper/build.gradle index a73698e..9c734b3 100644 --- a/loader/paper/build.gradle +++ b/loader/paper/build.gradle @@ -10,7 +10,7 @@ allprojects { outlet.allowedReleaseTypes = Set.of(ReleaseType.RELEASE) paperPluginYaml { - main = "top.offsetmonkey538.meshlib.platform.paper.MeshLibPlugin" + main = "top.offsetmonkey538.meshlib.paper.platform.PaperPlatformMain\$MeshLibPlugin" authors.add("OffsetMonkey538") load = "STARTUP" dependencies { diff --git a/loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PaperPlatformMain.java similarity index 64% rename from loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java rename to loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PaperPlatformMain.java index d595603..395a9cc 100644 --- a/loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/PaperPlatformMain.java +++ b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PaperPlatformMain.java @@ -1,10 +1,11 @@ -package top.offsetmonkey538.meshlib.platform.paper; +package top.offsetmonkey538.meshlib.paper.platform; import io.papermc.paper.network.ChannelInitializeListenerHolder; import net.kyori.adventure.key.Key; -import top.offsetmonkey538.meshlib.MESHLib; -import top.offsetmonkey538.meshlib.impl.ProtocolHandler; -import top.offsetmonkey538.meshlib.platform.PlatformMain; +import org.bukkit.plugin.java.JavaPlugin; +import top.offsetmonkey538.meshlib.common.MESHLib; +import top.offsetmonkey538.meshlib.common.impl.ProtocolHandler; +import top.offsetmonkey538.meshlib.common.platform.PlatformMain; public final class PaperPlatformMain implements PlatformMain { private static final Key HANDLER_KEY = Key.key("meshlib", "meshlib_vanilla_handler"); @@ -29,4 +30,14 @@ public static void setPlugin(MeshLibPlugin plugin) { public static MeshLibPlugin getPlugin() { return plugin; } + + + + public static final class MeshLibPlugin extends JavaPlugin { + @Override + public void onEnable() { + PaperPlatformMain.setPlugin(this); + MESHLib.initialize(); + } + } } diff --git a/loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java deleted file mode 100644 index 6d3dbf0..0000000 --- a/loader/paper/src/main/java/top/offsetmonkey538/meshlib/platform/paper/MeshLibPlugin.java +++ /dev/null @@ -1,13 +0,0 @@ -package top.offsetmonkey538.meshlib.platform.paper; - -import org.bukkit.plugin.java.JavaPlugin; -import top.offsetmonkey538.meshlib.MESHLib; - -public final class MeshLibPlugin extends JavaPlugin { - - @Override - public void onEnable() { - PaperPlatformMain.setPlugin(this); - MESHLib.initialize(); - } -} diff --git a/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain b/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain new file mode 100644 index 0000000..ce01ab1 --- /dev/null +++ b/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain @@ -0,0 +1 @@ +top.offsetmonkey538.meshlib.paper.platform.PaperPlatformMain \ No newline at end of file diff --git a/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain b/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain deleted file mode 100644 index 7d900a1..0000000 --- a/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.platform.PlatformMain +++ /dev/null @@ -1 +0,0 @@ -top.offsetmonkey538.meshlib.platform.paper.PaperPlatformMain \ No newline at end of file From 6b62339d370c28c3551b653eb1bae6e8b89d1635 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Mon, 26 Jan 2026 09:35:08 +0200 Subject: [PATCH 53/78] Refactoring --- .../meshlib/common/MESHLib.java | 4 ++-- .../{PlatformMain.java => PlatformUtil.java} | 4 ++-- loader/fabric/build.gradle | 2 +- .../meshlib/fabric/MESHLibInitializer.java | 11 +++++++++ .../fabric/mixin/ServerNetworkIoMixin.java | 4 ++-- .../fabric/platform/FabricPlatformMain.java | 24 ------------------- .../fabric/platform/PlatformUtilImpl.java | 17 +++++++++++++ ...ey538.meshlib.common.platform.PlatformMain | 1 - ...ey538.meshlib.common.platform.PlatformUtil | 1 + ...itializer.java => MESHLibInitializer.java} | 7 +++--- .../mixin/ServerConnectionListenerMixin.java | 4 ++-- ...latformMain.java => PlatformUtilImpl.java} | 9 ++----- ...ey538.meshlib.common.platform.PlatformMain | 1 - ...ey538.meshlib.common.platform.PlatformUtil | 1 + loader/paper/build.gradle | 2 +- ...latformMain.java => PlatformUtilImpl.java} | 16 ++++++------- ...ey538.meshlib.common.platform.PlatformMain | 1 - ...ey538.meshlib.common.platform.PlatformUtil | 1 + 18 files changed, 54 insertions(+), 56 deletions(-) rename common/src/main/java/top/offsetmonkey538/meshlib/common/platform/{PlatformMain.java => PlatformUtil.java} (86%) create mode 100644 loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/MESHLibInitializer.java delete mode 100644 loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/FabricPlatformMain.java create mode 100644 loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/PlatformUtilImpl.java delete mode 100644 loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain create mode 100644 loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil rename loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/{NeoforgeInitializer.java => MESHLibInitializer.java} (64%) rename loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/{NeoforgePlatformMain.java => PlatformUtilImpl.java} (59%) delete mode 100644 loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain create mode 100644 loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil rename loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/{PaperPlatformMain.java => PlatformUtilImpl.java} (68%) delete mode 100644 loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain create mode 100644 loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java index 021e9ba..8d9c9ad 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java @@ -13,7 +13,7 @@ import top.offsetmonkey538.meshlib.common.config.MESHLibConfig; import top.offsetmonkey538.meshlib.common.config.RouterConfigHandler; import top.offsetmonkey538.meshlib.common.example.ExampleMain; -import top.offsetmonkey538.meshlib.common.platform.PlatformMain; +import top.offsetmonkey538.meshlib.common.platform.PlatformUtil; import top.offsetmonkey538.monkeylib538.common.api.command.CommandRegistrationApi; import top.offsetmonkey538.monkeylib538.common.api.command.ConfigCommandApi; import top.offsetmonkey538.monkeylib538.common.api.lifecycle.ServerLifecycleApi; @@ -43,7 +43,7 @@ private MESHLib() {} public static void initialize() { - PlatformMain.enableVanillaHandler(); + PlatformUtil.enableVanillaHandler(); ExampleMain.onInitialize(); ConfigCommandApi.registerConfigCommand(CONFIG, MESHLib::reload, MOD_ID, "config"); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformUtil.java similarity index 86% rename from common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformMain.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformUtil.java index 9d5a72d..c832f38 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformUtil.java @@ -5,11 +5,11 @@ import static top.offsetmonkey538.meshlib.common.MESHLib.load; @ApiStatus.Internal -public interface PlatformMain { +public interface PlatformUtil { @SuppressWarnings("DeprecatedIsStillUsed") @Deprecated // Only for use in this class - PlatformMain INSTANCE = load(PlatformMain.class); + PlatformUtil INSTANCE = load(PlatformUtil.class); static void enableVanillaHandler() { INSTANCE.enableVanillaHandlerImpl(); diff --git a/loader/fabric/build.gradle b/loader/fabric/build.gradle index 580e6d6..ecd5757 100644 --- a/loader/fabric/build.gradle +++ b/loader/fabric/build.gradle @@ -13,7 +13,7 @@ allprojects { extra = ["discord": "https://discord.offsetmonkey538.top"] } environment = Environment.SERVER - serverEntrypoint("top.offsetmonkey538.meshlib.platform.fabric.FabricPlatformMain") + serverEntrypoint("top.offsetmonkey538.meshlib.fabric.MESHLibInitializer") mixin("mesh-lib.modded.mixins.json") depends("fabric-api", "*") diff --git a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/MESHLibInitializer.java b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/MESHLibInitializer.java new file mode 100644 index 0000000..7e86d66 --- /dev/null +++ b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/MESHLibInitializer.java @@ -0,0 +1,11 @@ +package top.offsetmonkey538.meshlib.fabric; + +import net.fabricmc.api.DedicatedServerModInitializer; +import top.offsetmonkey538.meshlib.common.MESHLib; + +public final class MESHLibInitializer implements DedicatedServerModInitializer { + @Override + public void onInitializeServer() { + MESHLib.initialize(); + } +} diff --git a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java index c614d4b..3a6f203 100644 --- a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java +++ b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java @@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import top.offsetmonkey538.meshlib.common.impl.ProtocolHandler; -import top.offsetmonkey538.meshlib.fabric.platform.FabricPlatformMain; +import top.offsetmonkey538.meshlib.fabric.platform.PlatformUtilImpl; import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; @@ -22,7 +22,7 @@ public abstract class ServerNetworkIoMixin { ) private void meshlib$addHttpHandler(Channel channel, CallbackInfo ci) { // This method is executed every time a new connection is started. Thus, I can just not add to the vanilla server when that's disabled - if (!FabricPlatformMain.isVanillaHandlerEnabled) return; + if (!PlatformUtilImpl.isVanillaHandlerEnabled) return; channel.pipeline().addFirst(MOD_ID, new ProtocolHandler()); } } diff --git a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/FabricPlatformMain.java b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/FabricPlatformMain.java deleted file mode 100644 index 95fdd70..0000000 --- a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/FabricPlatformMain.java +++ /dev/null @@ -1,24 +0,0 @@ -package top.offsetmonkey538.meshlib.fabric.platform; - -import net.fabricmc.api.DedicatedServerModInitializer; -import top.offsetmonkey538.meshlib.common.MESHLib; -import top.offsetmonkey538.meshlib.common.platform.PlatformMain; - -public final class FabricPlatformMain implements PlatformMain, DedicatedServerModInitializer { - public static boolean isVanillaHandlerEnabled = false; - - @Override - public void enableVanillaHandlerImpl() { - isVanillaHandlerEnabled = true; - } - - @Override - public void disableVanillaHandlerImpl() { - isVanillaHandlerEnabled = false; - } - - @Override - public void onInitializeServer() { - MESHLib.initialize(); - } -} diff --git a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/PlatformUtilImpl.java b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/PlatformUtilImpl.java new file mode 100644 index 0000000..92cb85a --- /dev/null +++ b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/PlatformUtilImpl.java @@ -0,0 +1,17 @@ +package top.offsetmonkey538.meshlib.fabric.platform; + +import top.offsetmonkey538.meshlib.common.platform.PlatformUtil; + +public final class PlatformUtilImpl implements PlatformUtil { + public static boolean isVanillaHandlerEnabled = false; + + @Override + public void enableVanillaHandlerImpl() { + isVanillaHandlerEnabled = true; + } + + @Override + public void disableVanillaHandlerImpl() { + isVanillaHandlerEnabled = false; + } +} diff --git a/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain b/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain deleted file mode 100644 index 42c3b86..0000000 --- a/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain +++ /dev/null @@ -1 +0,0 @@ -top.offsetmonkey538.meshlib.fabric.platform.FabricPlatformMain \ No newline at end of file diff --git a/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil b/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil new file mode 100644 index 0000000..a12b5da --- /dev/null +++ b/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil @@ -0,0 +1 @@ +top.offsetmonkey538.meshlib.fabric.platform.PlatformUtilImpl \ No newline at end of file diff --git a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/NeoforgeInitializer.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/MESHLibInitializer.java similarity index 64% rename from loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/NeoforgeInitializer.java rename to loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/MESHLibInitializer.java index 5961ce9..2f55ffd 100644 --- a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/NeoforgeInitializer.java +++ b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/MESHLibInitializer.java @@ -5,10 +5,9 @@ import net.neoforged.fml.common.Mod; import top.offsetmonkey538.meshlib.common.MESHLib; -@Mod("mesh_lib") -public final class NeoforgeInitializer { - - public NeoforgeInitializer(IEventBus modEventBus, ModContainer modContainer) { +@Mod("meshlib") +public final class MESHLibInitializer { + public MESHLibInitializer(IEventBus modEventBus, ModContainer modContainer) { MESHLib.initialize(); } } diff --git a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/mixin/ServerConnectionListenerMixin.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/mixin/ServerConnectionListenerMixin.java index e52efc1..3154a23 100644 --- a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/mixin/ServerConnectionListenerMixin.java +++ b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/mixin/ServerConnectionListenerMixin.java @@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import top.offsetmonkey538.meshlib.common.impl.ProtocolHandler; -import top.offsetmonkey538.meshlib.neoforge.platform.NeoforgePlatformMain; +import top.offsetmonkey538.meshlib.neoforge.platform.PlatformUtilImpl; import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; @@ -22,7 +22,7 @@ public abstract class ServerConnectionListenerMixin { ) private void meshlib$addHttpHandler(Channel channel, CallbackInfo ci) { // This method is executed every time a new connection is started. Thus, I can just not add to the vanilla server when that's disabled - if (!NeoforgePlatformMain.isVanillaHandlerEnabled) return; + if (!PlatformUtilImpl.isVanillaHandlerEnabled) return; channel.pipeline().addFirst(MOD_ID, new ProtocolHandler()); } } diff --git a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/NeoforgePlatformMain.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/PlatformUtilImpl.java similarity index 59% rename from loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/NeoforgePlatformMain.java rename to loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/PlatformUtilImpl.java index 1569a9e..d9ea9ad 100644 --- a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/NeoforgePlatformMain.java +++ b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/PlatformUtilImpl.java @@ -1,8 +1,8 @@ package top.offsetmonkey538.meshlib.neoforge.platform; -import top.offsetmonkey538.meshlib.common.platform.PlatformMain; +import top.offsetmonkey538.meshlib.common.platform.PlatformUtil; -public final class NeoforgePlatformMain implements PlatformMain { +public final class PlatformUtilImpl implements PlatformUtil { public static boolean isVanillaHandlerEnabled = false; @Override @@ -14,9 +14,4 @@ public void enableVanillaHandlerImpl() { public void disableVanillaHandlerImpl() { isVanillaHandlerEnabled = false; } - - // dis on for loading the PlatformMain service - public NeoforgePlatformMain() { - - } } diff --git a/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain b/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain deleted file mode 100644 index ffc6818..0000000 --- a/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain +++ /dev/null @@ -1 +0,0 @@ -top.offsetmonkey538.meshlib.neoforge.platform.NeoforgePlatformMain \ No newline at end of file diff --git a/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil b/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil new file mode 100644 index 0000000..dd127fa --- /dev/null +++ b/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil @@ -0,0 +1 @@ +top.offsetmonkey538.meshlib.neoforge.platform.PlatformUtilImpl \ No newline at end of file diff --git a/loader/paper/build.gradle b/loader/paper/build.gradle index 9c734b3..331958c 100644 --- a/loader/paper/build.gradle +++ b/loader/paper/build.gradle @@ -10,7 +10,7 @@ allprojects { outlet.allowedReleaseTypes = Set.of(ReleaseType.RELEASE) paperPluginYaml { - main = "top.offsetmonkey538.meshlib.paper.platform.PaperPlatformMain\$MeshLibPlugin" + main = "top.offsetmonkey538.meshlib.paper.platform.PaperPlatformMain\$MESHLibInitializer" authors.add("OffsetMonkey538") load = "STARTUP" dependencies { diff --git a/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PaperPlatformMain.java b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PlatformUtilImpl.java similarity index 68% rename from loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PaperPlatformMain.java rename to loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PlatformUtilImpl.java index 395a9cc..f6e078d 100644 --- a/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PaperPlatformMain.java +++ b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PlatformUtilImpl.java @@ -5,12 +5,12 @@ import org.bukkit.plugin.java.JavaPlugin; import top.offsetmonkey538.meshlib.common.MESHLib; import top.offsetmonkey538.meshlib.common.impl.ProtocolHandler; -import top.offsetmonkey538.meshlib.common.platform.PlatformMain; +import top.offsetmonkey538.meshlib.common.platform.PlatformUtil; -public final class PaperPlatformMain implements PlatformMain { +public final class PlatformUtilImpl implements PlatformUtil { private static final Key HANDLER_KEY = Key.key("meshlib", "meshlib_vanilla_handler"); - private static MeshLibPlugin plugin; + private static MESHLibInitializer plugin; @Override public void enableVanillaHandlerImpl() { @@ -23,20 +23,20 @@ public void disableVanillaHandlerImpl() { ChannelInitializeListenerHolder.removeListener(HANDLER_KEY); } - public static void setPlugin(MeshLibPlugin plugin) { - PaperPlatformMain.plugin = plugin; + public static void setPlugin(MESHLibInitializer plugin) { + PlatformUtilImpl.plugin = plugin; } - public static MeshLibPlugin getPlugin() { + public static MESHLibInitializer getPlugin() { return plugin; } - public static final class MeshLibPlugin extends JavaPlugin { + public static final class MESHLibInitializer extends JavaPlugin { @Override public void onEnable() { - PaperPlatformMain.setPlugin(this); + PlatformUtilImpl.setPlugin(this); MESHLib.initialize(); } } diff --git a/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain b/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain deleted file mode 100644 index ce01ab1..0000000 --- a/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformMain +++ /dev/null @@ -1 +0,0 @@ -top.offsetmonkey538.meshlib.paper.platform.PaperPlatformMain \ No newline at end of file diff --git a/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil b/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil new file mode 100644 index 0000000..2725f4f --- /dev/null +++ b/loader/paper/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil @@ -0,0 +1 @@ +top.offsetmonkey538.meshlib.paper.platform.PlatformUtilImpl \ No newline at end of file From 6e6d080ab8916c0261b0fa693ffa1b986a48abea Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Mon, 26 Jan 2026 09:46:33 +0200 Subject: [PATCH 54/78] Fix github actions --- .github/workflows/build_artifacts.yml | 2 ++ .github/workflows/publish.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index 06a939c..324aaa2 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -12,6 +12,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + submodules: true - name: Set up JDK 21 uses: actions/setup-java@v5 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 84b7775..4e8033f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -14,6 +14,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + submodules: true - name: Set up JDK 21 uses: actions/setup-java@v5 From fbaeb00260c88ebd8c2338d3321b32226b03e0e6 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Mon, 26 Jan 2026 09:53:29 +0200 Subject: [PATCH 55/78] Move to modded --- .../common/config/RouterConfigHandler.java | 2 +- .../assets/{mesh-lib => meshlib}/icon.png | Bin loader/fabric/build.gradle | 2 +- .../fabric/mixin/ServerNetworkIoMixin.java | 28 ------------------ ...ey538.meshlib.common.platform.PlatformUtil | 1 - .../src/main/resources/mesh-lib.mixins.json | 11 ------- loader/neoforge/build.gradle | 2 +- .../neoforge/platform/PlatformUtilImpl.java | 17 ----------- ...ey538.meshlib.common.platform.PlatformUtil | 1 - .../src/main/resources/mesh-lib.mixins.json | 11 ------- .../mixin/ServerConnectionListenerMixin.java | 4 +-- .../modded}/platform/PlatformUtilImpl.java | 2 +- ...ey538.meshlib.common.platform.PlatformUtil | 1 + .../main/resources/meshlib.modded.mixins.json | 11 +++++++ 14 files changed, 18 insertions(+), 75 deletions(-) rename common/src/main/resources/assets/{mesh-lib => meshlib}/icon.png (100%) delete mode 100644 loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java delete mode 100644 loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil delete mode 100644 loader/fabric/src/main/resources/mesh-lib.mixins.json delete mode 100644 loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/PlatformUtilImpl.java delete mode 100644 loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil delete mode 100644 loader/neoforge/src/main/resources/mesh-lib.mixins.json rename {loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge => modded/src/main/java/top/offsetmonkey538/meshlib/modded}/mixin/ServerConnectionListenerMixin.java (89%) rename {loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric => modded/src/main/java/top/offsetmonkey538/meshlib/modded}/platform/PlatformUtilImpl.java (88%) create mode 100644 modded/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil create mode 100644 modded/src/main/resources/meshlib.modded.mixins.json diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java index b9d109c..ad562d8 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java @@ -65,7 +65,7 @@ public static LiteralArgumentBuilder createExampleConfigCommand() { } final Consumer> finalAllHandler = allHandler; - return literal("mesh-lib").then(exampleCommand).then(literal("all").executes(context -> { + return literal(MOD_ID).then(exampleCommand).then(literal("all").executes(context -> { finalAllHandler.accept(context); return 1; })); diff --git a/common/src/main/resources/assets/mesh-lib/icon.png b/common/src/main/resources/assets/meshlib/icon.png similarity index 100% rename from common/src/main/resources/assets/mesh-lib/icon.png rename to common/src/main/resources/assets/meshlib/icon.png diff --git a/loader/fabric/build.gradle b/loader/fabric/build.gradle index ecd5757..7b1b0eb 100644 --- a/loader/fabric/build.gradle +++ b/loader/fabric/build.gradle @@ -14,7 +14,7 @@ allprojects { } environment = Environment.SERVER serverEntrypoint("top.offsetmonkey538.meshlib.fabric.MESHLibInitializer") - mixin("mesh-lib.modded.mixins.json") + mixin("meshlib.modded.mixins.json") depends("fabric-api", "*") depends("monkeylib538", ">=${rootProject.monkeylib538_version}") diff --git a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java deleted file mode 100644 index 3a6f203..0000000 --- a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/mixin/ServerNetworkIoMixin.java +++ /dev/null @@ -1,28 +0,0 @@ -package top.offsetmonkey538.meshlib.fabric.mixin; - -import io.netty.channel.Channel; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import top.offsetmonkey538.meshlib.common.impl.ProtocolHandler; -import top.offsetmonkey538.meshlib.fabric.platform.PlatformUtilImpl; - -import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; - -/** - * Mixin adding the {@link ProtocolHandler} to the minecraft netty pipeline - */ -@Mixin(targets = "net/minecraft/server/ServerNetworkIo$1") -public abstract class ServerNetworkIoMixin { - - @Inject( - method = "initChannel", - at = @At("TAIL") - ) - private void meshlib$addHttpHandler(Channel channel, CallbackInfo ci) { - // This method is executed every time a new connection is started. Thus, I can just not add to the vanilla server when that's disabled - if (!PlatformUtilImpl.isVanillaHandlerEnabled) return; - channel.pipeline().addFirst(MOD_ID, new ProtocolHandler()); - } -} diff --git a/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil b/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil deleted file mode 100644 index a12b5da..0000000 --- a/loader/fabric/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil +++ /dev/null @@ -1 +0,0 @@ -top.offsetmonkey538.meshlib.fabric.platform.PlatformUtilImpl \ No newline at end of file diff --git a/loader/fabric/src/main/resources/mesh-lib.mixins.json b/loader/fabric/src/main/resources/mesh-lib.mixins.json deleted file mode 100644 index 30bc74d..0000000 --- a/loader/fabric/src/main/resources/mesh-lib.mixins.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "required": true, - "package": "top.offsetmonkey538.meshlib.fabric.mixin", - "compatibilityLevel": "JAVA_21", - "server": [ - "ServerNetworkIoMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/loader/neoforge/build.gradle b/loader/neoforge/build.gradle index bf15b21..afb81d5 100644 --- a/loader/neoforge/build.gradle +++ b/loader/neoforge/build.gradle @@ -13,7 +13,7 @@ allprojects { required("monkeylib538", "[${rootProject.monkeylib538_version},)") } } - mixin("mesh-lib.modded.mixins.json") + mixin("meshlib.modded.mixins.json") } dependencies { diff --git a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/PlatformUtilImpl.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/PlatformUtilImpl.java deleted file mode 100644 index d9ea9ad..0000000 --- a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/platform/PlatformUtilImpl.java +++ /dev/null @@ -1,17 +0,0 @@ -package top.offsetmonkey538.meshlib.neoforge.platform; - -import top.offsetmonkey538.meshlib.common.platform.PlatformUtil; - -public final class PlatformUtilImpl implements PlatformUtil { - public static boolean isVanillaHandlerEnabled = false; - - @Override - public void enableVanillaHandlerImpl() { - isVanillaHandlerEnabled = true; - } - - @Override - public void disableVanillaHandlerImpl() { - isVanillaHandlerEnabled = false; - } -} diff --git a/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil b/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil deleted file mode 100644 index dd127fa..0000000 --- a/loader/neoforge/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil +++ /dev/null @@ -1 +0,0 @@ -top.offsetmonkey538.meshlib.neoforge.platform.PlatformUtilImpl \ No newline at end of file diff --git a/loader/neoforge/src/main/resources/mesh-lib.mixins.json b/loader/neoforge/src/main/resources/mesh-lib.mixins.json deleted file mode 100644 index 09cb2df..0000000 --- a/loader/neoforge/src/main/resources/mesh-lib.mixins.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "required": true, - "package": "top.offsetmonkey538.meshlib.neoforge.mixin", - "compatibilityLevel": "JAVA_21", - "server": [ - "ServerConnectionListenerMixin" - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/mixin/ServerConnectionListenerMixin.java b/modded/src/main/java/top/offsetmonkey538/meshlib/modded/mixin/ServerConnectionListenerMixin.java similarity index 89% rename from loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/mixin/ServerConnectionListenerMixin.java rename to modded/src/main/java/top/offsetmonkey538/meshlib/modded/mixin/ServerConnectionListenerMixin.java index 3154a23..5c15527 100644 --- a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/mixin/ServerConnectionListenerMixin.java +++ b/modded/src/main/java/top/offsetmonkey538/meshlib/modded/mixin/ServerConnectionListenerMixin.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.neoforge.mixin; +package top.offsetmonkey538.meshlib.modded.mixin; import io.netty.channel.Channel; import org.spongepowered.asm.mixin.Mixin; @@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import top.offsetmonkey538.meshlib.common.impl.ProtocolHandler; -import top.offsetmonkey538.meshlib.neoforge.platform.PlatformUtilImpl; +import top.offsetmonkey538.meshlib.modded.platform.PlatformUtilImpl; import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; diff --git a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/PlatformUtilImpl.java b/modded/src/main/java/top/offsetmonkey538/meshlib/modded/platform/PlatformUtilImpl.java similarity index 88% rename from loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/PlatformUtilImpl.java rename to modded/src/main/java/top/offsetmonkey538/meshlib/modded/platform/PlatformUtilImpl.java index 92cb85a..6d2bf06 100644 --- a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/platform/PlatformUtilImpl.java +++ b/modded/src/main/java/top/offsetmonkey538/meshlib/modded/platform/PlatformUtilImpl.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.fabric.platform; +package top.offsetmonkey538.meshlib.modded.platform; import top.offsetmonkey538.meshlib.common.platform.PlatformUtil; diff --git a/modded/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil b/modded/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil new file mode 100644 index 0000000..94d750c --- /dev/null +++ b/modded/src/main/resources/META-INF/services/top.offsetmonkey538.meshlib.common.platform.PlatformUtil @@ -0,0 +1 @@ +top.offsetmonkey538.meshlib.modded.platform.PlatformUtilImpl \ No newline at end of file diff --git a/modded/src/main/resources/meshlib.modded.mixins.json b/modded/src/main/resources/meshlib.modded.mixins.json new file mode 100644 index 0000000..7e3874d --- /dev/null +++ b/modded/src/main/resources/meshlib.modded.mixins.json @@ -0,0 +1,11 @@ +{ + "required": true, + "package": "top.offsetmonkey538.meshlib.modded.mixin", + "compatibilityLevel": "JAVA_21", + "server": [ + "ServerConnectionListenerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} From e94f83f28689412c2174ad9c7ba681ec42175d46 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Mon, 26 Jan 2026 10:46:11 +0200 Subject: [PATCH 56/78] Fix paper runServer task --- buildSrc | 2 +- gradle.properties | 2 +- loader/paper/1.21.1/gradle.properties | 2 +- loader/paper/build.gradle | 9 ++++----- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/buildSrc b/buildSrc index 44aa0ff..97cb1b2 160000 --- a/buildSrc +++ b/buildSrc @@ -1 +1 @@ -Subproject commit 44aa0ffadd024d65f75dd6edfe844d42be91d25f +Subproject commit 97cb1b28a0265d9fe6f90497a46bf86b01119687 diff --git a/gradle.properties b/gradle.properties index 1ff2ebe..4c3bf26 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-beta.2.1769251134126+b032519 +monkeylib538_version = 3.0.0-beta.2.1769416853692+a710cf2 ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.82.Final ## Bundeled with Minecraft, common needs to use diff --git a/loader/paper/1.21.1/gradle.properties b/loader/paper/1.21.1/gradle.properties index ea42a7c..05dc6f2 100644 --- a/loader/paper/1.21.1/gradle.properties +++ b/loader/paper/1.21.1/gradle.properties @@ -1,6 +1,6 @@ project_name = paper-1.21.1 -monkeylib538_suffix = 1.21 +monkeylib538_suffix = 1.21.1 # Minecraft version minecraft_version = 1.21.1 diff --git a/loader/paper/build.gradle b/loader/paper/build.gradle index 331958c..434e766 100644 --- a/loader/paper/build.gradle +++ b/loader/paper/build.gradle @@ -10,7 +10,7 @@ allprojects { outlet.allowedReleaseTypes = Set.of(ReleaseType.RELEASE) paperPluginYaml { - main = "top.offsetmonkey538.meshlib.paper.platform.PaperPlatformMain\$MESHLibInitializer" + main = "top.offsetmonkey538.meshlib.paper.platform.PlatformUtilImpl\$MESHLibInitializer" authors.add("OffsetMonkey538") load = "STARTUP" dependencies { @@ -24,12 +24,11 @@ allprojects { } subprojects { - dependencies { - shadow implementation("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+fabric+${project.monkeylib538_suffix}") - } - tasks.runServer { jvmArgs "-DmeshEnableExamples=true" + downloadPlugins { + url("https://maven.offsetmonkey538.top/releases/top/offsetmonkey538/monkeylib538/monkeylib538-paper-${project.monkeylib538_suffix}/${rootProject.monkeylib538_version}+paper+${project.monkeylib538_suffix}/monkeylib538-paper-${project.monkeylib538_suffix}-${rootProject.monkeylib538_version}+paper+${project.monkeylib538_suffix}-all.jar") + } } modrinth { From ff0d2bd1d5563e8192a82b8f227efd962473a9d4 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Mon, 26 Jan 2026 10:50:48 +0200 Subject: [PATCH 57/78] include netty-http --- loader/fabric/build.gradle | 2 ++ loader/neoforge/build.gradle | 2 ++ loader/paper/build.gradle | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/loader/fabric/build.gradle b/loader/fabric/build.gradle index 7b1b0eb..cd8c8f1 100644 --- a/loader/fabric/build.gradle +++ b/loader/fabric/build.gradle @@ -37,6 +37,8 @@ subprojects { modImplementation("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+fabric+${project.monkeylib538_suffix}") { exclude(group: "net.fabricmc.fabric-api") } + + includeRuntime "io.netty:netty-codec-http:${rootProject.netty_version}" } modrinth { diff --git a/loader/neoforge/build.gradle b/loader/neoforge/build.gradle index afb81d5..002b58e 100644 --- a/loader/neoforge/build.gradle +++ b/loader/neoforge/build.gradle @@ -27,6 +27,8 @@ subprojects { dependencies { implementation "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+neoforge+${project.monkeylib538_suffix}" + + includeRuntime "io.netty:netty-codec-http:${rootProject.netty_version}" } modrinth { diff --git a/loader/paper/build.gradle b/loader/paper/build.gradle index 434e766..81602f6 100644 --- a/loader/paper/build.gradle +++ b/loader/paper/build.gradle @@ -24,6 +24,12 @@ allprojects { } subprojects { + dependencies { + runtimeOnly("io.netty:netty-codec-http:${rootProject.netty_version}") { + transitive = false + } + } + tasks.runServer { jvmArgs "-DmeshEnableExamples=true" downloadPlugins { From 17c99b83bf679592b807ad3534ae4c768e67b5d0 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Mon, 26 Jan 2026 11:05:41 +0200 Subject: [PATCH 58/78] fix example all command (was meshlib all, now meshlib example all) --- .../meshlib/common/config/RouterConfigHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java index ad562d8..fd914a5 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java @@ -65,10 +65,10 @@ public static LiteralArgumentBuilder createExampleConfigCommand() { } final Consumer> finalAllHandler = allHandler; - return literal(MOD_ID).then(exampleCommand).then(literal("all").executes(context -> { + return literal(MOD_ID).then(exampleCommand.then(literal("all").executes(context -> { finalAllHandler.accept(context); return 1; - })); + }))); } private static int runCommand(final Map.Entry exampleRouter, final CommandContext context) { From bd21d44a47c3152bbce8c8e4e69427d9a020e969 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Mon, 26 Jan 2026 19:35:02 +0200 Subject: [PATCH 59/78] update monkeylib (now uses offsetutils538 and also jspecify so lots of package infos with lots of nullability checking :D) --- README.md | 4 +-- build.gradle | 3 +- .../meshlib/common/MESHLib.java | 15 +++++---- .../{ => api}/example/ExampleHttpHandler.java | 9 +++-- .../common/{ => api}/example/ExampleMain.java | 2 +- .../{ => api}/example/package-info.java | 5 ++- .../common/api/handler/HttpHandler.java | 5 ++- .../api/handler/HttpHandlerTypeRegistry.java | 19 +++++------ .../handlers/StaticContentHandler.java | 12 +++---- .../handlers/StaticDirectoryHandler.java | 29 +++++++++------- .../handler/handlers/StaticFileHandler.java | 12 +++---- .../api/handler/handlers/package-info.java | 7 ++++ .../common/api/handler/package-info.java | 7 ++++ .../meshlib/common/api/router/HttpRouter.java | 23 +++++++------ .../common/api/router/HttpRouterRegistry.java | 19 +++++------ .../common/api/router/package-info.java | 7 ++++ .../meshlib/common/api/rule/HttpRule.java | 2 +- .../common/api/rule/HttpRuleTypeRegistry.java | 19 +++++------ .../meshlib/common/api/rule/package-info.java | 7 ++++ .../common/api/rule/rules/DomainHttpRule.java | 8 ++--- .../common/api/rule/rules/PathHttpRule.java | 8 ++--- .../common/api/rule/rules/package-info.java | 7 ++++ .../common/api/util/HttpResponseUtil.java | 33 +++++++++---------- .../meshlib/common/api/util/package-info.java | 7 ++++ .../meshlib/common/config/MESHLibConfig.java | 8 ++--- .../common/config/RouterConfigHandler.java | 18 +++++----- .../meshlib/common/config/package-info.java | 4 +++ .../common/impl/HttpRouterRegistryImpl.java | 7 ++-- .../meshlib/common/impl/MainHttpHandler.java | 13 ++++---- .../meshlib/common/impl/ProtocolHandler.java | 4 +-- .../meshlib/common/impl/package-info.java | 4 +++ .../router/HttpHandlerTypeRegistryImpl.java | 5 ++- .../common/impl/router/package-info.java | 4 +++ .../router/rule/HttpRuleTypeRegistryImpl.java | 8 ++--- .../common/impl/router/rule/package-info.java | 4 +++ .../impl/util/HttpResponseUtilImpl.java | 31 ++++++++++------- .../common/impl/util/package-info.java | 4 +++ .../meshlib/common/package-info.java | 4 +++ .../meshlib/common/platform/PlatformUtil.java | 7 ++-- .../meshlib/common/platform/package-info.java | 4 +++ gradle.properties | 4 ++- .../meshlib/fabric/package-info.java | 4 +++ .../meshlib/neoforge/package-info.java | 4 +++ .../paper/platform/PlatformUtilImpl.java | 12 ------- .../meshlib/paper/platform/package-info.java | 4 +++ .../meshlib/modded/mixin/package-info.java | 4 +++ .../meshlib/modded/platform/package-info.java | 4 +++ 47 files changed, 254 insertions(+), 180 deletions(-) rename common/src/main/java/top/offsetmonkey538/meshlib/common/{ => api}/example/ExampleHttpHandler.java (85%) rename common/src/main/java/top/offsetmonkey538/meshlib/common/{ => api}/example/ExampleMain.java (95%) rename common/src/main/java/top/offsetmonkey538/meshlib/common/{ => api}/example/package-info.java (54%) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/config/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/impl/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/package-info.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/platform/package-info.java create mode 100644 loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/package-info.java create mode 100644 loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/package-info.java create mode 100644 loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/package-info.java create mode 100644 modded/src/main/java/top/offsetmonkey538/meshlib/modded/mixin/package-info.java create mode 100644 modded/src/main/java/top/offsetmonkey538/meshlib/modded/platform/package-info.java diff --git a/README.md b/README.md index efd06c5..a3b7c63 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ This class has to implement the `HttpHandler` interface, this will look somethin public class MyHttpHandler implements HttpHandler { @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception { + public void handleRequest(@NonNull ChannelHandlerContext ctx, @NonNull FullHttpRequest request) throws Exception { // Logic will go here } } @@ -64,7 +64,7 @@ public class MyHttpHandler implements HttpHandler { Now we'll need to actually implement the handler. You can google "HTTP Netty" for more info on how to handle HTTP requests with Netty. ```java -public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request) throws Exception { +public void handleRequest(@NonNull ChannelHandlerContext ctx, @NonNull FullHttpRequest request) throws Exception { // Write "Hello, World!" to a buffer, encoded in UTF-8 final ByteBuf content = Unpooled.copiedBuffer("Hello, World!", StandardCharsets.UTF_8); // Create a response with said buffer diff --git a/build.gradle b/build.gradle index c8c0fd6..221c435 100644 --- a/build.gradle +++ b/build.gradle @@ -39,8 +39,7 @@ subprojects { } dependencies { - //TODO: switch to this: compileOnlyApi "org.jspecify:jspecify:${rootProject.jspecify_version}" - compileOnlyApi "org.jetbrains:annotations:24.0.0" + compileOnlyApi "org.jspecify:jspecify:${rootProject.jspecify_version}" } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java index 8d9c9ad..011d26f 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java @@ -12,15 +12,15 @@ import top.offsetmonkey538.meshlib.common.api.rule.rules.PathHttpRule; import top.offsetmonkey538.meshlib.common.config.MESHLibConfig; import top.offsetmonkey538.meshlib.common.config.RouterConfigHandler; -import top.offsetmonkey538.meshlib.common.example.ExampleMain; +import top.offsetmonkey538.meshlib.common.api.example.ExampleMain; import top.offsetmonkey538.meshlib.common.platform.PlatformUtil; import top.offsetmonkey538.monkeylib538.common.api.command.CommandRegistrationApi; import top.offsetmonkey538.monkeylib538.common.api.command.ConfigCommandApi; import top.offsetmonkey538.monkeylib538.common.api.lifecycle.ServerLifecycleApi; import top.offsetmonkey538.monkeylib538.common.api.log.MonkeyLibLogger; -import top.offsetmonkey538.offsetconfig538.api.config.ConfigHolder; -import top.offsetmonkey538.offsetconfig538.api.config.ConfigManager; -import top.offsetmonkey538.offsetconfig538.api.event.OffsetConfig538Events; +import top.offsetmonkey538.offsetutils538.api.config.ConfigHolder; +import top.offsetmonkey538.offsetutils538.api.config.ConfigManager; +import top.offsetmonkey538.offsetutils538.api.config.event.JanksonConfigurationEvent; import java.util.ServiceLoader; @@ -49,7 +49,7 @@ public static void initialize() { ConfigCommandApi.registerConfigCommand(CONFIG, MESHLib::reload, MOD_ID, "config"); CommandRegistrationApi.registerCommand(RouterConfigHandler.createExampleConfigCommand()); - OffsetConfig538Events.JANKSON_CONFIGURATION_EVENT.listen(HttpRouter::configureJankson); + JanksonConfigurationEvent.JANKSON_CONFIGURATION_EVENT.listen(HttpRouter::configureJankson); HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.listen(DomainHttpRule::register); HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.listen(PathHttpRule::register); @@ -60,7 +60,7 @@ public static void initialize() { HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.listen(RouterConfigHandler::init); - ServerLifecycleApi.runOnServerStarting(MESHLib::reload); + ServerLifecycleApi.STARTING.listen(MESHLib::reload); } // TODO: move somewhere under api so others can invoke a reload? For example git pack manager after reloading its config cause that's where the rule for it will be stored. @@ -84,7 +84,8 @@ public static void reload() { public static T load(Class clazz) { - return ServiceLoader.load(clazz, clazz.getClassLoader()) + LOGGER.info("Loading service for: %s", clazz); + return ServiceLoader.load(clazz, MESHLib.class.getClassLoader()) .findFirst() .orElseThrow(() -> new RuntimeException("Failed to load service for " + clazz.getName())); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/example/ExampleHttpHandler.java similarity index 85% rename from common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleHttpHandler.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/example/ExampleHttpHandler.java index ee041d6..7d5f3e5 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/example/ExampleHttpHandler.java @@ -1,13 +1,12 @@ -package top.offsetmonkey538.meshlib.common.example; +package top.offsetmonkey538.meshlib.common.api.example; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; import top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; /** * An example {@link HttpHandler} implementation to learn from @@ -15,7 +14,7 @@ public record ExampleHttpHandler(String baseContent) implements HttpHandler { @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + public void handleRequest(ChannelHandlerContext ctx, FullHttpRequest request, HttpRule rule) throws Exception { // Calculate response using super amazing and hard math™ final String responseText = superCoolMethodForRunningTheHardAndAmazingCalculationForCalculationinatingTheResponseTM(request.uri()); @@ -33,7 +32,7 @@ public static void register(final HttpHandlerTypeRegistry registry) { registry.register("example-http", Data.class, ExampleHttpHandler.class, handler -> new Data(handler.baseContent), data -> new ExampleHttpHandler(data.content)); } - @ApiStatus.Internal + @Internal private static final class Data { private String content; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleMain.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/example/ExampleMain.java similarity index 95% rename from common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleMain.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/example/ExampleMain.java index 52fbbc4..a47045b 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/example/ExampleMain.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/example/ExampleMain.java @@ -1,4 +1,4 @@ -package top.offsetmonkey538.meshlib.common.example; +package top.offsetmonkey538.meshlib.common.api.example; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.common.api.router.HttpRouter; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/example/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/example/package-info.java similarity index 54% rename from common/src/main/java/top/offsetmonkey538/meshlib/common/example/package-info.java rename to common/src/main/java/top/offsetmonkey538/meshlib/common/api/example/package-info.java index 33c4ae1..d266916 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/example/package-info.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/example/package-info.java @@ -1,4 +1,7 @@ /** * This contains examples for how to implement {@link top.offsetmonkey538.meshlib.common.api.handler.HttpHandler HttpHandler}s */ -package top.offsetmonkey538.meshlib.common.example; +@NullMarked +package top.offsetmonkey538.meshlib.common.api.example; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java index 954ccc6..4e6f3ae 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java @@ -4,8 +4,7 @@ import io.netty.handler.codec.http.FullHttpRequest; import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.common.example.ExampleHttpHandler; -import org.jetbrains.annotations.NotNull; +import top.offsetmonkey538.meshlib.common.api.example.ExampleHttpHandler; /** * An http handler for you to implement :D @@ -26,5 +25,5 @@ public interface HttpHandler { * @param rule the rule used to match this handler * @throws Exception when anything goes wrong */ - void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception; + void handleRequest(ChannelHandlerContext ctx, FullHttpRequest request, HttpRule rule) throws Exception; } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandlerTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandlerTypeRegistry.java index 6296883..123f713 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandlerTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandlerTypeRegistry.java @@ -1,10 +1,9 @@ package top.offsetmonkey538.meshlib.common.api.handler; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; import top.offsetmonkey538.meshlib.common.impl.router.HttpHandlerTypeRegistryImpl; -import top.offsetmonkey538.offsetconfig538.api.event.Event; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; +import top.offsetmonkey538.offsetutils538.api.event.Event; import java.util.function.Function; @@ -16,21 +15,21 @@ public interface HttpHandlerTypeRegistry { /** * Instance */ - @ApiStatus.Internal - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated // Marking it as internal isn't enough cause I also need to prevent usage from other places in my code + @Internal HttpHandlerTypeRegistry INSTANCE = new HttpHandlerTypeRegistryImpl(); /** * Internal method for clearing the registry, no touch! */ - @ApiStatus.Internal + @Internal static void clear() { INSTANCE.clearImpl(); } void clearImpl(); - void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler); + void register(final String type, final Class dataType, final Class handlerType, final Function handlerToData, final Function dataToHandler); + HttpHandlerTypeRegistryImpl.HttpHandlerDefinition get(final String type) throws IllegalArgumentException; + HttpHandlerTypeRegistryImpl.HttpHandlerDefinition get(final Class type) throws IllegalArgumentException; /** @@ -58,7 +57,7 @@ interface HttpHandlerRegistrationEvent { /** * Internal method for invoking the event without providing the registry, no touch! */ - @ApiStatus.Internal + @Internal default void invoke() { register(INSTANCE); } @@ -68,6 +67,6 @@ default void invoke() { * * @param registry the registry to register to */ - void register(final @NotNull HttpHandlerTypeRegistry registry); + void register(final HttpHandlerTypeRegistry registry); } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticContentHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticContentHandler.java index 1a232e9..5bd64ab 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticContentHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticContentHandler.java @@ -2,29 +2,27 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; import top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil; - +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; public record StaticContentHandler(String content) implements HttpHandler { @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + public void handleRequest(ChannelHandlerContext ctx, FullHttpRequest request, HttpRule rule) throws Exception { HttpResponseUtil.sendString(ctx, request, content); } - @ApiStatus.Internal + @Internal public static void register(final HttpHandlerTypeRegistry registry) { registry.register("static-content", Data.class, StaticContentHandler.class, handler -> new Data(handler.content), data -> new StaticContentHandler(data.content)); } - @ApiStatus.Internal + @Internal private static final class Data { - private String content; + private String content = ""; @SuppressWarnings("unused") // Pretty sure this public no-args needs to exist cause jankson wants to create instances diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticDirectoryHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticDirectoryHandler.java index ef49899..dd09757 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticDirectoryHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticDirectoryHandler.java @@ -5,13 +5,17 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.*; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpUtil; +import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.CharsetUtil; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; import java.io.IOException; import java.net.URI; @@ -24,13 +28,16 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; -import static io.netty.handler.codec.http.HttpHeaderNames.*; +import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static top.offsetmonkey538.meshlib.common.MESHLib.LOGGER; -import static top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil.*; - +import static top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil.sendError; +import static top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil.sendFile; +import static top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil.sendPermanentRedirect; public record StaticDirectoryHandler(Path baseDir, boolean allowDirectoryList) implements HttpHandler { private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"); @@ -41,7 +48,7 @@ public StaticDirectoryHandler(final Path baseDir, final boolean allowDirectoryLi } @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + public void handleRequest(ChannelHandlerContext ctx, FullHttpRequest request, HttpRule rule) throws Exception { final String rawPath = new URI(rule.normalizeUri(request.uri())).getPath(); final Path requestedPath; try { @@ -169,18 +176,18 @@ private static void formatFileSize(StringBuilder builder, Path path) { builder.append("%.1f %ciB".formatted(value / 1024.0, charIterator.current())); } - @ApiStatus.Internal + @Internal public static void register(final HttpHandlerTypeRegistry registry) { registry.register("static-directory", Data.class, StaticDirectoryHandler.class, handler -> new Data(handler.baseDir, handler.allowDirectoryList), data -> new StaticDirectoryHandler(Path.of(data.baseDir), data.allowDirectoryList)); } - @ApiStatus.Internal + @Internal private static final class Data { - private String baseDir; + private String baseDir = ""; private boolean allowDirectoryList; - @SuppressWarnings("unused") // Pretty sure this public no-args needs to exist cause jankson wants to create instances + @SuppressWarnings("unused") public Data() { } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticFileHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticFileHandler.java index f27c31a..94ae267 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticFileHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/StaticFileHandler.java @@ -1,33 +1,31 @@ package top.offsetmonkey538.meshlib.common.api.handler.handlers; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.*; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; +import io.netty.handler.codec.http.FullHttpRequest; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; import top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; import java.nio.file.Path; - public record StaticFileHandler(Path fileToServe) implements HttpHandler { public StaticFileHandler(final Path fileToServe) { this.fileToServe = fileToServe.normalize().toAbsolutePath(); } @Override - public void handleRequest(@NotNull ChannelHandlerContext ctx, @NotNull FullHttpRequest request, @NotNull HttpRule rule) throws Exception { + public void handleRequest(ChannelHandlerContext ctx, FullHttpRequest request, HttpRule rule) throws Exception { HttpResponseUtil.sendFile(ctx, request, fileToServe); } - @ApiStatus.Internal + @Internal public static void register(final HttpHandlerTypeRegistry registry) { registry.register("static-file", Data.class, StaticFileHandler.class, handler -> new Data(handler.fileToServe), data -> new StaticFileHandler(Path.of(data.fileToServe))); } - @ApiStatus.Internal + @Internal private static final class Data { private String fileToServe; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/package-info.java new file mode 100644 index 0000000..bc73d92 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/handlers/package-info.java @@ -0,0 +1,7 @@ +/** + * Built-in {@link top.offsetmonkey538.meshlib.common.api.handler.HttpHandler HttpHandler}s + */ +@NullMarked +package top.offsetmonkey538.meshlib.common.api.handler.handlers; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/package-info.java new file mode 100644 index 0000000..faa0955 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/package-info.java @@ -0,0 +1,7 @@ +/** + * Http handler + */ +@NullMarked +package top.offsetmonkey538.meshlib.common.api.handler; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouter.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouter.java index 622e2b6..8b94029 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouter.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouter.java @@ -3,7 +3,6 @@ import blue.endless.jankson.Jankson; import blue.endless.jankson.JsonObject; import blue.endless.jankson.JsonPrimitive; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; @@ -11,12 +10,12 @@ import top.offsetmonkey538.meshlib.common.impl.router.HttpHandlerTypeRegistryImpl; import top.offsetmonkey538.meshlib.common.impl.router.rule.HttpRuleTypeRegistryImpl; -public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { +public record HttpRouter(HttpRule rule, HttpHandler handler) { /** * Configures the provided {@link Jankson.Builder} with serializers and deserializers for {@link HttpRule}s and {@link HttpHandler}s. *

- * When using my own config library, OffsetConfig538, its {@link top.offsetmonkey538.offsetconfig538.api.event.OffsetConfig538Events#JANKSON_CONFIGURATION_EVENT JANKSON_CONFIGURATION_EVENT} will have this configurator registered already and there's no need to call this method. + * When using my own config library, OffsetUtils538, its {@link top.offsetmonkey538.offsetutils538.api.config.event.JanksonConfigurationEvent#JANKSON_CONFIGURATION_EVENT JANKSON_CONFIGURATION_EVENT} will have this configurator registered already and there's no need to call this method. *
* For other config libraries... idk try to understand whatever the fuck I'm doing in this method I guess..... *

@@ -24,9 +23,9 @@ public record HttpRouter(@NotNull HttpRule rule, @NotNull HttpHandler handler) { * @param janksonBuilder the builder to configure * @return the builder instance */ - public static Jankson.Builder configureJankson(final @NotNull Jankson.Builder janksonBuilder) { + public static Jankson.Builder configureJankson(final Jankson.Builder janksonBuilder) { janksonBuilder.registerSerializer(HttpRule.class, (httpRule, marshaller) -> { - @SuppressWarnings({"unchecked", "deprecation"}) + @SuppressWarnings({"unchecked"}) // rule definition of ?,? extends HttpRule should match ?,HttpHandler, no? final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpRuleTypeRegistry.INSTANCE).get(httpRule.getClass()); @@ -37,8 +36,9 @@ public static Jankson.Builder configureJankson(final @NotNull Jankson.Builder ja janksonBuilder.registerDeserializer(JsonObject.class, HttpRule.class, (jsonObject, marshaller) -> { final String type = jsonObject.get(String.class, "type"); + if (type == null) throw new RuntimeException("HttpRule doesn't contain 'type' field!"); - @SuppressWarnings({"unchecked", "deprecation"}) // It's proooobably a subclass of Object... + @SuppressWarnings({"unchecked"}) // It's proooobably a subclass of Object... final HttpRuleTypeRegistryImpl.HttpRuleDefinition ruleDefinition = (HttpRuleTypeRegistryImpl.HttpRuleDefinition) ((HttpRuleTypeRegistryImpl) HttpRuleTypeRegistry.INSTANCE).get(type); final JsonObject dummyParent = new JsonObject(); @@ -46,14 +46,15 @@ public static Jankson.Builder configureJankson(final @NotNull Jankson.Builder ja dummyParent.put("dataHolder", jsonObject); final Object dataHolder = dummyParent.get(ruleDefinition.dataType(), "dataHolder"); + assert dataHolder != null; return ruleDefinition.dataToRule().apply(dataHolder); }); janksonBuilder.registerSerializer(HttpHandler.class, (httpHandler, marshaller) -> { - @SuppressWarnings({"unchecked", "deprecation"}) + @SuppressWarnings({"unchecked"}) // handler definition of ?,? extends HttpHandler should match ?,HttpHandler, no? - final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(httpHandler.getClass()); + final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) HttpHandlerTypeRegistry.INSTANCE.get(httpHandler.getClass()); final JsonObject result = (JsonObject) marshaller.serialize(handlerDefinition.handlerToData().apply(httpHandler)); result.put("type", JsonPrimitive.of(handlerDefinition.type())); @@ -62,15 +63,17 @@ public static Jankson.Builder configureJankson(final @NotNull Jankson.Builder ja janksonBuilder.registerDeserializer(JsonObject.class, HttpHandler.class, (jsonObject, marshaller) -> { final String type = jsonObject.get(String.class, "type"); + if (type == null) throw new RuntimeException("HttpRule doesn't contain 'type' field!"); - @SuppressWarnings({"unchecked", "deprecation"}) // It's proooobably a subclass of Object... - final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) ((HttpHandlerTypeRegistryImpl) HttpHandlerTypeRegistry.INSTANCE).get(type); + @SuppressWarnings({"unchecked"}) // It's proooobably a subclass of Object... + final HttpHandlerTypeRegistryImpl.HttpHandlerDefinition handlerDefinition = (HttpHandlerTypeRegistryImpl.HttpHandlerDefinition) HttpHandlerTypeRegistry.INSTANCE.get(type); final JsonObject dummyParent = new JsonObject(); jsonObject.remove("type"); dummyParent.put("dataHolder", jsonObject); final Object dataHolder = dummyParent.get(handlerDefinition.dataType(), "dataHolder"); + assert dataHolder != null; return handlerDefinition.dataToHandler().apply(dataHolder); }); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouterRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouterRegistry.java index d17867d..7de0344 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouterRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/HttpRouterRegistry.java @@ -1,9 +1,8 @@ package top.offsetmonkey538.meshlib.common.api.router; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.common.impl.HttpRouterRegistryImpl; -import top.offsetmonkey538.offsetconfig538.api.event.Event; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; +import top.offsetmonkey538.offsetutils538.api.event.Event; /** * Registry for {@link HttpRouter}s, use the {@link #HTTP_ROUTER_REGISTRATION_EVENT} event for registering your routers. @@ -12,21 +11,19 @@ public interface HttpRouterRegistry { /** * Instance */ - @ApiStatus.Internal - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated // Marking it as internal isn't enough cause I also need to prevent usage from other places in my code + @Internal HttpRouterRegistry INSTANCE = new HttpRouterRegistryImpl(); /** * Internal method for clearing the registry, no touch! */ - @ApiStatus.Internal + @Internal static void clear() { INSTANCE.clearImpl(); } - void clearImpl(); - void register(@NotNull final String id, @NotNull final HttpRouter router); + @Internal void clearImpl(); + void register(final String id, final HttpRouter router); /** @@ -51,7 +48,7 @@ interface HttpRouterRegistrationEvent { /** * Internal method for invoking the event without providing the registry, no touch! */ - @ApiStatus.Internal + @Internal default void invoke() { register(INSTANCE); } @@ -61,6 +58,6 @@ default void invoke() { * * @param registry the registry to register to */ - void register(final @NotNull HttpRouterRegistry registry); + void register(final HttpRouterRegistry registry); } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/package-info.java new file mode 100644 index 0000000..0389685 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/router/package-info.java @@ -0,0 +1,7 @@ +/** + * Http router + */ +@NullMarked +package top.offsetmonkey538.meshlib.common.api.router; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRule.java index aca429a..0150c81 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRule.java @@ -15,7 +15,7 @@ public interface HttpRule { * * @param uri the uri to modify */ - default String normalizeUri(final String uri) { + default String normalizeUri(final String uri) { // no-op return uri; } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRuleTypeRegistry.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRuleTypeRegistry.java index ac67db6..c01308a 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRuleTypeRegistry.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/HttpRuleTypeRegistry.java @@ -1,10 +1,9 @@ package top.offsetmonkey538.meshlib.common.api.rule; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; import top.offsetmonkey538.meshlib.common.impl.router.rule.HttpRuleTypeRegistryImpl; -import top.offsetmonkey538.offsetconfig538.api.event.Event; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; +import top.offsetmonkey538.offsetutils538.api.event.Event; import java.util.function.Function; @@ -15,21 +14,19 @@ public interface HttpRuleTypeRegistry { /** * Instance */ - @ApiStatus.Internal - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated // Marking it as internal isn't enough cause I also need to prevent usage from other places in my code + @Internal HttpRuleTypeRegistry INSTANCE = new HttpRuleTypeRegistryImpl(); /** * Internal method for clearing the registry, no touch! */ - @ApiStatus.Internal + @Internal static void clear() { INSTANCE.clearImpl(); } - void clearImpl(); - void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule); + @Internal void clearImpl(); + void register(final String type, final Class dataType, final Class ruleType, final Function ruleToData, final Function dataToRule); /** @@ -57,7 +54,7 @@ interface HttpRuleRegistrationEvent { /** * Internal method for invoking the event without providing the registry, no touch! */ - @ApiStatus.Internal + @Internal default void invoke() { register(INSTANCE); } @@ -67,6 +64,6 @@ default void invoke() { * * @param registry the registry to register to */ - void register(final @NotNull HttpRuleTypeRegistry registry); + void register(final HttpRuleTypeRegistry registry); } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/package-info.java new file mode 100644 index 0000000..93808ab --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/package-info.java @@ -0,0 +1,7 @@ +/** + * Http rule + */ +@NullMarked +package top.offsetmonkey538.meshlib.common.api.rule; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/DomainHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/DomainHttpRule.java index 26ade4b..d8caca4 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/DomainHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/DomainHttpRule.java @@ -2,9 +2,9 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; -import org.jetbrains.annotations.ApiStatus; import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; /** * @param domain i.e. map.example.com @@ -22,14 +22,14 @@ public boolean matches(FullHttpRequest request) { return host.equals(domain); } - @ApiStatus.Internal + @Internal public static void register(final HttpRuleTypeRegistry registry) { registry.register("domain", Data.class, DomainHttpRule.class, rule -> new Data(rule.domain), data -> new DomainHttpRule(data.domain)); } - @ApiStatus.Internal + @Internal private static final class Data { - private String domain; + private String domain = ""; @SuppressWarnings("unused") // Pretty sure this public no-args needs to exist cause jankson wants to create instances diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/PathHttpRule.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/PathHttpRule.java index b0b919d..4e3310c 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/PathHttpRule.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/PathHttpRule.java @@ -1,9 +1,9 @@ package top.offsetmonkey538.meshlib.common.api.rule.rules; import io.netty.handler.codec.http.FullHttpRequest; -import org.jetbrains.annotations.ApiStatus; import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; /** * @param path i.e. /map -> example.com/map @@ -24,14 +24,14 @@ public String normalizeUri(String uri) { return uri.replaceFirst(path, ""); } - @ApiStatus.Internal + @Internal public static void register(final HttpRuleTypeRegistry registry) { registry.register("path", Data.class, PathHttpRule.class, rule -> new Data(rule.path), data -> new PathHttpRule(data.path)); } - @ApiStatus.Internal + @Internal private static final class Data { - private String path; + private String path = ""; @SuppressWarnings("unused") // Pretty sure this public no-args needs to exist cause jankson wants to create instances diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/package-info.java new file mode 100644 index 0000000..63f7c30 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/rule/rules/package-info.java @@ -0,0 +1,7 @@ +/** + * Built-in {@link top.offsetmonkey538.meshlib.common.api.rule.HttpRule HttpRule}s + */ +@NullMarked +package top.offsetmonkey538.meshlib.common.api.rule.rules; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/HttpResponseUtil.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/HttpResponseUtil.java index 2868149..50eb324 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/HttpResponseUtil.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/HttpResponseUtil.java @@ -4,10 +4,9 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import top.offsetmonkey538.meshlib.common.impl.util.HttpResponseUtilImpl; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; import java.io.IOException; import java.nio.file.Path; @@ -19,7 +18,7 @@ public interface HttpResponseUtil { /** * Instance */ - @ApiStatus.Internal + @Internal HttpResponseUtil INSTANCE = new HttpResponseUtilImpl(); @@ -31,7 +30,7 @@ public interface HttpResponseUtil { * @param fileToSend the path to the file to send * @throws IOException when io go wrong :( */ - static void sendFile(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull Path fileToSend) throws IOException { + static void sendFile(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, Path fileToSend) throws IOException { INSTANCE.sendFileImpl(ctx, request, fileToSend); } @@ -44,7 +43,7 @@ static void sendFile(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpReque * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param newLocation the new location. */ - static void sendPermanentRedirect(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull String newLocation) { + static void sendPermanentRedirect(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, String newLocation) { INSTANCE.sendRedirectImpl(ctx, request, HttpResponseStatus.PERMANENT_REDIRECT, newLocation); } @@ -55,7 +54,7 @@ static void sendPermanentRedirect(@NotNull ChannelHandlerContext ctx, @Nullable * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param newLocation the new location. */ - static void sendTemporaryRedirect(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull String newLocation) { + static void sendTemporaryRedirect(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, String newLocation) { INSTANCE.sendRedirectImpl(ctx, request, HttpResponseStatus.TEMPORARY_REDIRECT, newLocation); } @@ -66,7 +65,7 @@ static void sendTemporaryRedirect(@NotNull ChannelHandlerContext ctx, @Nullable * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param content the plain text response to send */ - static void sendString(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull String content) { + static void sendString(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, String content) { INSTANCE.sendStringImpl(ctx, request, content); } @@ -79,7 +78,7 @@ static void sendString(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpReq * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus, Throwable) * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus, String) */ - static void sendError(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status) { + static void sendError(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, HttpResponseStatus status) { sendError(ctx, request, status, (String) null); } @@ -93,7 +92,7 @@ static void sendError(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequ * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus) * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus, String) */ - static void sendError(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status, @NotNull Throwable reason) { + static void sendError(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, HttpResponseStatus status, Throwable reason) { sendError(ctx, request, status, reason.getMessage()); } @@ -107,7 +106,7 @@ static void sendError(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequ * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus) * @see #sendError(ChannelHandlerContext, FullHttpRequest, HttpResponseStatus, Throwable) */ - static void sendError(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status, @Nullable String reason) { + static void sendError(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, HttpResponseStatus status, @Nullable String reason) { INSTANCE.sendErrorImpl(ctx, request, status, reason); } @@ -118,14 +117,14 @@ static void sendError(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequ * @param request the client request. Used to determine if keep-alive is to be used. Setting to null implies a non-keep-alive connection. * @param response the response to send */ - static void sendResponse(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull FullHttpResponse response) { + static void sendResponse(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, FullHttpResponse response) { INSTANCE.sendResponseImpl(ctx, request, response); } - void sendFileImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull Path fileToSend) throws IOException; - void sendRedirectImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status, @NotNull String newLocation); - void sendStringImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull String content); - void sendErrorImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status, @Nullable String reason); - void sendResponseImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull FullHttpResponse response); + @Internal void sendFileImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, Path fileToSend) throws IOException; + @Internal void sendRedirectImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, HttpResponseStatus status, String newLocation); + @Internal void sendStringImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, String content); + @Internal void sendErrorImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, HttpResponseStatus status, @Nullable String reason); + @Internal void sendResponseImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, FullHttpResponse response); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/package-info.java new file mode 100644 index 0000000..ba37ad7 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/util/package-info.java @@ -0,0 +1,7 @@ +/** + * Utilities + */ +@NullMarked +package top.offsetmonkey538.meshlib.common.api.util; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java index 16ab1d9..cabfc3a 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java @@ -1,16 +1,14 @@ package top.offsetmonkey538.meshlib.common.config; import blue.endless.jankson.Comment; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.monkeylib538.common.api.platform.LoaderUtil; -import top.offsetmonkey538.offsetconfig538.api.config.Config; +import top.offsetmonkey538.offsetutils538.api.config.Config; import java.nio.file.Path; import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; public final class MESHLibConfig implements Config { - @Comment("Used to figure out if mesh lib should inject into vanilla or not. Required as external port may differ from that's defined in server.properties") public String minecraftServerExternalPort = null; @Comment("Port the http server will bind to") @@ -18,12 +16,12 @@ public final class MESHLibConfig implements Config { @Override - public @NotNull Path getConfigDirPath() { + public Path getConfigDirPath() { return LoaderUtil.getConfigDir(); } @Override - public @NotNull String getId() { + public String getId() { return MOD_ID + "/main"; } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java index fd914a5..018b505 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java @@ -8,8 +8,7 @@ import com.google.common.collect.ImmutableMap; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.Nullable; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticContentHandler; import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticDirectoryHandler; @@ -21,6 +20,7 @@ import top.offsetmonkey538.meshlib.common.api.rule.rules.PathHttpRule; import top.offsetmonkey538.monkeylib538.common.api.command.CommandAbstractionApi; import top.offsetmonkey538.monkeylib538.common.api.platform.LoaderUtil; +import top.offsetmonkey538.offsetutils538.api.annotation.Unmodifiable; import java.io.IOException; import java.nio.file.Files; @@ -151,11 +151,11 @@ private static List gatherConfigFiles() throws IOException { } } - private static @NotNull Jankson configureJankson() { + private static Jankson configureJankson() { return HttpRouter.configureJankson(Jankson.builder()).build(); } - private static @Unmodifiable Map loadRouters(final @NotNull List configFiles, final @NotNull Jankson jankson) throws IllegalArgumentException { + private static @Unmodifiable Map loadRouters(final List configFiles, final Jankson jankson) throws IllegalArgumentException { final ImmutableMap.Builder resultBuilder = ImmutableMap.builder(); for (final Path path : configFiles) { @@ -183,7 +183,7 @@ private static List gatherConfigFiles() throws IOException { } @SuppressWarnings("DuplicateThrows") - private static HttpRouter loadRouter(final @NotNull String id, final @NotNull Path path, final @NotNull Jankson jankson) throws IOException, SyntaxError, Exception { + private static HttpRouter loadRouter(final String id, final Path path, final Jankson jankson) throws IOException, SyntaxError, Exception { final JsonObject json = jankson.load(Files.newInputStream(path)); try { @@ -194,13 +194,11 @@ private static HttpRouter loadRouter(final @NotNull String id, final @NotNull Pa } } - /** - * Exists because jankson requires a no-arg constructor to create an instance and then modify its fields, which wouldn't be possible with the record {@link HttpRouter} - */ + // Exists because jankson requires a no-arg constructor to create an instance and then modify its fields, which wouldn't be possible with the record {@link HttpRouter} @SuppressWarnings({"unused", "FieldMayBeFinal"}) private static class JanksonHttpRouter { - private HttpRule rule = null; - private HttpHandler handler = null; + private @Nullable HttpRule rule = null; + private @Nullable HttpHandler handler = null; public JanksonHttpRouter() { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/package-info.java new file mode 100644 index 0000000..316d9c6 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.common.config; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/HttpRouterRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/HttpRouterRegistryImpl.java index ff61092..0ebf8be 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/HttpRouterRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/HttpRouterRegistryImpl.java @@ -1,6 +1,5 @@ package top.offsetmonkey538.meshlib.common.impl; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.common.api.router.HttpRouter; import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; @@ -10,11 +9,11 @@ /** * Implementation of {@link HttpRouterRegistry} */ -public class HttpRouterRegistryImpl implements HttpRouterRegistry { +public final class HttpRouterRegistryImpl implements HttpRouterRegistry { private final Map routers = new HashMap<>(); @Override - public void register(@NotNull String id, @NotNull HttpRouter router) { + public void register(String id, HttpRouter router) { if (id.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); if (routers.containsKey(id)) throw new IllegalArgumentException("Handler with id '" + id + "' already registered!"); @@ -26,7 +25,7 @@ public void clearImpl() { routers.clear(); } - public @NotNull Iterable> iterable() { + public Iterable> iterable() { return this.routers.entrySet(); } } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java index 4e390eb..cf6a575 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java @@ -2,7 +2,9 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.codec.http.*; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpResponseStatus; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.common.api.router.HttpRouter; import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; @@ -16,12 +18,10 @@ import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; import static top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil.sendError; -/** - * Main HTTP handler for MESH. - *

- * Forwards the requests to {@link HttpHandler HttpHandler}s registered in {@link HttpRouterRegistry} +/* + * Forwards the requests to HttpHandler's registered in HttpRouterRegistry */ -public class MainHttpHandler extends SimpleChannelInboundHandler { +public final class MainHttpHandler extends SimpleChannelInboundHandler { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { @@ -32,7 +32,6 @@ protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) HttpRouter router = null; List matchedRouterIDs = new ArrayList<>(0); - //noinspection deprecation for (Map.Entry possibleRouter : ((HttpRouterRegistryImpl) HttpRouterRegistry.INSTANCE).iterable()) { if (!possibleRouter.getValue().rule().matches(request)) continue; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/ProtocolHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/ProtocolHandler.java index edb369b..bdbd0ed 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/ProtocolHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/ProtocolHandler.java @@ -11,11 +11,11 @@ import static top.offsetmonkey538.meshlib.common.MESHLib.LOGGER; import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; -/** +/* * Checks if a request is HTTP and either forwards it to {@link MainHttpHandler} if it is an HTTP request * and to the Minecraft handler otherwise. */ -public class ProtocolHandler extends ChannelInboundHandlerAdapter { +public final class ProtocolHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object request) throws Exception { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/package-info.java new file mode 100644 index 0000000..9f72d1d --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.common.impl; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/HttpHandlerTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/HttpHandlerTypeRegistryImpl.java index 7ff38b6..1e2f0f4 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/HttpHandlerTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/HttpHandlerTypeRegistryImpl.java @@ -1,6 +1,5 @@ package top.offsetmonkey538.meshlib.common.impl.router; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; @@ -12,7 +11,7 @@ /** * Implementation of {@link HttpRuleTypeRegistry} */ -public class HttpHandlerTypeRegistryImpl implements HttpHandlerTypeRegistry { +public final class HttpHandlerTypeRegistryImpl implements HttpHandlerTypeRegistry { private final Map> handlersById = new HashMap<>(); private final Map, HttpHandlerDefinition> handlersByType = new HashMap<>(); @@ -23,7 +22,7 @@ public void clearImpl() { } @Override - public void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class handlerType, @NotNull final Function handlerToData, @NotNull final Function dataToHandler) { + public void register(final String type, final Class dataType, final Class handlerType, final Function handlerToData, final Function dataToHandler) { if (type.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); if (handlersById.containsKey(type)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!"); if (handlersByType.containsKey(handlerType)) throw new IllegalArgumentException("Handler type for type '" + handlerType + "' already registered!"); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/package-info.java new file mode 100644 index 0000000..c03f8e2 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.common.impl.router; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/HttpRuleTypeRegistryImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/HttpRuleTypeRegistryImpl.java index fa3122c..2df9dac 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/HttpRuleTypeRegistryImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/HttpRuleTypeRegistryImpl.java @@ -1,6 +1,5 @@ package top.offsetmonkey538.meshlib.common.impl.router.rule; -import org.jetbrains.annotations.NotNull; import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; @@ -8,10 +7,7 @@ import java.util.Map; import java.util.function.Function; -/** - * Implementation of {@link HttpRuleTypeRegistry} - */ -public class HttpRuleTypeRegistryImpl implements HttpRuleTypeRegistry { +public final class HttpRuleTypeRegistryImpl implements HttpRuleTypeRegistry { private final Map> rulesById = new HashMap<>(); private final Map, HttpRuleDefinition> rulesByType = new HashMap<>(); @@ -22,7 +18,7 @@ public void clearImpl() { } @Override - public void register(@NotNull final String type, @NotNull final Class dataType, @NotNull final Class ruleType, @NotNull final Function ruleToData, @NotNull final Function dataToRule) { + public void register(final String type, final Class dataType, final Class ruleType, final Function ruleToData, final Function dataToRule) { if (type.isEmpty()) throw new IllegalArgumentException("Id may not be empty!"); if (rulesById.containsKey(type)) throw new IllegalArgumentException("Handler type with id '" + type + "' already registered!"); if (rulesByType.containsKey(ruleType)) throw new IllegalArgumentException("Handler type for type '" + ruleType + "' already registered!"); diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/package-info.java new file mode 100644 index 0000000..c8265b4 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/router/rule/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.common.impl.router.rule; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java index 648a844..006981f 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java @@ -5,18 +5,28 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.*; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.DefaultHttpResponse; +import io.netty.handler.codec.http.FullHttpRequest; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.handler.codec.http.HttpUtil; +import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.stream.ChunkedNioFile; import io.netty.util.CharsetUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import static io.netty.handler.codec.http.HttpHeaderNames.*; +import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; +import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; +import static io.netty.handler.codec.http.HttpHeaderNames.LOCATION; import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; import static io.netty.handler.codec.http.HttpResponseStatus.OK; @@ -25,9 +35,8 @@ import static top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil.sendError; public final class HttpResponseUtilImpl implements HttpResponseUtil { - @Override - public void sendFileImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull Path fileToSend) throws IOException { + public void sendFileImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, Path fileToSend) throws IOException { if (!Files.exists(fileToSend) || !Files.isRegularFile(fileToSend)) { sendError(ctx, request, HttpResponseStatus.NOT_FOUND); return; @@ -54,7 +63,7 @@ public void sendFileImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpR } @Override - public void sendRedirectImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status, @NotNull String newLocation) { + public void sendRedirectImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, HttpResponseStatus status, String newLocation) { final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.EMPTY_BUFFER); response.headers().set(LOCATION, newLocation); @@ -63,7 +72,7 @@ public void sendRedirectImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullH } @Override - public void sendStringImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull String content) { + public void sendStringImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, String content) { final ByteBuf byteBuf = Unpooled.copiedBuffer(content, CharsetUtil.UTF_8); final FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, byteBuf); @@ -75,7 +84,7 @@ public void sendStringImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHtt // TODO: Should the client be sent the reason? "Security through obscurity" and all that? A client can't really do much good with a "File /home/ubuntu/stupidServer/website/file.txt not found!" and they don't need this kind of info // Then again in some cases it'd probably be good to send some more specific info to the client? hmmmmmmmmmmmmmmmmmmmmmmmm @Override - public void sendErrorImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull HttpResponseStatus status, @Nullable String reason) { + public void sendErrorImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, HttpResponseStatus status, @Nullable String reason) { final StringBuilder messageBuilder = new StringBuilder("Failure: ").append(status); if (reason != null && !reason.isBlank()) messageBuilder.append("\nReason: ").append(reason); @@ -93,7 +102,7 @@ public void sendErrorImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttp } @Override - public void sendResponseImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NotNull FullHttpResponse response) { + public void sendResponseImpl(@NonNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NonNull FullHttpResponse response) { final boolean isKeepAlive = request != null && HttpUtil.isKeepAlive(request); response.headers().set(CONNECTION, isKeepAlive ? KEEP_ALIVE : CLOSE); @@ -103,7 +112,7 @@ public void sendResponseImpl(@NotNull ChannelHandlerContext ctx, @Nullable FullH if (!isKeepAlive) future.addListener(ChannelFutureListener.CLOSE); } - private static String getContentType(final @NotNull Path file) { + private static String getContentType(final @NonNull Path file) { final String result; try { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/package-info.java new file mode 100644 index 0000000..cecba7f --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.common.impl.util; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/package-info.java new file mode 100644 index 0000000..a5efa31 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.common; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformUtil.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformUtil.java index c832f38..ceedd80 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformUtil.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/PlatformUtil.java @@ -1,14 +1,11 @@ package top.offsetmonkey538.meshlib.common.platform; -import org.jetbrains.annotations.ApiStatus; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; import static top.offsetmonkey538.meshlib.common.MESHLib.load; -@ApiStatus.Internal public interface PlatformUtil { - - @SuppressWarnings("DeprecatedIsStillUsed") - @Deprecated // Only for use in this class + @Internal PlatformUtil INSTANCE = load(PlatformUtil.class); static void enableVanillaHandler() { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/package-info.java new file mode 100644 index 0000000..45b8d25 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/platform/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.common.platform; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 4c3bf26..3665197 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,13 +3,15 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-beta.2.1769416853692+a710cf2 +monkeylib538_version = 3.0.0-beta.2.1769432463033+801bc21 ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.82.Final ## Bundeled with Minecraft, common needs to use guava_version = 33.4.0-jre ## Bundeled with Minecraft, common needs to use brigadier_version = 1.3.10 +## JSpecify, check at https://mvnrepository.com/artifact/org.jspecify/jspecify +jspecify_version = 1.0.0 ## DevAuth, check at https://github.com/DJtheRedstoner/DevAuth devauth_version = 1.2.2 diff --git a/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/package-info.java b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/package-info.java new file mode 100644 index 0000000..3482422 --- /dev/null +++ b/loader/fabric/src/main/java/top/offsetmonkey538/meshlib/fabric/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.fabric; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/package-info.java b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/package-info.java new file mode 100644 index 0000000..2cf37cb --- /dev/null +++ b/loader/neoforge/src/main/java/top/offsetmonkey538/meshlib/neoforge/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.neoforge; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PlatformUtilImpl.java b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PlatformUtilImpl.java index f6e078d..bfa256e 100644 --- a/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PlatformUtilImpl.java +++ b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/PlatformUtilImpl.java @@ -10,8 +10,6 @@ public final class PlatformUtilImpl implements PlatformUtil { private static final Key HANDLER_KEY = Key.key("meshlib", "meshlib_vanilla_handler"); - private static MESHLibInitializer plugin; - @Override public void enableVanillaHandlerImpl() { if (ChannelInitializeListenerHolder.hasListener(HANDLER_KEY)) return; @@ -23,20 +21,10 @@ public void disableVanillaHandlerImpl() { ChannelInitializeListenerHolder.removeListener(HANDLER_KEY); } - public static void setPlugin(MESHLibInitializer plugin) { - PlatformUtilImpl.plugin = plugin; - } - - public static MESHLibInitializer getPlugin() { - return plugin; - } - - public static final class MESHLibInitializer extends JavaPlugin { @Override public void onEnable() { - PlatformUtilImpl.setPlugin(this); MESHLib.initialize(); } } diff --git a/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/package-info.java b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/package-info.java new file mode 100644 index 0000000..975721b --- /dev/null +++ b/loader/paper/src/main/java/top/offsetmonkey538/meshlib/paper/platform/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.paper.platform; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/modded/src/main/java/top/offsetmonkey538/meshlib/modded/mixin/package-info.java b/modded/src/main/java/top/offsetmonkey538/meshlib/modded/mixin/package-info.java new file mode 100644 index 0000000..6e3f2f0 --- /dev/null +++ b/modded/src/main/java/top/offsetmonkey538/meshlib/modded/mixin/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.modded.mixin; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/modded/src/main/java/top/offsetmonkey538/meshlib/modded/platform/package-info.java b/modded/src/main/java/top/offsetmonkey538/meshlib/modded/platform/package-info.java new file mode 100644 index 0000000..ccdeeb3 --- /dev/null +++ b/modded/src/main/java/top/offsetmonkey538/meshlib/modded/platform/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.modded.platform; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file From 284a7c0911d0eb0f2868179d394a3722ba6d29c1 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Tue, 27 Jan 2026 18:46:53 +0200 Subject: [PATCH 60/78] Allow hosting on separate port from minecraft, actually use the config now lol --- common/build.gradle | 1 + .../meshlib/common/MESHLib.java | 50 +++++++++++--- .../meshlib/common/config/MESHLibConfig.java | 5 +- .../meshlib/common/netty/NettyServer.java | 66 +++++++++++++++++++ .../meshlib/common/netty/package-info.java | 4 ++ gradle.properties | 4 +- 6 files changed, 118 insertions(+), 12 deletions(-) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/netty/NettyServer.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/netty/package-info.java diff --git a/common/build.gradle b/common/build.gradle index c2f21a7..ce8c170 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -15,6 +15,7 @@ repositories { dependencies { compileOnlyApi "top.offsetmonkey538.monkeylib538:monkeylib538-common:${rootProject.monkeylib538_version}+common" compileOnlyApi "io.netty:netty-codec-http:${rootProject.netty_version}" + compileOnlyApi "io.netty:netty-transport-classes-epoll:${rootProject.netty_version}" compileOnly "com.google.guava:guava:${rootProject.guava_version}" compileOnly "com.mojang:brigadier:${rootProject.brigadier_version}" } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java index 011d26f..9471fb8 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java @@ -1,6 +1,7 @@ package top.offsetmonkey538.meshlib.common; import com.google.common.base.Stopwatch; +import top.offsetmonkey538.meshlib.common.api.example.ExampleMain; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticContentHandler; import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticDirectoryHandler; @@ -12,16 +13,19 @@ import top.offsetmonkey538.meshlib.common.api.rule.rules.PathHttpRule; import top.offsetmonkey538.meshlib.common.config.MESHLibConfig; import top.offsetmonkey538.meshlib.common.config.RouterConfigHandler; -import top.offsetmonkey538.meshlib.common.api.example.ExampleMain; +import top.offsetmonkey538.meshlib.common.netty.NettyServer; import top.offsetmonkey538.meshlib.common.platform.PlatformUtil; import top.offsetmonkey538.monkeylib538.common.api.command.CommandRegistrationApi; import top.offsetmonkey538.monkeylib538.common.api.command.ConfigCommandApi; import top.offsetmonkey538.monkeylib538.common.api.lifecycle.ServerLifecycleApi; -import top.offsetmonkey538.monkeylib538.common.api.log.MonkeyLibLogger; import top.offsetmonkey538.offsetutils538.api.config.ConfigHolder; import top.offsetmonkey538.offsetutils538.api.config.ConfigManager; import top.offsetmonkey538.offsetutils538.api.config.event.JanksonConfigurationEvent; +import top.offsetmonkey538.offsetutils538.api.log.OffsetLogger; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import java.util.ServiceLoader; public final class MESHLib { @@ -37,9 +41,9 @@ private MESHLib() {} /** * Logger instance used by this mod */ - public static final MonkeyLibLogger LOGGER = MonkeyLibLogger.create(MOD_ID); + public static final OffsetLogger LOGGER = OffsetLogger.create(MOD_ID); - public static final ConfigHolder CONFIG = ConfigManager.init(ConfigHolder.create(MESHLibConfig::new, LOGGER::error)); + public static final ConfigHolder CONFIG = ConfigManager.init(ConfigHolder.create(MESHLibConfig::new, LOGGER)); public static void initialize() { @@ -66,20 +70,50 @@ public static void initialize() { // TODO: move somewhere under api so others can invoke a reload? For example git pack manager after reloading its config cause that's where the rule for it will be stored. public static void reload() { LOGGER.info("Reloading MESH Lib..."); - final Stopwatch stopwatch = Stopwatch.createStarted(); + final Stopwatch fullStopwatch = Stopwatch.createStarted(); + disableAllHandlers(); + final Stopwatch stageStopwatch = Stopwatch.createStarted(); HttpHandlerTypeRegistry.clear(); HttpRuleTypeRegistry.clear(); HttpRouterRegistry.clear(); - LOGGER.info("Registries cleared"); + LOGGER.info("Registries cleared in %s", stageStopwatch.stop()); + stageStopwatch.reset().start(); HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.getInvoker().invoke(); HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.getInvoker().invoke(); HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.getInvoker().invoke(); - LOGGER.info("Registries repopulated"); + LOGGER.info("Registries repopulated in %s", stageStopwatch.stop()); + stageStopwatch.reset().start(); + + // Check if config is OK + final List errors = new ArrayList<>(); + if (CONFIG.get().httpPort == null) errors.add("Field 'httpPort' not set!"); + if (CONFIG.get().minecraftServerExternalPort == null) errors.add("Field 'minecraftServerExternalPort' not set!"); + if (!errors.isEmpty()) { + LOGGER.error("There were problems with the config for MESH Lib, mod will be disabled, see below for more details!"); + errors.stream().map(string -> " " + string).forEach(LOGGER::error); + return; + } + + // Initialize + if (Objects.equals(CONFIG.get().minecraftServerExternalPort, CONFIG.get().httpPort)) { + LOGGER.info("Initializing MESH Lib on vanilla port %s...", CONFIG.get().minecraftServerExternalPort); + PlatformUtil.enableVanillaHandler(); + LOGGER.info("MESH Lib initialized on vanilla port %s!", CONFIG.get().minecraftServerExternalPort); + } else { + LOGGER.info("Initializing MESH Lib on custom port %s...", CONFIG.get().httpPort); + NettyServer.start(); + LOGGER.info("MESH Lib initialized on custom port %s!", CONFIG.get().httpPort); + } + + LOGGER.info("MESH Lib reloaded in %s!", fullStopwatch.stop()); + } - LOGGER.info("MESH Lib reloaded in %s!", stopwatch.stop()); + private static void disableAllHandlers() { + PlatformUtil.disableVanillaHandler(); + NettyServer.stop(); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java index cabfc3a..98887a5 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java @@ -1,6 +1,7 @@ package top.offsetmonkey538.meshlib.common.config; import blue.endless.jankson.Comment; +import org.jspecify.annotations.Nullable; import top.offsetmonkey538.monkeylib538.common.api.platform.LoaderUtil; import top.offsetmonkey538.offsetutils538.api.config.Config; @@ -10,9 +11,9 @@ public final class MESHLibConfig implements Config { @Comment("Used to figure out if mesh lib should inject into vanilla or not. Required as external port may differ from that's defined in server.properties") - public String minecraftServerExternalPort = null; + public @Nullable Integer minecraftServerExternalPort = null; @Comment("Port the http server will bind to") - public String httpPort = null; + public @Nullable Integer httpPort = null; @Override diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/netty/NettyServer.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/netty/NettyServer.java new file mode 100644 index 0000000..1c82f67 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/netty/NettyServer.java @@ -0,0 +1,66 @@ +package top.offsetmonkey538.meshlib.common.netty; + +import blue.endless.jankson.annotation.Nullable; +import com.google.common.base.Suppliers; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.ServerChannel; +import io.netty.channel.epoll.Epoll; +import io.netty.channel.epoll.EpollEventLoopGroup; +import io.netty.channel.epoll.EpollServerSocketChannel; +import io.netty.channel.local.LocalAddress; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import top.offsetmonkey538.meshlib.common.impl.ProtocolHandler; +import top.offsetmonkey538.monkeylib538.common.api.platform.LoaderUtil; + +import java.net.InetAddress; +import java.util.function.Supplier; + +import static top.offsetmonkey538.meshlib.common.MESHLib.CONFIG; +import static top.offsetmonkey538.meshlib.common.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; + +public final class NettyServer { + private NettyServer() {} + + public static final Supplier SERVER_EVENT_GROUP = Suppliers.memoize(() -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler((thread, cause) -> LOGGER.error("Caught unhandled exception in thread %s:", cause, thread.getName())).build())); + public static final Supplier SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(() -> new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler((thread, cause) -> LOGGER.error("Caught unhandled exception in thread %s:", cause, thread.getName())).build())); + + public static @Nullable ChannelFuture channelFuture; + + public static void start() { + final Class chennelClass; + final EventLoopGroup eventLoopGroup; + if (Epoll.isAvailable() && LoaderUtil.isEpollEnabled()) { + chennelClass = EpollServerSocketChannel.class; + eventLoopGroup = SERVER_EPOLL_EVENT_GROUP.get(); + LOGGER.info("Using epoll channel type"); + } else { + chennelClass = NioServerSocketChannel.class; + eventLoopGroup = SERVER_EVENT_GROUP.get(); + LOGGER.info("Using nio channel type"); + } + + final ServerBootstrap bootstrap = new ServerBootstrap(); + channelFuture = bootstrap.channel(chennelClass).group(eventLoopGroup).localAddress((InetAddress) null, CONFIG.get().httpPort).childHandler(new ChannelInitializer<>() { + @Override + protected void initChannel(Channel channel) throws Exception { + channel.pipeline().addFirst(MOD_ID, new ProtocolHandler()); + } + }).bind().syncUninterruptibly(); + } + + public static void stop() { + if (channelFuture == null) return; + try { + channelFuture.channel().close().sync(); + } catch (InterruptedException e) { + LOGGER.error("Interrupted while stopping Netty server"); + } + } +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/netty/package-info.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/netty/package-info.java new file mode 100644 index 0000000..c457ce4 --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/netty/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package top.offsetmonkey538.meshlib.common.netty; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3665197..332bcaf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,9 +3,9 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-beta.2.1769432463033+801bc21 +monkeylib538_version = 3.0.0-beta.2.1769529263556+local ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty -netty_version = 4.1.82.Final +netty_version = 4.1.97.Final ## Bundeled with Minecraft, common needs to use guava_version = 33.4.0-jre ## Bundeled with Minecraft, common needs to use From 1493fc41268520769d69296044508987fefb6a80 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Tue, 27 Jan 2026 18:47:19 +0200 Subject: [PATCH 61/78] optimize imports --- .../offsetmonkey538/meshlib/common/api/handler/HttpHandler.java | 2 +- .../offsetmonkey538/meshlib/common/impl/MainHttpHandler.java | 1 - .../top/offsetmonkey538/meshlib/common/netty/NettyServer.java | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java index 4e6f3ae..27c916f 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/handler/HttpHandler.java @@ -2,9 +2,9 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.FullHttpRequest; +import top.offsetmonkey538.meshlib.common.api.example.ExampleHttpHandler; import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; import top.offsetmonkey538.meshlib.common.api.rule.HttpRule; -import top.offsetmonkey538.meshlib.common.api.example.ExampleHttpHandler; /** * An http handler for you to implement :D diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java index cf6a575..fc10c2a 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MainHttpHandler.java @@ -5,7 +5,6 @@ import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; -import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.common.api.router.HttpRouter; import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/netty/NettyServer.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/netty/NettyServer.java index 1c82f67..4d7b872 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/netty/NettyServer.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/netty/NettyServer.java @@ -12,7 +12,6 @@ import io.netty.channel.epoll.Epoll; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; -import io.netty.channel.local.LocalAddress; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import top.offsetmonkey538.meshlib.common.impl.ProtocolHandler; From 37223ab155c4620079991ba58c43d83e8aacd04d Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Tue, 27 Jan 2026 18:48:26 +0200 Subject: [PATCH 62/78] update copyright notice --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 9e03ee7..6b9c8b3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024,2025 OffsetMonkey538 +Copyright (c) 2024-2026 OffsetMonkey538 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 41c35435b36927dd3d87437c82f480c4d0f2acf7 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Tue, 27 Jan 2026 19:00:27 +0200 Subject: [PATCH 63/78] set mod_website to modrinth page --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 332bcaf..d05e092 100644 --- a/gradle.properties +++ b/gradle.properties @@ -30,6 +30,6 @@ mod_version = 2.0.0-alpha.0 mod_name = MESH Lib mod_id = meshlib mod_description = Easy to use library for hosting an HTTP server on the Minecraft server's port -mod_website = https://github.com/OffsetMods538/MESH-Lib +mod_website = https://modrinth.com/mod/mesh-lib mod_issues = https://github.com/OffsetMods538/MESH-Lib/issues mod_sources = https://github.com/OffsetMods538/MESH-Lib From 4a26cb6456373426c9306cc1c8bbaf4a26ee4f3e Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Tue, 27 Jan 2026 19:05:58 +0200 Subject: [PATCH 64/78] update monkeylib --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index d05e092..3a55862 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-beta.2.1769529263556+local +monkeylib538_version = 3.0.0-beta.2.1769533505281+9e950f8 ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.97.Final ## Bundeled with Minecraft, common needs to use From ccf70b5c4ef9ffeef3e1c7ea6d2203231fe791b1 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Wed, 28 Jan 2026 17:33:55 +0200 Subject: [PATCH 65/78] Create MESHLibApi, move reload code there, add method to get exposed port and config option to set exposed port --- .../meshlib/common/MESHLib.java | 51 ++------------ .../meshlib/common/api/MESHLibApi.java | 34 +++++++++ .../meshlib/common/config/MESHLibConfig.java | 2 + .../meshlib/common/impl/MESHLibApiImpl.java | 69 +++++++++++++++++++ 4 files changed, 109 insertions(+), 47 deletions(-) create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/api/MESHLibApi.java create mode 100644 common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java index 9471fb8..8866b96 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java @@ -1,6 +1,7 @@ package top.offsetmonkey538.meshlib.common; import com.google.common.base.Stopwatch; +import top.offsetmonkey538.meshlib.common.api.MESHLibApi; import top.offsetmonkey538.meshlib.common.api.example.ExampleMain; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticContentHandler; @@ -50,7 +51,7 @@ public static void initialize() { PlatformUtil.enableVanillaHandler(); ExampleMain.onInitialize(); - ConfigCommandApi.registerConfigCommand(CONFIG, MESHLib::reload, MOD_ID, "config"); + ConfigCommandApi.registerConfigCommand(CONFIG, MESHLibApi::reload, MOD_ID, "config"); CommandRegistrationApi.registerCommand(RouterConfigHandler.createExampleConfigCommand()); JanksonConfigurationEvent.JANKSON_CONFIGURATION_EVENT.listen(HttpRouter::configureJankson); @@ -64,54 +65,10 @@ public static void initialize() { HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.listen(RouterConfigHandler::init); - ServerLifecycleApi.STARTING.listen(MESHLib::reload); + ServerLifecycleApi.STARTING.listen(MESHLibApi::reload); } - // TODO: move somewhere under api so others can invoke a reload? For example git pack manager after reloading its config cause that's where the rule for it will be stored. - public static void reload() { - LOGGER.info("Reloading MESH Lib..."); - final Stopwatch fullStopwatch = Stopwatch.createStarted(); - disableAllHandlers(); - - final Stopwatch stageStopwatch = Stopwatch.createStarted(); - HttpHandlerTypeRegistry.clear(); - HttpRuleTypeRegistry.clear(); - HttpRouterRegistry.clear(); - LOGGER.info("Registries cleared in %s", stageStopwatch.stop()); - stageStopwatch.reset().start(); - - - HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.getInvoker().invoke(); - HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.getInvoker().invoke(); - HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.getInvoker().invoke(); - LOGGER.info("Registries repopulated in %s", stageStopwatch.stop()); - stageStopwatch.reset().start(); - - // Check if config is OK - final List errors = new ArrayList<>(); - if (CONFIG.get().httpPort == null) errors.add("Field 'httpPort' not set!"); - if (CONFIG.get().minecraftServerExternalPort == null) errors.add("Field 'minecraftServerExternalPort' not set!"); - if (!errors.isEmpty()) { - LOGGER.error("There were problems with the config for MESH Lib, mod will be disabled, see below for more details!"); - errors.stream().map(string -> " " + string).forEach(LOGGER::error); - return; - } - - // Initialize - if (Objects.equals(CONFIG.get().minecraftServerExternalPort, CONFIG.get().httpPort)) { - LOGGER.info("Initializing MESH Lib on vanilla port %s...", CONFIG.get().minecraftServerExternalPort); - PlatformUtil.enableVanillaHandler(); - LOGGER.info("MESH Lib initialized on vanilla port %s!", CONFIG.get().minecraftServerExternalPort); - } else { - LOGGER.info("Initializing MESH Lib on custom port %s...", CONFIG.get().httpPort); - NettyServer.start(); - LOGGER.info("MESH Lib initialized on custom port %s!", CONFIG.get().httpPort); - } - - LOGGER.info("MESH Lib reloaded in %s!", fullStopwatch.stop()); - } - - private static void disableAllHandlers() { + public static void disableAllHandlers() { PlatformUtil.disableVanillaHandler(); NettyServer.stop(); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/MESHLibApi.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/MESHLibApi.java new file mode 100644 index 0000000..8ade47d --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/MESHLibApi.java @@ -0,0 +1,34 @@ +package top.offsetmonkey538.meshlib.common.api; + +import org.jspecify.annotations.Nullable; +import top.offsetmonkey538.meshlib.common.impl.MESHLibApiImpl; +import top.offsetmonkey538.offsetutils538.api.annotation.Internal; + +/** + * Api for interacting with mesh lib + */ +public interface MESHLibApi { + @Internal + MESHLibApi INSTANCE = new MESHLibApiImpl(); + + /** + * Reloads MESH Lib. + */ + static void reload() { + INSTANCE.reloadImpl(); + } + + /** + * Provides the external port as defined in the MESH Lib config. + *

{@code null} value indicates that MESH Lib has not been set up correctly and isn't running.

+ * + * @return the port the server is accessible from externally + */ + static @Nullable Integer getExternalPort() { + return INSTANCE.getExternalPortImpl(); + } + + + @Internal void reloadImpl(); + @Internal @Nullable Integer getExternalPortImpl(); +} diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java index 98887a5..50c7b8e 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java @@ -14,6 +14,8 @@ public final class MESHLibConfig implements Config { public @Nullable Integer minecraftServerExternalPort = null; @Comment("Port the http server will bind to") public @Nullable Integer httpPort = null; + @Comment("Port the http server will be accessed from externally. Used by for example Git Pack Manager when generating the download url. The HTTP server will still be hosted on the 'httpPort'. Useful when running the server behind some sort of proxy like docker, nginx, traefik, cloudflare tunnel, etc.") + public @Nullable Integer exposedPort = null; @Override diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java new file mode 100644 index 0000000..ee9381a --- /dev/null +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java @@ -0,0 +1,69 @@ +package top.offsetmonkey538.meshlib.common.impl; + +import com.google.common.base.Stopwatch; +import org.jspecify.annotations.Nullable; +import top.offsetmonkey538.meshlib.common.api.MESHLibApi; +import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; +import top.offsetmonkey538.meshlib.common.api.router.HttpRouterRegistry; +import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; +import top.offsetmonkey538.meshlib.common.netty.NettyServer; +import top.offsetmonkey538.meshlib.common.platform.PlatformUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import static top.offsetmonkey538.meshlib.common.MESHLib.CONFIG; +import static top.offsetmonkey538.meshlib.common.MESHLib.LOGGER; +import static top.offsetmonkey538.meshlib.common.MESHLib.disableAllHandlers; + +public final class MESHLibApiImpl implements MESHLibApi { + public void reloadImpl() { + LOGGER.info("Reloading MESH Lib..."); + final Stopwatch fullStopwatch = Stopwatch.createStarted(); + disableAllHandlers(); + + final Stopwatch stageStopwatch = Stopwatch.createStarted(); + HttpHandlerTypeRegistry.clear(); + HttpRuleTypeRegistry.clear(); + HttpRouterRegistry.clear(); + LOGGER.info("Registries cleared in %s", stageStopwatch.stop()); + stageStopwatch.reset().start(); + + + HttpHandlerTypeRegistry.HTTP_HANDLER_REGISTRATION_EVENT.getInvoker().invoke(); + HttpRuleTypeRegistry.HTTP_RULE_REGISTRATION_EVENT.getInvoker().invoke(); + HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.getInvoker().invoke(); + LOGGER.info("Registries repopulated in %s", stageStopwatch.stop()); + stageStopwatch.reset().start(); + + // Check if config is OK + final List errors = new ArrayList<>(); + if (CONFIG.get().httpPort == null) errors.add("Field 'httpPort' not set!"); + if (CONFIG.get().minecraftServerExternalPort == null) errors.add("Field 'minecraftServerExternalPort' not set!"); + if (!errors.isEmpty()) { + LOGGER.error("There were problems with the config for MESH Lib, mod will be disabled, see below for more details!"); + errors.stream().map(string -> " " + string).forEach(LOGGER::error); + return; + } + + // Initialize + if (Objects.equals(CONFIG.get().minecraftServerExternalPort, CONFIG.get().httpPort)) { + LOGGER.info("Initializing MESH Lib on vanilla port %s...", CONFIG.get().minecraftServerExternalPort); + PlatformUtil.enableVanillaHandler(); + LOGGER.info("MESH Lib initialized on vanilla port %s!", CONFIG.get().minecraftServerExternalPort); + } else { + LOGGER.info("Initializing MESH Lib on custom port %s...", CONFIG.get().httpPort); + NettyServer.start(); + LOGGER.info("MESH Lib initialized on custom port %s!", CONFIG.get().httpPort); + } + + LOGGER.info("MESH Lib reloaded in %s!", fullStopwatch.stop()); + } + + @Override + public @Nullable Integer getExternalPortImpl() { + if (CONFIG.get().exposedPort != null) return CONFIG.get().exposedPort; + return CONFIG.get().httpPort; + } +} From a8207acba7521c3c20e11a2c0acc68290b8603b5 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Wed, 28 Jan 2026 17:35:32 +0200 Subject: [PATCH 66/78] cleanup --- .../java/top/offsetmonkey538/meshlib/common/MESHLib.java | 4 ---- .../meshlib/common/impl/util/HttpResponseUtilImpl.java | 5 ++--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java index 8866b96..8d38b80 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java @@ -1,6 +1,5 @@ package top.offsetmonkey538.meshlib.common; -import com.google.common.base.Stopwatch; import top.offsetmonkey538.meshlib.common.api.MESHLibApi; import top.offsetmonkey538.meshlib.common.api.example.ExampleMain; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandlerTypeRegistry; @@ -24,9 +23,6 @@ import top.offsetmonkey538.offsetutils538.api.config.event.JanksonConfigurationEvent; import top.offsetmonkey538.offsetutils538.api.log.OffsetLogger; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; import java.util.ServiceLoader; public final class MESHLib { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java index 006981f..6167190 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/util/HttpResponseUtilImpl.java @@ -15,7 +15,6 @@ import io.netty.handler.codec.http.LastHttpContent; import io.netty.handler.stream.ChunkedNioFile; import io.netty.util.CharsetUtil; -import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; import top.offsetmonkey538.meshlib.common.api.util.HttpResponseUtil; @@ -102,7 +101,7 @@ public void sendErrorImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest r } @Override - public void sendResponseImpl(@NonNull ChannelHandlerContext ctx, @Nullable FullHttpRequest request, @NonNull FullHttpResponse response) { + public void sendResponseImpl(ChannelHandlerContext ctx, @Nullable FullHttpRequest request, FullHttpResponse response) { final boolean isKeepAlive = request != null && HttpUtil.isKeepAlive(request); response.headers().set(CONNECTION, isKeepAlive ? KEEP_ALIVE : CLOSE); @@ -112,7 +111,7 @@ public void sendResponseImpl(@NonNull ChannelHandlerContext ctx, @Nullable FullH if (!isKeepAlive) future.addListener(ChannelFutureListener.CLOSE); } - private static String getContentType(final @NonNull Path file) { + private static String getContentType(final Path file) { final String result; try { From 86028b3d19b32dcf937aed53ba20c8babbd560d4 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Thu, 29 Jan 2026 17:37:56 +0200 Subject: [PATCH 67/78] fixc github actions --- .github/workflows/build_artifacts.yml | 8 ++------ .github/workflows/publish.yml | 9 ++++----- .../meshlib/common/impl/MESHLibApiImpl.java | 4 ++-- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml index 324aaa2..90f6d91 100644 --- a/.github/workflows/build_artifacts.yml +++ b/.github/workflows/build_artifacts.yml @@ -45,12 +45,8 @@ jobs: MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} - - name: Delete common libs - run: rm -r ./common/build/libs - - name: Upload build artifacts uses: actions/upload-artifact@v4 with: - name: Artifacts - path: | - ./*/build/libs/ + name: Build Artifacts + path: ./loader/*/*/build/libs/ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4e8033f..4333922 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -33,12 +33,14 @@ jobs: env: IS_RELEASE: true DISABLE_PROPERTIES_UPDATE: true + VERSION_SUFFIX: "" - name: Upload to Modrinth run: ./gradlew modrinth env: IS_RELEASE: true DISABLE_PROPERTIES_UPDATE: true + VERSION_SUFFIX: "" MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} VERSION_NAME: ${{ github.event.release.name }} VERSION_IS_PRERELEASE: ${{ github.event.release.prerelease }} @@ -49,14 +51,11 @@ jobs: env: IS_RELEASE: true DISABLE_PROPERTIES_UPDATE: true + VERSION_SUFFIX: "" MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} - - name: Delete common libs - run: rm -r ./common/build/libs - - name: Upload to GitHub uses: softprops/action-gh-release@v2 with: - files: | - ./*/build/libs/*.jar + files: ./loader/*/*/build/libs/*.jar diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java index ee9381a..cd83f15 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java @@ -49,9 +49,9 @@ public void reloadImpl() { // Initialize if (Objects.equals(CONFIG.get().minecraftServerExternalPort, CONFIG.get().httpPort)) { - LOGGER.info("Initializing MESH Lib on vanilla port %s...", CONFIG.get().minecraftServerExternalPort); + LOGGER.info("Initializing MESH Lib on vanilla port %s...", CONFIG.get().httpPort); PlatformUtil.enableVanillaHandler(); - LOGGER.info("MESH Lib initialized on vanilla port %s!", CONFIG.get().minecraftServerExternalPort); + LOGGER.info("MESH Lib initialized on vanilla port %s!", CONFIG.get().httpPort); } else { LOGGER.info("Initializing MESH Lib on custom port %s...", CONFIG.get().httpPort); NettyServer.start(); From bbacf531fe5a44ddd14cdb99f8200ebafbbf8a3a Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Thu, 29 Jan 2026 18:58:32 +0200 Subject: [PATCH 68/78] use idexed color mode for icon --- .../src/main/resources/assets/meshlib/icon.png | Bin 1644 -> 821 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/common/src/main/resources/assets/meshlib/icon.png b/common/src/main/resources/assets/meshlib/icon.png index 5363c794ae76e9c35b900419feca20ecf4d0907e..4c9d1200e4672ce03d9aef4fbcc25c6cdbebc58a 100644 GIT binary patch delta 809 zcmV+^1J?ZP47CQ38Gi!+008vhk@)}s00DDSM?wIu&K&6g000tDOjJbx0068qGT(6~ z*;iwamrB=rx>paCtN;K226R$RQvm<}|NsAbdEov400O^BL_t(|obB6Da>F1D1W>!O z?|PZ9ikfSjKohyxr~Edw0SS^_w3l@B-$To3`M^F4w&`J&+c0KI&(z>vQj z(9bst4EYv-WlP34O#w)4EdcGdTQCbVQ({F+s;S&d7d7C zb}23a&(h>Wmw$jV0i7{Kgb-!{nn6UEU>acSY75x~S2k3-Df=T(sGn#5~PY2Ya=jY3S zc6chFH-CJ3J`Z5VCjt2J!dhU;S3jwiw&fWBem%5r$CMX<5jZx)+6(ZL)Qg`d5Qe^T z?(e?yPz2|1Wz6m7a|+AdFbxnXkOQ&+gFlA~hV6p|MZ0o*i5py=5VK!2aMNK@6|=WuYp0rXT3e@wu)T;qo7 z2}JF#(*e-W=zV+c`2Nx`r;~3G*#52N0j&SmT)swd4Y#i=UchsLbzNifQ9&`mxUTvD zz8lvMFyA-t0n2&9wkH!D@hMt|^}ptOl6o8uPV3AYtd4^L0O=VdpyXRX-wP nCL+7y?*w-%A7NSir)HTb=s5%Hz7NndLOG(prAc1Xe7iRQz&D{N#ebsyyc}x;ZGF!W21rnd#?|I? zO~JdJ2dJU>;ZV*g_+dMNNzhMkKHfk1`rG0)|NZdi2@~j{+5~Jd z!IJoi$bSYpK=lExU!Qg@MrgS{Od!ju=x$k*Xc74N`*-Xk1x=%XR9B~qJCe`qHV@E} z4U(uBBcy92j39+{24ouswumZSDIApgK&8GD7Hp8Rt_3`Q|89phf>#q@JHJ~@(2}7= z7K5~y0Negk(Rx2?Owc-N6K`v=mC~bM&|-kCoqxbq@u&BTogQ8@WEVF;0#iZMCg3sv zmC1W-2~%SL0RC*iVE_qZx1h=Z0DLCUAijp-uDTsTS~`eZ!1VMrh!3F9008*mqQ>Mf$xtBUlz6z@PyVrHZE(KQT9fb@2fV8z3VG6Qh`Mu4`B9iy;lfUO4<#e<=Ccr*&2IHIKX85*~8jSBlPFof5|gH+w~fTAtJ zQ*HC=h09JL6>EgYPOV;@&pT-oSQZ~(Vt^ipL*Bf-yoX!dwU+18EhBzVq-Ei)M}I$n z@LB+c_#Q-%L?i=Pj{1ZbB$7eh(pWs< zJPbgLwck4k19YI}_YOG9Q^Mq|mc$ysS}4yGF0IV2U6fN-VKpqS$Za)2%VT@yemAfc zOVK~4btQ4j^O{&AsIRms7NdU-HXBeptcx*$i%y^w@d0pUrFv2u^EqY=prOZ1MacxG zi#yWqs+quq0X(GsIazbkpw;!Ki@Ok?7%>B^O?e!tawNo? zfIt=z18j}r$O3dk1{j<$z#!xnhQa_!kb25G3@`vCbBP#WtJ|f|Bwy%-#C=5ua8Aus zqLH*_IOjF`r%(-ZF?A(gnLRf=T4t*WOzjE8-0Vo=C)S#vrLI&}>e3C5DEyqBC$$Y< kxt7p+Qdq0iFX$y^1tgAOHXW07*qoM6N<$f~5cI$^ZZW From 5f401272f73bfd5011e388b2c1b577c382d41c4c Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Sat, 31 Jan 2026 12:00:52 +0200 Subject: [PATCH 69/78] Update Loom to v1.15 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3a55862..3ee6d0e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ devauth_version = 1.2.2 ## Gradle plugins neoforged_moddev = 2.0.+ -fabric_loom = 1.14-SNAPSHOT +fabric_loom = 1.15-SNAPSHOT papermc_paperweight_userdev = 2.0.0-beta.19 jpenilla_run_task = 3.0.2 gradleup_shadow = 9.3.1 From 5dd5dd91c50118aae526a4e853c9915e64991d77 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Sat, 31 Jan 2026 12:14:18 +0200 Subject: [PATCH 70/78] Update monkeylib, depend on adventure api --- common/build.gradle | 6 +++++- common/gradle.properties | 6 +++++- gradle.properties | 2 +- loader/fabric/1.21.1/gradle.properties | 6 +++++- loader/fabric/build.gradle | 7 +++++++ loader/fabric/gradle.properties | 4 ++++ loader/neoforge/1.21.1/gradle.properties | 6 +++++- loader/neoforge/build.gradle | 11 +++++++++-- loader/neoforge/gradle.properties | 14 +++++++++----- modded/1.21.1/gradle.properties | 6 +++++- modded/build.gradle | 10 ++++++++-- modded/gradle.properties | 4 ++++ 12 files changed, 67 insertions(+), 15 deletions(-) diff --git a/common/build.gradle b/common/build.gradle index ce8c170..1034bef 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -13,9 +13,13 @@ repositories { } dependencies { - compileOnlyApi "top.offsetmonkey538.monkeylib538:monkeylib538-common:${rootProject.monkeylib538_version}+common" + compileOnlyApi("top.offsetmonkey538.monkeylib538:monkeylib538-common:${rootProject.monkeylib538_version}+common") { + exclude(group: "net.kyori") + } compileOnlyApi "io.netty:netty-codec-http:${rootProject.netty_version}" compileOnlyApi "io.netty:netty-transport-classes-epoll:${rootProject.netty_version}" compileOnly "com.google.guava:guava:${rootProject.guava_version}" compileOnly "com.mojang:brigadier:${rootProject.brigadier_version}" + + compileOnlyApi "net.kyori:adventure-api:${project.adventure_api_version}" } diff --git a/common/gradle.properties b/common/gradle.properties index e99725d..6d3b82c 100644 --- a/common/gradle.properties +++ b/common/gradle.properties @@ -1 +1,5 @@ -project_name = common +project_name = common + +# Dependencies +## Adventure +adventure_api_version = 4.26.1 \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3ee6d0e..87b51ef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-beta.2.1769533505281+9e950f8 +monkeylib538_version = 3.0.0-beta.2.1769852890443+b71b2ea ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.97.Final ## Bundeled with Minecraft, common needs to use diff --git a/loader/fabric/1.21.1/gradle.properties b/loader/fabric/1.21.1/gradle.properties index b5dde0d..4506086 100644 --- a/loader/fabric/1.21.1/gradle.properties +++ b/loader/fabric/1.21.1/gradle.properties @@ -1,7 +1,11 @@ project_name = fabric-1.21.1 commonModdedVersion = 1.21.1 -monkeylib538_suffix = 1.21 +# Dependencies +## MonkeyLib538 +monkeylib538_suffix = 1.21.1 +## Adventure +adventure_version = 5.14.2 # Minecraft version minecraft_version = 1.21.1 diff --git a/loader/fabric/build.gradle b/loader/fabric/build.gradle index cd8c8f1..0e6d457 100644 --- a/loader/fabric/build.gradle +++ b/loader/fabric/build.gradle @@ -17,6 +17,7 @@ allprojects { mixin("meshlib.modded.mixins.json") depends("fabric-api", "*") + depends("adventure-platform-fabric", "*") depends("monkeylib538", ">=${rootProject.monkeylib538_version}") } @@ -28,6 +29,10 @@ allprojects { modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-fabric:${rootProject.monkeylib538_version}+fabric") { exclude(group: "net.fabricmc.fabric-api") + exclude(group: "net.kyori") + } + modImplementation("net.kyori:adventure-platform-fabric:${project.adventure_version}") { + exclude(group: "net.fabricmc.fabric-api") } } } @@ -36,6 +41,7 @@ subprojects { dependencies { modImplementation("top.offsetmonkey538.monkeylib538:monkeylib538-fabric-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+fabric+${project.monkeylib538_suffix}") { exclude(group: "net.fabricmc.fabric-api") + exclude(group: "net.kyori") } includeRuntime "io.netty:netty-codec-http:${rootProject.netty_version}" @@ -44,6 +50,7 @@ subprojects { modrinth { dependencies { required.project "fabric-api" + required.project "adventure-platform-mod" required.project "monkeylib538" } } diff --git a/loader/fabric/gradle.properties b/loader/fabric/gradle.properties index ed59ba3..6cc5e81 100644 --- a/loader/fabric/gradle.properties +++ b/loader/fabric/gradle.properties @@ -1,5 +1,9 @@ project_name = fabric +# Dependencies +## Adventure +adventure_version = 6.8.0 + # Minecraft version minecraft_version = 1.21.11 supported_minecraft_versions = * diff --git a/loader/neoforge/1.21.1/gradle.properties b/loader/neoforge/1.21.1/gradle.properties index dd0b4f5..7957189 100644 --- a/loader/neoforge/1.21.1/gradle.properties +++ b/loader/neoforge/1.21.1/gradle.properties @@ -1,7 +1,11 @@ project_name = neoforge-1.21.1 commonModdedVersion = 1.21.1 -monkeylib538_suffix = 1.21 +# Dependencies +## MonkeyLib538 +monkeylib538_suffix = 1.21.1 +## Adventure +adventure_version = 6.0.1 # Minecraft version minecraft_version = 1.21.1 diff --git a/loader/neoforge/build.gradle b/loader/neoforge/build.gradle index 002b58e..02d6d39 100644 --- a/loader/neoforge/build.gradle +++ b/loader/neoforge/build.gradle @@ -10,6 +10,7 @@ allprojects { mod(rootProject.neo_mod_id) { authors = "OffsetMonkey538" dependencies { + required("adventure_platform_neoforge") required("monkeylib538", "[${rootProject.monkeylib538_version},)") } } @@ -17,7 +18,10 @@ allprojects { } dependencies { - compileOnly "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge:${rootProject.monkeylib538_version}+neoforge" + compileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-neoforge:${rootProject.monkeylib538_version}+neoforge") { + exclude(group: "net.kyori") + } + implementation "net.kyori:adventure-platform-neoforge:${project.adventure_version}" } } @@ -26,13 +30,16 @@ subprojects { outlet.allowedReleaseTypes = Set.of(ReleaseType.RELEASE) dependencies { - implementation "top.offsetmonkey538.monkeylib538:monkeylib538-neoforge-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+neoforge+${project.monkeylib538_suffix}" + implementation("top.offsetmonkey538.monkeylib538:monkeylib538-neoforge-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+neoforge+${project.monkeylib538_suffix}") { + exclude(group: "net.kyori") + } includeRuntime "io.netty:netty-codec-http:${rootProject.netty_version}" } modrinth { dependencies { + required.project "adventure-platform-mod" required.project "monkeylib538" } } diff --git a/loader/neoforge/gradle.properties b/loader/neoforge/gradle.properties index 0881a7e..6a8b468 100644 --- a/loader/neoforge/gradle.properties +++ b/loader/neoforge/gradle.properties @@ -1,8 +1,12 @@ -project_name = neoforge +project_name = neoforge + +# Dependencies +## Adventure +adventure_version = 6.8.0 # Minecraft version -minecraft_version = 1.21.11 +minecraft_version = 1.21.11 -neoforge_version = 21.11.37-beta -parchment_minecraft_version = 1.21.11 -parchment_mappings_version = 2025.12.20 +neoforge_version = 21.11.37-beta +parchment_minecraft_version = 1.21.11 +parchment_mappings_version = 2025.12.20 diff --git a/modded/1.21.1/gradle.properties b/modded/1.21.1/gradle.properties index af5bfc2..ae562f3 100644 --- a/modded/1.21.1/gradle.properties +++ b/modded/1.21.1/gradle.properties @@ -1,6 +1,10 @@ project_name = modded-1.21.1 -monkeylib538_suffix = 1.21 +# Dependencies +## MonkeyLib538 +monkeylib538_suffix = 1.21.1 +## Adventure +adventure_version = 6.0.1 # Minecraft version minecraft_version = 1.21.1 diff --git a/modded/build.gradle b/modded/build.gradle index 83d424d..1419f47 100644 --- a/modded/build.gradle +++ b/modded/build.gradle @@ -4,12 +4,18 @@ plugins { allprojects { dependencies { - modCompileOnly "top.offsetmonkey538.monkeylib538:monkeylib538-modded:${rootProject.monkeylib538_version}+modded" + modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-modded:${rootProject.monkeylib538_version}+modded") { + exclude(group: "net.kyori") + } + + modCompileOnly "net.kyori:adventure-platform-mod-shared-fabric-repack:${project.adventure_version}" } } subprojects { dependencies { - modCompileOnly "top.offsetmonkey538.monkeylib538:monkeylib538-modded-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+modded+${project.monkeylib538_suffix}" + modCompileOnly("top.offsetmonkey538.monkeylib538:monkeylib538-modded-${project.monkeylib538_suffix}:${rootProject.monkeylib538_version}+modded+${project.monkeylib538_suffix}") { + exclude(group: "net.kyori") + } } } diff --git a/modded/gradle.properties b/modded/gradle.properties index 1df7c91..e6ea150 100644 --- a/modded/gradle.properties +++ b/modded/gradle.properties @@ -1,4 +1,8 @@ project_name = modded +# Dependencies +## Adventure +adventure_version = 6.8.0 + # Minecraft version minecraft_version = 1.21.11 From 2a06f27c16d8616592dbc3fafedf7ea553408bbe Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Sat, 31 Jan 2026 19:28:53 +0200 Subject: [PATCH 71/78] Update buildSrc --- buildSrc | 2 +- loader/paper/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc b/buildSrc index 97cb1b2..31a2373 160000 --- a/buildSrc +++ b/buildSrc @@ -1 +1 @@ -Subproject commit 97cb1b28a0265d9fe6f90497a46bf86b01119687 +Subproject commit 31a2373f007a4ef7aaaabdcf4d28fd283ef42175 diff --git a/loader/paper/build.gradle b/loader/paper/build.gradle index 81602f6..096fa67 100644 --- a/loader/paper/build.gradle +++ b/loader/paper/build.gradle @@ -25,7 +25,7 @@ allprojects { subprojects { dependencies { - runtimeOnly("io.netty:netty-codec-http:${rootProject.netty_version}") { + include runtimeOnly("io.netty:netty-codec-http:${rootProject.netty_version}") { transitive = false } } From 1a435fccff0b13f9ed8c9664466d06c8fe1f1515 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Sat, 31 Jan 2026 19:29:54 +0200 Subject: [PATCH 72/78] Update monkeylib --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 87b51ef..565f667 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-beta.2.1769852890443+b71b2ea +monkeylib538_version = 3.0.0-beta.2.1769880528759+b61667d ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.97.Final ## Bundeled with Minecraft, common needs to use From 6cc77dd3fb0cc83339c0e7ebdc1b812a7cc172ec Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Sun, 1 Feb 2026 13:48:43 +0200 Subject: [PATCH 73/78] Update monkeylib, remove minecraftServerExternalPort config option --- .../offsetmonkey538/meshlib/common/config/MESHLibConfig.java | 2 -- .../offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java | 4 ++-- gradle.properties | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java index 50c7b8e..6a2a2e2 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/MESHLibConfig.java @@ -10,8 +10,6 @@ import static top.offsetmonkey538.meshlib.common.MESHLib.MOD_ID; public final class MESHLibConfig implements Config { - @Comment("Used to figure out if mesh lib should inject into vanilla or not. Required as external port may differ from that's defined in server.properties") - public @Nullable Integer minecraftServerExternalPort = null; @Comment("Port the http server will bind to") public @Nullable Integer httpPort = null; @Comment("Port the http server will be accessed from externally. Used by for example Git Pack Manager when generating the download url. The HTTP server will still be hosted on the 'httpPort'. Useful when running the server behind some sort of proxy like docker, nginx, traefik, cloudflare tunnel, etc.") diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java index cd83f15..6aabf8a 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java @@ -8,6 +8,7 @@ import top.offsetmonkey538.meshlib.common.api.rule.HttpRuleTypeRegistry; import top.offsetmonkey538.meshlib.common.netty.NettyServer; import top.offsetmonkey538.meshlib.common.platform.PlatformUtil; +import top.offsetmonkey538.monkeylib538.common.api.platform.LoaderUtil; import java.util.ArrayList; import java.util.List; @@ -40,7 +41,6 @@ public void reloadImpl() { // Check if config is OK final List errors = new ArrayList<>(); if (CONFIG.get().httpPort == null) errors.add("Field 'httpPort' not set!"); - if (CONFIG.get().minecraftServerExternalPort == null) errors.add("Field 'minecraftServerExternalPort' not set!"); if (!errors.isEmpty()) { LOGGER.error("There were problems with the config for MESH Lib, mod will be disabled, see below for more details!"); errors.stream().map(string -> " " + string).forEach(LOGGER::error); @@ -48,7 +48,7 @@ public void reloadImpl() { } // Initialize - if (Objects.equals(CONFIG.get().minecraftServerExternalPort, CONFIG.get().httpPort)) { + if (Objects.equals(LoaderUtil.getVanillaServerPort(), CONFIG.get().httpPort)) { LOGGER.info("Initializing MESH Lib on vanilla port %s...", CONFIG.get().httpPort); PlatformUtil.enableVanillaHandler(); LOGGER.info("MESH Lib initialized on vanilla port %s!", CONFIG.get().httpPort); diff --git a/gradle.properties b/gradle.properties index 565f667..433b6e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.jvmargs = -Xmx6G org.gradle.parallel = true # MonkeyLib538, check at https://github.com/OffsetMods538/MonkeyLib538 -monkeylib538_version = 3.0.0-beta.2.1769880528759+b61667d +monkeylib538_version = 3.0.0-beta.2.1769945990982+9b5ebb0 ## Netty, only used for netty-codec-http. Hopefully this version won't conflict with other versions of Netty netty_version = 4.1.97.Final ## Bundeled with Minecraft, common needs to use @@ -29,7 +29,7 @@ modrinth_minotaur = 2.+ mod_version = 2.0.0-alpha.0 mod_name = MESH Lib mod_id = meshlib -mod_description = Easy to use library for hosting an HTTP server on the Minecraft server's port +mod_description = Allows easily hosting an HTTP server on Minecraft. Works on 1.21.1+ fabric, neoforge and paper. mod_website = https://modrinth.com/mod/mesh-lib mod_issues = https://github.com/OffsetMods538/MESH-Lib/issues mod_sources = https://github.com/OffsetMods538/MESH-Lib From d34dbc594b2780adf030ba1347eb61590eb5d711 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Sun, 1 Feb 2026 14:57:48 +0200 Subject: [PATCH 74/78] Allow copying path of generated example configs --- .../common/config/RouterConfigHandler.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java index 018b505..012b039 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/config/RouterConfigHandler.java @@ -8,6 +8,10 @@ import com.google.common.collect.ImmutableMap; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.context.CommandContext; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.TextDecoration; import org.jspecify.annotations.Nullable; import top.offsetmonkey538.meshlib.common.api.handler.HttpHandler; import top.offsetmonkey538.meshlib.common.api.handler.handlers.StaticContentHandler; @@ -87,7 +91,18 @@ private static int runCommand(final Map.Entry exampleRouter, return 0; } - CommandAbstractionApi.sendMessage(context, "Created example config at '%s'!", routerPath); + CommandAbstractionApi.sendText(context, Component + .text("Created example config at '") + .append(Component + .text(routerPath.toString()) + .style(style -> style + .hoverEvent(HoverEvent.showText(Component.text("Click to copy"))) + .clickEvent(ClickEvent.copyToClipboard(routerPath.toString())) + .decorate(TextDecoration.UNDERLINED) + ) + ) + .append(Component.text("'!")) + ); return 1; } From dbe96cf63f057075ef7b15de1c8c94b3d0fdabe9 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Tue, 3 Feb 2026 17:10:50 +0200 Subject: [PATCH 75/78] register for telemetry --- .../main/java/top/offsetmonkey538/meshlib/common/MESHLib.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java index 8d38b80..77482e3 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java @@ -18,6 +18,7 @@ import top.offsetmonkey538.monkeylib538.common.api.command.CommandRegistrationApi; import top.offsetmonkey538.monkeylib538.common.api.command.ConfigCommandApi; import top.offsetmonkey538.monkeylib538.common.api.lifecycle.ServerLifecycleApi; +import top.offsetmonkey538.monkeylib538.common.api.telemetry.TelemetryRegistry; import top.offsetmonkey538.offsetutils538.api.config.ConfigHolder; import top.offsetmonkey538.offsetutils538.api.config.ConfigManager; import top.offsetmonkey538.offsetutils538.api.config.event.JanksonConfigurationEvent; @@ -44,6 +45,8 @@ private MESHLib() {} public static void initialize() { + TelemetryRegistry.register(MOD_ID); + PlatformUtil.enableVanillaHandler(); ExampleMain.onInitialize(); From 294970cdd0c092a8fa67b8b25ef928b3b30a9e81 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Sat, 7 Feb 2026 19:55:37 +0200 Subject: [PATCH 76/78] Run initial reload at server started event. Otherwise server proprties haven't loaded and thus the server port isnt derterminerded --- .../main/java/top/offsetmonkey538/meshlib/common/MESHLib.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java index 77482e3..79f0d3d 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java @@ -64,7 +64,7 @@ public static void initialize() { HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.listen(RouterConfigHandler::init); - ServerLifecycleApi.STARTING.listen(MESHLibApi::reload); + ServerLifecycleApi.STARTED.listen(MESHLibApi::reload); } public static void disableAllHandlers() { From 7fcfdc198168aedbba14a9970b1867f6083f901d Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Sat, 7 Feb 2026 20:04:42 +0200 Subject: [PATCH 77/78] argh pls work --- .../offsetmonkey538/meshlib/common/MESHLib.java | 8 ++++++-- .../meshlib/common/api/MESHLibApi.java | 8 ++++++++ .../meshlib/common/impl/MESHLibApiImpl.java | 17 +++++++++++------ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java index 79f0d3d..fc841be 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/MESHLib.java @@ -50,7 +50,10 @@ public static void initialize() { PlatformUtil.enableVanillaHandler(); ExampleMain.onInitialize(); - ConfigCommandApi.registerConfigCommand(CONFIG, MESHLibApi::reload, MOD_ID, "config"); + ConfigCommandApi.registerConfigCommand(CONFIG, () -> { + MESHLibApi.reload(); + MESHLibApi.initialize(); + }, MOD_ID, "config"); CommandRegistrationApi.registerCommand(RouterConfigHandler.createExampleConfigCommand()); JanksonConfigurationEvent.JANKSON_CONFIGURATION_EVENT.listen(HttpRouter::configureJankson); @@ -64,7 +67,8 @@ public static void initialize() { HttpRouterRegistry.HTTP_ROUTER_REGISTRATION_EVENT.listen(RouterConfigHandler::init); - ServerLifecycleApi.STARTED.listen(MESHLibApi::reload); + ServerLifecycleApi.STARTING.listen(MESHLibApi::reload); + ServerLifecycleApi.STARTED.listen(MESHLibApi::initialize); } public static void disableAllHandlers() { diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/MESHLibApi.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/MESHLibApi.java index 8ade47d..6801643 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/api/MESHLibApi.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/api/MESHLibApi.java @@ -18,6 +18,13 @@ static void reload() { INSTANCE.reloadImpl(); } + /** + * Initializes. + */ + static void initialize() { + INSTANCE.initializeImpl(); + } + /** * Provides the external port as defined in the MESH Lib config. *

{@code null} value indicates that MESH Lib has not been set up correctly and isn't running.

@@ -30,5 +37,6 @@ static void reload() { @Internal void reloadImpl(); + @Internal void initializeImpl(); @Internal @Nullable Integer getExternalPortImpl(); } diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java index 6aabf8a..1368924 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java @@ -22,8 +22,6 @@ public final class MESHLibApiImpl implements MESHLibApi { public void reloadImpl() { LOGGER.info("Reloading MESH Lib..."); final Stopwatch fullStopwatch = Stopwatch.createStarted(); - disableAllHandlers(); - final Stopwatch stageStopwatch = Stopwatch.createStarted(); HttpHandlerTypeRegistry.clear(); HttpRuleTypeRegistry.clear(); @@ -47,18 +45,25 @@ public void reloadImpl() { return; } + LOGGER.info("MESH Lib reloaded in %s!", fullStopwatch.stop()); + } + + @Override + public void initializeImpl() { + LOGGER.info("Initializing MESH Lib..."); + final Stopwatch stopwatch = Stopwatch.createStarted(); + disableAllHandlers(); + // Initialize if (Objects.equals(LoaderUtil.getVanillaServerPort(), CONFIG.get().httpPort)) { LOGGER.info("Initializing MESH Lib on vanilla port %s...", CONFIG.get().httpPort); PlatformUtil.enableVanillaHandler(); - LOGGER.info("MESH Lib initialized on vanilla port %s!", CONFIG.get().httpPort); + LOGGER.info("MESH Lib initialized on vanilla port %s in %s!", CONFIG.get().httpPort, stopwatch.stop()); } else { LOGGER.info("Initializing MESH Lib on custom port %s...", CONFIG.get().httpPort); NettyServer.start(); - LOGGER.info("MESH Lib initialized on custom port %s!", CONFIG.get().httpPort); + LOGGER.info("MESH Lib initialized on custom port %s in %s!", CONFIG.get().httpPort, stopwatch.stop()); } - - LOGGER.info("MESH Lib reloaded in %s!", fullStopwatch.stop()); } @Override From 2b7ac871f93bbe6024dd7c80b494c21a034b48b8 Mon Sep 17 00:00:00 2001 From: OffsetMonkey538 Date: Sat, 7 Feb 2026 21:42:32 +0200 Subject: [PATCH 78/78] don't try initializing when config wrobng --- .../meshlib/common/impl/MESHLibApiImpl.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java index 1368924..e682c93 100644 --- a/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java +++ b/common/src/main/java/top/offsetmonkey538/meshlib/common/impl/MESHLibApiImpl.java @@ -19,6 +19,8 @@ import static top.offsetmonkey538.meshlib.common.MESHLib.disableAllHandlers; public final class MESHLibApiImpl implements MESHLibApi { + private static boolean enabled = true; + public void reloadImpl() { LOGGER.info("Reloading MESH Lib..."); final Stopwatch fullStopwatch = Stopwatch.createStarted(); @@ -42,7 +44,7 @@ public void reloadImpl() { if (!errors.isEmpty()) { LOGGER.error("There were problems with the config for MESH Lib, mod will be disabled, see below for more details!"); errors.stream().map(string -> " " + string).forEach(LOGGER::error); - return; + enabled = false; } LOGGER.info("MESH Lib reloaded in %s!", fullStopwatch.stop()); @@ -50,6 +52,11 @@ public void reloadImpl() { @Override public void initializeImpl() { + if (!enabled) { + LOGGER.info("MESH Lib not enabled, not initializing!"); + return; + } + LOGGER.info("Initializing MESH Lib..."); final Stopwatch stopwatch = Stopwatch.createStarted(); disableAllHandlers();

Index of "); result.append(uriPath); result.append("
NameLast ModifiedSize