feature/undertow #4
9 changed files with 142 additions and 206 deletions
2
pom.xml
2
pom.xml
|
@ -52,7 +52,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.undertow</groupId>
|
<groupId>io.undertow</groupId>
|
||||||
<artifactId>undertow-core</artifactId>
|
<artifactId>undertow-core</artifactId>
|
||||||
<version>2.2.22.Final</version>
|
<version>2.3.5.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
|
|
@ -1,48 +1,51 @@
|
||||||
package ch.fritteli.labyrinth.server;
|
package ch.fritteli.labyrinth.server;
|
||||||
|
|
||||||
import ch.fritteli.labyrinth.server.handler.CreateHandler;
|
import ch.fritteli.labyrinth.server.handler.CreateHandler;
|
||||||
import ch.fritteli.labyrinth.server.handler.LanyrinthRenderHandler;
|
import ch.fritteli.labyrinth.server.handler.RenderHandler;
|
||||||
import io.undertow.Undertow;
|
import io.undertow.Undertow;
|
||||||
import io.undertow.server.RoutingHandler;
|
import io.undertow.server.RoutingHandler;
|
||||||
import io.undertow.util.StatusCodes;
|
|
||||||
import io.vavr.control.Try;
|
import io.vavr.control.Try;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class LabyrinthServer {
|
public class LabyrinthServer {
|
||||||
|
@NonNull
|
||||||
|
private final ServerConfig config;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Undertow undertow;
|
private final Undertow undertow;
|
||||||
|
|
||||||
private LabyrinthServer(@NonNull final ServerConfig config) {
|
private LabyrinthServer(@NonNull final ServerConfig config) {
|
||||||
log.info("Starting Server at http://{}:{}/", config.getAddress().getHostAddress(), config.getPort());
|
this.config = config;
|
||||||
final RoutingHandler routingHandler = new RoutingHandler().get(CreateHandler.PATH_TEMPLATE, new CreateHandler())
|
final String hostAddress = config.getAddress().getHostAddress();
|
||||||
.post("/render", new LanyrinthRenderHandler())
|
final int port = config.getPort();
|
||||||
.setFallbackHandler(exchange -> exchange.setStatusCode(
|
log.info("Starting Server at http://{}:{}/", hostAddress, port);
|
||||||
StatusCodes.NOT_FOUND)
|
final RoutingHandler routingHandler = new RoutingHandler()
|
||||||
.getResponseSender()
|
.get(CreateHandler.PATH_TEMPLATE, new CreateHandler())
|
||||||
.send("Resource %s not found".formatted(
|
.post("/render", new RenderHandler());
|
||||||
exchange.getRequestURI())));
|
|
||||||
this.undertow = Undertow.builder()
|
this.undertow = Undertow.builder()
|
||||||
.addHttpListener(config.getPort(), config.getAddress().getHostAddress())
|
.addHttpListener(port, hostAddress)
|
||||||
.setHandler(routingHandler)
|
.setHandler(routingHandler)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public static Try<LabyrinthServer> createAndStartServer() {
|
public static Try<LabyrinthServer> createAndStartServer() {
|
||||||
final Try<LabyrinthServer> serverOption = Try.of(ServerConfig::init).mapTry(LabyrinthServer::new);
|
return Try.of(ServerConfig::init)
|
||||||
serverOption.forEach(LabyrinthServer::start);
|
.flatMapTry(LabyrinthServer::createAndStartServer);
|
||||||
return serverOption;
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Try<LabyrinthServer> createAndStartServer(@NonNull final ServerConfig config) {
|
||||||
|
return Try.of(() -> new LabyrinthServer(config))
|
||||||
|
.peek(LabyrinthServer::start);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void start() {
|
private void start() {
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper"));
|
Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper"));
|
||||||
this.undertow.start();
|
this.undertow.start();
|
||||||
Try.of(() -> (InetSocketAddress) this.undertow.getListenerInfo().get(0).getAddress())
|
log.info("Listeing on http://{}:{}", this.config.getAddress().getHostAddress(), this.config.getPort());
|
||||||
.onFailure(e -> log.warn("Started server, unable to determine listeing address/port."))
|
|
||||||
.forEach(address -> log.info("Listening on http://{}:{}", address.getHostString(), address.getPort()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stop() {
|
private void stop() {
|
||||||
|
@ -51,110 +54,6 @@ public class LabyrinthServer {
|
||||||
log.info("Server stopped.");
|
log.info("Server stopped.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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<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 byte[] render;
|
|
||||||
// try {
|
|
||||||
// final Labyrinth labyrinth = new Labyrinth(width, height, id);
|
|
||||||
// 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.getMessage()).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();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @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();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 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 void handleRender(final HttpExchange exchange) {
|
// private void handleRender(final HttpExchange exchange) {
|
||||||
// this.executorService.submit(() -> {
|
// this.executorService.submit(() -> {
|
||||||
// try {
|
// try {
|
||||||
|
@ -198,23 +97,4 @@ public class LabyrinthServer {
|
||||||
// }
|
// }
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// 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();
|
|
||||||
// }
|
|
||||||
// return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
LabyrinthServer.createAndStartServer().onFailure(e -> log.error("Failed to create server. Stopping.", e));
|
LabyrinthServer.createAndStartServer()
|
||||||
|
.onFailure(e -> log.error("Failed to create server. Stopping.", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,38 +17,51 @@ import java.util.function.Function;
|
||||||
|
|
||||||
public enum OutputType {
|
public enum OutputType {
|
||||||
TEXT_PLAIN("text/plain; charset=UTF-8",
|
TEXT_PLAIN("text/plain; charset=UTF-8",
|
||||||
labyrinth -> TextRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8),
|
|
||||||
"txt",
|
"txt",
|
||||||
|
labyrinth -> TextRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8),
|
||||||
false,
|
false,
|
||||||
"t",
|
"t",
|
||||||
"text"
|
"text"),
|
||||||
),
|
|
||||||
HTML("text/html",
|
HTML("text/html",
|
||||||
labyrinth -> HTMLRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8),
|
|
||||||
"html",
|
"html",
|
||||||
|
labyrinth -> HTMLRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8),
|
||||||
false,
|
false,
|
||||||
"h",
|
"h",
|
||||||
"html"
|
"html"),
|
||||||
),
|
PDF("application/pdf",
|
||||||
PDF("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "pdf", false, "p", "pdf"),
|
"pdf",
|
||||||
PDFFILE("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "pdf", true, "f", "pdffile"),
|
labyrinth -> PDFRenderer.newInstance().render(labyrinth),
|
||||||
BINARY("application/octet-stream", SerializerDeserializer::serialize, "laby", true, "b", "binary");
|
false,
|
||||||
|
"p",
|
||||||
|
"pdf"),
|
||||||
|
PDFFILE("application/pdf",
|
||||||
|
"pdf",
|
||||||
|
labyrinth -> PDFRenderer.newInstance().render(labyrinth),
|
||||||
|
true,
|
||||||
|
"f",
|
||||||
|
"pdffile"),
|
||||||
|
BINARY("application/octet-stream",
|
||||||
|
"laby",
|
||||||
|
SerializerDeserializer::serialize,
|
||||||
|
true,
|
||||||
|
"b",
|
||||||
|
"binary");
|
||||||
@Getter
|
@Getter
|
||||||
@NonNull
|
@NonNull
|
||||||
private final String contentType;
|
private final String contentType;
|
||||||
|
@Getter
|
||||||
@NonNull
|
@NonNull
|
||||||
private final List<String> names;
|
private final String fileExtension;
|
||||||
@NonNull
|
@NonNull
|
||||||
private final Function<Labyrinth, byte[]> render;
|
private final Function<Labyrinth, byte[]> render;
|
||||||
@Getter
|
@Getter
|
||||||
private final boolean attachment;
|
private final boolean attachment;
|
||||||
@Getter
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final String fileExtension;
|
private final List<String> names;
|
||||||
|
|
||||||
OutputType(@NonNull final String contentType,
|
OutputType(@NonNull final String contentType,
|
||||||
@NonNull final Function<Labyrinth, byte[]> render,
|
|
||||||
@NonNull final String fileExtension,
|
@NonNull final String fileExtension,
|
||||||
|
@NonNull final Function<Labyrinth, byte[]> render,
|
||||||
final boolean attachment,
|
final boolean attachment,
|
||||||
@NonNull final String... names) {
|
@NonNull final String... names) {
|
||||||
this.contentType = contentType;
|
this.contentType = contentType;
|
||||||
|
@ -58,20 +71,23 @@ public enum OutputType {
|
||||||
this.names = List.of(names);
|
this.names = List.of(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public static Option<OutputType> ofString(@Nullable final String name) {
|
public static Option<OutputType> ofString(@Nullable final String name) {
|
||||||
if (name == null) {
|
return Option.of(name)
|
||||||
return Option.none();
|
.map(String::toLowerCase)
|
||||||
}
|
.flatMap(nameLC -> Stream.of(values())
|
||||||
final String nameLC = name.toLowerCase();
|
.find(param -> param.names.contains(nameLC)));
|
||||||
return Stream.of(values()).find(param -> param.names.contains(nameLC));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return this.names.last();
|
return this.names.last();
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] render(@NonNull final Labyrinth labyrinth) {
|
@NonNull
|
||||||
|
public byte[] render(@NonNull final Labyrinth labyrinth) throws Exception {
|
||||||
return this.render.apply(labyrinth);
|
return this.render.apply(labyrinth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,8 @@ public class ServerConfig {
|
||||||
public static final String SYSPROP_HOST = "fritteli.labyrinth.server.host";
|
public static final String SYSPROP_HOST = "fritteli.labyrinth.server.host";
|
||||||
public static final String SYSPROP_PORT = "fritteli.labyrinth.server.port";
|
public static final String SYSPROP_PORT = "fritteli.labyrinth.server.port";
|
||||||
|
|
||||||
@NonNull InetAddress address;
|
@NonNull
|
||||||
|
InetAddress address;
|
||||||
int port;
|
int port;
|
||||||
|
|
||||||
public ServerConfig(@Nullable final String address, final int port) throws ConfigurationException {
|
public ServerConfig(@Nullable final String address, final int port) throws ConfigurationException {
|
||||||
|
@ -26,19 +27,6 @@ public class ServerConfig {
|
||||||
log.debug("host={}, port={}", this.address, this.port);
|
log.debug("host={}, port={}", this.address, this.port);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private static InetAddress validateAddress(@Nullable final String address) {
|
|
||||||
return Try.of(() -> InetAddress.getByName(address))
|
|
||||||
.getOrElseThrow(cause -> new ConfigurationException("Invalid hostname/address: " + address, cause));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int validatePort(final int port) {
|
|
||||||
if (port < 0 || port > 0xFFFF) {
|
|
||||||
throw new ConfigurationException("Port out of range (0..65535): " + port);
|
|
||||||
}
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static ServerConfig init() throws ConfigurationException {
|
public static ServerConfig init() throws ConfigurationException {
|
||||||
final String host = System.getProperty(SYSPROP_HOST);
|
final String host = System.getProperty(SYSPROP_HOST);
|
||||||
|
@ -47,15 +35,30 @@ public class ServerConfig {
|
||||||
return new ServerConfig(host, port);
|
return new ServerConfig(host, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static InetAddress validateAddress(@Nullable final String address) {
|
||||||
|
return Try.of(() -> InetAddress.getByName(address))
|
||||||
|
.getOrElseThrow(cause -> new ConfigurationException(
|
||||||
|
"Invalid hostname/address: %s".formatted(address),
|
||||||
|
cause
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int validatePort(final int port) {
|
||||||
|
if (port < 0 || port > 0xFFFF) {
|
||||||
|
throw new ConfigurationException("Port out of range (0..65535): %s".formatted(port));
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
private static int validatePort(@Nullable final String portString) {
|
private static int validatePort(@Nullable final String portString) {
|
||||||
if (portString == null) {
|
if (portString == null) {
|
||||||
log.info("No port configured; using default.");
|
log.info("No port configured; using default.");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return Try.of(() -> Integer.valueOf(portString))
|
return Try.of(() -> Integer.valueOf(portString))
|
||||||
.map(ServerConfig::validatePort)
|
|
||||||
.getOrElseThrow(cause -> new ConfigurationException(
|
.getOrElseThrow(cause -> new ConfigurationException(
|
||||||
"Failed to parse port specified in system property '" + SYSPROP_PORT + "': " + portString,
|
"Failed to parse port specified in system property '%s': %s".formatted(SYSPROP_PORT, portString),
|
||||||
cause
|
cause
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,22 +5,28 @@ import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.util.StatusCodes;
|
import io.undertow.util.StatusCodes;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class AbstractHttpHandler implements HttpHandler {
|
public abstract class AbstractHttpHandler implements HttpHandler {
|
||||||
@Override
|
@Override
|
||||||
public final void handleRequest(final HttpServerExchange exchange) {
|
public final void handleRequest(@NonNull final HttpServerExchange exchange) {
|
||||||
if (exchange.isInIoThread()) {
|
try (final MDC.MDCCloseable mdc = MDC.putCloseable("correlationId", UUID.randomUUID().toString())) {
|
||||||
exchange.dispatch(this);
|
if (exchange.isInIoThread()) {
|
||||||
return;
|
log.debug("Dispatching request");
|
||||||
}
|
exchange.dispatch(this);
|
||||||
try {
|
return;
|
||||||
this.handle(exchange);
|
}
|
||||||
} catch (@NonNull final Exception e) {
|
try {
|
||||||
log.error("Error handling request", e);
|
this.handle(exchange);
|
||||||
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
|
} catch (@NonNull final Exception e) {
|
||||||
.getResponseSender()
|
log.error("Error handling request", e);
|
||||||
.send(StatusCodes.INTERNAL_SERVER_ERROR_STRING);
|
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
|
||||||
|
.getResponseSender()
|
||||||
|
.send(StatusCodes.INTERNAL_SERVER_ERROR_STRING);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,15 @@ import io.vavr.control.Try;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class CreateHandler extends AbstractHttpHandler {
|
public class CreateHandler extends AbstractHttpHandler {
|
||||||
|
@ -26,19 +30,34 @@ public class CreateHandler extends AbstractHttpHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handle(@NonNull final HttpServerExchange exchange) {
|
protected void handle(@NonNull final HttpServerExchange exchange) {
|
||||||
|
final Instant start = Instant.now();
|
||||||
|
log.debug("Handling create request");
|
||||||
this.createLabyrinthFromRequestParameters(exchange.getQueryParameters())
|
this.createLabyrinthFromRequestParameters(exchange.getQueryParameters())
|
||||||
.onFailure(e -> {
|
.onFailure(e -> {
|
||||||
|
log.error("Error creating Labyrinth from request", e);
|
||||||
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
|
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||||
exchange.setReasonPhrase(e.getMessage());
|
exchange.setReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR_STRING);
|
||||||
}).forEach(tuple -> {
|
})
|
||||||
|
.forEach(tuple -> {
|
||||||
final OutputType outputType = tuple._1();
|
final OutputType outputType = tuple._1();
|
||||||
final Labyrinth labyrinth = tuple._2();
|
final Labyrinth labyrinth = tuple._2();
|
||||||
final byte[] bytes = outputType.render(labyrinth);
|
final byte[] bytes;
|
||||||
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, outputType.getContentType());
|
try {
|
||||||
exchange.getResponseHeaders().put(HttpString.tryFromString("X-Labyrinth-ID"), String.valueOf(labyrinth.getRandomSeed()));
|
bytes = outputType.render(labyrinth);
|
||||||
exchange.getResponseHeaders().put(HttpString.tryFromString("X-Labyrinth-Width"), String.valueOf(labyrinth.getWidth()));
|
} catch (@NonNull final Exception e) {
|
||||||
exchange.getResponseHeaders().put(HttpString.tryFromString("X-Labyrinth-Height"), String.valueOf(labyrinth.getHeight()));
|
log.error("Error rendering Labyrinth", e);
|
||||||
|
exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
|
||||||
|
exchange.setReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR_STRING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
exchange.getResponseHeaders()
|
||||||
|
.put(Headers.CONTENT_TYPE, outputType.getContentType())
|
||||||
|
.put(HttpString.tryFromString("X-Labyrinth-ID"), String.valueOf(labyrinth.getRandomSeed()))
|
||||||
|
.put(HttpString.tryFromString("X-Labyrinth-Width"), String.valueOf(labyrinth.getWidth()))
|
||||||
|
.put(HttpString.tryFromString("X-Labyrinth-Height"), String.valueOf(labyrinth.getHeight()))
|
||||||
|
.put(HttpString.tryFromString("X-Labyrinth-Generation-Duration-millis"), String.valueOf(start.until(Instant.now(), ChronoUnit.MILLIS)));
|
||||||
exchange.getResponseSender().send(ByteBuffer.wrap(bytes));
|
exchange.getResponseSender().send(ByteBuffer.wrap(bytes));
|
||||||
|
log.debug("Create request handled.");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,13 +92,24 @@ public class CreateHandler extends AbstractHttpHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() {
|
@NonNull
|
||||||
final Option<OutputType> output = getParameterValues(RequestParameter.OUTPUT).foldLeft(Option.none(), (type, param) -> type.orElse(() -> OutputType.ofString(param)));
|
Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() {
|
||||||
final Option<Integer> width = getParameterValues(RequestParameter.WIDTH).foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Integer.parseInt(param)).toOption()));
|
final Option<OutputType> output = getParameterValues(RequestParameter.OUTPUT)
|
||||||
final Option<Integer> height = getParameterValues(RequestParameter.HEIGHT).foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Integer.parseInt(param)).toOption()));
|
.foldLeft(Option.none(), (type, param) -> type.orElse(() -> OutputType.ofString(param)));
|
||||||
final Option<Long> id = getParameterValues(RequestParameter.ID).foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Long.parseLong(param)).toOption()));
|
final Option<Integer> width = getParameterValues(RequestParameter.WIDTH)
|
||||||
|
.foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Integer.parseInt(param)).toOption()));
|
||||||
|
final Option<Integer> height = getParameterValues(RequestParameter.HEIGHT)
|
||||||
|
.foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Integer.parseInt(param)).toOption()));
|
||||||
|
final Option<Long> id = getParameterValues(RequestParameter.ID)
|
||||||
|
.foldLeft(Option.none(), (value, param) -> value.orElse(() -> Try.of(() -> Long.parseLong(param)).toOption()));
|
||||||
|
|
||||||
return Try.of(() -> Tuple.of(output.get(), new Labyrinth(width.get(), height.get(), id.getOrElse(() -> new Random().nextLong()))));
|
return Try.of(() -> {
|
||||||
|
final OutputType t1 = output.get();
|
||||||
|
final Integer width1 = width.get();
|
||||||
|
final Integer height1 = height.get();
|
||||||
|
final Long orElse = id.getOrElse(() -> new Random().nextLong());
|
||||||
|
return Tuple.of(t1, new Labyrinth(width1, height1, orElse));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
|
@ -4,7 +4,7 @@ import io.undertow.server.HttpHandler;
|
||||||
import io.undertow.server.HttpServerExchange;
|
import io.undertow.server.HttpServerExchange;
|
||||||
import io.undertow.util.StatusCodes;
|
import io.undertow.util.StatusCodes;
|
||||||
|
|
||||||
public class LanyrinthRenderHandler implements HttpHandler {
|
public class RenderHandler implements HttpHandler {
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(final HttpServerExchange exchange) {
|
public void handleRequest(final HttpServerExchange exchange) {
|
||||||
if (exchange.isInIoThread()) {
|
if (exchange.isInIoThread()) {
|
|
@ -6,7 +6,7 @@
|
||||||
<!-- encoders are by default assigned the type
|
<!-- encoders are by default assigned the type
|
||||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
|
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{HH:mm:ss.SSS} %-5level %X{correlationId} [%thread] %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue