This commit is contained in:
		
							parent
							
								
									ad320f0d7f
								
							
						
					
					
						commit
						41995979e1
					
				
					 4 changed files with 232 additions and 1 deletions
				
			
		
							
								
								
									
										198
									
								
								src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/main/java/ch/fritteli/labyrinth/server/LabyrinthServer.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,198 @@ | |||
| package ch.fritteli.labyrinth.server; | ||||
| 
 | ||||
| import ch.fritteli.labyrinth.generator.model.Labyrinth; | ||||
| import ch.fritteli.labyrinth.generator.renderer.html.HTMLRenderer; | ||||
| import ch.fritteli.labyrinth.generator.renderer.pdf.PDFRenderer; | ||||
| import ch.fritteli.labyrinth.generator.renderer.text.TextRenderer; | ||||
| import com.sun.net.httpserver.Headers; | ||||
| import com.sun.net.httpserver.HttpServer; | ||||
| import io.vavr.collection.HashMap; | ||||
| import io.vavr.collection.HashSet; | ||||
| import io.vavr.collection.List; | ||||
| import io.vavr.collection.Map; | ||||
| import io.vavr.collection.Set; | ||||
| import io.vavr.collection.Stream; | ||||
| import io.vavr.control.Option; | ||||
| import io.vavr.control.Try; | ||||
| import lombok.Getter; | ||||
| import lombok.NonNull; | ||||
| import org.jetbrains.annotations.Nullable; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.io.OutputStream; | ||||
| import java.net.InetSocketAddress; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| public class LabyrinthServer { | ||||
| 
 | ||||
|     private final HttpServer httpServer; | ||||
| 
 | ||||
|     public LabyrinthServer(final int port) throws IOException { | ||||
|         this.httpServer = HttpServer.create(new InetSocketAddress(port), 0); | ||||
|         this.httpServer.createContext("/create", exchange -> { | ||||
|             final String requestMethod = exchange.getRequestMethod(); | ||||
|             if (!requestMethod.equals("GET")) { | ||||
|                 exchange.getResponseBody().close(); | ||||
|                 exchange.sendResponseHeaders(405, -1); | ||||
|                 exchange.close(); | ||||
|                 return; | ||||
|             } | ||||
|             final Map<RequestParameter, String> requestParams = this.parseQueryString(exchange.getRequestURI().getQuery()); | ||||
|             final int width = this.getOrDefault(requestParams.get(RequestParameter.WIDTH), Integer::valueOf, 1); | ||||
|             final int height = this.getOrDefault(requestParams.get(RequestParameter.HEIGHT), Integer::valueOf, 1); | ||||
|             final Option<Long> seedOption = requestParams.get(RequestParameter.ID).toTry().map(Long::valueOf).toOption(); | ||||
|             final long seed; | ||||
|             final Option<OutputType> outputOption = requestParams.get(RequestParameter.OUTPUT).flatMap(OutputType::ofString); | ||||
|             final OutputType output; | ||||
|             final Headers responseHeaders = exchange.getResponseHeaders(); | ||||
|             boolean needsRedirect = false; | ||||
|             if (seedOption.isEmpty()) { | ||||
|                 needsRedirect = true; | ||||
|                 seed = System.nanoTime(); | ||||
|             } else { | ||||
|                 seed = seedOption.get(); | ||||
|             } | ||||
|             if (outputOption.isEmpty()) { | ||||
|                 needsRedirect = true; | ||||
|                 output = OutputType.HTML; | ||||
|             } else { | ||||
|                 output = outputOption.get(); | ||||
|             } | ||||
|             if (needsRedirect) { | ||||
|                 responseHeaders.add("Location", "/create?width=" + width + "&height=" + height + "&output=" + output.toString() + "&id=" + seed); | ||||
|                 exchange.sendResponseHeaders(302, -1); | ||||
|                 exchange.close(); | ||||
|                 return; | ||||
|             } | ||||
|             responseHeaders.add("Content-type", output.getContentType()); | ||||
|             exchange.sendResponseHeaders(200, 0); | ||||
|             final Labyrinth labyrinth = new Labyrinth(width, height, seed); | ||||
|             final byte[] render = output.render(labyrinth); | ||||
|             final OutputStream responseBody = exchange.getResponseBody(); | ||||
|             responseBody.write(render); | ||||
|             responseBody.flush(); | ||||
|             exchange.close(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public static Option<LabyrinthServer> createListener() { | ||||
|         final String listenerPort = System.getProperty("fritteli.labyrinth.listenerport"); | ||||
|         final Option<LabyrinthServer> listenerOption = Option.of(listenerPort) | ||||
|                 .toTry() | ||||
|                 .map(Integer::valueOf) | ||||
|                 .onFailure(cause -> System.err.println("Invalid port specified via system property 'fritteli.labyrinth.listenerport': " | ||||
|                         + listenerPort | ||||
|                         + ". Not starting webserver.")) | ||||
|                 .mapTry(LabyrinthServer::new) | ||||
|                 .onFailure(cause -> System.err.println("Failed to create Listener: " + cause)) | ||||
|                 .toOption(); | ||||
|         listenerOption.forEach(LabyrinthServer::start); | ||||
|         return listenerOption; | ||||
|     } | ||||
| 
 | ||||
|     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); | ||||
|     } | ||||
| 
 | ||||
|     @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(); | ||||
|     } | ||||
| 
 | ||||
|     public void start() { | ||||
|         Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper")); | ||||
|         this.httpServer.start(); | ||||
|         System.out.println("Listening on " + this.httpServer.getAddress()); | ||||
|     } | ||||
| 
 | ||||
|     public void stop() { | ||||
|         System.out.println("Stopping listener ..."); | ||||
|         this.httpServer.stop(5); | ||||
|         System.out.println("Listener stopped."); | ||||
|     } | ||||
| 
 | ||||
|     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(); | ||||
|             } | ||||
|             final String nameLC = name.toLowerCase(); | ||||
|             return Stream.of(values()) | ||||
|                     .find(param -> param.names.contains(nameLC)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private enum OutputType { | ||||
|         TEXT_PLAIN("text/plain; charset=UTF-8", labyrinth -> TextRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), "t", "text"), | ||||
|         HTML("text/html", labyrinth -> HTMLRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), "h", "html"), | ||||
|         PDF("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "p", "pdf"); | ||||
|         @Getter | ||||
|         @NonNull | ||||
|         private final String contentType; | ||||
|         @NonNull | ||||
|         private final List<String> names; | ||||
|         @NonNull | ||||
|         private final Function<Labyrinth, byte[]> render; | ||||
| 
 | ||||
|         OutputType(@NonNull final String contentType, @NonNull final Function<Labyrinth, byte[]> render, @NonNull final String... names) { | ||||
|             this.contentType = contentType; | ||||
|             this.render = render; | ||||
|             this.names = List.of(names); | ||||
|         } | ||||
| 
 | ||||
|         static Option<OutputType> ofString(@Nullable final String name) { | ||||
|             if (name == null) { | ||||
|                 return Option.none(); | ||||
|             } | ||||
|             final String nameLC = name.toLowerCase(); | ||||
|             return Stream.of(values()) | ||||
|                     .find(param -> param.names.contains(nameLC)); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public String toString() { | ||||
|             return this.names.last(); | ||||
|         } | ||||
| 
 | ||||
|         byte[] render(@NonNull final Labyrinth labyrinth) { | ||||
|             return this.render.apply(labyrinth); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										8
									
								
								src/main/java/ch/fritteli/labyrinth/server/Main.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/main/java/ch/fritteli/labyrinth/server/Main.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| package ch.fritteli.labyrinth.server; | ||||
| 
 | ||||
| public class Main { | ||||
|     public static void main(String[] args) { | ||||
|         LabyrinthServer.createListener() | ||||
|                 .onEmpty(() -> System.err.println("Failed to create server. Stopping.")); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue