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 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="Maven: ch.fritteli.labyrinth:labyrinth-generator:0.0.1" level="project" />
+    <orderEntry type="library" name="Maven: org.jetbrains:annotations:19.0.0" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.pdfbox:pdfbox:2.0.20" level="project" />
+    <orderEntry type="library" name="Maven: org.apache.pdfbox:fontbox:2.0.20" level="project" />
+    <orderEntry type="library" name="Maven: commons-logging:commons-logging:1.2" level="project" />
+    <orderEntry type="library" name="Maven: io.vavr:vavr:0.10.2" level="project" />
+    <orderEntry type="library" name="Maven: io.vavr:vavr-match:0.10.2" level="project" />
+    <orderEntry type="library" scope="PROVIDED" name="Maven: org.projectlombok:lombok:1.18.12" level="project" />
   </component>
 </module>
\ 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 @@
 	<groupId>ch.fritteli.labyrinth</groupId>
 	<artifactId>labyrinth-server</artifactId>
 	<version>0.0.1-SNAPSHOT</version>
+
+	<dependencies>
+		<dependency>
+			<groupId>ch.fritteli.labyrinth</groupId>
+			<artifactId>labyrinth-generator</artifactId>
+			<version>0.0.1</version>
+		</dependency>
+		<dependency>
+			<groupId>io.vavr</groupId>
+			<artifactId>vavr</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+		</dependency>
+	</dependencies>
+
 	<build>
 		<plugins>
 			<plugin>
@@ -20,7 +37,7 @@
 				<configuration>
 					<archive>
 						<manifest>
-							<mainClass>ch.fritteli.labyrinth.Main</mainClass>
+							<mainClass>ch.fritteli.labyrinth.server.Main</mainClass>
 						</manifest>
 					</archive>
 					<descriptorRefs>
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<RequestParameter, String> 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<Long> seedOption = requestParams.get(RequestParameter.ID).toTry().map(Long::valueOf).toOption();
+            final long seed;
+            final Option<OutputType> 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<LabyrinthServer> createListener() {
+        final String listenerPort = System.getProperty("fritteli.labyrinth.listenerport");
+        final Option<LabyrinthServer> 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> T getOrDefault(@NonNull final Option<String> input, @NonNull final Function<String, T> mapper, @Nullable final T defaultValue) {
+        return input.toTry().map(mapper).getOrElse(defaultValue);
+    }
+
+    @NonNull
+    private Map<RequestParameter, String> parseQueryString(@Nullable final String query) {
+        if (query == null) {
+            return HashMap.empty();
+        }
+        HashMap<RequestParameter, String> result = HashMap.empty();
+        final String[] parts = query.split("&");
+        for (final String part : parts) {
+            final int split = part.indexOf('=');
+            if (split == -1) {
+                final Try<RequestParameter> 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<RequestParameter> 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<String> names;
+
+        RequestParameter(@NonNull final String... names) {
+            this.names = HashSet.of(names);
+        }
+
+        static Option<RequestParameter> 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<String> names;
+        @NonNull
+        private final Function<Labyrinth, byte[]> render;
+
+        OutputType(@NonNull final String contentType, @NonNull final Function<Labyrinth, byte[]> render, @NonNull final String... names) {
+            this.contentType = contentType;
+            this.render = render;
+            this.names = List.of(names);
+        }
+
+        static Option<OutputType> 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."));
+    }
+}