Compare commits
No commits in common. "1e3e01be23f17a0b0f61892cc2349569fe3f0ecb" and "a613a92adb71edbf24cc0d99a79c3344929c669e" have entirely different histories.
1e3e01be23
...
a613a92adb
10 changed files with 11 additions and 233 deletions
|
@ -4,9 +4,4 @@ COPY target/maze-server-*.jar /app/
|
||||||
RUN rm /app/*-sources.jar
|
RUN rm /app/*-sources.jar
|
||||||
RUN mv /app/*.jar /app/app.jar
|
RUN mv /app/*.jar /app/app.jar
|
||||||
|
|
||||||
CMD java \
|
CMD java -Dfritteli.maze.server.host=0.0.0.0 -Dfritteli.maze.server.port=80 -jar /app/app.jar
|
||||||
-Dfritteli.maze.server.host=0.0.0.0 \
|
|
||||||
-Dfritteli.maze.server.port=80 \
|
|
||||||
-Dfritteli.maze.maxheight=256 \
|
|
||||||
-Dfritteli.maze.maxwidth=256 \
|
|
||||||
-jar /app/app.jar
|
|
||||||
|
|
2
pom.xml
2
pom.xml
|
@ -56,7 +56,7 @@
|
||||||
</distributionManagement>
|
</distributionManagement>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<maze-generator.version>0.3.0</maze-generator.version>
|
<maze-generator.version>0.2.1</maze-generator.version>
|
||||||
<maven-site-plugin.version>4.0.0-M8</maven-site-plugin.version>
|
<maven-site-plugin.version>4.0.0-M8</maven-site-plugin.version>
|
||||||
<undertow.version>2.3.18.Final</undertow.version>
|
<undertow.version>2.3.18.Final</undertow.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
package ch.fritteli.maze.server;
|
|
||||||
|
|
||||||
import ch.fritteli.maze.generator.algorithm.MazeGeneratorAlgorithm;
|
|
||||||
import ch.fritteli.maze.generator.algorithm.RandomDepthFirst;
|
|
||||||
import ch.fritteli.maze.generator.algorithm.wilson.Wilson;
|
|
||||||
import ch.fritteli.maze.generator.model.Maze;
|
|
||||||
import io.vavr.collection.List;
|
|
||||||
import io.vavr.collection.Stream;
|
|
||||||
import io.vavr.control.Option;
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public enum Algorithm {
|
|
||||||
RANDOM_DEPTH_FIRST(RandomDepthFirst::new, "random", "random-depth-first"),
|
|
||||||
WILSON(Wilson::new, "wilson");
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private final Function<Maze, MazeGeneratorAlgorithm> creator;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@NotNull
|
|
||||||
private final List<String> names;
|
|
||||||
|
|
||||||
Algorithm(@NotNull final Function<Maze, MazeGeneratorAlgorithm> creator,
|
|
||||||
@NotNull final String... names) {
|
|
||||||
this.creator = creator;
|
|
||||||
this.names = List.of(names);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static Option<Algorithm> ofString(@Nullable final String name) {
|
|
||||||
return Option.of(name)
|
|
||||||
.map(String::toLowerCase)
|
|
||||||
.flatMap(nameLC -> Stream.of(values())
|
|
||||||
.find(algorithm -> algorithm.getNames().contains(nameLC)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public MazeGeneratorAlgorithm createAlgorithm(@NotNull final Maze maze) {
|
|
||||||
return this.creator.apply(maze);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,8 +3,6 @@ package ch.fritteli.maze.server;
|
||||||
import ch.fritteli.maze.server.handler.CreateHandler;
|
import ch.fritteli.maze.server.handler.CreateHandler;
|
||||||
import ch.fritteli.maze.server.handler.RenderV1Handler;
|
import ch.fritteli.maze.server.handler.RenderV1Handler;
|
||||||
import ch.fritteli.maze.server.handler.RenderV2Handler;
|
import ch.fritteli.maze.server.handler.RenderV2Handler;
|
||||||
import ch.fritteli.maze.server.handler.RenderV3Handler;
|
|
||||||
import ch.fritteli.maze.server.handler.RenderVxHandler;
|
|
||||||
import io.undertow.Undertow;
|
import io.undertow.Undertow;
|
||||||
import io.undertow.server.RoutingHandler;
|
import io.undertow.server.RoutingHandler;
|
||||||
import io.vavr.control.Try;
|
import io.vavr.control.Try;
|
||||||
|
@ -26,9 +24,7 @@ public class MazeServer {
|
||||||
final RoutingHandler routingHandler = new RoutingHandler()
|
final RoutingHandler routingHandler = new RoutingHandler()
|
||||||
.get(CreateHandler.PATH_TEMPLATE, new CreateHandler(config.maxMazeHeight(), config.maxMazeWidth()))
|
.get(CreateHandler.PATH_TEMPLATE, new CreateHandler(config.maxMazeHeight(), config.maxMazeWidth()))
|
||||||
.post(RenderV1Handler.PATH_TEMPLATE, new RenderV1Handler())
|
.post(RenderV1Handler.PATH_TEMPLATE, new RenderV1Handler())
|
||||||
.post(RenderV2Handler.PATH_TEMPLATE, new RenderV2Handler())
|
.post(RenderV2Handler.PATH_TEMPLATE, new RenderV2Handler());
|
||||||
.post(RenderV3Handler.PATH_TEMPLATE, new RenderV3Handler())
|
|
||||||
.post(RenderVxHandler.PATH_TEMPLATE, new RenderVxHandler());
|
|
||||||
|
|
||||||
this.undertow = Undertow.builder()
|
this.undertow = Undertow.builder()
|
||||||
.addHttpListener(port, hostAddress)
|
.addHttpListener(port, hostAddress)
|
||||||
|
|
|
@ -7,7 +7,6 @@ import ch.fritteli.maze.generator.renderer.pdf.PDFRenderer;
|
||||||
import ch.fritteli.maze.generator.renderer.text.TextRenderer;
|
import ch.fritteli.maze.generator.renderer.text.TextRenderer;
|
||||||
import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1;
|
import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1;
|
||||||
import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2;
|
import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2;
|
||||||
import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3;
|
|
||||||
import io.vavr.collection.List;
|
import io.vavr.collection.List;
|
||||||
import io.vavr.collection.Stream;
|
import io.vavr.collection.Stream;
|
||||||
import io.vavr.control.Option;
|
import io.vavr.control.Option;
|
||||||
|
@ -24,8 +23,7 @@ public enum OutputType {
|
||||||
maze -> TextRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8),
|
maze -> TextRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8),
|
||||||
false,
|
false,
|
||||||
"t",
|
"t",
|
||||||
"text",
|
"text"),
|
||||||
"txt"),
|
|
||||||
HTML("text/html",
|
HTML("text/html",
|
||||||
"html",
|
"html",
|
||||||
maze -> HTMLRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8),
|
maze -> HTMLRenderer.newInstance().render(maze).getBytes(StandardCharsets.UTF_8),
|
||||||
|
@ -67,13 +65,7 @@ public enum OutputType {
|
||||||
SerializerDeserializerV2::serialize,
|
SerializerDeserializerV2::serialize,
|
||||||
true,
|
true,
|
||||||
"v",
|
"v",
|
||||||
"binaryv2"),
|
"binaryv2");
|
||||||
BINARY_V3("application/octet-stream",
|
|
||||||
"maz3",
|
|
||||||
SerializerDeserializerV3::serialize,
|
|
||||||
true,
|
|
||||||
"3",
|
|
||||||
"binaryv3");
|
|
||||||
@Getter
|
@Getter
|
||||||
@NotNull
|
@NotNull
|
||||||
private final String contentType;
|
private final String contentType;
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class CreateHandler extends AbstractHttpHandler {
|
||||||
.put(HttpString.tryFromString("X-Maze-ID"), String.valueOf(maze.getRandomSeed()))
|
.put(HttpString.tryFromString("X-Maze-ID"), String.valueOf(maze.getRandomSeed()))
|
||||||
.put(HttpString.tryFromString("X-Maze-Width"), String.valueOf(maze.getWidth()))
|
.put(HttpString.tryFromString("X-Maze-Width"), String.valueOf(maze.getWidth()))
|
||||||
.put(HttpString.tryFromString("X-Maze-Height"), String.valueOf(maze.getHeight()))
|
.put(HttpString.tryFromString("X-Maze-Height"), String.valueOf(maze.getHeight()))
|
||||||
.put(HttpString.tryFromString("X-Maze-Algorithm"), maze.getAlgorithm())
|
.put(HttpString.tryFromString("X-Maze-Algorithm"), generatedMaze.generatorName())
|
||||||
.put(HttpString.tryFromString("X-Maze-Generation-Duration-millis"), String.valueOf(durationMillis));
|
.put(HttpString.tryFromString("X-Maze-Generation-Duration-millis"), String.valueOf(durationMillis));
|
||||||
if (outputType.isAttachment()) {
|
if (outputType.isAttachment()) {
|
||||||
exchange.getResponseHeaders()
|
exchange.getResponseHeaders()
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package ch.fritteli.maze.server.handler;
|
package ch.fritteli.maze.server.handler;
|
||||||
|
|
||||||
import ch.fritteli.maze.generator.algorithm.MazeGeneratorAlgorithm;
|
import ch.fritteli.maze.generator.algorithm.RandomDepthFirst;
|
||||||
import ch.fritteli.maze.generator.model.Maze;
|
import ch.fritteli.maze.generator.model.Maze;
|
||||||
import ch.fritteli.maze.generator.model.Position;
|
import ch.fritteli.maze.generator.model.Position;
|
||||||
import ch.fritteli.maze.server.Algorithm;
|
|
||||||
import ch.fritteli.maze.server.InvalidRequestParameterException;
|
import ch.fritteli.maze.server.InvalidRequestParameterException;
|
||||||
import ch.fritteli.maze.server.OutputType;
|
import ch.fritteli.maze.server.OutputType;
|
||||||
import io.vavr.collection.Stream;
|
import io.vavr.collection.Stream;
|
||||||
|
@ -34,7 +33,6 @@ class ParametersToMazeExtractor {
|
||||||
final Option<Long> id = getParameterValue(RequestParameter.ID);
|
final Option<Long> id = getParameterValue(RequestParameter.ID);
|
||||||
final Option<Position> start = getParameterValue(RequestParameter.START);
|
final Option<Position> start = getParameterValue(RequestParameter.START);
|
||||||
final Option<Position> end = getParameterValue(RequestParameter.END);
|
final Option<Position> end = getParameterValue(RequestParameter.END);
|
||||||
final Option<Algorithm> algorithm = getParameterValue(RequestParameter.ALGORITHM);
|
|
||||||
|
|
||||||
if (output.isEmpty()) {
|
if (output.isEmpty()) {
|
||||||
return Try.failure(new InvalidRequestParameterException("Path parameter %s is required and must be one of: %s".formatted(
|
return Try.failure(new InvalidRequestParameterException("Path parameter %s is required and must be one of: %s".formatted(
|
||||||
|
@ -79,11 +77,8 @@ class ParametersToMazeExtractor {
|
||||||
} else {
|
} else {
|
||||||
maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong()));
|
maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong()));
|
||||||
}
|
}
|
||||||
|
new RandomDepthFirst(maze).run();
|
||||||
final MazeGeneratorAlgorithm generator = algorithm.getOrElse(Algorithm.WILSON)
|
return new GeneratedMaze(maze, output.get(), RandomDepthFirst.class.getSimpleName());
|
||||||
.createAlgorithm(maze);
|
|
||||||
generator.run();
|
|
||||||
return new GeneratedMaze(maze, output.get());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +87,6 @@ class ParametersToMazeExtractor {
|
||||||
return parameter.getParameterValue(this.queryParameters);
|
return parameter.getParameterValue(this.queryParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
public record GeneratedMaze(@NotNull Maze maze, @NotNull OutputType outputType) {
|
public record GeneratedMaze(@NotNull Maze maze, @NotNull OutputType outputType, @NotNull String generatorName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
package ch.fritteli.maze.server.handler;
|
|
||||||
|
|
||||||
import ch.fritteli.maze.generator.model.Maze;
|
|
||||||
import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3;
|
|
||||||
import ch.fritteli.maze.server.OutputType;
|
|
||||||
import io.undertow.server.HttpServerExchange;
|
|
||||||
import io.undertow.util.HeaderValues;
|
|
||||||
import io.undertow.util.Headers;
|
|
||||||
import io.undertow.util.StatusCodes;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class RenderV3Handler extends AbstractHttpHandler {
|
|
||||||
|
|
||||||
public static final String PATH_TEMPLATE = "/render/v3/{output}";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handle(@NotNull final HttpServerExchange exchange) {
|
|
||||||
log.debug("Handling render request");
|
|
||||||
|
|
||||||
if (exchange.isInIoThread()) {
|
|
||||||
exchange.dispatch(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
exchange.getRequestReceiver().receiveFullBytes((httpServerExchange, bytes) -> {
|
|
||||||
final OutputType output = this.getOutputType(httpServerExchange);
|
|
||||||
final byte[] render;
|
|
||||||
try {
|
|
||||||
final Maze maze = SerializerDeserializerV3.deserialize(bytes);
|
|
||||||
render = output.render(maze);
|
|
||||||
} catch (final Exception e) {
|
|
||||||
log.error("Error rendering binary maze data", e);
|
|
||||||
httpServerExchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
|
|
||||||
.getResponseSender()
|
|
||||||
.send("Error rendering maze: %s".formatted(e.getMessage()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
httpServerExchange
|
|
||||||
.setStatusCode(StatusCodes.OK)
|
|
||||||
.getResponseHeaders()
|
|
||||||
.put(Headers.CONTENT_TYPE, output.getContentType());
|
|
||||||
httpServerExchange.getResponseSender()
|
|
||||||
.send(ByteBuffer.wrap(render));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private OutputType getOutputType(@NotNull final HttpServerExchange httpServerExchange) {
|
|
||||||
return RequestParameter.OUTPUT.<OutputType>getParameterValue(httpServerExchange.getQueryParameters())
|
|
||||||
.getOrElse(() -> {
|
|
||||||
final HeaderValues accept = httpServerExchange.getRequestHeaders().get(Headers.ACCEPT);
|
|
||||||
if (accept.contains(OutputType.HTML.getContentType())) {
|
|
||||||
return OutputType.HTML;
|
|
||||||
}
|
|
||||||
return OutputType.TEXT_PLAIN;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
package ch.fritteli.maze.server.handler;
|
|
||||||
|
|
||||||
import ch.fritteli.maze.generator.model.Maze;
|
|
||||||
import ch.fritteli.maze.generator.serialization.MazeConstants;
|
|
||||||
import ch.fritteli.maze.generator.serialization.v1.SerializerDeserializerV1;
|
|
||||||
import ch.fritteli.maze.generator.serialization.v2.SerializerDeserializerV2;
|
|
||||||
import ch.fritteli.maze.generator.serialization.v3.SerializerDeserializerV3;
|
|
||||||
import ch.fritteli.maze.server.OutputType;
|
|
||||||
import io.undertow.server.HttpHandler;
|
|
||||||
import io.undertow.server.HttpServerExchange;
|
|
||||||
import io.undertow.util.HeaderValues;
|
|
||||||
import io.undertow.util.Headers;
|
|
||||||
import io.undertow.util.StatusCodes;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class RenderVxHandler implements HttpHandler {
|
|
||||||
public static final String PATH_TEMPLATE = "/render/dyn/{output}";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleRequest(@NotNull final HttpServerExchange exchange) {
|
|
||||||
log.debug("Handling render request");
|
|
||||||
|
|
||||||
if (exchange.isInIoThread()) {
|
|
||||||
exchange.dispatch(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
exchange.getRequestReceiver().receiveFullBytes((httpServerExchange, bytes) -> {
|
|
||||||
final OutputType output = this.getOutputType(httpServerExchange);
|
|
||||||
final byte[] render;
|
|
||||||
try {
|
|
||||||
final Version version = this.getVersion(bytes);
|
|
||||||
final Maze maze = switch (version) {
|
|
||||||
case V1 -> SerializerDeserializerV1.deserialize(bytes);
|
|
||||||
case V2 -> SerializerDeserializerV2.deserialize(bytes);
|
|
||||||
case V3 -> SerializerDeserializerV3.deserialize(bytes);
|
|
||||||
};
|
|
||||||
render = output.render(maze);
|
|
||||||
} catch (final Exception e) {
|
|
||||||
log.error("Error rendering binary maze data", e);
|
|
||||||
httpServerExchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
|
|
||||||
.getResponseSender()
|
|
||||||
.send("Error rendering maze: %s".formatted(e.getMessage()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
httpServerExchange
|
|
||||||
.setStatusCode(StatusCodes.OK)
|
|
||||||
.getResponseHeaders()
|
|
||||||
.put(Headers.CONTENT_TYPE, output.getContentType());
|
|
||||||
httpServerExchange.getResponseSender()
|
|
||||||
.send(ByteBuffer.wrap(render));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private Version getVersion(@NotNull final byte[] bytes) throws IllegalArgumentException {
|
|
||||||
if (bytes.length < 3) {
|
|
||||||
throw new IllegalArgumentException("Invalid input: too short");
|
|
||||||
}
|
|
||||||
if (bytes[0] == MazeConstants.MAGIC_BYTE_1 && bytes[1] == MazeConstants.MAGIC_BYTE_2) {
|
|
||||||
final byte version = bytes[2];
|
|
||||||
return switch (version) {
|
|
||||||
case SerializerDeserializerV1.VERSION_BYTE -> Version.V1;
|
|
||||||
case SerializerDeserializerV2.VERSION_BYTE -> Version.V2;
|
|
||||||
case SerializerDeserializerV3.VERSION_BYTE -> Version.V3;
|
|
||||||
default -> throw new IllegalArgumentException("Invalid version: " + version);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Invalid input: not a Maze file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private OutputType getOutputType(@NotNull final HttpServerExchange httpServerExchange) {
|
|
||||||
return RequestParameter.OUTPUT.<OutputType>getParameterValue(httpServerExchange.getQueryParameters())
|
|
||||||
.getOrElse(() -> {
|
|
||||||
final HeaderValues accept = httpServerExchange.getRequestHeaders().get(Headers.ACCEPT);
|
|
||||||
if (accept.contains(OutputType.HTML.getContentType())) {
|
|
||||||
return OutputType.HTML;
|
|
||||||
}
|
|
||||||
return OutputType.TEXT_PLAIN;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum Version {
|
|
||||||
V1, V2, V3;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package ch.fritteli.maze.server.handler;
|
package ch.fritteli.maze.server.handler;
|
||||||
|
|
||||||
import ch.fritteli.maze.generator.model.Position;
|
import ch.fritteli.maze.generator.model.Position;
|
||||||
import ch.fritteli.maze.server.Algorithm;
|
|
||||||
import ch.fritteli.maze.server.OutputType;
|
import ch.fritteli.maze.server.OutputType;
|
||||||
import io.vavr.Tuple2;
|
import io.vavr.Tuple2;
|
||||||
import io.vavr.collection.HashMap;
|
import io.vavr.collection.HashMap;
|
||||||
|
@ -45,9 +44,7 @@ enum RequestParameter {
|
||||||
return new Position(x, y);
|
return new Position(x, y);
|
||||||
})
|
})
|
||||||
.toOption()
|
.toOption()
|
||||||
.onEmpty(() -> log.debug("Unparseable value for parameter 'end': '{}'", p)), "e", "end"),
|
.onEmpty(() -> log.debug("Unparseable value for parameter 'end': '{}'", p)), "e", "end");
|
||||||
ALGORITHM(p -> Algorithm.ofString(p)
|
|
||||||
.onEmpty(() -> log.debug("Unparseable value for parameter 'algorithm': '{}'", p)), "a", "algorithm");
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private final Function<String, Option<?>> extractor;
|
private final Function<String, Option<?>> extractor;
|
||||||
@Getter
|
@Getter
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue