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,12 +5,17 @@ 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) { | ||||||
|  |         try (final MDC.MDCCloseable mdc = MDC.putCloseable("correlationId", UUID.randomUUID().toString())) { | ||||||
|             if (exchange.isInIoThread()) { |             if (exchange.isInIoThread()) { | ||||||
|  |                 log.debug("Dispatching request"); | ||||||
|                 exchange.dispatch(this); |                 exchange.dispatch(this); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  | @ -23,6 +28,7 @@ public abstract class AbstractHttpHandler implements HttpHandler { | ||||||
|                         .send(StatusCodes.INTERNAL_SERVER_ERROR_STRING); |                         .send(StatusCodes.INTERNAL_SERVER_ERROR_STRING); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     protected abstract void handle(@NonNull final HttpServerExchange exchange) throws Exception; |     protected abstract void handle(@NonNull final HttpServerExchange exchange) throws Exception; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue