diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml
index 49d0442..0d456eb 100644
--- a/dependency-reduced-pom.xml
+++ b/dependency-reduced-pom.xml
@@ -3,7 +3,7 @@
4.0.0
org.openautonomousconnection
WebServer
- 1.0.0-BETA.1.8
+ 1.0.0-BETA.1.9
The default DNS-Server
https://open-autonomous-connection.org/
diff --git a/pom.xml b/pom.xml
index 52e39dc..e92d580 100644
--- a/pom.xml
+++ b/pom.xml
@@ -112,7 +112,7 @@
org.openautonomousconnection
Protocol
- 1.0.0-BETA.7.6
+ 1.0.0-BETA.7.7
org.projectlombok
diff --git a/src/main/java/org/openautonomousconnection/webserver/Main.java b/src/main/java/org/openautonomousconnection/webserver/Main.java
index 51ad5c7..3bca1e1 100644
--- a/src/main/java/org/openautonomousconnection/webserver/Main.java
+++ b/src/main/java/org/openautonomousconnection/webserver/Main.java
@@ -3,17 +3,26 @@ package org.openautonomousconnection.webserver;
import dev.unlegitdqrk.unlegitlibrary.command.CommandExecutor;
import dev.unlegitdqrk.unlegitlibrary.command.CommandManager;
import dev.unlegitdqrk.unlegitlibrary.command.CommandPermission;
+import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
+import dev.unlegitdqrk.unlegitlibrary.event.Listener;
import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager;
+import dev.unlegitdqrk.unlegitlibrary.network.system.client.events.state.ClientConnectedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.packets.PacketHandler;
+import dev.unlegitdqrk.unlegitlibrary.network.system.server.NetworkServer;
+import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketSendEvent;
+import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.state.ServerStoppedEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
import lombok.Getter;
import org.openautonomousconnection.protocol.ProtocolBridge;
import org.openautonomousconnection.protocol.ProtocolValues;
+import org.openautonomousconnection.protocol.side.server.events.S_CustomClientConnectedEvent;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.webserver.commands.StopCommand;
import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Scanner;
public class Main {
@@ -35,6 +44,8 @@ public class Main {
values.packetHandler = new PacketHandler();
values.eventManager = new EventManager();
+ if (!Files.exists(new File("config.properties").toPath())) Files.createFile(new File("config.properties").toPath());
+
ConfigurationManager config = new ConfigurationManager(new File("config.properties"));
config.loadProperties();
@@ -85,11 +96,12 @@ public class Main {
Scanner scanner = new Scanner(System.in);
- while (true) {
- System.out.print(commandExecutor.getName() + "> ");
- String line = scanner.nextLine();
- commandManager.execute(commandExecutor, line);
- }
-
+// while (protocolBridge.getProtocolServer().getNetwork().isServerOnline()) {
+// System.out.print(commandExecutor.getName() + "> ");
+// String line = scanner.nextLine();
+// commandManager.execute(commandExecutor, line);
+// }
+//
+// System.exit(0);
}
}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/webserver/WebServer.java b/src/main/java/org/openautonomousconnection/webserver/WebServer.java
index 4a2da0f..2d49274 100644
--- a/src/main/java/org/openautonomousconnection/webserver/WebServer.java
+++ b/src/main/java/org/openautonomousconnection/webserver/WebServer.java
@@ -1,5 +1,7 @@
package org.openautonomousconnection.webserver;
+import dev.unlegitdqrk.unlegitlibrary.event.Listener;
+import dev.unlegitdqrk.unlegitlibrary.network.system.server.events.packets.S_PacketReadEvent;
import dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
import lombok.Getter;
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
@@ -14,19 +16,34 @@ import org.openautonomousconnection.protocol.side.web.managers.RuleManager;
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
import org.openautonomousconnection.webserver.api.SessionContext;
import org.openautonomousconnection.webserver.runtime.JavaPageDispatcher;
+import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.WebHasher;
import java.io.*;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+/**
+ * OAC WebServer implementation.
+ */
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
public final class WebServer extends ProtocolWebServer {
private static final int STREAM_CHUNK_SIZE = 64 * 1024;
private static final long STREAM_THRESHOLD = 2L * 1024 * 1024;
+ /**
+ * Dedicated executor for streaming to avoid blocking network receive thread.
+ */
+ private static final ExecutorService STREAM_EXECUTOR = Executors.newCachedThreadPool(r -> {
+ Thread t = new Thread(r, "oac-web-stream");
+ t.setDaemon(true);
+ return t;
+ });
+
@Getter
private final WebHasher hasher;
@@ -38,28 +55,27 @@ public final class WebServer extends ProtocolWebServer {
) throws Exception {
super(authFile, rulesFile, sessionExpire, maxUpload);
- // NOTE: Values chosen as safe defaults.
- // move them to Main and pass them in here (no hidden assumptions).
this.hasher = new WebHasher(
- 120_000, // PBKDF2 iterations
- 16, // salt bytes
- 32 // key bytes
+ 120_000,
+ 16,
+ 32
);
}
@Override
public WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request) {
+
try {
String path = request.getPath() == null ? "/" : request.getPath();
if (RuleManager.isDenied(path)) {
- return new WebResponsePacket(403, "text/plain; charset=utf-8", Map.of(), "Forbidden".getBytes());
+ return new WebResponsePacket(403, "text/plain; charset=utf-8", HeaderMaps.mutable(), "Forbidden".getBytes());
}
if (RuleManager.requiresAuth(path)) {
SessionContext ctx = SessionContext.from(client, this, request.getHeaders());
if (!ctx.isValid()) {
- return new WebResponsePacket(401, "text/plain; charset=utf-8", Map.of(), "Authentication required".getBytes());
+ return new WebResponsePacket(401, "text/plain; charset=utf-8", HeaderMaps.mutable(), "Authentication required".getBytes());
}
}
@@ -69,8 +85,12 @@ public final class WebServer extends ProtocolWebServer {
return serveFile(client, path);
} catch (Exception e) {
- return new WebResponsePacket(500, "text/plain; charset=utf-8", Map.of(),
- ("Internal Error: " + e.getMessage()).getBytes());
+ return new WebResponsePacket(
+ 500,
+ "text/plain; charset=utf-8",
+ HeaderMaps.mutable(),
+ ("Internal Error: " + e.getClass().getName() + ": " + e.getMessage()).getBytes()
+ );
}
}
@@ -81,34 +101,64 @@ public final class WebServer extends ProtocolWebServer {
File root = getContentFolder().getCanonicalFile();
File file = new File(root, path).getCanonicalFile();
- if (!file.getPath().startsWith(root.getPath()))
- return new WebResponsePacket(403, "text/plain; charset=utf-8", Map.of(), "Forbidden".getBytes());
- if (!file.exists() || !file.isFile())
- return new WebResponsePacket(404, "text/plain; charset=utf-8", Map.of(), "Not found".getBytes());
+ if (!file.getPath().startsWith(root.getPath())) {
+ return new WebResponsePacket(403, "text/plain; charset=utf-8", HeaderMaps.mutable(), "Forbidden".getBytes());
+ }
+ if (!file.exists() || !file.isFile()) {
+ return new WebResponsePacket(404, "text/plain; charset=utf-8", HeaderMaps.mutable(), "Not found".getBytes());
+ }
String contentType = ContentTypeResolver.resolve(file.getName());
long size = file.length();
if (size >= STREAM_THRESHOLD) {
- streamFile(client, file, contentType);
- return null;
+ startStreamingAsync(client, file, contentType);
+
+ // IMPORTANT: Never return null. If your client expects a normal response, this keeps the pipeline stable.
+ // The actual bytes are delivered via WebStream* packets.
+ return new WebResponsePacket(
+ 202,
+ "text/plain; charset=utf-8",
+ HeaderMaps.mutable(Map.of("x-oac-stream", "1")),
+ "Streaming started".getBytes()
+ );
}
byte[] data = Files.readAllBytes(file.toPath());
- return new WebResponsePacket(200, contentType, Map.of(), data);
+ return new WebResponsePacket(200, contentType, HeaderMaps.mutable(), data);
+ }
+
+ private void startStreamingAsync(CustomConnectedClient client, File file, String contentType) {
+ STREAM_EXECUTOR.execute(() -> {
+ try {
+ streamFile(client, file, contentType);
+ } catch (Exception e) {
+ // Never let streaming errors kill your server threads.
+ try {
+ client.getConnection().sendPacket(new WebStreamEndPacket(false), TransportProtocol.TCP);
+ } catch (Exception ignored) {
+ // ignore: client may already be gone
+ }
+ }
+ });
}
private void streamFile(CustomConnectedClient client, File file, String contentType) throws IOException, ClassNotFoundException {
long total = file.length();
- client.getConnection().sendPacket(new WebStreamStartPacket(200, contentType, Map.of("name", file.getName()), total), TransportProtocol.TCP);
+ client.getConnection().sendPacket(
+ new WebStreamStartPacket(200, contentType, Map.of("name", file.getName()), total),
+ TransportProtocol.TCP
+ );
try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
byte[] buf = new byte[STREAM_CHUNK_SIZE];
int seq = 0;
int r;
while ((r = in.read(buf)) != -1) {
- byte[] chunk = (r == buf.length) ? buf : Arrays.copyOf(buf, r);
+ // Always copy: never hand out the reusable buffer reference.
+ byte[] chunk = Arrays.copyOf(buf, r);
+
client.getConnection().sendPacket(
new WebStreamChunkPacket(seq++, chunk),
ContentTypeResolver.isVideoFile(file.getName()) ? TransportProtocol.UDP : TransportProtocol.TCP
@@ -118,4 +168,4 @@ public final class WebServer extends ProtocolWebServer {
client.getConnection().sendPacket(new WebStreamEndPacket(true), TransportProtocol.TCP);
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/webserver/runtime/JavaPageDispatcher.java b/src/main/java/org/openautonomousconnection/webserver/runtime/JavaPageDispatcher.java
index b7857d2..34b58ed 100644
--- a/src/main/java/org/openautonomousconnection/webserver/runtime/JavaPageDispatcher.java
+++ b/src/main/java/org/openautonomousconnection/webserver/runtime/JavaPageDispatcher.java
@@ -7,6 +7,7 @@ import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
import org.openautonomousconnection.webserver.WebServer;
import org.openautonomousconnection.webserver.api.WebPage;
import org.openautonomousconnection.webserver.api.WebPageContext;
+import org.openautonomousconnection.webserver.utils.HeaderMaps;
import org.openautonomousconnection.webserver.utils.RequestParams;
import org.openautonomousconnection.webserver.utils.WebHasher;
@@ -77,7 +78,7 @@ public final class JavaPageDispatcher {
return new WebResponsePacket(
code,
"text/plain; charset=utf-8",
- Map.of(),
+ HeaderMaps.mutable(),
msg.getBytes(StandardCharsets.UTF_8)
);
}
diff --git a/src/main/java/org/openautonomousconnection/webserver/utils/HeaderMaps.java b/src/main/java/org/openautonomousconnection/webserver/utils/HeaderMaps.java
new file mode 100644
index 0000000..d7a2209
--- /dev/null
+++ b/src/main/java/org/openautonomousconnection/webserver/utils/HeaderMaps.java
@@ -0,0 +1,32 @@
+package org.openautonomousconnection.webserver.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Small helper utilities for mutable header maps.
+ */
+public final class HeaderMaps {
+
+ private HeaderMaps() {
+ }
+
+ /**
+ * Creates an empty mutable header map.
+ *
+ * @return mutable map
+ */
+ public static Map mutable() {
+ return new HashMap<>();
+ }
+
+ /**
+ * Creates a mutable header map initialized with the given entries.
+ *
+ * @param initial initial entries (may be null)
+ * @return mutable map
+ */
+ public static Map mutable(Map initial) {
+ return (initial == null || initial.isEmpty()) ? new HashMap<>() : new HashMap<>(initial);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/org/openautonomousconnection/webserver/utils/HttpsProxy.java b/src/main/java/org/openautonomousconnection/webserver/utils/HttpsProxy.java
index c579bbc..b203c64 100644
--- a/src/main/java/org/openautonomousconnection/webserver/utils/HttpsProxy.java
+++ b/src/main/java/org/openautonomousconnection/webserver/utils/HttpsProxy.java
@@ -43,7 +43,7 @@ public final class HttpsProxy {
return new WebResponsePacket(
502,
"text/plain; charset=utf-8",
- Map.of(),
+ HeaderMaps.mutable(),
("Bad Gateway: " + e.getClass().getName() + ": " + e.getMessage()).getBytes(StandardCharsets.UTF_8)
);
}
@@ -54,7 +54,7 @@ public final class HttpsProxy {
return new WebResponsePacket(
508,
"text/plain; charset=utf-8",
- Map.of(),
+ HeaderMaps.mutable(),
"Too many redirects".getBytes(StandardCharsets.UTF_8)
);
}
@@ -82,7 +82,7 @@ public final class HttpsProxy {
return new WebResponsePacket(
502,
"text/plain; charset=utf-8",
- Map.of(),
+ HeaderMaps.mutable(),
("Bad Gateway: redirect without Location (code=" + code + ")").getBytes(StandardCharsets.UTF_8)
);
}
@@ -104,8 +104,7 @@ public final class HttpsProxy {
con.disconnect();
}
- Map headers = new HashMap<>();
- return new WebResponsePacket(code, contentType, headers, body);
+ return new WebResponsePacket(code, contentType, HeaderMaps.mutable(), body);
}
private static byte[] readAllBytes(InputStream in) throws Exception {