From 87b16cb24a1b49fb27b2e3b2445aec9dc0cac4ff Mon Sep 17 00:00:00 2001 From: Manuel Friedli Date: Fri, 13 Dec 2024 22:01:53 +0100 Subject: [PATCH] Allow limiting max height and/or width when creating a maze. --- .../ch/fritteli/maze/server/MazeServer.java | 4 +- .../ch/fritteli/maze/server/ServerConfig.java | 47 +++++++++++++++++-- .../maze/server/handler/CreateHandler.java | 12 ++++- .../handler/ParametersToMazeExtractor.java | 17 ++++++- 4 files changed, 69 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/fritteli/maze/server/MazeServer.java b/src/main/java/ch/fritteli/maze/server/MazeServer.java index f1639cb..84a1fcb 100644 --- a/src/main/java/ch/fritteli/maze/server/MazeServer.java +++ b/src/main/java/ch/fritteli/maze/server/MazeServer.java @@ -22,7 +22,7 @@ public class MazeServer { final int port = config.port(); log.info("Starting Server at http://{}:{}/", hostAddress, port); final RoutingHandler routingHandler = new RoutingHandler() - .get(CreateHandler.PATH_TEMPLATE, new CreateHandler()) + .get(CreateHandler.PATH_TEMPLATE, new CreateHandler(config.maxMazeHeight(), config.maxMazeWidth())) .post(RenderV1Handler.PATH_TEMPLATE, new RenderV1Handler()) .post(RenderV2Handler.PATH_TEMPLATE, new RenderV2Handler()); @@ -47,7 +47,7 @@ public class MazeServer { private void start() { Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper")); this.undertow.start(); - final InetSocketAddress address = (InetSocketAddress) this.undertow.getListenerInfo().get(0).getAddress(); + final InetSocketAddress address = (InetSocketAddress) this.undertow.getListenerInfo().getFirst().getAddress(); final String hostAddress = address.getAddress().getHostAddress(); final int port = address.getPort(); diff --git a/src/main/java/ch/fritteli/maze/server/ServerConfig.java b/src/main/java/ch/fritteli/maze/server/ServerConfig.java index 374606f..cd85ab9 100644 --- a/src/main/java/ch/fritteli/maze/server/ServerConfig.java +++ b/src/main/java/ch/fritteli/maze/server/ServerConfig.java @@ -8,26 +8,41 @@ import org.jetbrains.annotations.Nullable; import java.net.InetAddress; @Slf4j -public record ServerConfig(@NotNull InetAddress address, int port) { +public record ServerConfig(@NotNull InetAddress address, + int port, + int maxMazeHeight, + int maxMazeWidth) { public static final String SYSPROP_HOST = "fritteli.maze.server.host"; public static final String SYSPROP_PORT = "fritteli.maze.server.port"; + public static final String SYSPROP_MAX_MAZE_HEIGHT = "fritteli.maze.maxheight"; + public static final String SYSPROP_MAX_MAZE_WIDTH = "fritteli.maze.maxwidth"; - public ServerConfig(@NotNull final InetAddress address, int port) { + public ServerConfig(@NotNull final InetAddress address, + final int port, + final int maxMazeHeight, + final int maxMazeWidth) { this.address = address; this.port = validatePort(port); + this.maxMazeHeight = validateDimension(maxMazeHeight, "height"); + this.maxMazeWidth = validateDimension(maxMazeWidth, "width"); log.debug("host={}, port={}", this.address, this.port); } - public ServerConfig(@Nullable final String address, final int port) throws ConfigurationException { - this(validateAddress(address), port); + public ServerConfig(@Nullable final String address, final int port, final int maxMazeHeight, final int maxMazeWidth) + throws ConfigurationException { + this(validateAddress(address), port, maxMazeHeight, maxMazeWidth); } @NotNull public static ServerConfig init() throws ConfigurationException { final String host = System.getProperty(SYSPROP_HOST); final String portString = System.getProperty(SYSPROP_PORT); + final String maxMazeHeightString = System.getProperty(SYSPROP_MAX_MAZE_HEIGHT); + final String maxMazeWidthString = System.getProperty(SYSPROP_MAX_MAZE_WIDTH); final int port = validatePort(portString); - return new ServerConfig(host, port); + final int maxMazeHeight = validateDimension(maxMazeHeightString, "height", SYSPROP_MAX_MAZE_HEIGHT); + final int maxMazeWidth = validateDimension(maxMazeWidthString, "width", SYSPROP_MAX_MAZE_WIDTH); + return new ServerConfig(host, port, maxMazeHeight, maxMazeWidth); } @NotNull @@ -57,4 +72,26 @@ public record ServerConfig(@NotNull InetAddress address, int port) { cause )); } + + private static int validateDimension(final int dimension, @NotNull final String identifier) { + if (dimension < 0) { + throw new ConfigurationException("Maximum %s is negative : %s".formatted(dimension, identifier)); + } + return dimension; + } + + private static int validateDimension(@Nullable final String dimensionString, + @NotNull final String identifier, + @NotNull final String syspropName) { + if (dimensionString == null) { + log.info("No maximum {} configured; using default (unlimited).", identifier); + return 0; + } + return Try.of(() -> Integer.valueOf(dimensionString)) + .getOrElseThrow(cause -> new ConfigurationException( + "Failed to parse maximum %s specified in system property '%s': %s" + .formatted(identifier, syspropName, dimensionString), + cause + )); + } } diff --git a/src/main/java/ch/fritteli/maze/server/handler/CreateHandler.java b/src/main/java/ch/fritteli/maze/server/handler/CreateHandler.java index a14954b..7ad7892 100644 --- a/src/main/java/ch/fritteli/maze/server/handler/CreateHandler.java +++ b/src/main/java/ch/fritteli/maze/server/handler/CreateHandler.java @@ -22,6 +22,13 @@ import java.util.Map; public class CreateHandler extends AbstractHttpHandler { public static final String PATH_TEMPLATE = "/create/{output}"; + private final int maxHeight; + private final int maxWidth; + + public CreateHandler(final int maxHeight, final int maxWidth) { + this.maxHeight = maxHeight; + this.maxWidth = maxWidth; + } @Override protected void handle(@NotNull final HttpServerExchange exchange) { @@ -29,10 +36,11 @@ public class CreateHandler extends AbstractHttpHandler { log.debug("Handling create request"); this.createMazeFromRequestParameters(exchange.getQueryParameters()) .onFailure(e -> { - log.error("Error creating maze from request", e); if (e instanceof InvalidRequestParameterException) { + log.debug("Error creating maze from request", e); exchange.setStatusCode(StatusCodes.BAD_REQUEST); } else { + log.error("Error creating maze from request", e); exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR); } exchange.getResponseSender() @@ -75,6 +83,6 @@ public class CreateHandler extends AbstractHttpHandler { @NotNull private Try createMazeFromRequestParameters(final Map> queryParameters) { - return new ParametersToMazeExtractor(queryParameters).createMaze(); + return new ParametersToMazeExtractor(queryParameters, this.maxHeight, this.maxWidth).createMaze(); } } diff --git a/src/main/java/ch/fritteli/maze/server/handler/ParametersToMazeExtractor.java b/src/main/java/ch/fritteli/maze/server/handler/ParametersToMazeExtractor.java index bca9efa..1b93e38 100644 --- a/src/main/java/ch/fritteli/maze/server/handler/ParametersToMazeExtractor.java +++ b/src/main/java/ch/fritteli/maze/server/handler/ParametersToMazeExtractor.java @@ -20,6 +20,8 @@ class ParametersToMazeExtractor { @NotNull private final Map> queryParameters; + private final int maxHeight; + private final int maxWidth; @NotNull Try createMaze() { @@ -49,12 +51,23 @@ class ParametersToMazeExtractor { ))); } + final int desiredHeight = height.get(); + final int desiredWidth = width.get(); + + if (this.maxHeight != 0 && desiredHeight > this.maxHeight) { + return Try.failure(new InvalidRequestParameterException("Specified height (%s) is greater than allowed maximum of %s".formatted(desiredHeight, this.maxHeight))); + } + + if (this.maxWidth != 0 && desiredWidth > this.maxWidth) { + return Try.failure(new InvalidRequestParameterException("Specified width (%s) is greater than allowed maximum of %s".formatted(desiredWidth, this.maxWidth))); + } + return Try.of(() -> { final Maze maze; if (start.isDefined() && end.isDefined()) { - maze = new Maze(width.get(), height.get(), id.getOrElse(() -> new Random().nextLong()), start.get(), end.get()); + maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong()), start.get(), end.get()); } else { - maze = new Maze(width.get(), height.get(), id.getOrElse(() -> new Random().nextLong())); + maze = new Maze(desiredWidth, desiredHeight, id.getOrElse(() -> new Random().nextLong())); } new RandomDepthFirst(maze).run(); return new GeneratedMaze(maze, output.get(), RandomDepthFirst.class.getSimpleName());