Add a very basic HTTP server that generates labyrinths via GET requests to /create
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		
							parent
							
								
									8c1dc92cc8
								
							
						
					
					
						commit
						ccba8a9a11
					
				
					 2 changed files with 223 additions and 28 deletions
				
			
		|  | @ -1,46 +1,62 @@ | ||||||
| package ch.fritteli.labyrinth; | package ch.fritteli.labyrinth; | ||||||
| 
 | 
 | ||||||
| import ch.fritteli.labyrinth.model.Labyrinth; | import ch.fritteli.labyrinth.model.Labyrinth; | ||||||
|  | import ch.fritteli.labyrinth.net.TheListener; | ||||||
| import ch.fritteli.labyrinth.renderer.html.HTMLRenderer; | import ch.fritteli.labyrinth.renderer.html.HTMLRenderer; | ||||||
| import ch.fritteli.labyrinth.renderer.htmlfile.HTMLFileRenderer; | import ch.fritteli.labyrinth.renderer.htmlfile.HTMLFileRenderer; | ||||||
| import ch.fritteli.labyrinth.renderer.pdffile.PDFFileRenderer; | import ch.fritteli.labyrinth.renderer.pdffile.PDFFileRenderer; | ||||||
| import ch.fritteli.labyrinth.renderer.text.TextRenderer; | import ch.fritteli.labyrinth.renderer.text.TextRenderer; | ||||||
| import ch.fritteli.labyrinth.renderer.textfile.TextFileRenderer; | import ch.fritteli.labyrinth.renderer.textfile.TextFileRenderer; | ||||||
|  | import io.vavr.control.Try; | ||||||
| import lombok.NonNull; | import lombok.NonNull; | ||||||
| 
 | 
 | ||||||
|  | import java.io.IOException; | ||||||
| import java.nio.file.Path; | import java.nio.file.Path; | ||||||
| import java.nio.file.Paths; | import java.nio.file.Paths; | ||||||
| 
 | 
 | ||||||
