diff --git a/.drone.yml b/.drone.yml
index e4c41bc..940a6dc 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -4,7 +4,7 @@ name: default
steps:
- name: test
- image: maven:3.6-jdk-11
+ image: maven:3.8-openjdk-18-slim
commands:
- mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
- mvn test -B
@@ -14,13 +14,13 @@ steps:
- master
- feature/*
- name: deploy
- image: maven:3.6-jdk-11
+ image: maven:3.8-openjdk-18-slim
environment:
REPO_TOKEN:
from_secret: repo-token
commands:
- mvn -s maven-settings.xml deploy -DskipTests=true
- trigger:
+ when:
branch:
- master
event:
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 0000000..abccd12
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,5 @@
+FROM openjdk:11
+
+COPY target/labyrinth-server-*.jar /app/app.jar
+
+CMD java -Dfritteli.labyrinth.server.host=0.0.0.0 -Dfritteli.labyrinth.server.port=80 -jar /app/app.jar
diff --git a/labyrinth-server.iml b/labyrinth-server.iml
deleted file mode 100644
index ee5c0f7..0000000
--- a/labyrinth-server.iml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 9be4803..3518f8b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,8 +15,9 @@
0.0.1-SNAPSHOT
- 1.2.10
- 1.7.35
+ 1.4.6
+ 1.18.26
+ 2.0.5
@@ -57,23 +58,21 @@
- maven-assembly-plugin
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.2.4
-
-
+
+
ch.fritteli.labyrinth.server.Main
-
-
-
- jar-with-dependencies
-
+
+
- make-assembly
package
- single
+ shade
diff --git a/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java b/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java
index a3a3790..689d9bc 100644
--- a/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java
+++ b/src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java
@@ -24,7 +24,6 @@ import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
-import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
@@ -38,32 +37,166 @@ public class LabyrinthServer {
@NonNull
private final HttpServer httpServer;
@NonNull
- private final ExecutorService executorService = new ThreadPoolExecutor(
- 0,
- 1_000,
- 5,
- TimeUnit.SECONDS,
- new SynchronousQueue<>()
+ private final ExecutorService executorService = new ThreadPoolExecutor(0,
+ 1_000,
+ 5,
+ TimeUnit.SECONDS,
+ new SynchronousQueue<>()
);
- public LabyrinthServer(@NonNull final ServerConfig config) throws IOException, URISyntaxException {
+ public static Option createAndStartServer() {
+ final Option serverOption = Try.of(ServerConfig::init)
+ .mapTry(LabyrinthServer::new)
+ .onFailure(cause -> log.error(
+ "Failed to create LabyrinthServer.",
+ cause
+ ))
+ .toOption();
+ serverOption.forEach(LabyrinthServer::start);
+ return serverOption;
+ }
+
+ public LabyrinthServer(@NonNull final ServerConfig config) throws IOException {
this.httpServer = HttpServer.create(new InetSocketAddress(config.getAddress(), config.getPort()), 5);
this.httpServer.createContext("/", new StaticResourcesFileHandler(this.executorService));
this.httpServer.createContext("/create", this::handleCreate);
this.httpServer.createContext("/render", this::handleRender);
}
- public static Option createAndStartServer() {
- final Option serverOption = Try.of(ServerConfig::init)
- .mapTry(LabyrinthServer::new)
- .onFailure(cause -> log.error("Failed to create LabyrinthServer.", cause))
- .toOption();
- serverOption.forEach(LabyrinthServer::start);
- return serverOption;
+ public void start() {
+ Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper"));
+ this.httpServer.start();
+ log.info("Listening on http://{}:{}",
+ this.httpServer.getAddress().getHostString(),
+ this.httpServer.getAddress().getPort()
+ );
}
- private T getOrDefault(@NonNull final Option input, @NonNull final Function mapper, @Nullable final T defaultValue) {
- return input.toTry().map(mapper).getOrElse(defaultValue);
+ private void handleCreate(HttpExchange exchange) {
+ this.executorService.submit(() -> {
+ log.debug("Handling request to {}", exchange.getRequestURI());
+ try {
+ final String requestMethod = exchange.getRequestMethod();
+ if (!requestMethod.equals("GET")) {
+ exchange.getResponseBody().close();
+ exchange.sendResponseHeaders(405, -1);
+ return;
+ }
+ final Map requestParams = this.parseQueryString(exchange.getRequestURI()
+ .getQuery());
+ final int width = this.getOrDefault(requestParams.get(RequestParameter.WIDTH), Integer::valueOf, 5);
+ final int height = this.getOrDefault(requestParams.get(RequestParameter.HEIGHT), Integer::valueOf, 7);
+ final Option idOption = requestParams.get(RequestParameter.ID)
+ .toTry()
+ .map(Long::valueOf)
+ .toOption();
+ final Option outputOption = requestParams.get(RequestParameter.OUTPUT)
+ .flatMap(OutputType::ofString);
+ final Headers responseHeaders = exchange.getResponseHeaders();
+ final AtomicBoolean needsRedirect = new AtomicBoolean(false);
+ final long id = idOption.onEmpty(() -> needsRedirect.set(true)).getOrElse(System::nanoTime);
+ final OutputType output = outputOption.onEmpty(() -> needsRedirect.set(true))
+ .getOrElse(OutputType.HTML);
+ if (needsRedirect.get()) {
+ responseHeaders.add("Location",
+ "create?width=" + width + "&height=" + height + "&output=" + output.toString() +
+ "&id=" + id
+ );
+ exchange.sendResponseHeaders(302, -1);
+ return;
+ }
+ final Labyrinth labyrinth = new Labyrinth(width, height, id);
+ final byte[] render;
+ try {
+ render = output.render(labyrinth);
+ } catch (Exception e) {
+ responseHeaders.add("Content-type", "text/plain; charset=UTF-8");
+ exchange.sendResponseHeaders(500, 0);
+ final OutputStream responseBody = exchange.getResponseBody();
+ responseBody.write(("Error: " + e).getBytes(StandardCharsets.UTF_8));
+ responseBody.flush();
+ return;
+ }
+ responseHeaders.add("Content-type", output.getContentType());
+ if (output.isAttachment()) {
+ responseHeaders.add(
+ "Content-disposition",
+ String.format("attachment; filename=\"labyrinth-%dx%d-%d.%s\"",
+ width,
+ height,
+ id,
+ output.getFileExtension()
+ )
+ );
+ }
+ exchange.sendResponseHeaders(200, 0);
+ final OutputStream responseBody = exchange.getResponseBody();
+ responseBody.write(render);
+ responseBody.flush();
+ } catch (Exception e) {
+ log.error("FSCK!", e);
+ } finally {
+ exchange.close();
+ }
+ });
+ }
+
+ private void handleRender(final HttpExchange exchange) {
+ this.executorService.submit(() -> {
+ try {
+ log.debug("Handling request to {}", exchange.getRequestURI());
+ final String requestMethod = exchange.getRequestMethod();
+ if (!requestMethod.equals("POST")) {
+ exchange.getResponseBody().close();
+ exchange.sendResponseHeaders(405, -1);
+ return;
+ }
+ final byte[] bytes = exchange.getRequestBody().readAllBytes();
+
+ final Labyrinth labyrinth = SerializerDeserializer.deserialize(bytes);
+
+ final OutputType output = exchange.getRequestHeaders()
+ .get("Accept")
+ .contains(OutputType.HTML.getContentType()) ?
+ OutputType.HTML :
+ OutputType.TEXT_PLAIN;
+ final byte[] render;
+ final Headers responseHeaders = exchange.getResponseHeaders();
+ try {
+ render = output.render(labyrinth);
+ } catch (Exception e) {
+ responseHeaders.add("Content-type", "text/plain; charset=UTF-8");
+ exchange.sendResponseHeaders(500, 0);
+ final OutputStream responseBody = exchange.getResponseBody();
+ responseBody.write(("Error: " + e).getBytes(StandardCharsets.UTF_8));
+ responseBody.flush();
+ return;
+ }
+ responseHeaders.add("Content-type", output.getContentType());
+ exchange.sendResponseHeaders(200, 0);
+ final OutputStream responseBody = exchange.getResponseBody();
+ responseBody.write(render);
+ responseBody.flush();
+ } catch (Exception e) {
+ log.error("FSCK!", e);
+ } finally {
+ exchange.close();
+ }
+ });
+ }
+
+ public void stop() {
+ log.info("Stopping server ...");
+ this.httpServer.stop(5);
+ this.executorService.shutdown();
+ try {
+ if (!this.executorService.awaitTermination(5, TimeUnit.SECONDS)) {
+ log.warn("Timeout occurred while awaiting termination of executor service");
+ }
+ } catch (final InterruptedException e) {
+ log.error("Failed to await termination of executor service", e);
+ }
+ log.info("Server stopped.");
}
@NonNull
@@ -92,126 +225,16 @@ public class LabyrinthServer {
return result;
}
+ private T getOrDefault(@NonNull final Option input,
+ @NonNull final Function mapper,
+ @Nullable final T defaultValue) {
+ return input.toTry().map(mapper).getOrElse(defaultValue);
+ }
+
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();
- log.info("Listening on http://{}:{}", this.httpServer.getAddress().getHostString(), this.httpServer.getAddress().getPort());
- }
-
- public void stop() {
- log.info("Stopping server ...");
- this.httpServer.stop(5);
- this.executorService.shutdown();
- try {
- if (!this.executorService.awaitTermination(5, TimeUnit.SECONDS)) {
- log.warn("Timeout occurred while awaiting termination of executor service");
- }
- } catch (final InterruptedException e) {
- log.error("Failed to await termination of executor service", e);
- }
- log.info("Server stopped.");
- }
-
- private void handleCreate(HttpExchange exchange) throws IOException {
- this.executorService.submit(() -> {
- log.debug("Handling request to {}", exchange.getRequestURI());
- try {
- final String requestMethod = exchange.getRequestMethod();
- if (!requestMethod.equals("GET")) {
- exchange.getResponseBody().close();
- exchange.sendResponseHeaders(405, -1);
- return;
- }
- final Map requestParams = this.parseQueryString(exchange.getRequestURI().getQuery());
- final int width = this.getOrDefault(requestParams.get(RequestParameter.WIDTH), Integer::valueOf, 5);
- final int height = this.getOrDefault(requestParams.get(RequestParameter.HEIGHT), Integer::valueOf, 5);
- final Option idOption = requestParams.get(RequestParameter.ID).toTry().map(Long::valueOf).toOption();
- final Option outputOption = requestParams.get(RequestParameter.OUTPUT).flatMap(OutputType::ofString);
- final Headers responseHeaders = exchange.getResponseHeaders();
- final AtomicBoolean needsRedirect = new AtomicBoolean(false);
- final long id = idOption.onEmpty(() -> needsRedirect.set(true)).getOrElse(System::nanoTime);
- final OutputType output = outputOption.onEmpty(() -> needsRedirect.set(true)).getOrElse(OutputType.HTML);
- if (needsRedirect.get()) {
- responseHeaders.add("Location", "/create?width=" + width + "&height=" + height + "&output=" + output.toString() + "&id=" + id);
- exchange.sendResponseHeaders(302, -1);
- return;
- }
- final Labyrinth labyrinth = new Labyrinth(width, height, id);
- final byte[] render;
- try {
- render = output.render(labyrinth);
- } catch (Exception e) {
- responseHeaders.add("Content-type", "text/plain; charset=UTF-8");
- exchange.sendResponseHeaders(500, 0);
- final OutputStream responseBody = exchange.getResponseBody();
- responseBody.write(("Error: " + e).getBytes(StandardCharsets.UTF_8));
- responseBody.flush();
- return;
- }
- responseHeaders.add("Content-type", output.getContentType());
- if (output.equals(OutputType.BINARY)) {
- responseHeaders.add("Content-disposition", "attachment; filename=\"labyrinth-" + width + "x" + height + "-" + id + ".laby\"");
- }
- exchange.sendResponseHeaders(200, 0);
- final OutputStream responseBody = exchange.getResponseBody();
- responseBody.write(render);
- responseBody.flush();
- } catch (Exception e) {
- log.error("FSCK!", e);
- } finally {
- exchange.close();
- }
- });
- }
-
- private void handleRender(final HttpExchange exchange) throws IOException {
- this.executorService.submit(() -> {
- try {
- log.debug("Handling request to {}", exchange.getRequestURI());
- final String requestMethod = exchange.getRequestMethod();
- if (!requestMethod.equals("POST")) {
- exchange.getResponseBody().close();
- exchange.sendResponseHeaders(405, -1);
- return;
- }
- final byte[] bytes = exchange.getRequestBody().readAllBytes();
-
- final Labyrinth labyrinth = SerializerDeserializer.deserialize(bytes);
-
- final OutputType output = exchange.getRequestHeaders()
- .get("Accept")
- .contains("text/html") ?
- OutputType.HTML :
- OutputType.TEXT_PLAIN;
- final byte[] render;
- final Headers responseHeaders = exchange.getResponseHeaders();
- try {
- render = output.render(labyrinth);
- } catch (Exception e) {
- responseHeaders.add("Content-type", "text/plain; charset=UTF-8");
- exchange.sendResponseHeaders(500, 0);
- final OutputStream responseBody = exchange.getResponseBody();
- responseBody.write(("Error: " + e).getBytes(StandardCharsets.UTF_8));
- responseBody.flush();
- return;
- }
- responseHeaders.add("Content-type", output.getContentType());
- exchange.sendResponseHeaders(200, 0);
- final OutputStream responseBody = exchange.getResponseBody();
- responseBody.write(render);
- responseBody.flush();
- } catch (Exception e) {
- log.error("FSCK!", e);
- } finally {
- exchange.close();
- }
- });
- }
-
private enum RequestParameter {
WIDTH("w", "width"),
HEIGHT("h", "height"),
@@ -227,16 +250,34 @@ public class LabyrinthServer {
if (name == null) {
return Option.none();
}
- return Stream.of(values())
- .find(param -> param.names.exists(name::equalsIgnoreCase));
+ return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase));
}
}
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"),
- BINARY("application/octet-stream", SerializerDeserializer::serialize, "b", "binary");
+ TEXT_PLAIN("text/plain; charset=UTF-8",
+ 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"
+ ),
+ PDF("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "pdf", false, "p", "pdf"),
+ PDFFILE("application/pdf",
+ labyrinth -> PDFRenderer.newInstance().render(labyrinth),
+ "pdf",
+ true,
+ "f",
+ "pdffile"
+ ),
+ BINARY("application/octet-stream", SerializerDeserializer::serialize, "laby", true, "b", "binary");
@Getter
@NonNull
private final String contentType;
@@ -244,10 +285,21 @@ public class LabyrinthServer {
private final List names;
@NonNull
private final Function render;
+ @Getter
+ private final boolean attachment;
+ @Getter
+ @NonNull
+ private final String fileExtension;
- OutputType(@NonNull final String contentType, @NonNull final Function render, @NonNull final String... names) {
+ OutputType(@NonNull final String contentType,
+ @NonNull final Function render,
+ @NonNull final String fileExtension,
+ final boolean attachment,
+ @NonNull final String... names) {
this.contentType = contentType;
this.render = render;
+ this.fileExtension = fileExtension;
+ this.attachment = attachment;
this.names = List.of(names);
}
@@ -256,9 +308,7 @@ public class LabyrinthServer {
return Option.none();
}
final String nameLC = name.toLowerCase();
- return Stream.of(values())
- .find(param -> param.names.contains(nameLC));
-
+ return Stream.of(values()).find(param -> param.names.contains(nameLC));
}
@Override
diff --git a/src/main/java/ch/fritteli/labyrinth/server/StaticResourcesFileHandler.java b/src/main/java/ch/fritteli/labyrinth/server/StaticResourcesFileHandler.java
index d15f1fd..d1cabfc 100644
--- a/src/main/java/ch/fritteli/labyrinth/server/StaticResourcesFileHandler.java
+++ b/src/main/java/ch/fritteli/labyrinth/server/StaticResourcesFileHandler.java
@@ -21,6 +21,7 @@ public class StaticResourcesFileHandler implements HttpHandler {
public StaticResourcesFileHandler(final ExecutorService executorService) {
this.executorService = executorService;
+ log.debug("Created {}", this.getClass().getSimpleName());
}
private static void redirect(@NonNull final HttpExchange exchange, @NonNull final String target) throws IOException {
@@ -44,7 +45,9 @@ public class StaticResourcesFileHandler implements HttpHandler {
log.debug("Resource '{}' not found in classpath.", path);
return new byte[0];
}
- return IOUtils.toByteArray(stream);
+ final byte[] response = IOUtils.toByteArray(stream);
+ log.debug("Sending reply; {} bytes", response.length);
+ return response;
}
@Nullable
@@ -66,7 +69,7 @@ public class StaticResourcesFileHandler implements HttpHandler {
final String path = requestURI.getPath();
log.debug("Handling request to {}", path);
if ("/".equals(path)) {
- redirect(exchange, "/index.html");
+ redirect(exchange, "index.html");
return;
}
if (!path.startsWith("/")) {
diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml
index 08ae395..4bba3c1 100644
--- a/src/main/resources/logback.xml
+++ b/src/main/resources/logback.xml
@@ -1,6 +1,6 @@
-
+