feature/docker #2
8 changed files with 219 additions and 192 deletions
|
@ -4,7 +4,7 @@ name: default
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: test
|
- name: test
|
||||||
image: maven:3.6-jdk-11
|
image: maven:3.8-openjdk-18-slim
|
||||||
commands:
|
commands:
|
||||||
- mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
|
- mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
|
||||||
- mvn test -B
|
- mvn test -B
|
||||||
|
@ -14,13 +14,13 @@ steps:
|
||||||
- master
|
- master
|
||||||
- feature/*
|
- feature/*
|
||||||
- name: deploy
|
- name: deploy
|
||||||
image: maven:3.6-jdk-11
|
image: maven:3.8-openjdk-18-slim
|
||||||
environment:
|
environment:
|
||||||
REPO_TOKEN:
|
REPO_TOKEN:
|
||||||
from_secret: repo-token
|
from_secret: repo-token
|
||||||
commands:
|
commands:
|
||||||
- mvn -s maven-settings.xml deploy -DskipTests=true
|
- mvn -s maven-settings.xml deploy -DskipTests=true
|
||||||
trigger:
|
when:
|
||||||
branch:
|
branch:
|
||||||
- master
|
- master
|
||||||
event:
|
event:
|
||||||
|
|
5
docker/Dockerfile
Normal file
5
docker/Dockerfile
Normal file
|
@ -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
|
|
@ -1,31 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_11">
|
|
||||||
<output url="file://$MODULE_DIR$/target/classes" />
|
|
||||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
|
||||||
<content url="file://$MODULE_DIR$">
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
|
|
||||||
<sourceFolder url="file://$MODULE_DIR$/target/generated-sources/annotations" isTestSource="false" generated="true" />
|
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
|
||||||
</content>
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
<orderEntry type="module" module-name="labyrinth-generator" />
|
|
||||||
<orderEntry type="library" name="Maven: org.apache.pdfbox:pdfbox:2.0.25" level="project" />
|
|
||||||
<orderEntry type="library" name="Maven: org.apache.pdfbox:fontbox:2.0.25" 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" />
|
|
||||||
<orderEntry type="library" name="Maven: org.jetbrains:annotations:19.0.0" level="project" />
|
|
||||||
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.35" level="project" />
|
|
||||||
<orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.10" level="project" />
|
|
||||||
<orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.10" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-api:5.6.1" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="Maven: org.apiguardian:apiguardian-api:1.1.0" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" />
|
|
||||||
<orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.6.1" level="project" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
23
pom.xml
23
pom.xml
|
@ -15,8 +15,9 @@
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<logback.version>1.2.10</logback.version>
|
<logback.version>1.4.6</logback.version>
|
||||||
<slf4j.version>1.7.35</slf4j.version>
|
<lombok.version>1.18.26</lombok.version>
|
||||||
|
<slf4j.version>2.0.5</slf4j.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -57,23 +58,21 @@
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.2.4</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<archive>
|
<transformers>
|
||||||
<manifest>
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
<mainClass>ch.fritteli.labyrinth.server.Main</mainClass>
|
<mainClass>ch.fritteli.labyrinth.server.Main</mainClass>
|
||||||
</manifest>
|
</transformer>
|
||||||
</archive>
|
</transformers>
|
||||||
<descriptorRefs>
|
|
||||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
|
||||||
</descriptorRefs>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>make-assembly</id>
|
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>single</goal>
|
<goal>shade</goal>
|
||||||
</goals>
|
</goals>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
|
|
@ -24,7 +24,6 @@ import org.jetbrains.annotations.Nullable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.SynchronousQueue;
|
import java.util.concurrent.SynchronousQueue;
|
||||||
|
@ -38,32 +37,166 @@ public class LabyrinthServer {
|
||||||
@NonNull
|
@NonNull
|
||||||
private final HttpServer httpServer;
|
private final HttpServer httpServer;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final ExecutorService executorService = new ThreadPoolExecutor(
|
private final ExecutorService executorService = new ThreadPoolExecutor(0,
|
||||||
0,
|
|
||||||
1_000,
|
1_000,
|
||||||
5,
|
5,
|
||||||
TimeUnit.SECONDS,
|
TimeUnit.SECONDS,
|
||||||
new SynchronousQueue<>()
|
new SynchronousQueue<>()
|
||||||
);
|
);
|
||||||
|
|
||||||
public LabyrinthServer(@NonNull final ServerConfig config) throws IOException, URISyntaxException {
|
public static Option<LabyrinthServer> createAndStartServer() {
|
||||||
|
final Option<LabyrinthServer> 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 = HttpServer.create(new InetSocketAddress(config.getAddress(), config.getPort()), 5);
|
||||||
this.httpServer.createContext("/", new StaticResourcesFileHandler(this.executorService));
|
this.httpServer.createContext("/", new StaticResourcesFileHandler(this.executorService));
|
||||||
this.httpServer.createContext("/create", this::handleCreate);
|
this.httpServer.createContext("/create", this::handleCreate);
|
||||||
this.httpServer.createContext("/render", this::handleRender);
|
this.httpServer.createContext("/render", this::handleRender);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Option<LabyrinthServer> createAndStartServer() {
|
public void start() {
|
||||||
final Option<LabyrinthServer> serverOption = Try.of(ServerConfig::init)
|
Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper"));
|
||||||
.mapTry(LabyrinthServer::new)
|
this.httpServer.start();
|
||||||
.onFailure(cause -> log.error("Failed to create LabyrinthServer.", cause))
|
log.info("Listening on http://{}:{}",
|
||||||
.toOption();
|
this.httpServer.getAddress().getHostString(),
|
||||||
serverOption.forEach(LabyrinthServer::start);
|
this.httpServer.getAddress().getPort()
|
||||||
return serverOption;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T getOrDefault(@NonNull final Option<String> input, @NonNull final Function<String, T> mapper, @Nullable final T defaultValue) {
|
private void handleCreate(HttpExchange exchange) {
|
||||||
return input.toTry().map(mapper).getOrElse(defaultValue);
|
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<RequestParameter, String> 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<Long> idOption = requestParams.get(RequestParameter.ID)
|
||||||
|
.toTry()
|
||||||
|
.map(Long::valueOf)
|
||||||
|
.toOption();
|
||||||
|
final Option<OutputType> 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
|
@NonNull
|
||||||
|
@ -92,126 +225,16 @@ public class LabyrinthServer {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
private RequestParameter normalizeParameterName(final String paramName) {
|
private RequestParameter normalizeParameterName(final String paramName) {
|
||||||
return RequestParameter.parseName(paramName).get();
|
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<RequestParameter, String> 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<Long> idOption = requestParams.get(RequestParameter.ID).toTry().map(Long::valueOf).toOption();
|
|
||||||
final Option<OutputType> 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 {
|
private enum RequestParameter {
|
||||||
WIDTH("w", "width"),
|
WIDTH("w", "width"),
|
||||||
HEIGHT("h", "height"),
|
HEIGHT("h", "height"),
|
||||||
|
@ -227,16 +250,34 @@ public class LabyrinthServer {
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
return Option.none();
|
return Option.none();
|
||||||
}
|
}
|
||||||
return Stream.of(values())
|
return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase));
|
||||||
.find(param -> param.names.exists(name::equalsIgnoreCase));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum OutputType {
|
private enum OutputType {
|
||||||
TEXT_PLAIN("text/plain; charset=UTF-8", labyrinth -> TextRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), "t", "text"),
|
TEXT_PLAIN("text/plain; charset=UTF-8",
|
||||||
HTML("text/html", labyrinth -> HTMLRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), "h", "html"),
|
labyrinth -> TextRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8),
|
||||||
PDF("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "p", "pdf"),
|
"txt",
|
||||||
BINARY("application/octet-stream", SerializerDeserializer::serialize, "b", "binary");
|
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
|
@Getter
|
||||||
@NonNull
|
@NonNull
|
||||||
private final String contentType;
|
private final String contentType;
|
||||||
|
@ -244,10 +285,21 @@ public class LabyrinthServer {
|
||||||
private final List<String> names;
|
private final List<String> names;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Function<Labyrinth, byte[]> render;
|
private final Function<Labyrinth, byte[]> render;
|
||||||
|
@Getter
|
||||||
|
private final boolean attachment;
|
||||||
|
@Getter
|
||||||
|
@NonNull
|
||||||
|
private final String fileExtension;
|
||||||
|
|
||||||
OutputType(@NonNull final String contentType, @NonNull final Function<Labyrinth, byte[]> render, @NonNull final String... names) {
|
OutputType(@NonNull final String contentType,
|
||||||
|
@NonNull final Function<Labyrinth, byte[]> render,
|
||||||
|
@NonNull final String fileExtension,
|
||||||
|
final boolean attachment,
|
||||||
|
@NonNull final String... names) {
|
||||||
this.contentType = contentType;
|
this.contentType = contentType;
|
||||||
this.render = render;
|
this.render = render;
|
||||||
|
this.fileExtension = fileExtension;
|
||||||
|
this.attachment = attachment;
|
||||||
this.names = List.of(names);
|
this.names = List.of(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,9 +308,7 @@ public class LabyrinthServer {
|
||||||
return Option.none();
|
return Option.none();
|
||||||
}
|
}
|
||||||
final String nameLC = name.toLowerCase();
|
final String nameLC = name.toLowerCase();
|
||||||
return Stream.of(values())
|
return Stream.of(values()).find(param -> param.names.contains(nameLC));
|
||||||
.find(param -> param.names.contains(nameLC));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -21,6 +21,7 @@ public class StaticResourcesFileHandler implements HttpHandler {
|
||||||
|
|
||||||
public StaticResourcesFileHandler(final ExecutorService executorService) {
|
public StaticResourcesFileHandler(final ExecutorService executorService) {
|
||||||
this.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 {
|
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);
|
log.debug("Resource '{}' not found in classpath.", path);
|
||||||
return new byte[0];
|
return new byte[0];
|
||||||
}
|
}
|
||||||
return IOUtils.toByteArray(stream);
|
final byte[] response = IOUtils.toByteArray(stream);
|
||||||
|
log.debug("Sending reply; {} bytes", response.length);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -66,7 +69,7 @@ public class StaticResourcesFileHandler implements HttpHandler {
|
||||||
final String path = requestURI.getPath();
|
final String path = requestURI.getPath();
|
||||||
log.debug("Handling request to {}", path);
|
log.debug("Handling request to {}", path);
|
||||||
if ("/".equals(path)) {
|
if ("/".equals(path)) {
|
||||||
redirect(exchange, "/index.html");
|
redirect(exchange, "index.html");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!path.startsWith("/")) {
|
if (!path.startsWith("/")) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
|
<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook"/>
|
||||||
|
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<!-- encoders are by default assigned the type
|
<!-- encoders are by default assigned the type
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
<option label="HTML Document" value="html"></option>
|
<option label="HTML Document" value="html"></option>
|
||||||
<option label="Plain text" value="text"></option>
|
<option label="Plain text" value="text"></option>
|
||||||
<option label="PDF Document" value="pdf"></option>
|
<option label="PDF Document" value="pdf"></option>
|
||||||
|
<option label="PDF Document (Download)" value="pdffile"></option>
|
||||||
<option label="Binary" value="binary"></option>
|
<option label="Binary" value="binary"></option>
|
||||||
</select>
|
</select>
|
||||||
<label for="id">Seed (optional):</label><input id="id" name="id" type="number">
|
<label for="id">Seed (optional):</label><input id="id" name="id" type="number">
|
||||||
|
|
Loading…
Reference in a new issue