Bug fixes (im frustrated)
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>WebServer</artifactId>
|
<artifactId>WebServer</artifactId>
|
||||||
<version>1.0.0-BETA.1.8</version>
|
<version>1.0.0-BETA.1.9</version>
|
||||||
<description>The default DNS-Server</description>
|
<description>The default DNS-Server</description>
|
||||||
<url>https://open-autonomous-connection.org/</url>
|
<url>https://open-autonomous-connection.org/</url>
|
||||||
<issueManagement>
|
<issueManagement>
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -112,7 +112,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openautonomousconnection</groupId>
|
<groupId>org.openautonomousconnection</groupId>
|
||||||
<artifactId>Protocol</artifactId>
|
<artifactId>Protocol</artifactId>
|
||||||
<version>1.0.0-BETA.7.6</version>
|
<version>1.0.0-BETA.7.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
|
|||||||
@@ -3,17 +3,26 @@ package org.openautonomousconnection.webserver;
|
|||||||
import dev.unlegitdqrk.unlegitlibrary.command.CommandExecutor;
|
import dev.unlegitdqrk.unlegitlibrary.command.CommandExecutor;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.command.CommandManager;
|
import dev.unlegitdqrk.unlegitlibrary.command.CommandManager;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.command.CommandPermission;
|
import dev.unlegitdqrk.unlegitlibrary.command.CommandPermission;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.EventListener;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
import dev.unlegitdqrk.unlegitlibrary.event.EventManager;
|
||||||
|
import dev.unlegitdqrk.unlegitlibrary.event.Listener;
|
||||||
import dev.unlegitdqrk.unlegitlibrary.file.ConfigurationManager;
|
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.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 dev.unlegitdqrk.unlegitlibrary.network.system.utils.ClientAuthMode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.openautonomousconnection.protocol.ProtocolBridge;
|
import org.openautonomousconnection.protocol.ProtocolBridge;
|
||||||
import org.openautonomousconnection.protocol.ProtocolValues;
|
import org.openautonomousconnection.protocol.ProtocolValues;
|
||||||
|
import org.openautonomousconnection.protocol.side.server.events.S_CustomClientConnectedEvent;
|
||||||
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
import org.openautonomousconnection.protocol.versions.ProtocolVersion;
|
||||||
import org.openautonomousconnection.webserver.commands.StopCommand;
|
import org.openautonomousconnection.webserver.commands.StopCommand;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
@@ -35,6 +44,8 @@ public class Main {
|
|||||||
values.packetHandler = new PacketHandler();
|
values.packetHandler = new PacketHandler();
|
||||||
values.eventManager = new EventManager();
|
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"));
|
ConfigurationManager config = new ConfigurationManager(new File("config.properties"));
|
||||||
config.loadProperties();
|
config.loadProperties();
|
||||||
|
|
||||||
@@ -85,11 +96,12 @@ public class Main {
|
|||||||
|
|
||||||
Scanner scanner = new Scanner(System.in);
|
Scanner scanner = new Scanner(System.in);
|
||||||
|
|
||||||
while (true) {
|
// while (protocolBridge.getProtocolServer().getNetwork().isServerOnline()) {
|
||||||
System.out.print(commandExecutor.getName() + "> ");
|
// System.out.print(commandExecutor.getName() + "> ");
|
||||||
String line = scanner.nextLine();
|
// String line = scanner.nextLine();
|
||||||
commandManager.execute(commandExecutor, line);
|
// commandManager.execute(commandExecutor, line);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
// System.exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.openautonomousconnection.webserver;
|
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 dev.unlegitdqrk.unlegitlibrary.network.system.utils.TransportProtocol;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import org.openautonomousconnection.protocol.annotations.ProtocolInfo;
|
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.protocol.versions.ProtocolVersion;
|
||||||
import org.openautonomousconnection.webserver.api.SessionContext;
|
import org.openautonomousconnection.webserver.api.SessionContext;
|
||||||
import org.openautonomousconnection.webserver.runtime.JavaPageDispatcher;
|
import org.openautonomousconnection.webserver.runtime.JavaPageDispatcher;
|
||||||
|
import org.openautonomousconnection.webserver.utils.HeaderMaps;
|
||||||
import org.openautonomousconnection.webserver.utils.WebHasher;
|
import org.openautonomousconnection.webserver.utils.WebHasher;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAC WebServer implementation.
|
||||||
|
*/
|
||||||
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
@ProtocolInfo(protocolSide = ProtocolVersion.ProtocolSide.WEB)
|
||||||
public final class WebServer extends ProtocolWebServer {
|
public final class WebServer extends ProtocolWebServer {
|
||||||
|
|
||||||
private static final int STREAM_CHUNK_SIZE = 64 * 1024;
|
private static final int STREAM_CHUNK_SIZE = 64 * 1024;
|
||||||
private static final long STREAM_THRESHOLD = 2L * 1024 * 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
|
@Getter
|
||||||
private final WebHasher hasher;
|
private final WebHasher hasher;
|
||||||
|
|
||||||
@@ -38,28 +55,27 @@ public final class WebServer extends ProtocolWebServer {
|
|||||||
) throws Exception {
|
) throws Exception {
|
||||||
super(authFile, rulesFile, sessionExpire, maxUpload);
|
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(
|
this.hasher = new WebHasher(
|
||||||
120_000, // PBKDF2 iterations
|
120_000,
|
||||||
16, // salt bytes
|
16,
|
||||||
32 // key bytes
|
32
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request) {
|
public WebResponsePacket onWebRequest(CustomConnectedClient client, WebRequestPacket request) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String path = request.getPath() == null ? "/" : request.getPath();
|
String path = request.getPath() == null ? "/" : request.getPath();
|
||||||
|
|
||||||
if (RuleManager.isDenied(path)) {
|
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)) {
|
if (RuleManager.requiresAuth(path)) {
|
||||||
SessionContext ctx = SessionContext.from(client, this, request.getHeaders());
|
SessionContext ctx = SessionContext.from(client, this, request.getHeaders());
|
||||||
if (!ctx.isValid()) {
|
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);
|
return serveFile(client, path);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return new WebResponsePacket(500, "text/plain; charset=utf-8", Map.of(),
|
return new WebResponsePacket(
|
||||||
("Internal Error: " + e.getMessage()).getBytes());
|
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 root = getContentFolder().getCanonicalFile();
|
||||||
File file = new File(root, path).getCanonicalFile();
|
File file = new File(root, path).getCanonicalFile();
|
||||||
|
|
||||||
if (!file.getPath().startsWith(root.getPath()))
|
if (!file.getPath().startsWith(root.getPath())) {
|
||||||
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 (!file.exists() || !file.isFile())
|
}
|
||||||
return new WebResponsePacket(404, "text/plain; charset=utf-8", Map.of(), "Not found".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());
|
String contentType = ContentTypeResolver.resolve(file.getName());
|
||||||
long size = file.length();
|
long size = file.length();
|
||||||
|
|
||||||
if (size >= STREAM_THRESHOLD) {
|
if (size >= STREAM_THRESHOLD) {
|
||||||
streamFile(client, file, contentType);
|
startStreamingAsync(client, file, contentType);
|
||||||
return null;
|
|
||||||
|
// 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());
|
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 {
|
private void streamFile(CustomConnectedClient client, File file, String contentType) throws IOException, ClassNotFoundException {
|
||||||
long total = file.length();
|
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))) {
|
try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
|
||||||
byte[] buf = new byte[STREAM_CHUNK_SIZE];
|
byte[] buf = new byte[STREAM_CHUNK_SIZE];
|
||||||
int seq = 0;
|
int seq = 0;
|
||||||
int r;
|
int r;
|
||||||
while ((r = in.read(buf)) != -1) {
|
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(
|
client.getConnection().sendPacket(
|
||||||
new WebStreamChunkPacket(seq++, chunk),
|
new WebStreamChunkPacket(seq++, chunk),
|
||||||
ContentTypeResolver.isVideoFile(file.getName()) ? TransportProtocol.UDP : TransportProtocol.TCP
|
ContentTypeResolver.isVideoFile(file.getName()) ? TransportProtocol.UDP : TransportProtocol.TCP
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import org.openautonomousconnection.protocol.side.web.ProtocolWebServer;
|
|||||||
import org.openautonomousconnection.webserver.WebServer;
|
import org.openautonomousconnection.webserver.WebServer;
|
||||||
import org.openautonomousconnection.webserver.api.WebPage;
|
import org.openautonomousconnection.webserver.api.WebPage;
|
||||||
import org.openautonomousconnection.webserver.api.WebPageContext;
|
import org.openautonomousconnection.webserver.api.WebPageContext;
|
||||||
|
import org.openautonomousconnection.webserver.utils.HeaderMaps;
|
||||||
import org.openautonomousconnection.webserver.utils.RequestParams;
|
import org.openautonomousconnection.webserver.utils.RequestParams;
|
||||||
import org.openautonomousconnection.webserver.utils.WebHasher;
|
import org.openautonomousconnection.webserver.utils.WebHasher;
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ public final class JavaPageDispatcher {
|
|||||||
return new WebResponsePacket(
|
return new WebResponsePacket(
|
||||||
code,
|
code,
|
||||||
"text/plain; charset=utf-8",
|
"text/plain; charset=utf-8",
|
||||||
Map.of(),
|
HeaderMaps.mutable(),
|
||||||
msg.getBytes(StandardCharsets.UTF_8)
|
msg.getBytes(StandardCharsets.UTF_8)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<String, String> 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<String, String> mutable(Map<String, String> initial) {
|
||||||
|
return (initial == null || initial.isEmpty()) ? new HashMap<>() : new HashMap<>(initial);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ public final class HttpsProxy {
|
|||||||
return new WebResponsePacket(
|
return new WebResponsePacket(
|
||||||
502,
|
502,
|
||||||
"text/plain; charset=utf-8",
|
"text/plain; charset=utf-8",
|
||||||
Map.of(),
|
HeaderMaps.mutable(),
|
||||||
("Bad Gateway: " + e.getClass().getName() + ": " + e.getMessage()).getBytes(StandardCharsets.UTF_8)
|
("Bad Gateway: " + e.getClass().getName() + ": " + e.getMessage()).getBytes(StandardCharsets.UTF_8)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ public final class HttpsProxy {
|
|||||||
return new WebResponsePacket(
|
return new WebResponsePacket(
|
||||||
508,
|
508,
|
||||||
"text/plain; charset=utf-8",
|
"text/plain; charset=utf-8",
|
||||||
Map.of(),
|
HeaderMaps.mutable(),
|
||||||
"Too many redirects".getBytes(StandardCharsets.UTF_8)
|
"Too many redirects".getBytes(StandardCharsets.UTF_8)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ public final class HttpsProxy {
|
|||||||
return new WebResponsePacket(
|
return new WebResponsePacket(
|
||||||
502,
|
502,
|
||||||
"text/plain; charset=utf-8",
|
"text/plain; charset=utf-8",
|
||||||
Map.of(),
|
HeaderMaps.mutable(),
|
||||||
("Bad Gateway: redirect without Location (code=" + code + ")").getBytes(StandardCharsets.UTF_8)
|
("Bad Gateway: redirect without Location (code=" + code + ")").getBytes(StandardCharsets.UTF_8)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -104,8 +104,7 @@ public final class HttpsProxy {
|
|||||||
con.disconnect();
|
con.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> headers = new HashMap<>();
|
return new WebResponsePacket(code, contentType, HeaderMaps.mutable(), body);
|
||||||
return new WebResponsePacket(code, contentType, headers, body);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] readAllBytes(InputStream in) throws Exception {
|
private static byte[] readAllBytes(InputStream in) throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user