feature/render-binary #1
					 9 changed files with 362 additions and 62 deletions
				
			
		|  | @ -20,7 +20,9 @@ steps: | |||
|         from_secret: repo-token | ||||
|     commands: | ||||
|       - mvn -s maven-settings.xml deploy -DskipTests=true | ||||
|     when: | ||||
|     trigger: | ||||
|       branch: | ||||
|         include: | ||||
|           - master | ||||
|         - master | ||||
|       event: | ||||
|         exclude: | ||||
|         - pull_request | ||||
|  |  | |||
|  | @ -12,16 +12,17 @@ | |||
|     </content> | ||||
|     <orderEntry type="inheritedJdk" /> | ||||
|     <orderEntry type="sourceFolder" forTests="false" /> | ||||
|     <orderEntry type="library" name="Maven: ch.fritteli.labyrinth:labyrinth-generator:0.0.1" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: org.apache.pdfbox:pdfbox:2.0.20" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: org.apache.pdfbox:fontbox:2.0.20" level="project" /> | ||||
|     <orderEntry type="module" module-name="labyrinth-generator" /> | ||||
|     <orderEntry type="library" name="Maven: org.apache.pdfbox:pdfbox:2.0.25" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: org.apache.pdfbox:fontbox:2.0.25" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: commons-logging:commons-logging:1.2" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: io.vavr:vavr:0.10.2" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: io.vavr:vavr-match:0.10.2" level="project" /> | ||||
|     <orderEntry type="library" scope="PROVIDED" name="Maven: org.projectlombok:lombok:1.18.12" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: org.jetbrains:annotations:19.0.0" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.30" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: org.slf4j:slf4j-simple:1.7.30" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.35" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.10" level="project" /> | ||||
|     <orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.10" level="project" /> | ||||
|     <orderEntry type="library" scope="TEST" name="Maven: org.junit.jupiter:junit-jupiter-api:5.6.1" level="project" /> | ||||
|     <orderEntry type="library" scope="TEST" name="Maven: org.apiguardian:apiguardian-api:1.1.0" level="project" /> | ||||
|     <orderEntry type="library" scope="TEST" name="Maven: org.opentest4j:opentest4j:1.2.0" level="project" /> | ||||
|  |  | |||
							
								
								
									
										31
									
								
								pom.xml
									
										
									
									
									
								
							
							
						
						
									
										31
									
								
								pom.xml
									
										
									
									
									
								
							|  | @ -14,11 +14,16 @@ | |||
| 	<artifactId>labyrinth-server</artifactId> | ||||
| 	<version>0.0.1-SNAPSHOT</version> | ||||
| 
 | ||||
| 	<properties> | ||||
| 		<logback.version>1.2.10</logback.version> | ||||
| 		<slf4j.version>1.7.35</slf4j.version> | ||||
| 	</properties> | ||||
| 
 | ||||
