feat(web): move main port hooking to lightweight separate library for interoperability
This commit is contained in:
parent
3acc4b2420
commit
915f60b6b4
|
@ -0,0 +1,12 @@
|
|||
plugins {
|
||||
id("jfmod.module")
|
||||
}
|
||||
|
||||
base {
|
||||
archivesName.set("libjf-mainhttp-v0")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val fabricVersion: String by rootProject.extra
|
||||
implementation(fabricApi.module("fabric-api-base", fabricVersion))
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package io.gitlab.jfronny.libjf.mainhttp.api.v0;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface MainHttpHandler {
|
||||
default boolean isActive() {
|
||||
return true;
|
||||
}
|
||||
|
||||
byte @Nullable [] handle(byte[] request);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package io.gitlab.jfronny.libjf.mainhttp.api.v0;
|
||||
|
||||
import io.gitlab.jfronny.libjf.mainhttp.impl.MainHttp;
|
||||
|
||||
public interface ServerState {
|
||||
static void onActivate(Runnable listener) {
|
||||
MainHttp.ON_ACTIVATE.register(listener);
|
||||
}
|
||||
|
||||
static boolean isActive() {
|
||||
return !MainHttp.GAME_PORT.isEmpty();
|
||||
}
|
||||
|
||||
static int getPort() {
|
||||
return MainHttp.GAME_PORT.getTopmost();
|
||||
}
|
||||
}
|
|
@ -1,16 +1,11 @@
|
|||
package io.gitlab.jfronny.libjf.web.impl.variant.shared;
|
||||
package io.gitlab.jfronny.libjf.mainhttp.impl;
|
||||
|
||||
import io.gitlab.jfronny.libjf.LibJf;
|
||||
import io.gitlab.jfronny.libjf.web.api.v1.HttpRequest;
|
||||
import io.gitlab.jfronny.libjf.web.api.v1.HttpResponse;
|
||||
import io.gitlab.jfronny.libjf.web.impl.JfWeb;
|
||||
import io.gitlab.jfronny.libjf.mainhttp.impl.util.Trie;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.List;
|
||||
|
||||
public class HttpDecoder extends ChannelInboundHandlerAdapter {
|
||||
|
@ -45,20 +40,13 @@ public class HttpDecoder extends ChannelInboundHandlerAdapter {
|
|||
byte[] data = new byte[buf.readableBytes()];
|
||||
buf.readBytes(data);
|
||||
buf.release();
|
||||
// Parse and process request
|
||||
try (ByteArrayInputStream is = new ByteArrayInputStream(data);
|
||||
HttpResponse response = JfWeb.getHandler().handle(HttpRequest.read(is));
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||
// Write and send response
|
||||
response.write(os);
|
||||
os.flush();
|
||||
ctx.pipeline()
|
||||
.firstContext()
|
||||
.writeAndFlush(Unpooled.wrappedBuffer(os.toByteArray()))
|
||||
.addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
// Process request
|
||||
ctx.pipeline()
|
||||
.firstContext()
|
||||
.writeAndFlush(Unpooled.wrappedBuffer(MainHttp.handle(data)))
|
||||
.addListener(ChannelFutureListener.CLOSE);
|
||||
} catch (RuntimeException re) {
|
||||
LibJf.LOGGER.error("Could not process HTTP", re);
|
||||
MainHttp.LOGGER.error("Could not process HTTP", re);
|
||||
} finally {
|
||||
if (passOn) {
|
||||
buf.resetReaderIndex();
|
|
@ -0,0 +1,42 @@
|
|||
package io.gitlab.jfronny.libjf.mainhttp.impl;
|
||||
|
||||
import io.gitlab.jfronny.libjf.mainhttp.api.v0.MainHttpHandler;
|
||||
import io.gitlab.jfronny.libjf.mainhttp.impl.util.ClaimPool;
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MainHttp {
|
||||
public static final Event<Runnable> ON_ACTIVATE = EventFactory.createArrayBacked(Runnable.class, listeners -> () -> {
|
||||
for (Runnable listener : listeners) listener.run();
|
||||
});
|
||||
public static final ClaimPool<Integer> GAME_PORT = new ClaimPool<>();
|
||||
public static final String MOD_ID = "libjf-mainhttp";
|
||||
public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID);
|
||||
private static final List<MainHttpHandler> activeHandlers = FabricLoader.getInstance()
|
||||
.getEntrypoints(MOD_ID + ":v0", MainHttpHandler.class)
|
||||
.stream()
|
||||
.filter(MainHttpHandler::isActive)
|
||||
.toList();
|
||||
private static final byte[] NOT_FOUND = """
|
||||
HTTP/1.1 404 Not Found
|
||||
Connection: keep-alive
|
||||
Content-Length: 0
|
||||
""".getBytes();
|
||||
|
||||
public static boolean isEnabled() {
|
||||
return !activeHandlers.isEmpty();
|
||||
}
|
||||
|
||||
public static byte[] handle(byte[] request) {
|
||||
for (MainHttpHandler handler : activeHandlers) {
|
||||
byte[] option = handler.handle(request);
|
||||
if (option != null) return option;
|
||||
}
|
||||
return NOT_FOUND;
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package io.gitlab.jfronny.libjf.web.impl.mixin;
|
||||
package io.gitlab.jfronny.libjf.mainhttp.impl.mixin;
|
||||
|
||||
import io.gitlab.jfronny.libjf.web.impl.JfWebConfig;
|
||||
import io.gitlab.jfronny.libjf.mainhttp.impl.MainHttp;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
|
||||
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
||||
|
@ -8,8 +8,8 @@ import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class JfWebMixinPlugin implements IMixinConfigPlugin {
|
||||
private static final String MIXIN_PACKAGE = "io.gitlab.jfronny.libjf.web.impl.mixin.";
|
||||
public class JfMainHTTPMixinPlugin implements IMixinConfigPlugin {
|
||||
private static final String MIXIN_PACKAGE = "io.gitlab.jfronny.libjf.mainhttp.impl.mixin.";
|
||||
|
||||
@Override
|
||||
public void onLoad(String mixinPackage) {
|
||||
|
@ -23,7 +23,7 @@ public class JfWebMixinPlugin implements IMixinConfigPlugin {
|
|||
@Override
|
||||
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
|
||||
return switch (mixinClassName) {
|
||||
case MIXIN_PACKAGE + "ServerNetworkIoMixin", MIXIN_PACKAGE + "ServerNetworkIo$1Mixin" -> JfWebConfig.port == -1;
|
||||
case MIXIN_PACKAGE + "ServerNetworkIoMixin", MIXIN_PACKAGE + "ServerNetworkIo$1Mixin" -> MainHttp.isEnabled();
|
||||
default -> throw new IllegalArgumentException("Unexpected mixin: " + mixinClassName);
|
||||
};
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package io.gitlab.jfronny.libjf.web.impl.mixin;
|
||||
package io.gitlab.jfronny.libjf.mainhttp.impl.mixin;
|
||||
|
||||
import io.gitlab.jfronny.libjf.web.impl.variant.shared.HttpDecoder;
|
||||
import io.gitlab.jfronny.libjf.mainhttp.impl.HttpDecoder;
|
||||
import io.netty.channel.Channel;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
|
@ -1,7 +1,7 @@
|
|||
package io.gitlab.jfronny.libjf.web.impl.mixin;
|
||||
package io.gitlab.jfronny.libjf.mainhttp.impl.mixin;
|
||||
|
||||
import io.gitlab.jfronny.libjf.web.impl.util.ClaimPool;
|
||||
import io.gitlab.jfronny.libjf.web.impl.variant.shared.SharedWebServer;
|
||||
import io.gitlab.jfronny.libjf.mainhttp.impl.MainHttp;
|
||||
import io.gitlab.jfronny.libjf.mainhttp.impl.util.ClaimPool;
|
||||
import net.minecraft.server.ServerNetworkIo;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
|
@ -15,12 +15,13 @@ import java.util.Set;
|
|||
|
||||
@Mixin(ServerNetworkIo.class)
|
||||
public class ServerNetworkIoMixin {
|
||||
@Unique private final Set<ClaimPool<Integer>.Claim> libjf$portClaim = new HashSet<>();
|
||||
@Unique
|
||||
private final Set<ClaimPool<Integer>.Claim> libjf$portClaim = new HashSet<>();
|
||||
|
||||
@Inject(method = "bind(Ljava/net/InetAddress;I)V", at = @At("HEAD"))
|
||||
void onBind(InetAddress address, int port, CallbackInfo ci) {
|
||||
libjf$portClaim.add(SharedWebServer.gamePort.claim(port));
|
||||
SharedWebServer.emitActive();
|
||||
libjf$portClaim.add(MainHttp.GAME_PORT.claim(port));
|
||||
MainHttp.ON_ACTIVATE.invoker().run();
|
||||
}
|
||||
|
||||
@Inject(method = "stop()V", at = @At("HEAD"))
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.web.impl.util;
|
||||
package io.gitlab.jfronny.libjf.mainhttp.impl.util;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
|
@ -1,4 +1,4 @@
|
|||
package io.gitlab.jfronny.libjf.web.impl.variant.shared;
|
||||
package io.gitlab.jfronny.libjf.mainhttp.impl.util;
|
||||
|
||||
import it.unimi.dsi.fastutil.chars.Char2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.chars.Char2ObjectMap;
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "libjf-mainhttp-v0",
|
||||
"name": "LibJF MainHTTP",
|
||||
"version": "${version}",
|
||||
"authors": [
|
||||
"JFronny"
|
||||
],
|
||||
"contact": {
|
||||
"email": "projects.contact@frohnmeyer-wds.de",
|
||||
"homepage": "https://jfronny.gitlab.io",
|
||||
"issues": "https://git.frohnmeyer-wds.de/JfMods/LibJF/issues",
|
||||
"sources": "https://git.frohnmeyer-wds.de/JfMods/LibJF"
|
||||
},
|
||||
"license": "MIT",
|
||||
"environment": "*",
|
||||
"mixins": ["libjf-mainhttp-v0.mixins.json"],
|
||||
"depends": {
|
||||
"fabricloader": ">=0.12.0",
|
||||
"minecraft": "*"
|
||||
},
|
||||
"custom": {
|
||||
"modmenu": {
|
||||
"parent": "libjf",
|
||||
"badges": ["library"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "io.gitlab.jfronny.libjf.web.impl.mixin",
|
||||
"package": "io.gitlab.jfronny.libjf.mainhttp.impl.mixin",
|
||||
"compatibilityLevel": "JAVA_16",
|
||||
"plugin": "io.gitlab.jfronny.libjf.web.impl.mixin.JfWebMixinPlugin",
|
||||
"plugin": "io.gitlab.jfronny.libjf.mainhttp.impl.mixin.JfMainHTTPMixinPlugin",
|
||||
"server": [
|
||||
"ServerNetworkIo$1Mixin",
|
||||
"ServerNetworkIoMixin"
|
|
@ -12,6 +12,7 @@ dependencies {
|
|||
val fabricVersion: String by rootProject.extra
|
||||
api(devProject(":libjf-base"))
|
||||
api(devProject(":libjf-config-core-v2"))
|
||||
api(devProject(":libjf-mainhttp-v0"))
|
||||
include(modImplementation(fabricApi.module("fabric-command-api-v2", fabricVersion))!!)
|
||||
|
||||
annotationProcessor(project(":libjf-config-compiler-plugin-v2"))
|
||||
|
|
|
@ -44,7 +44,7 @@ public class HttpResponseImpl implements HttpResponse {
|
|||
this.version = "HTTP/1.1";
|
||||
this.statusCode = statusCode;
|
||||
|
||||
this.header = new HashMap<>();
|
||||
this.header = new LinkedHashMap<>();
|
||||
|
||||
addHeader("Connection", "keep-alive");
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ public class HttpResponseImpl implements HttpResponse {
|
|||
@Override
|
||||
public HttpResponseImpl addHeader(String key, String value) {
|
||||
ensureOpen();
|
||||
Set<String> valueSet = header.computeIfAbsent(key, k -> new HashSet<>());
|
||||
Set<String> valueSet = header.computeIfAbsent(key, k -> new LinkedHashSet<>());
|
||||
valueSet.add(value);
|
||||
return this;
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ public class HttpResponseImpl implements HttpResponse {
|
|||
@Override
|
||||
public HttpResponseImpl removeHeader(String key, String value) {
|
||||
ensureOpen();
|
||||
Set<String> valueSet = header.computeIfAbsent(key, k -> new HashSet<>());
|
||||
Set<String> valueSet = header.computeIfAbsent(key, k -> new LinkedHashSet<>());
|
||||
valueSet.remove(value);
|
||||
return this;
|
||||
}
|
||||
|
@ -88,14 +88,15 @@ public class HttpResponseImpl implements HttpResponse {
|
|||
public void write(OutputStream out) throws IOException {
|
||||
OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
|
||||
|
||||
Map<String, Set<String>> finalHeaders = new LinkedHashMap<>(header);
|
||||
if (data != null) {
|
||||
addHeader("Transfer-Encoding", "chunked");
|
||||
finalHeaders.computeIfAbsent("Transfer-Encoding", k -> new LinkedHashSet<>()).add("chunked");
|
||||
} else {
|
||||
addHeader("Content-Length", "0");
|
||||
finalHeaders.computeIfAbsent("Content-Length", k -> new LinkedHashSet<>()).add("0");
|
||||
}
|
||||
|
||||
writeLine(writer, version + " " + statusCode.getCode() + " " + statusCode.getMessage());
|
||||
for (Entry<String, Set<String>> e : header.entrySet()) {
|
||||
for (Entry<String, Set<String>> e : finalHeaders.entrySet()) {
|
||||
if (e.getValue().isEmpty()) continue;
|
||||
writeLine(writer, e.getKey() + ": " + StringUtils.join(e.getValue(), ", "));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package io.gitlab.jfronny.libjf.web.impl.variant.shared;
|
||||
|
||||
import io.gitlab.jfronny.libjf.mainhttp.api.v0.MainHttpHandler;
|
||||
import io.gitlab.jfronny.libjf.mainhttp.api.v0.ServerState;
|
||||
import io.gitlab.jfronny.libjf.web.api.v1.*;
|
||||
import io.gitlab.jfronny.libjf.web.impl.JfWeb;
|
||||
import io.gitlab.jfronny.libjf.web.impl.JfWebConfig;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
public class MainHttpHandlerImpl implements MainHttpHandler {
|
||||
public MainHttpHandlerImpl() {
|
||||
ServerState.onActivate(SharedWebServer::emitActive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return JfWebConfig.port == -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @Nullable [] handle(byte[] request) {
|
||||
// Parse and process request
|
||||
try (ByteArrayInputStream is = new ByteArrayInputStream(request);
|
||||
HttpResponse response = JfWeb.getHandler().handle(HttpRequest.read(is))) {
|
||||
if (response.getStatusCode() == HttpStatusCode.NOT_FOUND) return null;
|
||||
// Write and send response
|
||||
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
|
||||
response.write(os);
|
||||
os.flush();
|
||||
return os.toByteArray();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
package io.gitlab.jfronny.libjf.web.impl.variant.shared;
|
||||
|
||||
import io.gitlab.jfronny.libjf.mainhttp.api.v0.ServerState;
|
||||
import io.gitlab.jfronny.libjf.web.impl.host.RequestHandler;
|
||||
import io.gitlab.jfronny.libjf.web.impl.util.ClaimPool;
|
||||
import io.gitlab.jfronny.libjf.web.impl.variant.AbstractWebServer;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class SharedWebServer extends AbstractWebServer {
|
||||
public static final ClaimPool<Integer> gamePort = new ClaimPool<>();
|
||||
public static final Set<Runnable> onActive = new LinkedHashSet<>();
|
||||
|
||||
public static void emitActive() {
|
||||
|
@ -23,9 +22,8 @@ public class SharedWebServer extends AbstractWebServer {
|
|||
|
||||
@Override
|
||||
public String getServerRoot() {
|
||||
Integer gamePort = this.gamePort.getTopmost();
|
||||
if (gamePort == null) throw new UnsupportedOperationException("Attempted to get server root on unhosted server");
|
||||
else return getServerRoot(gamePort);
|
||||
if (!ServerState.isActive()) throw new UnsupportedOperationException("Attempted to get server root on unhosted server");
|
||||
else return getServerRoot(ServerState.getPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,6 +45,6 @@ public class SharedWebServer extends AbstractWebServer {
|
|||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return !gamePort.isEmpty();
|
||||
return ServerState.isActive();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,17 +15,18 @@
|
|||
},
|
||||
"license": "MIT",
|
||||
"environment": "*",
|
||||
"mixins": ["libjf-web-v1.mixins.json"],
|
||||
"entrypoints": {
|
||||
"main": ["io.gitlab.jfronny.libjf.web.impl.JfWeb"],
|
||||
"libjf:coprocess": ["io.gitlab.jfronny.libjf.web.impl.JfWeb"],
|
||||
"libjf:config": ["io.gitlab.jfronny.libjf.web.impl.JFC_JfWebConfig"]
|
||||
"libjf:config": ["io.gitlab.jfronny.libjf.web.impl.JFC_JfWebConfig"],
|
||||
"libjf-mainhttp:v0": ["io.gitlab.jfronny.libjf.web.impl.variant.shared.MainHttpHandlerImpl"]
|
||||
},
|
||||
"depends": {
|
||||
"fabricloader": ">=0.12.0",
|
||||
"minecraft": "*",
|
||||
"libjf-base": ">=${version}",
|
||||
"libjf-config-core-v2": ">=${version}",
|
||||
"libjf-mainhttp-v0": ">=${version}",
|
||||
"fabric-command-api-v2": "*"
|
||||
},
|
||||
"custom": {
|
||||
|
|
|
@ -17,11 +17,16 @@ include("libjf-base")
|
|||
include("libjf-config-core-v2")
|
||||
include("libjf-config-commands")
|
||||
include("libjf-config-ui-tiny")
|
||||
include("libjf-config-compiler-plugin-v2")
|
||||
|
||||
include("libjf-data-v0")
|
||||
include("libjf-data-manipulation-v0")
|
||||
include("libjf-devutil")
|
||||
include("libjf-translate-v1")
|
||||
include("libjf-unsafe-v0")
|
||||
include("libjf-web-v1")
|
||||
|
||||
include("libjf-config-compiler-plugin-v2")
|
||||
include("libjf-devutil")
|
||||
|
||||
include("libjf-translate-v1")
|
||||
|
||||
include("libjf-unsafe-v0")
|
||||
|
||||
include("libjf-mainhttp-v0")
|
||||
include("libjf-web-v1")
|
||||
|
|
Loading…
Reference in New Issue