diff --git a/labyrinth-server.iml b/labyrinth-server.iml
index e34c156..fd372d5 100644
--- a/labyrinth-server.iml
+++ b/labyrinth-server.iml
@@ -12,5 +12,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 5967c56..28f3f51 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,6 +13,23 @@
ch.fritteli.labyrinth
labyrinth-server
0.0.1-SNAPSHOT
+
+
+
+ ch.fritteli.labyrinth
+ labyrinth-generator
+ 0.0.1
+
+
+ io.vavr
+ vavr
+
+
+ org.projectlombok
+ lombok
+
+
+
@@ -20,7 +37,7 @@
- ch.fritteli.labyrinth.Main
+ ch.fritteli.labyrinth.server.Main
diff --git a/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java b/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java
new file mode 100644
index 0000000..93fd8a1
--- /dev/null
+++ b/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java
@@ -0,0 +1,198 @@
+package ch.fritteli.labyrinth.server;
+
+import ch.fritteli.labyrinth.generator.model.Labyrinth;
+import ch.fritteli.labyrinth.generator.renderer.html.HTMLRenderer;
+import ch.fritteli.labyrinth.generator.renderer.pdf.PDFRenderer;
+import ch.fritteli.labyrinth.generator.renderer.text.TextRenderer;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpServer;
+import io.vavr.collection.HashMap;
+import io.vavr.collection.HashSet;
+import io.vavr.collection.List;
+import io.vavr.collection.Map;
+import io.vavr.collection.Set;
+import io.vavr.collection.Stream;
+import io.vavr.control.Option;
+import io.vavr.control.Try;
+import lombok.Getter;
+import lombok.NonNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.function.Function;
+
+public class LabyrinthServer {
+
+ private final HttpServer httpServer;
+
+ public LabyrinthServer(final int port) throws IOException {
+ this.httpServer = HttpServer.create(new InetSocketAddress(port), 0);
+ this.httpServer.createContext("/create", exchange -> {
+ final String requestMethod = exchange.getRequestMethod();
+ if (!requestMethod.equals("GET")) {
+ exchange.getResponseBody().close();
+ exchange.sendResponseHeaders(405, -1);
+ exchange.close();
+ return;
+ }
+ final Map requestParams = this.parseQueryString(exchange.getRequestURI().getQuery());
+ final int width = this.getOrDefault(requestParams.get(RequestParameter.WIDTH), Integer::valueOf, 1);
+ final int height = this.getOrDefault(requestParams.get(RequestParameter.HEIGHT), Integer::valueOf, 1);
+ final Option seedOption = requestParams.get(RequestParameter.ID).toTry().map(Long::valueOf).toOption();
+ final long seed;
+ final Option outputOption = requestParams.get(RequestParameter.OUTPUT).flatMap(OutputType::ofString);
+ final OutputType output;
+ final Headers responseHeaders = exchange.getResponseHeaders();
+ boolean needsRedirect = false;
+ if (seedOption.isEmpty()) {
+ needsRedirect = true;
+ seed = System.nanoTime();
+ } else {
+ seed = seedOption.get();
+ }
+ if (outputOption.isEmpty()) {
+ needsRedirect = true;
+ output = OutputType.HTML;
+ } else {
+ output = outputOption.get();
+ }
+ if (needsRedirect) {
+ responseHeaders.add("Location", "/create?width=" + width + "&height=" + height + "&output=" + output.toString() + "&id=" + seed);
+ exchange.sendResponseHeaders(302, -1);
+ exchange.close();
+ return;
+ }
+ responseHeaders.add("Content-type", output.getContentType());
+ exchange.sendResponseHeaders(200, 0);
+ final Labyrinth labyrinth = new Labyrinth(width, height, seed);
+ final byte[] render = output.render(labyrinth);
+ final OutputStream responseBody = exchange.getResponseBody();
+ responseBody.write(render);
+ responseBody.flush();
+ exchange.close();
+ });
+ }
+
+ public static Option createListener() {
+ final String listenerPort = System.getProperty("fritteli.labyrinth.listenerport");
+ final Option listenerOption = Option.of(listenerPort)
+ .toTry()
+ .map(Integer::valueOf)
+ .onFailure(cause -> System.err.println("Invalid port specified via system property 'fritteli.labyrinth.listenerport': "
+ + listenerPort
+ + ". Not starting webserver."))
+ .mapTry(LabyrinthServer::new)
+ .onFailure(cause -> System.err.println("Failed to create Listener: " + cause))
+ .toOption();
+ listenerOption.forEach(LabyrinthServer::start);
+ return listenerOption;
+ }
+
+ private T getOrDefault(@NonNull final Option input, @NonNull final Function mapper, @Nullable final T defaultValue) {
+ return input.toTry().map(mapper).getOrElse(defaultValue);
+ }
+
+ @NonNull
+ private Map parseQueryString(@Nullable final String query) {
+ if (query == null) {
+ return HashMap.empty();
+ }
+ HashMap result = HashMap.empty();
+ final String[] parts = query.split("&");
+ for (final String part : parts) {
+ final int split = part.indexOf('=');
+ if (split == -1) {
+ final Try tryKey = Try.of(() -> this.normalizeParameterName(part));
+ if (tryKey.isSuccess()) {
+ result = result.put(tryKey.get(), null);
+ }
+ } else {
+ final String key = part.substring(0, split);
+ final String value = part.substring(split + 1);
+ final Try tryKey = Try.of(() -> this.normalizeParameterName(key));
+ if (tryKey.isSuccess()) {
+ result = result.put(tryKey.get(), value);
+ }
+ }
+ }
+ return result;
+ }
+
+ private RequestParameter normalizeParameterName(final String paramName) {
+ return RequestParameter.parseName(paramName).get();
+ }
+
+ public void start() {
+ Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper"));
+ this.httpServer.start();
+ System.out.println("Listening on " + this.httpServer.getAddress());
+ }
+
+ public void stop() {
+ System.out.println("Stopping listener ...");
+ this.httpServer.stop(5);
+ System.out.println("Listener stopped.");
+ }
+
+ 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();
+ }
+ final String nameLC = name.toLowerCase();
+ return Stream.of(values())
+ .find(param -> param.names.contains(nameLC));
+ }
+ }
+
+ private enum OutputType {
+ TEXT_PLAIN("text/plain; charset=UTF-8", labyrinth -> TextRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), "t", "text"),
+ HTML("text/html", labyrinth -> HTMLRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), "h", "html"),
+ PDF("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "p", "pdf");
+ @Getter
+ @NonNull
+ private final String contentType;
+ @NonNull
+ private final List names;
+ @NonNull
+ private final Function render;
+
+ OutputType(@NonNull final String contentType, @NonNull final Function render, @NonNull final String... names) {
+ this.contentType = contentType;
+ this.render = render;
+ this.names = List.of(names);
+ }
+
+ static Option ofString(@Nullable final String name) {
+ if (name == null) {
+ return Option.none();
+ }
+ final String nameLC = name.toLowerCase();
+ return Stream.of(values())
+ .find(param -> param.names.contains(nameLC));
+
+ }
+
+ @Override
+ public String toString() {
+ return this.names.last();
+ }
+
+ byte[] render(@NonNull final Labyrinth labyrinth) {
+ return this.render.apply(labyrinth);
+ }
+ }
+}
diff --git a/src/main/java/ch/fritteli/labyrinth/server/Main.java b/src/main/java/ch/fritteli/labyrinth/server/Main.java
new file mode 100644
index 0000000..dd5e963
--- /dev/null
+++ b/src/main/java/ch/fritteli/labyrinth/server/Main.java
@@ -0,0 +1,8 @@
+package ch.fritteli.labyrinth.server;
+
+public class Main {
+ public static void main(String[] args) {
+ LabyrinthServer.createListener()
+ .onEmpty(() -> System.err.println("Failed to create server. Stopping."));
+ }
+}