| public class Main { | public class Main { | ||||||
|     public static void main(@NonNull final String[] args) { |     public static void main(@NonNull final String[] args) throws IOException { | ||||||
|         int width = 100; |         final String listenerPort = System.getProperty("fritteli.labyrinth.listenerport"); | ||||||
|         int height = 100; |         if (listenerPort != null) { | ||||||
|         final Labyrinth labyrinth = new Labyrinth(width, height/*, 0*/); |             final Try<Integer> portTry = Try.of(() -> Integer.valueOf(listenerPort)); | ||||||
|         final TextRenderer textRenderer = TextRenderer.newInstance(); |             if (portTry.isFailure()) { | ||||||
|         final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance(); |                 System.err.println("Invalid port specified via sysprop 'fritteli.labyrinth.listenerport': " + listenerPort + ". Not starting webserver."); | ||||||
|         final Path userHome = Paths.get(System.getProperty("user.home")); |             } else { | ||||||
|         final String baseFilename = getBaseFilename(labyrinth); |                 final TheListener listener = new TheListener(portTry.get()); | ||||||
|         final TextFileRenderer textFileRenderer = TextFileRenderer.newInstance() |                 listener.start(); | ||||||
|                 .setTargetLabyrinthFile(userHome.resolve(baseFilename + ".txt")) |                 System.out.println("Listening on port " + portTry.get()); | ||||||
|                 .setTargetSolutionFile(userHome.resolve(baseFilename + "-solution.txt")); |             } | ||||||
|         final HTMLFileRenderer htmlFileRenderer = HTMLFileRenderer.newInstance() |         } else { | ||||||
|                 .setTargetFile(userHome.resolve(baseFilename + ".html")); |             System.out.println("In order to start the webserver, specify the port to listen to via the system property 'fritteli.labyrinth.listenerport', i.e.: -Dfritteli.labyrinth.listenerport=12345"); | ||||||
|         final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance() |             int width = 100; | ||||||
|                 .setTargetFile(userHome.resolve(baseFilename + ".pdf")); |             int height = 100; | ||||||
|  |             final Labyrinth labyrinth = new Labyrinth(width, height/*, 0*/); | ||||||
|  |             final TextRenderer textRenderer = TextRenderer.newInstance(); | ||||||
|  |             final HTMLRenderer htmlRenderer = HTMLRenderer.newInstance(); | ||||||
|  |             final Path userHome = Paths.get(System.getProperty("user.home")); | ||||||
|  |             final String baseFilename = getBaseFilename(labyrinth); | ||||||
|  |             final TextFileRenderer textFileRenderer = TextFileRenderer.newInstance() | ||||||
|  |                     .setTargetLabyrinthFile(userHome.resolve(baseFilename + ".txt")) | ||||||
|  |                     .setTargetSolutionFile(userHome.resolve(baseFilename + "-solution.txt")); | ||||||
|  |             final HTMLFileRenderer htmlFileRenderer = HTMLFileRenderer.newInstance() | ||||||
|  |                     .setTargetFile(userHome.resolve(baseFilename + ".html")); | ||||||
|  |             final PDFFileRenderer pdfFileRenderer = PDFFileRenderer.newInstance() | ||||||
|  |                     .setTargetFile(userHome.resolve(baseFilename + ".pdf")); | ||||||
| 
 | 
 | ||||||
|         System.out.println("Labyrinth-ID: " + labyrinth.getRandomSeed()); |             System.out.println("Labyrinth-ID: " + labyrinth.getRandomSeed()); | ||||||
|         // Render Labyrinth to stdout |             // Render Labyrinth to stdout | ||||||
|         System.out.println(textRenderer.render(labyrinth)); |             System.out.println(textRenderer.render(labyrinth)); | ||||||
|         // Render Labyrinth solution to stdout |             // Render Labyrinth solution to stdout | ||||||
|         System.out.println(textRenderer.setRenderSolution(true).render(labyrinth)); |             System.out.println(textRenderer.setRenderSolution(true).render(labyrinth)); | ||||||
|         // Render HTML to stdout |             // Render HTML to stdout | ||||||
|         System.out.println(htmlRenderer.render(labyrinth)); |             System.out.println(htmlRenderer.render(labyrinth)); | ||||||
|         // Render Labyrinth and solution to (separate) files |             // Render Labyrinth and solution to (separate) files | ||||||
|         System.out.println(textFileRenderer.render(labyrinth)); |             System.out.println(textFileRenderer.render(labyrinth)); | ||||||
|         // Render HTML to file |             // Render HTML to file | ||||||
|         System.out.println(htmlFileRenderer.render(labyrinth)); |             System.out.println(htmlFileRenderer.render(labyrinth)); | ||||||
|         // Render PDF to file |             // Render PDF to file | ||||||
|         System.out.println(pdfFileRenderer.render(labyrinth)); |             System.out.println(pdfFileRenderer.render(labyrinth)); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static String getBaseFilename(@NonNull final Labyrinth labyrinth) { |     private static String getBaseFilename(@NonNull final Labyrinth labyrinth) { | ||||||
|  |  | ||||||
							
								
								
									
										179
									
								
								src/main/java/ch/fritteli/labyrinth/net/TheListener.java
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										179
									
								
								src/main/java/ch/fritteli/labyrinth/net/TheListener.java
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,179 @@ | ||||||
|  | package ch.fritteli.labyrinth.net; | ||||||
|  | 
 | ||||||
|  | import ch.fritteli.labyrinth.model.Labyrinth; | ||||||
|  | import ch.fritteli.labyrinth.renderer.html.HTMLRenderer; | ||||||
|  | import ch.fritteli.labyrinth.renderer.pdf.PDFRenderer; | ||||||
|  | import ch.fritteli.labyrinth.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 TheListener { | ||||||
|  | 
 | ||||||
|  |     private final HttpServer httpServer; | ||||||
|  | 
 | ||||||
|  |     public TheListener(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(); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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() { | ||||||
|  |         this.httpServer.start(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void stop() { | ||||||
|  |         this.httpServer.stop(10); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue