Add simple Server.
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Manuel Friedli 2020-10-06 02:09:03 +02:00
parent ad320f0d7f
commit 41995979e1
4 changed files with 232 additions and 1 deletions

View file

@ -12,5 +12,13 @@
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <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> </component>
</module> </module>

19
pom.xml
View file

@ -13,6 +13,23 @@
<groupId>ch.fritteli.labyrinth</groupId> <groupId>ch.fritteli.labyrinth</groupId>
<artifactId>labyrinth-server</artifactId> <artifactId>labyrinth-server</artifactId>
<version>0.0.1-SNAPSHOT</version> <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> <build>
<plugins> <plugins>
<plugin> <plugin>
@ -20,7 +37,7 @@
<configuration> <configuration>
<archive> <archive>
<manifest> <manifest>
<mainClass>ch.fritteli.labyrinth.Main</mainClass> <mainClass>ch.fritteli.labyrinth.server.Main</mainClass>
</manifest> </manifest>
</archive> </archive>
<descriptorRefs> <descriptorRefs>

View file

@ -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);
}
}
}

View file

@ -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."));
}
}