| 	<dependencies> | ||||
| 		<dependency> | ||||
| 			<groupId>ch.fritteli.labyrinth</groupId> | ||||
| 			<artifactId>labyrinth-generator</artifactId> | ||||
| 			<version>0.0.1</version> | ||||
| 			<version>0.0.2</version> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>io.vavr</groupId> | ||||
|  | @ -35,12 +40,12 @@ | |||
| 		<dependency> | ||||
| 			<groupId>org.slf4j</groupId> | ||||
| 			<artifactId>slf4j-api</artifactId> | ||||
| 			<version>1.7.30</version> | ||||
| 			<version>${slf4j.version}</version> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>org.slf4j</groupId> | ||||
| 			<artifactId>slf4j-simple</artifactId> | ||||
| 			<version>1.7.30</version> | ||||
| 			<groupId>ch.qos.logback</groupId> | ||||
| 			<artifactId>logback-classic</artifactId> | ||||
| 			<version>${logback.version}</version> | ||||
| 		</dependency> | ||||
| 		<dependency> | ||||
| 			<groupId>org.junit.jupiter</groupId> | ||||
|  | @ -95,12 +100,24 @@ | |||
| 	</distributionManagement> | ||||
| 	<repositories> | ||||
| 		<repository> | ||||
| 			<id>repo.gittr.ch</id> | ||||
| 			<url>https://repo.gittr.ch/</url> | ||||
| 			<id>repo.gittr.ch.releases</id> | ||||
| 			<url>https://repo.gittr.ch/releases/</url> | ||||
| 			<releases> | ||||
| 				<enabled>true</enabled> | ||||
| 				<updatePolicy>never</updatePolicy> | ||||
| 			</releases> | ||||
| 			<snapshots> | ||||
| 				<enabled>false</enabled> | ||||
| 				<updatePolicy>never</updatePolicy> | ||||
| 			</snapshots> | ||||
| 		</repository> | ||||
| 		<repository> | ||||
| 			<id>repo.gittr.ch.snapshots</id> | ||||
| 			<url>https://repo.gittr.ch/snapshots/</url> | ||||
| 			<releases> | ||||
| 				<enabled>false</enabled> | ||||
| 				<updatePolicy>never</updatePolicy> | ||||
| 			</releases> | ||||
| 			<snapshots> | ||||
| 				<enabled>true</enabled> | ||||
| 				<updatePolicy>always</updatePolicy> | ||||
|  |  | |||
|  | @ -4,10 +4,16 @@ 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 ch.fritteli.labyrinth.generator.serialization.SerializerDeserializer; | ||||
| import com.sun.net.httpserver.Headers; | ||||
| import com.sun.net.httpserver.HttpExchange; | ||||
| import com.sun.net.httpserver.HttpServer; | ||||
| import io.vavr.collection.*; | ||||
| 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; | ||||
|  | @ -18,17 +24,33 @@ import org.jetbrains.annotations.Nullable; | |||
| import java.io.IOException; | ||||
| import java.io.OutputStream; | ||||
| import java.net.InetSocketAddress; | ||||
| import java.net.URISyntaxException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.concurrent.ExecutorService; | ||||
| import java.util.concurrent.SynchronousQueue; | ||||
| import java.util.concurrent.ThreadPoolExecutor; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| import java.util.concurrent.atomic.AtomicBoolean; | ||||
| import java.util.function.Function; | ||||
| 
 | ||||
