package io.gitlab.jfronny.libjf.web.impl; import io.gitlab.jfronny.libjf.LibJf; import io.gitlab.jfronny.libjf.web.api.*; import io.gitlab.jfronny.libjf.web.impl.util.WebPaths; import io.gitlab.jfronny.libjf.web.impl.util.bluemapcore.*; import net.fabricmc.loader.api.FabricLoader; 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 class JfWebServer implements WebServer { private HttpServer server = null; private final RequestHandler handler = new RequestHandler(); private final int port; private final int maxConnections; private final DefaultFileHost dfh = new DefaultFileHost(); public JfWebServer(int port, int maxConnections) { this.port = port; this.maxConnections = maxConnections; } @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); } @Override public String registerFile(String webPath, Path file, Boolean readOnSend) throws IOException { if (readOnSend) { if (!Files.exists(file)) throw new FileNotFoundException(); register(webPath, s -> { HttpResponse resp = new HttpResponse(HttpStatusCode.OK); resp.addHeader("Content-Type", Files.probeContentType(file)); resp.addHeader("Content-Length", String.valueOf(Files.size(file))); FileInputStream fs = new FileInputStream(file.toFile()); resp.setData(fs); return resp; }); return WebPaths.concat(getServerRoot(), webPath); } else { return registerFile(webPath, Files.readAllBytes(file), Files.probeContentType(file)); } } @Override public String registerFile(String webPath, byte[] data, String contentType) { return register(webPath, s -> { HttpResponse resp = new HttpResponse(HttpStatusCode.OK); resp.addHeader("Content-Type", contentType); resp.addHeader("Content-Length", String.valueOf(data.length)); ByteArrayInputStream fs = new ByteArrayInputStream(data); resp.setData(fs); return resp; }); } @Override public String registerDir(String webPath, Path dir, Boolean readOnSend) throws IOException { try (Stream 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 = new HttpResponse(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 = new HttpResponse(HttpStatusCode.NOT_FOUND); } return resp; }); } else { 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()); try { registerFile(wp, s, false); } catch (IOException e) { LibJf.LOGGER.error("Could not register static file", e); } }); } return WebPaths.concat(getServerRoot(), webPath); } } @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); } }); } @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); } @Override public String getServerRoot() { String ip = JfWebConfig.serverIp; if (!ip.startsWith("http")) ip = "http://" + ip; if (JfWebConfig.portOverride != -1) { return WebPaths.simplify(ip) + ":" + JfWebConfig.portOverride; } return WebPaths.simplify(ip) + ":" + JfWebConfig.port; } @Override public synchronized void stop() { for (AdvancedSubServer subServer : handler.subServers.values()) subServer.onStop(); if (server != null) { try { server.close(); server.join(); } catch (InterruptedException e) { //It is most likely already dead } } } @Override public synchronized void restart() { JfWebConfig.ensureValidPort(); int tmpPort = port; if (server != null) { tmpPort = server.getPort(); stop(); } handler.clear(); server = new HttpServer(null, tmpPort, maxConnections, handler, () -> { if (JfWebConfig.enableFileHost) dfh.register(this); FabricLoader.getInstance().getEntrypointContainers(LibJf.MOD_ID + ":web", WebInit.class).forEach(entrypoint -> { WebInit init = entrypoint.getEntrypoint(); init.register(this); }); }); server.start(); try { server.waitUntilReady(); } catch (InterruptedException e) { stop(); LibJf.LOGGER.error("Server could not be readied", e); } for (AdvancedSubServer subServer : handler.subServers.values()) { subServer.onStart(); } } @Override public boolean isActive() { return server != null && server.isAlive(); } }