feature/undertow #4
					 7 changed files with 98 additions and 77 deletions
				
			
		
							
								
								
									
										2
									
								
								pom.xml
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								pom.xml
									
										
									
									
									
								
							|  | @ -20,7 +20,7 @@ | ||||||
|         <java.target.version>17</java.target.version> |         <java.target.version>17</java.target.version> | ||||||
|         <jetbrains-annotations.version>24.0.1</jetbrains-annotations.version> |         <jetbrains-annotations.version>24.0.1</jetbrains-annotations.version> | ||||||
|         <junit-jupiter.version>5.9.2</junit-jupiter.version> |         <junit-jupiter.version>5.9.2</junit-jupiter.version> | ||||||
|         <labyrinth-generator.version>0.0.3</labyrinth-generator.version> |         <labyrinth-generator.version>0.0.4</labyrinth-generator.version> | ||||||
|         <logback.version>1.4.6</logback.version> |         <logback.version>1.4.6</logback.version> | ||||||
|         <lombok.version>1.18.26</lombok.version> |         <lombok.version>1.18.26</lombok.version> | ||||||
|         <slf4j.version>2.0.7</slf4j.version> |         <slf4j.version>2.0.7</slf4j.version> | ||||||
|  |  | ||||||
|  | @ -5,24 +5,23 @@ 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.vavr.control.Try; | import io.vavr.control.Try; | ||||||
|  | import java.net.InetSocketAddress; | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
| 
 | 
 | ||||||
| @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) { | ||||||
|         this.config = config; |  | ||||||
|         final String hostAddress = config.getAddress().getHostAddress(); |         final String hostAddress = config.getAddress().getHostAddress(); | ||||||
|         final int port = config.getPort(); |         final int port = config.getPort(); | ||||||
|         log.info("Starting Server at http://{}:{}/", hostAddress, port); |         log.info("Starting Server at http://{}:{}/", hostAddress, port); | ||||||
|         final RoutingHandler routingHandler = new RoutingHandler() |         final RoutingHandler routingHandler = new RoutingHandler() | ||||||
|                 .get(CreateHandler.PATH_TEMPLATE, new CreateHandler()) |                 .get(CreateHandler.PATH_TEMPLATE, new CreateHandler()) | ||||||
|                 .post("/render", new RenderHandler()); |                 .post(RenderHandler.PATH_TEMPLATE, new RenderHandler()); | ||||||
| 
 | 
 | ||||||
|         this.undertow = Undertow.builder() |         this.undertow = Undertow.builder() | ||||||
|                 .addHttpListener(port, hostAddress) |                 .addHttpListener(port, hostAddress) | ||||||
|  | @ -45,7 +44,11 @@ public class LabyrinthServer { | ||||||
|     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(); | ||||||
|         log.info("Listening on http://{}:{}", this.config.getAddress().getHostAddress(), this.config.getPort()); |         final InetSocketAddress address = (InetSocketAddress) this.undertow.getListenerInfo().get(0).getAddress(); | ||||||
|  |         final String hostAddress = address.getAddress().getHostAddress(); | ||||||
|  |         final int port = address.getPort(); | ||||||
|  | 
 | ||||||
|  |         log.info("Listening on http://{}:{}", hostAddress, port); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void stop() { |     private void stop() { | ||||||
|  |  | ||||||
|  | @ -30,13 +30,13 @@ public enum OutputType { | ||||||
|             "html"), |             "html"), | ||||||
|     PDF("application/pdf", |     PDF("application/pdf", | ||||||
|             "pdf", |             "pdf", | ||||||
|             labyrinth -> PDFRenderer.newInstance().render(labyrinth), |             labyrinth -> PDFRenderer.newInstance().render(labyrinth).toByteArray(), | ||||||
|             false, |             false, | ||||||
|             "p", |             "p", | ||||||
|             "pdf"), |             "pdf"), | ||||||
|     PDFFILE("application/pdf", |     PDFFILE("application/pdf", | ||||||
|             "pdf", |             "pdf", | ||||||
|             labyrinth -> PDFRenderer.newInstance().render(labyrinth), |             labyrinth -> PDFRenderer.newInstance().render(labyrinth).toByteArray(), | ||||||
|             true, |             true, | ||||||
|             "f", |             "f", | ||||||
|             "pdffile"), |             "pdffile"), | ||||||
|  |  | ||||||
|  | @ -55,6 +55,15 @@ public class CreateHandler extends AbstractHttpHandler { | ||||||
|                             .put(HttpString.tryFromString("X-Labyrinth-Width"), String.valueOf(labyrinth.getWidth())) |                             .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-Height"), String.valueOf(labyrinth.getHeight())) | ||||||
|                             .put(HttpString.tryFromString("X-Labyrinth-Generation-Duration-millis"), String.valueOf(durationMillis)); |                             .put(HttpString.tryFromString("X-Labyrinth-Generation-Duration-millis"), String.valueOf(durationMillis)); | ||||||
|  |                     if (outputType.isAttachment()) { | ||||||
|  |                         exchange.getResponseHeaders() | ||||||
|  |                                 .put(Headers.CONTENT_DISPOSITION, "attachment; filename=\"labyrinth-%dx%d-%d.%s\"".formatted( | ||||||
|  |                                         labyrinth.getWidth(), | ||||||
|  |                                         labyrinth.getHeight(), | ||||||
|  |                                         labyrinth.getRandomSeed(), | ||||||
|  |                                         outputType.getFileExtension() | ||||||
|  |                                 )); | ||||||
|  |                     } | ||||||
|                     exchange.getResponseSender().send(ByteBuffer.wrap(bytes)); |                     exchange.getResponseSender().send(ByteBuffer.wrap(bytes)); | ||||||
|                     log.debug("Create request handled in {}ms.", durationMillis); |                     log.debug("Create request handled in {}ms.", durationMillis); | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|  | @ -4,39 +4,20 @@ import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||||
| import ch.fritteli.labyrinth.server.OutputType; | import ch.fritteli.labyrinth.server.OutputType; | ||||||
| import io.vavr.Tuple; | import io.vavr.Tuple; | ||||||
| import io.vavr.Tuple2; | import io.vavr.Tuple2; | ||||||
| import io.vavr.collection.HashMap; |  | ||||||
| import io.vavr.collection.HashMultimap; |  | ||||||
| import io.vavr.collection.HashSet; |  | ||||||
| import io.vavr.collection.List; |  | ||||||
| import io.vavr.collection.Multimap; |  | ||||||
| import io.vavr.collection.Set; |  | ||||||
| import io.vavr.collection.Stream; | import io.vavr.collection.Stream; | ||||||
| import io.vavr.control.Option; | import io.vavr.control.Option; | ||||||
| import io.vavr.control.Try; | import io.vavr.control.Try; | ||||||
| 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.function.Function; |  | ||||||
| import lombok.Getter; |  | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.RequiredArgsConstructor; | ||||||
| import org.jetbrains.annotations.Nullable; |  | ||||||
| 
 | 
 | ||||||
|  | @RequiredArgsConstructor | ||||||
| class ParametersToLabyrinthExtractor { | class ParametersToLabyrinthExtractor { | ||||||
| 
 | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|     private final Multimap<RequestParameter, ?> queryParameters; |     private final Map<String, Deque<String>> queryParameters; | ||||||
| 
 |  | ||||||
|     ParametersToLabyrinthExtractor(@NonNull final Map<String, Deque<String>> queryParameters) { |  | ||||||
|         this.queryParameters = HashMap.ofAll(queryParameters) |  | ||||||
|                 .foldLeft( |  | ||||||
|                         HashMultimap.withSet().empty(), |  | ||||||
|                         (map, tuple) -> RequestParameter.parseName(tuple._1()).map(parameter -> Stream.ofAll(tuple._2()) |  | ||||||
|                                         .flatMap(parameter::extractParameterValue) |  | ||||||
|                                         .foldLeft(map, (m, value) -> m.put(parameter, value))) |  | ||||||
|                                 .getOrElse(map) |  | ||||||
|                 ); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|     Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() { |     Try<Tuple2<OutputType, Labyrinth>> createLabyrinth() { | ||||||
|  | @ -69,43 +50,6 @@ class ParametersToLabyrinthExtractor { | ||||||
| 
 | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|     private <T> Option<T> getParameterValue(@NonNull final RequestParameter parameter) { |     private <T> Option<T> getParameterValue(@NonNull final RequestParameter parameter) { | ||||||
|         return (Option<T>) this.queryParameters.getOrElse(parameter, List.empty()) |         return parameter.getParameterValue(this.queryParameters); | ||||||
|                 .headOption(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Slf4j |  | ||||||
|     private enum RequestParameter { |  | ||||||
|         WIDTH(p -> Try.of(() -> Integer.parseInt(p)) |  | ||||||
|                 .toOption() |  | ||||||
|                 .onEmpty(() -> log.debug("Unparseable value for parameter 'width': '{}'", p)), "w", "width"), |  | ||||||
|         HEIGHT(p -> Try.of(() -> Integer.parseInt(p)) |  | ||||||
|                 .toOption() |  | ||||||
|                 .onEmpty(() -> log.debug("Unparseable value for parameter 'height': '{}'", p)), "h", "height"), |  | ||||||
|         ID(p -> Try.of(() -> Long.parseLong(p)) |  | ||||||
|                 .toOption() |  | ||||||
|                 .onEmpty(() -> log.debug("Unparseable value for parameter 'id': '{}'", p)), "i", "id"), |  | ||||||
|         OUTPUT(p -> OutputType.ofString(p) |  | ||||||
|                 .onEmpty(() -> log.debug("Unparseable value for parameter 'output': '{}'", p)), "o", "output"); |  | ||||||
|         @NonNull |  | ||||||
|         private final Function<String, Option<?>> extractor; |  | ||||||
|         @Getter |  | ||||||
|         @NonNull |  | ||||||
|         private final Set<String> names; |  | ||||||
| 
 |  | ||||||
|         RequestParameter(@NonNull final Function<String, Option<?>> extractor, @NonNull final String... names) { |  | ||||||
|             this.extractor = extractor; |  | ||||||
|             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)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         @NonNull Option<?> extractParameterValue(@NonNull final String parameter) { |  | ||||||
|             return this.extractor.apply(parameter); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,17 +3,19 @@ package ch.fritteli.labyrinth.server.handler; | ||||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||||
| import ch.fritteli.labyrinth.generator.serialization.SerializerDeserializer; | import ch.fritteli.labyrinth.generator.serialization.SerializerDeserializer; | ||||||
| import ch.fritteli.labyrinth.server.OutputType; | import ch.fritteli.labyrinth.server.OutputType; | ||||||
| import io.undertow.server.HttpHandler; |  | ||||||
| import io.undertow.server.HttpServerExchange; | import io.undertow.server.HttpServerExchange; | ||||||
|  | import io.undertow.util.HeaderValues; | ||||||
| import io.undertow.util.Headers; | import io.undertow.util.Headers; | ||||||
| import io.undertow.util.StatusCodes; | import io.undertow.util.StatusCodes; | ||||||
| import io.vavr.control.Option; |  | ||||||
| import lombok.extern.slf4j.Slf4j; |  | ||||||
| 
 |  | ||||||
| import java.nio.ByteBuffer; | import java.nio.ByteBuffer; | ||||||
|  | import lombok.NonNull; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
| 
 | 
 | ||||||
| @Slf4j | @Slf4j | ||||||
| public class RenderHandler extends AbstractHttpHandler { | public class RenderHandler extends AbstractHttpHandler { | ||||||
|  | 
 | ||||||
|  |     public static final String PATH_TEMPLATE = "/render/{output}"; | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void handle(final HttpServerExchange exchange) { |     public void handle(final HttpServerExchange exchange) { | ||||||
|         log.debug("Handling render request"); |         log.debug("Handling render request"); | ||||||
|  | @ -23,13 +25,10 @@ public class RenderHandler extends AbstractHttpHandler { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         exchange.getRequestReceiver().receiveFullBytes((httpServerExchange, bytes) -> { |         exchange.getRequestReceiver().receiveFullBytes((httpServerExchange, bytes) -> { | ||||||
|             final Labyrinth labyrinth = SerializerDeserializer.deserialize(bytes); |             final OutputType output = this.getOutputType(httpServerExchange); | ||||||
|             final OutputType output = Option.of(httpServerExchange.getRequestHeaders().get(Headers.ACCEPT)) |  | ||||||
|                     .exists(values -> values.contains(OutputType.HTML.getContentType())) ? |  | ||||||
|                     OutputType.HTML : |  | ||||||
|                     OutputType.TEXT_PLAIN; |  | ||||||
|             final byte[] render; |             final byte[] render; | ||||||
|             try { |             try { | ||||||
|  |                 final Labyrinth labyrinth = SerializerDeserializer.deserialize(bytes); | ||||||
|                 render = output.render(labyrinth); |                 render = output.render(labyrinth); | ||||||
|             } catch (final Exception e) { |             } catch (final Exception e) { | ||||||
|                 log.error("Error rendering binary labyrinth data", e); |                 log.error("Error rendering binary labyrinth data", e); | ||||||
|  | @ -46,4 +45,16 @@ public class RenderHandler extends AbstractHttpHandler { | ||||||
|                     .send(ByteBuffer.wrap(render)); |                     .send(ByteBuffer.wrap(render)); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     private OutputType getOutputType(@NonNull 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; | ||||||
|  |                 }); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -0,0 +1,54 @@ | ||||||
|  | package ch.fritteli.labyrinth.server.handler; | ||||||
|  | 
 | ||||||
|  | import ch.fritteli.labyrinth.server.OutputType; | ||||||
|  | import io.vavr.Tuple2; | ||||||
|  | import io.vavr.collection.HashMap; | ||||||
|  | import io.vavr.collection.HashSet; | ||||||
|  | import io.vavr.collection.Set; | ||||||
|  | import io.vavr.control.Option; | ||||||
|  | import io.vavr.control.Try; | ||||||
|  | import java.util.Deque; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.function.Function; | ||||||
|  | import lombok.Getter; | ||||||
|  | import lombok.NonNull; | ||||||
|  | import lombok.extern.slf4j.Slf4j; | ||||||
|  | 
 | ||||||
|  | @Slf4j | ||||||
|  | enum RequestParameter { | ||||||
|  |     WIDTH(p -> Try.of(() -> Integer.parseInt(p)) | ||||||
|  |             .toOption() | ||||||
|  |             .onEmpty(() -> log.debug("Unparseable value for parameter 'width': '{}'", p)), "w", "width"), | ||||||
|  |     HEIGHT(p -> Try.of(() -> Integer.parseInt(p)) | ||||||
|  |             .toOption() | ||||||
|  |             .onEmpty(() -> log.debug("Unparseable value for parameter 'height': '{}'", p)), "h", "height"), | ||||||
|  |     ID(p -> Try.of(() -> Long.parseLong(p)) | ||||||
|  |             .toOption() | ||||||
|  |             .onEmpty(() -> log.debug("Unparseable value for parameter 'id': '{}'", p)), "i", "id"), | ||||||
|  |     OUTPUT(p -> OutputType.ofString(p) | ||||||
|  |             .onEmpty(() -> log.debug("Unparseable value for parameter 'output': '{}'", p)), "o", "output"); | ||||||
|  |     @NonNull | ||||||
|  |     private final Function<String, Option<?>> extractor; | ||||||
|  |     @Getter | ||||||
|  |     @NonNull | ||||||
|  |     private final Set<String> names; | ||||||
|  | 
 | ||||||
|  |     RequestParameter(@NonNull final Function<String, Option<?>> extractor, @NonNull final String... names) { | ||||||
|  |         this.extractor = extractor; | ||||||
|  |         this.names = HashSet.of(names); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     Option<?> extractParameterValue(@NonNull final String parameter) { | ||||||
|  |         return this.extractor.apply(parameter); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     public <T> Option<T> getParameterValue(@NonNull final Map<String, Deque<String>> queryParameters) { | ||||||
|  |         return (Option<T>) HashMap.ofAll(queryParameters) | ||||||
|  |                 .filterKeys(this.names::contains) | ||||||
|  |                 .flatMap(Tuple2::_2) | ||||||
|  |                 .flatMap(this::extractParameterValue) | ||||||
|  |                 .headOption(); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue