feat(web): represent virtual hosts as tree and avoid collisions
ci/woodpecker/push/docs Pipeline was successful Details
ci/woodpecker/push/jfmod Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2023-08-30 22:18:34 +02:00
parent f54f8d59c4
commit 3acc4b2420
Signed by: Johannes
GPG Key ID: E76429612C2929F4
22 changed files with 280 additions and 208 deletions

View File

@ -1,6 +0,0 @@
package io.gitlab.jfronny.libjf.web.api.v1;
public interface AdvancedSubServer extends SubServer {
void onStop();
void onStart();
}

View File

@ -1,7 +0,0 @@
package io.gitlab.jfronny.libjf.web.api.v1;
import java.io.IOException;
public interface ContentProvider {
HttpResponse handle(HttpRequest request) throws IOException;
}

View File

@ -22,14 +22,19 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package io.gitlab.jfronny.libjf.web.impl.variant.hosted;
package io.gitlab.jfronny.libjf.web.api.v1;
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.util.HttpRequestImpl;
import io.gitlab.jfronny.libjf.web.impl.util.HttpResponseImpl;
import io.gitlab.jfronny.libjf.web.impl.host.VirtualHostBranch;
import java.io.IOException;
@FunctionalInterface
public interface HttpRequestHandler {
HttpResponse handle(HttpRequest request);
HttpResponse handle(HttpRequest request) throws IOException;
default VirtualHostNode asHostNode() {
VirtualHostBranch impl = new VirtualHostBranch();
impl.setContent(this);
return impl;
}
}

View File

@ -0,0 +1,50 @@
package io.gitlab.jfronny.libjf.web.api.v1;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public record PathSegment(String segment, @Nullable PathSegment next) {
public PathSegment(String segment, @Nullable PathSegment next) {
if (segment.isEmpty() || segment.equals(".") || segment.contains("/")) {
PathSegment from = Objects.requireNonNull(of(segment, next));
this.segment = from.segment;
this.next = from.next;
} else {
this.segment = segment;
this.next = next;
}
}
public PathSegment(String path) {
this(path, null);
}
@Override
public String toString() {
return next == null ? segment : segment + "/" + next;
}
public PathSegment concat(@Nullable PathSegment seg) {
return seg == null ? this : new PathSegment(segment, next == null ? seg : next.concat(seg));
}
public static @Nullable PathSegment concat(@Nullable PathSegment seg1, @Nullable PathSegment seg2) {
return seg1 == null ? seg2 : seg1.concat(seg2);
}
public static @Nullable PathSegment of(String path) {
return of(path, null);
}
private static @Nullable PathSegment of(String path, PathSegment next) {
Deque<String> segmentStack = new LinkedList<>();
for (String s : path.split("/")) {
if (s.isEmpty() || s.equals(".")) continue;
if (s.equals("..")) segmentStack.pop();
segmentStack.push(s);
}
for (String s : segmentStack) next = new PathSegment(s, next);
return next;
}
}

View File

@ -1,7 +0,0 @@
package io.gitlab.jfronny.libjf.web.api.v1;
import java.io.IOException;
public interface SubServer {
HttpResponse handle(HttpRequest request, String[] segments) throws IOException;
}

View File

@ -0,0 +1,10 @@
package io.gitlab.jfronny.libjf.web.api.v1;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
@FunctionalInterface
public interface VirtualHostNode {
HttpResponse handle(HttpRequest request, @Nullable PathSegment path) throws IOException;
}

View File

@ -1,5 +1,5 @@
package io.gitlab.jfronny.libjf.web.api.v1;
public interface WebInit {
public interface WebEntrypoint {
void register(WebServer api);
}

View File

@ -6,13 +6,16 @@ import java.io.IOException;
import java.nio.file.Path;
public interface WebServer {
String register(String webPath, ContentProvider provider);
String registerFile(String webPath, Path file, boolean readOnSend) throws IOException;
String registerFile(String webPath, byte[] data, String contentType);
String registerDir(String webPath, Path dir, boolean readOnSend) throws IOException;
String registerSubServer(String webPath, SubServer subServer);
String registerSubServer(String webPath, AdvancedSubServer subServer);
String register(PathSegment path, VirtualHostNode provider);
default String register(PathSegment path, HttpRequestHandler provider) {
return register(path, provider.asHostNode());
}
String registerFile(PathSegment path, Path file, boolean readOnSend) throws IOException;
String registerFile(PathSegment path, byte[] data, String contentType);
String registerDir(PathSegment path, Path dir, boolean readOnSend) throws IOException;
String getServerRoot();
void onStart(Runnable listener);
void onStop(Runnable listener);
void stop();
void queueRestart(Runnable callback);
boolean isActive();

View File

@ -6,6 +6,7 @@ import io.gitlab.jfronny.libjf.Flags;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.coprocess.CoProcess;
import io.gitlab.jfronny.libjf.web.api.v1.WebServer;
import io.gitlab.jfronny.libjf.web.impl.host.RequestHandler;
import io.gitlab.jfronny.libjf.web.impl.variant.hosted.HostedWebServer;
import io.gitlab.jfronny.libjf.web.impl.variant.shared.SharedWebServer;
import net.fabricmc.api.EnvType;
@ -15,8 +16,6 @@ import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.text.Text;
import org.jetbrains.annotations.ApiStatus;
import java.time.chrono.IsoEra;
import static net.minecraft.server.command.CommandManager.literal;
public class JfWeb implements CoProcess, ModInitializer {

View File

@ -1,52 +0,0 @@
package io.gitlab.jfronny.libjf.web.impl;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.web.api.v1.*;
import io.gitlab.jfronny.libjf.web.impl.util.*;
import io.gitlab.jfronny.libjf.web.impl.variant.hosted.HttpRequestHandler;
import java.util.*;
public class RequestHandler implements HttpRequestHandler {
public Map<String, AdvancedSubServer> subServers = new HashMap<>();
public Map<String, ContentProvider> contentProviders = new HashMap<>();
@Override
public HttpResponse handle(HttpRequest request) {
HttpResponse resp = null;
try {
String webPath = WebPaths.simplify(request.getPath());
if (webPath.isEmpty())
webPath = "index.html";
if (contentProviders.containsKey(webPath)) {
resp = contentProviders.get(webPath).handle(request);
}
else {
String[] segments = webPath.split("/");
for (int i = segments.length - 1; i >= 0; i--) {
String wp = WebPaths.concat(Arrays.copyOfRange(segments, 0, i));
if (subServers.containsKey(wp)) {
resp = subServers.get(wp).handle(request, Arrays.copyOfRange(segments, i, segments.length));
break;
}
}
if (resp == null) {
resp = new HttpResponseImpl(HttpStatusCode.NOT_FOUND);
}
}
} catch (Throwable e) {
LibJf.LOGGER.error("Caught error while sending", e);
resp = new HttpResponseImpl(HttpStatusCode.INTERNAL_SERVER_ERROR);
}
if (resp.getHeader("Cache-Control").isEmpty())
resp.addHeader("Cache-Control", "no-cache");
if (resp.getHeader("Server").isEmpty())
resp.addHeader("Server", "LibWeb using BlueMapCore");
return resp;
}
public void clear() {
subServers.clear();
contentProviders.clear();
}
}

View File

@ -1,7 +1,7 @@
package io.gitlab.jfronny.libjf.web.impl;
package io.gitlab.jfronny.libjf.web.impl.host;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.web.api.v1.WebInit;
import io.gitlab.jfronny.libjf.web.api.v1.WebEntrypoint;
import io.gitlab.jfronny.libjf.web.api.v1.WebServer;
import net.fabricmc.loader.api.FabricLoader;
@ -9,7 +9,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class DefaultFileHost implements WebInit {
public class DefaultFileHost implements WebEntrypoint {
@Override
public void register(WebServer api) {
Path p = FabricLoader.getInstance().getConfigDir().resolve("wwwroot");
@ -21,7 +21,7 @@ public class DefaultFileHost implements WebInit {
}
}
try {
LibJf.LOGGER.info(api.registerDir("/", p, false));
LibJf.LOGGER.info(api.registerDir(null, p, false));
} catch (IOException e) {
LibJf.LOGGER.error("Could not register wwwroot", e);
}

View File

@ -0,0 +1,38 @@
package io.gitlab.jfronny.libjf.web.impl.host;
import io.gitlab.jfronny.libjf.web.api.v1.*;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public record ReadOnSendDirNode(Path fsPath) implements VirtualHostNode {
@Override
public HttpResponse handle(HttpRequest request, @Nullable PathSegment path) throws IOException {
return handle(request, path, fsPath);
}
private static HttpResponse handle(HttpRequest request, @Nullable PathSegment path, Path fsPath) throws IOException {
if (path == null) {
if (Files.isRegularFile(fsPath) && Files.isReadable(fsPath)) {
HttpResponse resp = request.createResponse(HttpStatusCode.OK);
resp.addHeader("Content-Type", Files.probeContentType(fsPath));
resp.addHeader("Content-Length", String.valueOf(Files.size(fsPath)));
resp.setData(Files.newInputStream(fsPath));
return resp;
} else return request.createResponse(HttpStatusCode.NOT_FOUND);
} else {
if (!Files.isDirectory(fsPath)) return request.createResponse(HttpStatusCode.NOT_FOUND);
try (Stream<Path> s = Files.list(fsPath)) {
for (Path sub : s.toList()) {
if (path.segment().equals(sub.getFileName().toString())) {
return handle(request, path.next(), sub);
}
}
}
return request.createResponse(HttpStatusCode.NOT_FOUND);
}
}
}

View File

@ -0,0 +1,29 @@
package io.gitlab.jfronny.libjf.web.impl.host;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.web.api.v1.*;
import io.gitlab.jfronny.libjf.web.api.v1.HttpRequestHandler;
public class RequestHandler extends VirtualHostBranch implements HttpRequestHandler {
@Override
public HttpResponse handle(HttpRequest request) {
HttpResponse resp;
try {
PathSegment path = PathSegment.of(request.getPath());
if (path == null) path = new PathSegment("index.html");
resp = handle(request, path);
} catch (Throwable e) {
LibJf.LOGGER.error("Caught error while sending", e);
resp = request.createResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
}
if (resp.getHeader("Cache-Control").isEmpty())
resp.addHeader("Cache-Control", "no-cache");
if (resp.getHeader("Server").isEmpty())
resp.addHeader("Server", "LibWeb using BlueMapCore");
return resp;
}
public void clear() {
children.clear();
}
}

View File

@ -0,0 +1,47 @@
package io.gitlab.jfronny.libjf.web.impl.host;
import io.gitlab.jfronny.libjf.web.api.v1.*;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.*;
public class VirtualHostBranch implements VirtualHostNode {
protected final Map<String, VirtualHostNode> children = new HashMap<>();
protected HttpRequestHandler content;
@Override
public HttpResponse handle(HttpRequest request, @Nullable PathSegment path) throws IOException {
if (path == null) {
if (content != null) return content.handle(request);
else return request.createResponse(HttpStatusCode.NOT_FOUND);
} else {
VirtualHostNode child = children.get(path.segment());
if (child == null) return request.createResponse(HttpStatusCode.NOT_FOUND);
else return child.handle(request, path.next());
}
}
public void register(PathSegment path, VirtualHostNode node) {
Objects.requireNonNull(path);
if (path.next() == null) {
if (children.containsKey(path.segment())) {
VirtualHostNode childNode = children.get(path.segment());
if (node instanceof VirtualHostBranch next
&& childNode instanceof VirtualHostBranch source) {
if (next.content != null) source.setContent(next.content);
next.children.forEach((k, v) -> source.register(PathSegment.of(k), v));
} else throw new UnsupportedOperationException("VirtualHostNode already exists for path and merging is unsupported for " + childNode.getClass() + " and " + node.getClass());
} else children.put(path.segment(), node);
} else {
VirtualHostNode child = children.computeIfAbsent(path.segment(), s -> new VirtualHostBranch());
if (child instanceof VirtualHostBranch source) source.register(path.next(), node);
else throw new UnsupportedOperationException("Cannot register VirtualHostNode to non-branch VirtualHostNode " + child.getClass());
}
}
public void setContent(HttpRequestHandler content) {
if (this.content != null) throw new UnsupportedOperationException("Content already set for virtual host node");
this.content = Objects.requireNonNull(content);
}
}

View File

@ -0,0 +1,12 @@
package io.gitlab.jfronny.libjf.web.impl.util;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
public class RunnableEvent {
public static Event<Runnable> create() {
return EventFactory.createArrayBacked(Runnable.class, listeners -> () -> {
for (Runnable listener : listeners) listener.run();
});
}
}

View File

@ -1,20 +1,10 @@
package io.gitlab.jfronny.libjf.web.impl.util;
import io.gitlab.jfronny.libjf.web.impl.JfWebConfig;
public class WebPaths {
public static String concat(String s1, String s2) {
return simplify(s1) + "/" + simplify(s2);
}
public static String concat(String[] elements) {
StringBuilder s = new StringBuilder();
for (String element : elements) {
s.append("/").append(element);
}
return simplify(s.toString());
}
public static String getHttp(String ip) {
if (!ip.startsWith("http"))
ip = "http://" + ip;
@ -36,8 +26,7 @@ public class WebPaths {
StringBuilder q = new StringBuilder();
for (String s1 : simplifyPart(s, false).split("/")) {
String w = simplifyPart(s1, true);
if (w != null && w.length() != 0)
q.append("/").append(w);
if (w != null && !w.isEmpty()) q.append("/").append(w);
}
String result = simplifyPart(q.toString(), false);
if (http) result = "http://" + result;

View File

@ -2,16 +2,17 @@ package io.gitlab.jfronny.libjf.web.impl.variant;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.web.api.v1.*;
import io.gitlab.jfronny.libjf.web.impl.*;
import io.gitlab.jfronny.libjf.web.impl.util.*;
import io.gitlab.jfronny.libjf.web.impl.JfWebConfig;
import io.gitlab.jfronny.libjf.web.impl.host.*;
import io.gitlab.jfronny.libjf.web.impl.util.RunnableEvent;
import io.gitlab.jfronny.libjf.web.impl.util.WebPaths;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.loader.api.FabricLoader;
import org.jetbrains.annotations.ApiStatus;
import javax.management.openmbean.KeyAlreadyExistsException;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.stream.Stream;
public abstract class AbstractWebServer implements WebServer {
@ -28,20 +29,16 @@ public abstract class AbstractWebServer implements WebServer {
}
@Override
public String register(String webPath, ContentProvider provider) {
webPath = WebPaths.simplify(webPath);
if (handler.contentProviders.containsKey(webPath))
throw new KeyAlreadyExistsException("A ContentProvider already exists at that address (" + handler.contentProviders.get(webPath).getClass() + ")");
handler.contentProviders.put(webPath, provider);
return WebPaths.concat(getServerRoot(), webPath);
public String register(PathSegment path, VirtualHostNode provider) {
handler.register(path, provider);
return toUrl(path);
}
@Override
public String registerFile(String webPath, Path file, boolean readOnSend) throws IOException {
public String registerFile(PathSegment path, Path file, boolean readOnSend) throws IOException {
if (readOnSend) {
if (!Files.exists(file))
throw new FileNotFoundException();
register(webPath, s -> {
if (!Files.exists(file)) throw new FileNotFoundException();
return register(path, s -> {
HttpResponse resp = HttpResponse.create(HttpStatusCode.OK);
resp.addHeader("Content-Type", Files.probeContentType(file));
resp.addHeader("Content-Length", String.valueOf(Files.size(file)));
@ -49,16 +46,14 @@ public abstract class AbstractWebServer implements WebServer {
resp.setData(fs);
return resp;
});
return WebPaths.concat(getServerRoot(), webPath);
}
else {
return registerFile(webPath, Files.readAllBytes(file), Files.probeContentType(file));
} else {
return registerFile(path, Files.readAllBytes(file), Files.probeContentType(file));
}
}
@Override
public String registerFile(String webPath, byte[] data, String contentType) {
return register(webPath, s -> {
public String registerFile(PathSegment path, byte[] data, String contentType) {
return register(path, s -> {
HttpResponse resp = HttpResponse.create(HttpStatusCode.OK);
resp.addHeader("Content-Type", contentType);
resp.addHeader("Content-Length", String.valueOf(data.length));
@ -69,88 +64,54 @@ public abstract class AbstractWebServer implements WebServer {
}
@Override
public String registerDir(String webPath, Path dir, boolean readOnSend) throws IOException {
try (Stream<Path> contentPath = Files.walk(dir)) {
if (readOnSend) {
return registerSubServer(webPath, (s, t) -> {
final boolean[] c = {false};
final Path[] p_f = new Path[1];
contentPath.filter(Files::isRegularFile)
.filter(Files::isReadable)
.forEach(q -> {
if (c[0])
return;
Path p = dir.toAbsolutePath().relativize(q.toAbsolutePath());
String wp = webPath;
for (Path path : p) {
wp = WebPaths.concat(wp, path.toString());
}
if (Objects.equals(WebPaths.simplify(wp), WebPaths.simplify(WebPaths.concat(t)))) {
p_f[0] = q;
c[0] = true;
}
});
HttpResponse resp;
if (c[0]) {
resp = HttpResponse.create(HttpStatusCode.OK);
resp.addHeader("Content-Type", Files.probeContentType(p_f[0]));
resp.addHeader("Content-Length", String.valueOf(Files.size(p_f[0])));
FileInputStream fs = new FileInputStream(p_f[0].toFile());
resp.setData(fs);
} else {
resp = HttpResponse.create(HttpStatusCode.NOT_FOUND);
}
return resp;
});
} else {
public String registerDir(PathSegment path, Path dir, boolean readOnSend) throws IOException {
if (readOnSend) return register(path, new ReadOnSendDirNode(dir));
else {
try (Stream<Path> contentPath = Files.walk(dir)) {
contentPath.filter(Files::isRegularFile)
.filter(Files::isReadable)
.forEach(s -> {
Path p = dir.toAbsolutePath().relativize(s.toAbsolutePath());
String wp = webPath;
for (Path path : p) wp = WebPaths.concat(wp, path.toString());
Path p = dir.toAbsolutePath().normalize().relativize(s.toAbsolutePath().normalize());
PathSegment subPath = path.concat(PathSegment.of(p.toString()));
try {
registerFile(wp, s, false);
registerFile(subPath, s, false);
} catch (IOException e) {
LibJf.LOGGER.error("Could not register static file", e);
}
});
}
return WebPaths.concat(getServerRoot(), webPath);
return toUrl(path);
}
}
@Override
public String registerSubServer(String webPath, SubServer subServer) {
return registerSubServer(webPath, new AdvancedSubServer() {
@Override
public void onStop() {
}
@Override
public void onStart() {
}
@Override
public HttpResponse handle(HttpRequest request, String[] segments) throws IOException {
return subServer.handle(request, segments);
}
});
private String toUrl(PathSegment path) {
return WebPaths.concat(getServerRoot(), path.toString());
}
private final Event<Runnable> onStart = RunnableEvent.create();
@Override
public String registerSubServer(String webPath, AdvancedSubServer subServer) {
webPath = WebPaths.simplify(webPath);
if (handler.subServers.containsKey(webPath))
throw new KeyAlreadyExistsException("A Subserver already exists at that address (" + handler.subServers.get(webPath).getClass() + ")");
handler.subServers.put(webPath, subServer);
return WebPaths.concat(getServerRoot(), webPath);
public void onStart(Runnable listener) {
onStart.register(listener);
}
protected void emitStart() {
onStart.invoker().run();
}
private final Event<Runnable> onStop = RunnableEvent.create();
@Override
public void onStop(Runnable listener) {
onStop.register(listener);
}
protected void emitStop() {
onStop.invoker().run();
}
protected void performRegistrations() {
if (JfWebConfig.enableFileHost) dfh.register(this);
FabricLoader.getInstance().getEntrypointContainers(LibJf.MOD_ID + ":web", WebInit.class).forEach(entrypoint -> {
WebInit init = entrypoint.getEntrypoint();
FabricLoader.getInstance().getEntrypointContainers(LibJf.MOD_ID + ":web", WebEntrypoint.class).forEach(entrypoint -> {
WebEntrypoint init = entrypoint.getEntrypoint();
init.register(this);
});
}

View File

@ -1,8 +1,8 @@
package io.gitlab.jfronny.libjf.web.impl.variant.hosted;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.web.api.v1.AdvancedSubServer;
import io.gitlab.jfronny.libjf.web.impl.*;
import io.gitlab.jfronny.libjf.web.impl.host.RequestHandler;
import io.gitlab.jfronny.libjf.web.impl.variant.AbstractWebServer;
public class HostedWebServer extends AbstractWebServer {
@ -23,7 +23,7 @@ public class HostedWebServer extends AbstractWebServer {
@Override
public synchronized void stop() {
for (AdvancedSubServer subServer : handler.subServers.values()) subServer.onStop();
emitStop();
if (server != null) {
try {
server.close();
@ -51,9 +51,7 @@ public class HostedWebServer extends AbstractWebServer {
stop();
LibJf.LOGGER.error("Server could not be readied", e);
}
for (AdvancedSubServer subServer : handler.subServers.values()) {
subServer.onStart();
}
emitStart();
callback.run();
}

View File

@ -25,6 +25,7 @@
package io.gitlab.jfronny.libjf.web.impl.variant.hosted;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.web.api.v1.HttpRequestHandler;
import java.io.IOException;
import java.net.*;

View File

@ -4,9 +4,6 @@ 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.web.impl.RequestHandler;
import io.gitlab.jfronny.libjf.web.impl.util.HttpRequestImpl;
import io.gitlab.jfronny.libjf.web.impl.util.HttpResponseImpl;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
@ -34,18 +31,25 @@ public class HttpDecoder extends ChannelInboundHandlerAdapter {
buf.markReaderIndex();
boolean passOn = true;
try {
// Check whether buf starts with an HTTP method, as a request would
Trie<String> current = METHOD;
while (buf.isReadable()
&& current != null
&& current.content == null)
current = current.next.get((char) buf.readByte());
if (current == null || current.content == null) return;
// Method identified, this is HTTP!
passOn = false;
// Read all data from buffer
buf.resetReaderIndex();
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()
@ -53,8 +57,6 @@ public class HttpDecoder extends ChannelInboundHandlerAdapter {
.writeAndFlush(Unpooled.wrappedBuffer(os.toByteArray()))
.addListener(ChannelFutureListener.CLOSE);
}
buf.release();
passOn = false;
} catch (RuntimeException re) {
LibJf.LOGGER.error("Could not process HTTP", re);
} finally {

View File

@ -1,19 +1,20 @@
package io.gitlab.jfronny.libjf.web.impl.variant.shared;
import io.gitlab.jfronny.libjf.web.api.v1.AdvancedSubServer;
import io.gitlab.jfronny.libjf.web.impl.*;
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.LinkedHashSet;
import java.util.Set;
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() {
for (Runnable runnable : onActive) runnable.run();
for (Iterator<Runnable> iterator = onActive.iterator(); iterator.hasNext(); iterator.remove()) {
Runnable runnable = iterator.next();
runnable.run();
}
}
public SharedWebServer(RequestHandler handler) {
@ -35,10 +36,10 @@ public class SharedWebServer extends AbstractWebServer {
@Override
public void queueRestart(Runnable callback) {
onActive.add(() -> {
for (AdvancedSubServer subServer : handler.subServers.values()) subServer.onStop();
emitStop();
handler.clear();
performRegistrations();
for (AdvancedSubServer subServer : handler.subServers.values()) subServer.onStart();
emitStart();
callback.run();
});
if (isActive()) emitActive();

View File

@ -2,24 +2,23 @@ package io.gitlab.jfronny.libjf.web.test;
import io.gitlab.jfronny.libjf.LibJf;
import io.gitlab.jfronny.libjf.web.api.v1.*;
import io.gitlab.jfronny.libjf.web.impl.util.HttpResponseImpl;
import net.fabricmc.loader.api.FabricLoader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class WebTest implements WebInit {
public class WebTest implements WebEntrypoint {
@Override
public void register(WebServer api) {
Path sourcePath = FabricLoader.getInstance()
.getModContainer("libjf-web-v1-testmod")
.flatMap(modContainer -> modContainer.findPath("test.html"))
.orElseThrow();
LibJf.LOGGER.info(api.register("/test/0.html", request -> request.createResponse(HttpStatusCode.OK).setData(Files.readString(sourcePath))));
LibJf.LOGGER.info(api.register(PathSegment.of("test/0.html"), request -> request.createResponse(HttpStatusCode.OK).setData(Files.readString(sourcePath))));
try {
LibJf.LOGGER.info(api.registerFile("/test/1.html", sourcePath, false));
LibJf.LOGGER.info(api.registerFile("/test/2.html", sourcePath, true));
LibJf.LOGGER.info(api.registerFile(PathSegment.of("test/1.html"), sourcePath, false));
LibJf.LOGGER.info(api.registerFile(PathSegment.of("test/2.html"), sourcePath, true));
} catch (IOException e) {
throw new RuntimeException("Could not register hosted files", e);
}