feat(web): represent virtual hosts as tree and avoid collisions
This commit is contained in:
parent
f54f8d59c4
commit
3acc4b2420
|
@ -1,6 +0,0 @@
|
|||
package io.gitlab.jfronny.libjf.web.api.v1;
|
||||
|
||||
public interface AdvancedSubServer extends SubServer {
|
||||
void onStop();
|
||||
void onStart();
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
package io.gitlab.jfronny.libjf.web.api.v1;
|
||||
|
||||
public interface WebInit {
|
||||
public interface WebEntrypoint {
|
||||
void register(WebServer api);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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.*;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue