Merge pull request 'feature/docker' (#2) from feature/docker into master
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			Reviewed-on: java/labyrinth-server#2
This commit is contained in:
		
						commit
						be20725a4a
					
				
					 8 changed files with 219 additions and 192 deletions
				
			
		|  | @ -4,7 +4,7 @@ name: default | ||||||
| 
 | 
 | ||||||
| steps: | steps: | ||||||
|   - name: test |   - name: test | ||||||
|     image: maven:3.6-jdk-11 |     image: maven:3.8-openjdk-18-slim | ||||||
|     commands: |     commands: | ||||||
|       - mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V |       - mvn clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V | ||||||
|       - mvn test -B |       - mvn test -B | ||||||
|  | @ -14,13 +14,13 @@ steps: | ||||||
|           - master |           - master | ||||||
|           - feature/* |           - feature/* | ||||||
|   - name: deploy |   - name: deploy | ||||||
|     image: maven:3.6-jdk-11 |     image: maven:3.8-openjdk-18-slim | ||||||
|     environment: |     environment: | ||||||
|       REPO_TOKEN: |       REPO_TOKEN: | ||||||
|         from_secret: repo-token |         from_secret: repo-token | ||||||
|     commands: |     commands: | ||||||
|       - mvn -s maven-settings.xml deploy -DskipTests=true |       - mvn -s maven-settings.xml deploy -DskipTests=true | ||||||
|     trigger: |     when: | ||||||
|       branch: |       branch: | ||||||
|         - master |         - master | ||||||
|       event: |       event: | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								docker/Dockerfile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docker/Dockerfile
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,5 @@ | ||||||
|  | FROM openjdk:11 | ||||||
|  | 
 | ||||||
|  | COPY target/labyrinth-server-*.jar /app/app.jar | ||||||
|  | 
 | ||||||
|  | CMD java -Dfritteli.labyrinth.server.host=0.0.0.0 -Dfritteli.labyrinth.server.port=80 -jar /app/app.jar | ||||||
|  | @ -1,31 +0,0 @@ | ||||||
| <?xml version="1.0" encoding="UTF-8"?> |  | ||||||
| <module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4"> |  | ||||||
|   <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_11"> |  | ||||||
|     <output url="file://$MODULE_DIR$/target/classes" /> |  | ||||||
|     <output-test url="file://$MODULE_DIR$/target/test-classes" /> |  | ||||||
|     <content url="file://$MODULE_DIR$"> |  | ||||||
|       <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> |  | ||||||
|       <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> |  | ||||||
|       <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" /> |  | ||||||
|       <sourceFolder url="file://$MODULE_DIR$/target/generated-sources/annotations" isTestSource="false" generated="true" /> |  | ||||||
|       <excludeFolder url="file://$MODULE_DIR$/target" /> |  | ||||||
|     </content> |  | ||||||
|     <orderEntry type="inheritedJdk" /> |  | ||||||
|     <orderEntry type="sourceFolder" forTests="false" /> |  | ||||||
|     <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.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" /> |  | ||||||
|     <orderEntry type="library" scope="TEST" name="Maven: org.junit.platform:junit-platform-commons:1.6.1" level="project" /> |  | ||||||
|   </component> |  | ||||||
| </module> |  | ||||||
							
								
								
									
										23
									
								
								pom.xml
									
										
									
									
									
								
							
							
						
						
									
										23
									
								
								pom.xml
									
										
									
									
									
								
							|  | @ -15,8 +15,9 @@ | ||||||
| 	<version>0.0.1-SNAPSHOT</version> | 	<version>0.0.1-SNAPSHOT</version> | ||||||
| 
 | 
 | ||||||
| 	<properties> | 	<properties> | ||||||
| 		<logback.version>1.2.10</logback.version> | 		<logback.version>1.4.6</logback.version> | ||||||
| 		<slf4j.version>1.7.35</slf4j.version> | 		<lombok.version>1.18.26</lombok.version> | ||||||
|  | 		<slf4j.version>2.0.5</slf4j.version> | ||||||
| 	</properties> | 	</properties> | ||||||
| 
 | 
 | ||||||
| 	<dependencies> | 	<dependencies> | ||||||
|  | @ -57,23 +58,21 @@ | ||||||
| 	<build> | 	<build> | ||||||
| 		<plugins> | 		<plugins> | ||||||
| 			<plugin> | 			<plugin> | ||||||
| 				<artifactId>maven-assembly-plugin</artifactId> | 				<groupId>org.apache.maven.plugins</groupId> | ||||||
|  | 				<artifactId>maven-shade-plugin</artifactId> | ||||||
|  | 				<version>3.2.4</version> | ||||||
| 				<configuration> | 				<configuration> | ||||||
| 					<archive> | 					<transformers> | ||||||
| 						<manifest> | 						<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> | ||||||
| 							<mainClass>ch.fritteli.labyrinth.server.Main</mainClass> | 							<mainClass>ch.fritteli.labyrinth.server.Main</mainClass> | ||||||
| 						</manifest> | 						</transformer> | ||||||
| 					</archive> | 					</transformers> | ||||||
| 					<descriptorRefs> |  | ||||||
| 						<descriptorRef>jar-with-dependencies</descriptorRef> |  | ||||||
| 					</descriptorRefs> |  | ||||||
| 				</configuration> | 				</configuration> | ||||||
| 				<executions> | 				<executions> | ||||||
| 					<execution> | 					<execution> | ||||||
| 						<id>make-assembly</id> |  | ||||||
| 						<phase>package</phase> | 						<phase>package</phase> | ||||||
| 						<goals> | 						<goals> | ||||||
| 							<goal>single</goal> | 							<goal>shade</goal> | ||||||
| 						</goals> | 						</goals> | ||||||
| 					</execution> | 					</execution> | ||||||
| 				</executions> | 				</executions> | ||||||
|  |  | ||||||
|  | @ -24,7 +24,6 @@ import org.jetbrains.annotations.Nullable; | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.io.OutputStream; | import java.io.OutputStream; | ||||||
| import java.net.InetSocketAddress; | import java.net.InetSocketAddress; | ||||||
| import java.net.URISyntaxException; |  | ||||||
| import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||||
| import java.util.concurrent.ExecutorService; | import java.util.concurrent.ExecutorService; | ||||||
| import java.util.concurrent.SynchronousQueue; | import java.util.concurrent.SynchronousQueue; | ||||||
|  | @ -38,32 +37,166 @@ public class LabyrinthServer { | ||||||
|     @NonNull |     @NonNull | ||||||
|     private final HttpServer httpServer; |     private final HttpServer httpServer; | ||||||
|     @NonNull |     @NonNull | ||||||
|     private final ExecutorService executorService = new ThreadPoolExecutor( |     private final ExecutorService executorService = new ThreadPoolExecutor(0, | ||||||
|             0, |  | ||||||
|                                                                            1_000, |                                                                            1_000, | ||||||
|                                                                            5, |                                                                            5, | ||||||
|                                                                            TimeUnit.SECONDS, |                                                                            TimeUnit.SECONDS, | ||||||
|                                                                            new SynchronousQueue<>() |                                                                            new SynchronousQueue<>() | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|     public LabyrinthServer(@NonNull final ServerConfig config) throws IOException, URISyntaxException { |     public static Option<LabyrinthServer> createAndStartServer() { | ||||||
|  |         final Option<LabyrinthServer> serverOption = Try.of(ServerConfig::init) | ||||||
|  |                                                         .mapTry(LabyrinthServer::new) | ||||||
|  |                                                         .onFailure(cause -> log.error( | ||||||
|  |                                                                 "Failed to create LabyrinthServer.", | ||||||
|  |                                                                 cause | ||||||
|  |                                                         )) | ||||||
|  |                                                         .toOption(); | ||||||
|  |         serverOption.forEach(LabyrinthServer::start); | ||||||
|  |         return serverOption; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public LabyrinthServer(@NonNull final ServerConfig config) throws IOException { | ||||||
|         this.httpServer = HttpServer.create(new InetSocketAddress(config.getAddress(), config.getPort()), 5); |         this.httpServer = HttpServer.create(new InetSocketAddress(config.getAddress(), config.getPort()), 5); | ||||||
|         this.httpServer.createContext("/", new StaticResourcesFileHandler(this.executorService)); |         this.httpServer.createContext("/", new StaticResourcesFileHandler(this.executorService)); | ||||||
|         this.httpServer.createContext("/create", this::handleCreate); |         this.httpServer.createContext("/create", this::handleCreate); | ||||||
|         this.httpServer.createContext("/render", this::handleRender); |         this.httpServer.createContext("/render", this::handleRender); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static Option<LabyrinthServer> createAndStartServer() { |     public void start() { | ||||||
|         final Option<LabyrinthServer> serverOption = Try.of(ServerConfig::init) |         Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper")); | ||||||
|                 .mapTry(LabyrinthServer::new) |         this.httpServer.start(); | ||||||
|                 .onFailure(cause -> log.error("Failed to create LabyrinthServer.", cause)) |         log.info("Listening on http://{}:{}", | ||||||
|                 .toOption(); |                  this.httpServer.getAddress().getHostString(), | ||||||
|         serverOption.forEach(LabyrinthServer::start); |                  this.httpServer.getAddress().getPort() | ||||||
|         return serverOption; |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private <T> T getOrDefault(@NonNull final Option<String> input, @NonNull final Function<String, T> mapper, @Nullable final T defaultValue) { |     private void handleCreate(HttpExchange exchange) { | ||||||
|         return input.toTry().map(mapper).getOrElse(defaultValue); |         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 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.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(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void handleRender(final HttpExchange exchange) { | ||||||
|  |         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(OutputType.HTML.getContentType()) ? | ||||||
|  |                                           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(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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."); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|  | @ -92,126 +225,16 @@ public class LabyrinthServer { | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     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 RequestParameter normalizeParameterName(final String paramName) { |     private RequestParameter normalizeParameterName(final String paramName) { | ||||||
|         return RequestParameter.parseName(paramName).get(); |         return RequestParameter.parseName(paramName).get(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void start() { |  | ||||||
|         Runtime.getRuntime().addShutdownHook(new Thread(this::stop, "listener-stopper")); |  | ||||||
|         this.httpServer.start(); |  | ||||||
|         log.info("Listening on http://{}:{}", this.httpServer.getAddress().getHostString(), this.httpServer.getAddress().getPort()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     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 { |  | ||||||
|         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 { |     private enum RequestParameter { | ||||||
|         WIDTH("w", "width"), |         WIDTH("w", "width"), | ||||||
|         HEIGHT("h", "height"), |         HEIGHT("h", "height"), | ||||||
|  | @ -227,16 +250,34 @@ public class LabyrinthServer { | ||||||
|             if (name == null) { |             if (name == null) { | ||||||
|                 return Option.none(); |                 return Option.none(); | ||||||
|             } |             } | ||||||
|             return Stream.of(values()) |             return Stream.of(values()).find(param -> param.names.exists(name::equalsIgnoreCase)); | ||||||
|                     .find(param -> param.names.exists(name::equalsIgnoreCase)); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private enum OutputType { |     private enum OutputType { | ||||||
|         TEXT_PLAIN("text/plain; charset=UTF-8", labyrinth -> TextRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), "t", "text"), |         TEXT_PLAIN("text/plain; charset=UTF-8", | ||||||
|         HTML("text/html", labyrinth -> HTMLRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), "h", "html"), |                    labyrinth -> TextRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), | ||||||
|         PDF("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "p", "pdf"), |                    "txt", | ||||||
|         BINARY("application/octet-stream", SerializerDeserializer::serialize, "b", "binary"); |                    false, | ||||||
|  |                    "t", | ||||||
|  |                    "text" | ||||||
|  |         ), | ||||||
|  |         HTML("text/html", | ||||||
|  |              labyrinth -> HTMLRenderer.newInstance().render(labyrinth).getBytes(StandardCharsets.UTF_8), | ||||||
|  |              "html", | ||||||
|  |              false, | ||||||
|  |              "h", | ||||||
|  |              "html" | ||||||
|  |         ), | ||||||
|  |         PDF("application/pdf", labyrinth -> PDFRenderer.newInstance().render(labyrinth), "pdf", false, "p", "pdf"), | ||||||
|  |         PDFFILE("application/pdf", | ||||||
|  |                 labyrinth -> PDFRenderer.newInstance().render(labyrinth), | ||||||
|  |                 "pdf", | ||||||
|  |                 true, | ||||||
|  |                 "f", | ||||||
|  |                 "pdffile" | ||||||
|  |         ), | ||||||
|  |         BINARY("application/octet-stream", SerializerDeserializer::serialize, "laby", true, "b", "binary"); | ||||||
|         @Getter |         @Getter | ||||||
|         @NonNull |         @NonNull | ||||||
|         private final String contentType; |         private final String contentType; | ||||||
|  | @ -244,10 +285,21 @@ public class LabyrinthServer { | ||||||
|         private final List<String> names; |         private final List<String> names; | ||||||
|         @NonNull |         @NonNull | ||||||
|         private final Function<Labyrinth, byte[]> render; |         private final Function<Labyrinth, byte[]> render; | ||||||
|  |         @Getter | ||||||
|  |         private final boolean attachment; | ||||||
|  |         @Getter | ||||||
|  |         @NonNull | ||||||
|  |         private final String fileExtension; | ||||||
| 
 | 
 | ||||||
|         OutputType(@NonNull final String contentType, @NonNull final Function<Labyrinth, byte[]> render, @NonNull final String... names) { |         OutputType(@NonNull final String contentType, | ||||||
|  |                    @NonNull final Function<Labyrinth, byte[]> render, | ||||||
|  |                    @NonNull final String fileExtension, | ||||||
|  |                    final boolean attachment, | ||||||
|  |                    @NonNull final String... names) { | ||||||
|             this.contentType = contentType; |             this.contentType = contentType; | ||||||
|             this.render = render; |             this.render = render; | ||||||
|  |             this.fileExtension = fileExtension; | ||||||
|  |             this.attachment = attachment; | ||||||
|             this.names = List.of(names); |             this.names = List.of(names); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -256,9 +308,7 @@ public class LabyrinthServer { | ||||||
|                 return Option.none(); |                 return Option.none(); | ||||||
|             } |             } | ||||||
|             final String nameLC = name.toLowerCase(); |             final String nameLC = name.toLowerCase(); | ||||||
|             return Stream.of(values()) |             return Stream.of(values()).find(param -> param.names.contains(nameLC)); | ||||||
|                     .find(param -> param.names.contains(nameLC)); |  | ||||||
| 
 |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ public class StaticResourcesFileHandler implements HttpHandler { | ||||||
| 
 | 
 | ||||||
|     public StaticResourcesFileHandler(final ExecutorService executorService) { |     public StaticResourcesFileHandler(final ExecutorService executorService) { | ||||||
|         this.executorService = executorService; |         this.executorService = executorService; | ||||||
|  |         log.debug("Created {}", this.getClass().getSimpleName()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static void redirect(@NonNull final HttpExchange exchange, @NonNull final String target) throws IOException { |     private static void redirect(@NonNull final HttpExchange exchange, @NonNull final String target) throws IOException { | ||||||
|  | @ -44,7 +45,9 @@ public class StaticResourcesFileHandler implements HttpHandler { | ||||||
|             log.debug("Resource '{}' not found in classpath.", path); |             log.debug("Resource '{}' not found in classpath.", path); | ||||||
|             return new byte[0]; |             return new byte[0]; | ||||||
|         } |         } | ||||||
|         return IOUtils.toByteArray(stream); |         final byte[] response = IOUtils.toByteArray(stream); | ||||||
|  |         log.debug("Sending reply; {} bytes", response.length); | ||||||
|  |         return response; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Nullable |     @Nullable | ||||||
|  | @ -66,7 +69,7 @@ public class StaticResourcesFileHandler implements HttpHandler { | ||||||
|                 final String path = requestURI.getPath(); |                 final String path = requestURI.getPath(); | ||||||
|                 log.debug("Handling request to {}", path); |                 log.debug("Handling request to {}", path); | ||||||
|                 if ("/".equals(path)) { |                 if ("/".equals(path)) { | ||||||
|                     redirect(exchange, "/index.html"); |                     redirect(exchange, "index.html"); | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
|                 if (!path.startsWith("/")) { |                 if (!path.startsWith("/")) { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| <?xml version="1.0" encoding="utf-8" ?> | <?xml version="1.0" encoding="utf-8" ?> | ||||||
| <configuration> | <configuration> | ||||||
| 	<shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/> | 	<shutdownHook class="ch.qos.logback.core.hook.DefaultShutdownHook"/> | ||||||
| 
 | 
 | ||||||
| 	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | 	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||||||
| 		<!-- encoders are by default assigned the type | 		<!-- encoders are by default assigned the type | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ | ||||||
|                 <option label="HTML Document" value="html"></option> |                 <option label="HTML Document" value="html"></option> | ||||||
|                 <option label="Plain text" value="text"></option> |                 <option label="Plain text" value="text"></option> | ||||||
|                 <option label="PDF Document" value="pdf"></option> |                 <option label="PDF Document" value="pdf"></option> | ||||||
|  |                 <option label="PDF Document (Download)" value="pdffile"></option> | ||||||
|                 <option label="Binary" value="binary"></option> |                 <option label="Binary" value="binary"></option> | ||||||
|             </select> |             </select> | ||||||
|             <label for="id">Seed (optional):</label><input id="id" name="id" type="number"> |             <label for="id">Seed (optional):</label><input id="id" name="id" type="number"> | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue