From 2e3e11fbb02a6622e41e93ae3e112bab44469ba2 Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Mon, 3 Apr 2023 22:49:23 +0200 Subject: [PATCH] Cleanup, refactoring. --- .../labyrinth/server/LabyrinthServer.java | 40 +++---- .../fritteli/labyrinth/server/OutputType.java | 20 ++-- .../labyrinth/server/ServerConfig.java | 12 +- .../server/StaticResourcesFileHandler.java | 109 ------------------ .../server/handler/CreateHandler.java | 91 +++++++++++---- .../LanyrinthRenderHandler.java | 2 +- .../UndertowPlayground.java | 28 ----- src/main/resources/logback.xml | 2 +- 8 files changed, 105 insertions(+), 199 deletions(-) delete mode 100644 src/main/java/ch/fritteli/labyrinth/server/StaticResourcesFileHandler.java rename src/main/java/ch/fritteli/labyrinth/server/{undertow_playground => handler}/LanyrinthRenderHandler.java (90%) delete mode 100644 src/main/java/ch/fritteli/labyrinth/server/undertow_playground/UndertowPlayground.java diff --git a/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java b/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java index 8ed349f..14d162d 100644 --- a/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java +++ b/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java @@ -1,10 +1,9 @@ package ch.fritteli.labyrinth.server; import ch.fritteli.labyrinth.server.handler.CreateHandler; -import ch.fritteli.labyrinth.server.undertow_playground.LanyrinthRenderHandler; +import ch.fritteli.labyrinth.server.handler.LanyrinthRenderHandler; import io.undertow.Undertow; import io.undertow.server.RoutingHandler; -import io.undertow.server.handlers.RedirectHandler; import io.undertow.util.StatusCodes; import io.vavr.control.Try; import lombok.NonNull; @@ -17,36 +16,33 @@ public class LabyrinthServer { @NonNull private final Undertow undertow; + private LabyrinthServer(@NonNull final ServerConfig config) { + log.info("Starting Server at http://{}:{}/", config.getAddress().getHostAddress(), config.getPort()); + final RoutingHandler routingHandler = new RoutingHandler().get(CreateHandler.PATH_TEMPLATE, new CreateHandler()) + .post("/render", new LanyrinthRenderHandler()) + .setFallbackHandler(exchange -> exchange.setStatusCode( + StatusCodes.NOT_FOUND) + .getResponseSender() + .send("Resource %s not found".formatted( + exchange.getRequestURI()))); + this.undertow = Undertow.builder() + .addHttpListener(config.getPort(), config.getAddress().getHostAddress()) + .setHandler(routingHandler) + .build(); + } + public static Try createAndStartServer() { final Try serverOption = Try.of(ServerConfig::init).mapTry(LabyrinthServer::new); serverOption.forEach(LabyrinthServer::start); return serverOption; } - private LabyrinthServer(@NonNull final ServerConfig config) { - log.info("Starting Server at http://{}:{}/", config.getAddress().getHostAddress(), config.getPort()); - final RoutingHandler routingHandler = new RoutingHandler().get("/", new RedirectHandler("/create/text")) - .get("/create", new RedirectHandler("/create/text")) - .get("/create/{output}", new CreateHandler()) - .post("/render", new LanyrinthRenderHandler()) - .setFallbackHandler(exchange -> { - exchange.setStatusCode(StatusCodes.NOT_FOUND) - .getResponseSender() - .send("Resource %s not found".formatted( - exchange.getRequestURI())); - }); - this.undertow = Undertow.builder() - .addHttpListener(config.getPort(), config.getAddress().getHostAddress()) - .setHandler(routingHandler) - .build(); - } - private void start() { Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper")); this.undertow.start(); Try.of(() -> (InetSocketAddress) this.undertow.getListenerInfo().get(0).getAddress()) - .onFailure(e -> log.warn("Started server, unable to determine listeing address/port.")) - .forEach(address -> log.info("Listening on http://{}:{}", address.getHostString(), address.getPort())); + .onFailure(e -> log.warn("Started server, unable to determine listeing address/port.")) + .forEach(address -> log.info("Listening on http://{}:{}", address.getHostString(), address.getPort())); } private void stop() { diff --git a/src/main/java/ch/fritteli/labyrinth/server/OutputType.java b/src/main/java/ch/fritteli/labyrinth/server/OutputType.java index e627968..29acf8c 100644 --- a/src/main/java/ch/fritteli/labyrinth/server/OutputType.java +++ b/src/main/java/ch/fritteli/labyrinth/server/OutputType.java @@ -17,18 +17,18 @@ import java.util.function.Function; public enum OutputType { TEXT_PLAIN("text/plain; charset=UTF-8", - labyrinth -> TextRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), - "txt", - false, - "t", - "text" + labyrinth -> TextRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), + "txt", + false, + "t", + "text" ), HTML("text/html", - labyrinth -> HTMLRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), - "html", - false, - "h", - "html" + labyrinth -> HTMLRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), + "html", + false, + "h", + "html" ), PDF("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "pdf", false, "p", "pdf"), PDFFILE("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "pdf", true, "f", "pdffile"), diff --git a/src/main/java/ch/fritteli/labyrinth/server/ServerConfig.java b/src/main/java/ch/fritteli/labyrinth/server/ServerConfig.java index fc161dd..2ac4426 100644 --- a/src/main/java/ch/fritteli/labyrinth/server/ServerConfig.java +++ b/src/main/java/ch/fritteli/labyrinth/server/ServerConfig.java @@ -29,7 +29,7 @@ public class ServerConfig { @NonNull private static InetAddress validateAddress(@Nullable final String address) { return Try.of(() -> InetAddress.getByName(address)) - .getOrElseThrow(cause -> new ConfigurationException("Invalid hostname/address: " + address, cause)); + .getOrElseThrow(cause -> new ConfigurationException("Invalid hostname/address: " + address, cause)); } private static int validatePort(final int port) { @@ -53,10 +53,10 @@ public class ServerConfig { return 0; } return Try.of(() -> Integer.valueOf(portString)) - .map(ServerConfig::validatePort) - .getOrElseThrow(cause -> new ConfigurationException( - "Failed to parse port specified in system property '" + SYSPROP_PORT + "': " + portString, - cause - )); + .map(ServerConfig::validatePort) + .getOrElseThrow(cause -> new ConfigurationException( + "Failed to parse port specified in system property '" + SYSPROP_PORT + "': " + portString, + cause + )); } } diff --git a/src/main/java/ch/fritteli/labyrinth/server/StaticResourcesFileHandler.java b/src/main/java/ch/fritteli/labyrinth/server/StaticResourcesFileHandler.java deleted file mode 100644 index e6911e8..0000000 --- a/src/main/java/ch/fritteli/labyrinth/server/StaticResourcesFileHandler.java +++ /dev/null @@ -1,109 +0,0 @@ -package ch.fritteli.labyrinth.server; - -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; -import org.apache.pdfbox.io.IOUtils; -import org.jetbrains.annotations.Nullable; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.ExecutorService; - -@Slf4j -public class StaticResourcesFileHandler implements HttpHandler { - - public static final String WEBASSETS_DIRECTORY = "webassets"; - private final ExecutorService executorService; - - public StaticResourcesFileHandler(final ExecutorService executorService) { - this.executorService = executorService; - log.debug("Created {}", this.getClass().getSimpleName()); - } - - @Override - public void handle(@NonNull final HttpExchange exchange) throws IOException { - this.executorService.submit(() -> { - try { - final URI requestURI = exchange.getRequestURI(); - final String path = requestURI.getPath(); - log.debug("Handling request to {}", path); - if ("/".equals(path)) { - redirect(exchange, "index.html"); - return; - } - if (!path.startsWith("/")) { - notFound(exchange, path); - return; - } - final String mimeType = getMimeType(path); - if (mimeType == null) { - notFound(exchange, path); - return; - } - final byte[] responseBytes = getBytes(path); - if (responseBytes.length == 0) { - notFound(exchange, path); - return; - } - log.debug( - "Serving {}{} with mimetype {}: {} bytes", - WEBASSETS_DIRECTORY, - path, - mimeType, - responseBytes.length - ); - exchange.getResponseHeaders().add("Content-type", mimeType); - exchange.sendResponseHeaders(200, 0); - exchange.getResponseBody().write(responseBytes); - exchange.getResponseBody().flush(); - } catch (Exception e) { - log.error("FSCK!", e); - } finally { - exchange.close(); - } - }); - } - - private static void redirect(@NonNull final HttpExchange exchange, @NonNull final String target) throws - IOException { - log.debug("Sending redirect to {}", target); - exchange.getResponseHeaders().add("Location", target); - exchange.sendResponseHeaders(302, -1); - } - - private static void notFound(@NonNull final HttpExchange exchange, @NonNull final String path) throws IOException { - log.debug("Resource '{}' not found, replying with HTTP 404", path); - exchange.getResponseHeaders().add("Content-type", "text/plain; charset=utf-8"); - exchange.sendResponseHeaders(404, 0); - exchange.getResponseBody().write("404 - Not found".getBytes(StandardCharsets.UTF_8)); - exchange.getResponseBody().flush(); - } - - @Nullable - private static String getMimeType(@NonNull final String path) { - if (path.endsWith(".html")) { - return "text/html"; - } - if (path.endsWith(".css")) { - return "text/css"; - } - return null; - } - - @NonNull - private static byte[] getBytes(@NonNull final String path) throws IOException { - final InputStream stream = StaticResourcesFileHandler.class.getClassLoader() - .getResourceAsStream(WEBASSETS_DIRECTORY + path); - if (stream == null) { - log.debug("Resource '{}' not found in classpath.", path); - return new byte[0]; - } - final byte[] response = IOUtils.toByteArray(stream); - log.debug("Sending reply; {} bytes", response.length); - return response; - } -} diff --git a/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java b/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java index c1a023f..8525918 100644 --- a/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java +++ b/src/main/java/ch/fritteli/labyrinth/server/handler/CreateHandler.java @@ -2,14 +2,18 @@ package ch.fritteli.labyrinth.server.handler; import ch.fritteli.labyrinth.generator.model.Labyrinth; import ch.fritteli.labyrinth.server.OutputType; -import ch.fritteli.labyrinth.server.handler.AbstractHttpHandler; -import ch.fritteli.labyrinth.server.undertow_playground.UndertowPlayground; import io.undertow.server.HttpServerExchange; import io.undertow.util.Headers; import io.undertow.util.HttpString; +import io.undertow.util.StatusCodes; +import io.vavr.Tuple; +import io.vavr.Tuple2; +import io.vavr.collection.*; import io.vavr.control.Option; +import io.vavr.control.Try; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.Nullable; import java.nio.ByteBuffer; import java.util.Deque; @@ -18,26 +22,69 @@ import java.util.Random; @Slf4j public class CreateHandler extends AbstractHttpHandler { - @Override - protected void handle(@NonNull final HttpServerExchange exchange) throws Exception { - final Map> queryParameters = exchange.getQueryParameters(); - final Option output = UndertowPlayground.getFirstOption(queryParameters, "output"); - final Option width = UndertowPlayground.getIntOption(queryParameters, "width"); - final Option height = UndertowPlayground.getIntOption(queryParameters, "height"); - final Option id = UndertowPlayground.getIntOption(queryParameters, "id"); + public static final String PATH_TEMPLATE = "/create/{output}"; - log.info("Output: {}", output); - log.info("Width: {}", width); - log.info("Height: {}", height); - log.info("Id: {}", id); - final Integer theId = id.getOrElse(() -> new Random().nextInt()); - final Labyrinth labyrinth = new Labyrinth(width.get(), height.get(), theId); - final OutputType outputType = output.flatMap(OutputType::ofString).get(); - final byte[] result = outputType.render(labyrinth); - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, outputType.getContentType()); - exchange.getResponseHeaders().put(HttpString.tryFromString("X-Labyrinth-ID"), String.valueOf(theId)); - exchange.getResponseHeaders().put(HttpString.tryFromString("X-Labyrinth-Width"), String.valueOf(width.get())); - exchange.getResponseHeaders().put(HttpString.tryFromString("X-Labyrinth-Height"), String.valueOf(height.get())); - exchange.getResponseSender().send(ByteBuffer.wrap(result)); + @Override + protected void handle(@NonNull final HttpServerExchange exchange) { + this.createLabyrinthFromRequestParameters(exchange.getQueryParameters()) + .onFailure(e -> { + exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); + exchange.setReasonPhrase(e.getMessage()); + }).forEach(tuple -> { + final OutputType outputType = tuple._1(); + final Labyrinth labyrinth = tuple._2(); + final byte[] bytes = outputType.render(labyrinth); + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, outputType.getContentType()); + exchange.getResponseHeaders().put(HttpString.tryFromString("X-Labyrinth-ID"), String.valueOf(labyrinth.getRandomSeed())); + exchange.getResponseHeaders().put(HttpString.tryFromString("X-Labyrinth-Width"), String.valueOf(labyrinth.getWidth())); + exchange.getResponseHeaders().put(HttpString.tryFromString("X-Labyrinth-Height"), String.valueOf(labyrinth.getHeight())); + exchange.getResponseSender().send(ByteBuffer.wrap(bytes)); + }); + } + + private @NonNull Try> createLabyrinthFromRequestParameters(final Map> queryParameters) { + return new ParametersToLabyrinthExtractor(queryParameters).createLabyrinth(); + } + + private enum RequestParameter { + WIDTH("w", "width"), HEIGHT("h", "height"), ID("i", "id"), OUTPUT("o", "output"); + private final Set names; + + RequestParameter(@NonNull final String... names) { + this.names = HashSet.of(names); + } + + static Option parseName(@Nullable final String name) { + if (name == null) { + return Option.none(); + } + return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase)); + } + } + + private static class ParametersToLabyrinthExtractor { + @NonNull + private final Multimap queryParameters; + + ParametersToLabyrinthExtractor(@NonNull final Map> queryParameters) { + this.queryParameters = HashMap.ofAll(queryParameters).foldLeft(HashMultimap.withSet().empty(), (map, tuple) -> { + final String key = tuple._1(); + return Stream.ofAll(tuple._2()).foldLeft(map, (m, value) -> m.put(key, value)); + }); + } + + @NonNull Try> createLabyrinth() { + final Option output = getParameterValues(RequestParameter.OUTPUT).foldLeft(Option.none(), (type, param) -> type.orElse(() -> OutputType.ofString(param))); + final Option width = getParameterValues(RequestParameter.WIDTH).foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Integer.parseInt(param)).toOption())); + final Option height = getParameterValues(RequestParameter.HEIGHT).foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Integer.parseInt(param)).toOption())); + final Option id = getParameterValues(RequestParameter.ID).foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Long.parseLong(param)).toOption())); + + return Try.of(() -> Tuple.of(output.get(), new Labyrinth(width.get(), height.get(), id.getOrElse(() -> new Random().nextLong())))); + } + + @NonNull + private Stream getParameterValues(@NonNull final RequestParameter parameter) { + return parameter.names.toStream().flatMap(name -> Stream.ofAll(this.queryParameters.getOrElse(name, List.empty()))); + } } } diff --git a/src/main/java/ch/fritteli/labyrinth/server/undertow_playground/LanyrinthRenderHandler.java b/src/main/java/ch/fritteli/labyrinth/server/handler/LanyrinthRenderHandler.java similarity index 90% rename from src/main/java/ch/fritteli/labyrinth/server/undertow_playground/LanyrinthRenderHandler.java rename to src/main/java/ch/fritteli/labyrinth/server/handler/LanyrinthRenderHandler.java index b176f09..e1b17fd 100644 --- a/src/main/java/ch/fritteli/labyrinth/server/undertow_playground/LanyrinthRenderHandler.java +++ b/src/main/java/ch/fritteli/labyrinth/server/handler/LanyrinthRenderHandler.java @@ -1,4 +1,4 @@ -package ch.fritteli.labyrinth.server.undertow_playground; +package ch.fritteli.labyrinth.server.handler; import io.undertow.server.HttpHandler; import io.undertow.server.HttpServerExchange; diff --git a/src/main/java/ch/fritteli/labyrinth/server/undertow_playground/UndertowPlayground.java b/src/main/java/ch/fritteli/labyrinth/server/undertow_playground/UndertowPlayground.java deleted file mode 100644 index 1caf698..0000000 --- a/src/main/java/ch/fritteli/labyrinth/server/undertow_playground/UndertowPlayground.java +++ /dev/null @@ -1,28 +0,0 @@ -package ch.fritteli.labyrinth.server.undertow_playground; - -import io.undertow.server.HttpHandler; -import io.undertow.server.HttpServerExchange; -import io.undertow.server.RoutingHandler; -import io.undertow.util.HeaderValues; -import io.undertow.util.Headers; -import io.vavr.control.Option; -import lombok.NonNull; -import lombok.extern.slf4j.Slf4j; - -import java.util.Deque; -import java.util.Map; - -@Slf4j -public class UndertowPlayground { - @NonNull - public static Option getIntOption(@NonNull final Map> queryParams, - @NonNull final String paramName) { - return getFirstOption(queryParams, paramName).toTry().map(Integer::parseInt).toOption(); - } - - @NonNull - public static Option getFirstOption(@NonNull final Map> queryParams, - @NonNull final String paramName) { - return Option.of(queryParams.get(paramName)).map(Deque::peek).flatMap(Option::of); - } -} diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index f484fc8..b2ea4fb 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -13,5 +13,5 @@ - +