| @Slf4j | ||||
| public class LabyrinthServer { | ||||
|     @NonNull | ||||
|     private final HttpServer httpServer; | ||||
|     @NonNull | ||||
|     private final ExecutorService executorService = new ThreadPoolExecutor( | ||||
|             0, | ||||
|             1_000, | ||||
|             5, | ||||
|             TimeUnit.SECONDS, | ||||
|             new SynchronousQueue<>() | ||||
|     ); | ||||
| 
 | ||||
|     public LabyrinthServer(@NonNull final ServerConfig config) throws IOException { | ||||
|     public LabyrinthServer(@NonNull final ServerConfig config) throws IOException, URISyntaxException { | ||||
|         this.httpServer = HttpServer.create(new InetSocketAddress(config.getAddress(), config.getPort()), 5); | ||||
|         this.httpServer.createContext("/", new StaticResourcesFileHandler(this.executorService)); | ||||
|         this.httpServer.createContext("/create", this::handleCreate); | ||||
|         this.httpServer.createContext("/render", this::handleRender); | ||||
|     } | ||||
| 
 | ||||
|     public static Option<LabyrinthServer> createAndStartServer() { | ||||
|  | @ -83,52 +105,111 @@ public class LabyrinthServer { | |||
|     public void stop() { | ||||
|         log.info("Stopping server ..."); | ||||
|         this.httpServer.stop(5); | ||||
|         this.executorService.shutdown(); | ||||
|         try { | ||||
|             if (!this.executorService.awaitTermination(5, TimeUnit.SECONDS)) { | ||||
|                 log.warn("Timeout occurred while awaiting termination of executor service"); | ||||
|             } | ||||
|         } catch (final InterruptedException e) { | ||||
|             log.error("Failed to await termination of executor service", e); | ||||
|         } | ||||
|         log.info("Server stopped."); | ||||
|     } | ||||
| 
 | ||||
|     private void handleCreate(HttpExchange exchange) throws IOException { | ||||
|         log.debug("Handling request to {}", exchange.getRequestURI()); | ||||
|         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, 5); | ||||
|         final int height = this.getOrDefault(requestParams.get(RequestParameter.HEIGHT), Integer::valueOf, 5); | ||||
|         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); | ||||
|             exchange.close(); | ||||
|             return; | ||||
|         } | ||||
|         final Labyrinth labyrinth = new Labyrinth(width, height, id); | ||||
|         final byte[] render; | ||||
|         try { | ||||
|             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).getBytes(StandardCharsets.UTF_8)); | ||||
|             responseBody.flush(); | ||||
|             exchange.close(); | ||||
|             return; | ||||
|         } | ||||
|         responseHeaders.add("Content-type", output.getContentType()); | ||||
|         exchange.sendResponseHeaders(200, 0); | ||||
|         final OutputStream responseBody = exchange.getResponseBody(); | ||||
|         responseBody.write(render); | ||||
|         responseBody.flush(); | ||||
|         exchange.close(); | ||||
|         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, 5); | ||||
|                 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 Labyrinth labyrinth = new Labyrinth(width, height, id); | ||||
|                 final byte[] render; | ||||
|                 try { | ||||
|                     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).getBytes(StandardCharsets.UTF_8)); | ||||
|                     responseBody.flush(); | ||||
|                     return; | ||||
|                 } | ||||
|                 responseHeaders.add("Content-type", output.getContentType()); | ||||
|                 if (output.equals(OutputType.BINARY)) { | ||||
|                     responseHeaders.add("Content-disposition", "attachment; filename=\"labyrinth-" + width + "x" + height + "-" + id + ".laby\""); | ||||
|                 } | ||||
|                 exchange.sendResponseHeaders(200, 0); | ||||
|                 final OutputStream responseBody = exchange.getResponseBody(); | ||||
|                 responseBody.write(render); | ||||
|                 responseBody.flush(); | ||||
|             } catch (Exception e) { | ||||
|                 log.error("FSCK!", e); | ||||
|             } finally { | ||||
|                 exchange.close(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void handleRender(final HttpExchange exchange) throws IOException { | ||||
|         this.executorService.submit(() -> { | ||||
|             try { | ||||
|                 log.debug("Handling request to {}", exchange.getRequestURI()); | ||||
|                 final String requestMethod = exchange.getRequestMethod(); | ||||
|                 if (!requestMethod.equals("POST")) { | ||||
|                     exchange.getResponseBody().close(); | ||||
|                     exchange.sendResponseHeaders(405, -1); | ||||
|                     return; | ||||
|                 } | ||||
|                 final byte[] bytes = exchange.getRequestBody().readAllBytes(); | ||||
| 
 | ||||
|                 final Labyrinth labyrinth = SerializerDeserializer.deserialize(bytes); | ||||
| 
 | ||||
|                 final OutputType output = exchange.getRequestHeaders() | ||||
|                         .get("Accept") | ||||
|                         .contains("text/html") ? | ||||
|                         OutputType.HTML : | ||||
|                         OutputType.TEXT_PLAIN; | ||||
|                 final byte[] render; | ||||
|                 final Headers responseHeaders = exchange.getResponseHeaders(); | ||||
|                 try { | ||||
|                     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).getBytes(StandardCharsets.UTF_8)); | ||||
|                     responseBody.flush(); | ||||
|                     return; | ||||
|                 } | ||||
|                 responseHeaders.add("Content-type", output.getContentType()); | ||||
|                 exchange.sendResponseHeaders(200, 0); | ||||
|                 final OutputStream responseBody = exchange.getResponseBody(); | ||||
|                 responseBody.write(render); | ||||
|                 responseBody.flush(); | ||||
|             } catch (Exception e) { | ||||
|                 log.error("FSCK!", e); | ||||
|             } finally { | ||||
|                 exchange.close(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private enum RequestParameter { | ||||
|  | @ -154,7 +235,8 @@ public class LabyrinthServer { | |||
|     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"); | ||||
|         PDF("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "p", "pdf"), | ||||
|         BINARY("application/octet-stream", SerializerDeserializer::serialize, "b", "binary"); | ||||
|         @Getter | ||||
|         @NonNull | ||||
|         private final String contentType; | ||||
|  |  | |||
|  | @ -0,0 +1,98 @@ | |||
| package ch.fritteli.labyrinth.server; | ||||
| 
 | ||||
| import com.sun.net.httpserver.HttpExchange; | ||||
| import com.sun.net.httpserver.HttpHandler; | ||||
| import lombok.NonNull; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.pdfbox.io.IOUtils; | ||||
| import org.jetbrains.annotations.Nullable; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.net.URI; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.concurrent.ExecutorService; | ||||
| 
 | ||||
| @Slf4j | ||||
| public class StaticResourcesFileHandler implements HttpHandler { | ||||
| 
 | ||||
|     public static final String WEBASSETS_DIRECTORY = "webassets"; | ||||
|     private final ExecutorService executorService; | ||||
| 
 | ||||
|     public StaticResourcesFileHandler(final ExecutorService executorService) { | ||||
|         this.executorService = executorService; | ||||
|     } | ||||
| 
 | ||||
|     private static void redirect(@NonNull final HttpExchange exchange, @NonNull final String target) throws IOException { | ||||
|         log.debug("Sending redirect to {}", target); | ||||
|         exchange.getResponseHeaders().add("Location", target); | ||||
|         exchange.sendResponseHeaders(302, -1); | ||||
|     } | ||||
| 
 | ||||
|     private static void notFound(@NonNull final HttpExchange exchange, @NonNull final String path) throws IOException { | ||||
|         log.debug("Resource '{}' not found, replying with HTTP 404", path); | ||||
|         exchange.getResponseHeaders().add("Content-type", "text/plain; charset=utf-8"); | ||||
|         exchange.sendResponseHeaders(404, 0); | ||||
|         exchange.getResponseBody().write("404 - Not found".getBytes(StandardCharsets.UTF_8)); | ||||
|         exchange.getResponseBody().flush(); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private static byte[] getBytes(@NonNull final String path) throws IOException { | ||||
|         final InputStream stream = StaticResourcesFileHandler.class.getClassLoader().getResourceAsStream(WEBASSETS_DIRECTORY + path); | ||||
|         if (stream == null) { | ||||
|             log.debug("Resource '{}' not found in classpath.", path); | ||||
|             return new byte[0]; | ||||
|         } | ||||
|         return IOUtils.toByteArray(stream); | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private static String getMimeType(@NonNull final String path) { | ||||
|         if (path.endsWith(".html")) { | ||||
|             return "text/html"; | ||||
|         } | ||||
|         if (path.endsWith(".css")) { | ||||
|             return "text/css"; | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void handle(@NonNull final HttpExchange exchange) throws IOException { | ||||
|         this.executorService.submit(() -> { | ||||
|             try { | ||||
|                 final URI requestURI = exchange.getRequestURI(); | ||||
|                 final String path = requestURI.getPath(); | ||||
|                 log.debug("Handling request to {}", path); | ||||
|                 if ("/".equals(path)) { | ||||
|                     redirect(exchange, "/index.html"); | ||||
|                     return; | ||||
|                 } | ||||
|                 if (!path.startsWith("/")) { | ||||
|                     notFound(exchange, path); | ||||
|                     return; | ||||
|                 } | ||||
|                 final String mimeType = getMimeType(path); | ||||
|                 if (mimeType == null) { | ||||
|                     notFound(exchange, path); | ||||
|                     return; | ||||
|                 } | ||||
|                 final byte[] responseBytes = getBytes(path); | ||||
|                 if (responseBytes.length == 0) { | ||||
|                     notFound(exchange, path); | ||||
|                     return; | ||||
|                 } | ||||
|                 log.debug("Serving {}{} with mimetype {}: {} bytes", WEBASSETS_DIRECTORY, path, mimeType, responseBytes.length); | ||||
|                 exchange.getResponseHeaders().add("Content-type", mimeType); | ||||
|                 exchange.sendResponseHeaders(200, 0); | ||||
|                 exchange.getResponseBody().write(responseBytes); | ||||
|                 exchange.getResponseBody().flush(); | ||||
|             } catch (Exception e) { | ||||
|                 log.error("FSCK!", e); | ||||
|             } finally { | ||||
|                 exchange.close(); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								src/main/resources/logback.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/main/resources/logback.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| <?xml version="1.0" encoding="utf-8" ?> | ||||
| <configuration> | ||||
| 	<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/> | ||||
| 
 | ||||
| 	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||||
| 		<!-- encoders are by default assigned the type | ||||
| 		ch.qos.logback.classic.encoder.PatternLayoutEncoder --> | ||||
| 		<encoder> | ||||
| 			<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||||
| 		</encoder> | ||||
| 	</appender> | ||||
| 
 | ||||
| 	<root level="info"> | ||||
| 		<appender-ref ref="STDOUT"/> | ||||
| 	</root> | ||||
| 	<logger name="ch.fritteli.labyrinth.server.StaticResourcesFileHandler" level="debug"/> | ||||
| </configuration> | ||||
							
								
								
									
										32
									
								
								src/main/resources/webassets/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/main/resources/webassets/index.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,32 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <title>Labyrinth Generator</title> | ||||
|     <link rel="stylesheet" href="style.css"> | ||||
| </head> | ||||
| <body> | ||||
| <div class="content"> | ||||
|     <h1>Labyrinth Generator</h1> | ||||
|     <p>Enter some values, click the "Create!" button and see what happens!</p> | ||||
|     <form action="/create" method="get"> | ||||
|         <div class="inputs-wrapper"> | ||||
|             <label for="width">Width:</label><input id="width" name="width" type="number" min="1" required> | ||||
|             <label for="height">Height:</label><input id="height" name="height" type="number" min="1" required> | ||||
|             <label for="output">Output format:</label> | ||||
|             <select id="output" name="output" required> | ||||
|                 <option label="HTML Document" value="html"></option> | ||||
|                 <option label="Plain text" value="text"></option> | ||||
|                 <option label="PDF Document" value="pdf"></option> | ||||
|                 <option label="Binary" value="binary"></option> | ||||
|             </select> | ||||
|             <label for="id">Seed (optional):</label><input id="id" name="id" type="number"> | ||||
|         </div> | ||||
|         <div class="controls-wrapper"> | ||||
|             <button type="submit" class="primary">Create!</button> | ||||
|             <button type="reset">Reset form</button> | ||||
|         </div> | ||||
|     </form> | ||||
| </div> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										51
									
								
								src/main/resources/webassets/style.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/main/resources/webassets/style.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| :root { | ||||
|     --color-background: #181a1b; | ||||
|     --color-foreground: #e8e6e3; | ||||
|     --color-border: #5d6164; | ||||
|     --color-background-highlight: #292b2c; | ||||
|     --color-foreground-highlight: #f8f7f4; | ||||
|     --color-border-highlight: #6e7275; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
|     display: flex; | ||||
|     font-family: sans-serif; | ||||
|     justify-content: center; | ||||
| } | ||||
| 
 | ||||
| body, button, input, select { | ||||
|     background-color: var(--color-background); | ||||
|     color: var(--color-foreground); | ||||
| } | ||||
| 
 | ||||
| button, input, select { | ||||
|     border: 1px solid var(--color-border); | ||||
| } | ||||
| 
 | ||||
| button:active, input:active, select:active, | ||||
| button:focus, input:focus, select:focus, | ||||
| button:hover, input:hover, select:hover { | ||||
|     background-color: var(--color-background-highlight); | ||||
|     border-color: var(--color-border-highlight); | ||||
|     color: var(--color-foreground-highlight); | ||||
| } | ||||
| 
 | ||||
| button.primary { | ||||
|     background-color: #ffcc00; | ||||
|     border-color: #eebb00; | ||||
|     color: var(--color-background); | ||||
| } | ||||
| 
 | ||||
| .content { | ||||
|     width: 75%; | ||||
| } | ||||
| 
 | ||||
| .content h1 { | ||||
|     text-align: center; | ||||
| } | ||||
| 
 | ||||
| .inputs-wrapper { | ||||
|     display: grid; | ||||
|     grid-row-gap: 1em; | ||||
|     grid-template-columns: 0.5fr 1fr; | ||||
| } | ||||
|  | @ -4,7 +4,6 @@ import org.junit.jupiter.api.BeforeEach; | |||
| import org.junit.jupiter.api.Test; | ||||
| 
 | ||||
| import java.net.UnknownHostException; | ||||
| import java.util.Properties; | ||||
| 
 | ||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||||
| import static org.junit.jupiter.api.Assertions.assertThrows; | ||||
|  | @ -12,7 +11,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; | |||
| class ServerConfigTest { | ||||
|     @BeforeEach | ||||
|     void clearSysProperties() { | ||||
|         System.setProperties(new Properties()); | ||||
|         System.clearProperty(ServerConfig.SYSPROP_HOST); | ||||
|         System.clearProperty(ServerConfig.SYSPROP_PORT); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue