diff --git a/CHANGELOG.md b/CHANGELOG.md index 2854b78..300ab19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Proteus Changelog. ## Unreleased ### No issue +**Improve multipart handling.** + + +[66acf750c442f54](https://github.com/noboomu/proteus/commit/66acf750c442f54) Joshua Bauer *2020-09-18 19:17:36* + **Added support for multiple file uploads.** diff --git a/proteus-core/src/main/java/io/sinistral/proteus/ProteusApplication.java b/proteus-core/src/main/java/io/sinistral/proteus/ProteusApplication.java index 2c67f3d..9c66caa 100644 --- a/proteus-core/src/main/java/io/sinistral/proteus/ProteusApplication.java +++ b/proteus-core/src/main/java/io/sinistral/proteus/ProteusApplication.java @@ -30,6 +30,7 @@ import io.undertow.util.Headers; import io.undertow.util.Methods; import org.apache.commons.lang3.time.DurationFormatUtils; +import org.checkerframework.checker.units.qual.C; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xnio.Options; @@ -51,6 +52,9 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; @@ -64,8 +68,7 @@ * @author jbauer */ -public class ProteusApplication -{ +public class ProteusApplication { private static Logger log = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ProteusApplication.class.getCanonicalName()); @@ -87,7 +90,6 @@ public class ProteusApplication @Inject public Config config; - public List> registeredModules = new ArrayList<>(); public Injector injector; @@ -134,7 +136,9 @@ public ProteusApplication(URL configURL) public void start() { - if (this.isRunning()) { + + if (this.isRunning()) + { log.warn("Server has already started..."); return; } @@ -149,7 +153,8 @@ public void start() injector = injector.createChildInjector(modules); - if (rootHandlerClass == null && rootHandler == null) { + if (rootHandlerClass == null && rootHandler == null) + { log.warn("No root handler class or root HttpHandler was specified, using default ServerDefaultHttpHandler."); rootHandlerClass = ServerDefaultHttpHandler.class; } @@ -162,43 +167,48 @@ public void start() serviceManager = new ServiceManager(services); - serviceManager.addListener(new Listener() - { + serviceManager.addListener(new Listener() { public void stopped() { + undertow.stop(); } public void healthy() { + startupDuration = Duration.ofMillis(System.currentTimeMillis() - startTime); - for (ListenerInfo info : undertow.getListenerInfo()) { + for (ListenerInfo info : undertow.getListenerInfo()) + { log.debug("listener info: " + info); SocketAddress address = info.getAddress(); - if (address != null) { + if (address != null) + { ports.add(((java.net.InetSocketAddress) address).getPort()); } } printStatus(); - running.set(true); } public void failure(Service service) { + log.error("Service failure: " + service); startupDuration = Duration.ofMillis(System.currentTimeMillis() - startTime); - for (ListenerInfo info : undertow.getListenerInfo()) { + for (ListenerInfo info : undertow.getListenerInfo()) + { log.debug("listener info: " + info); SocketAddress address = info.getAddress(); - if (address != null) { + if (address != null) + { ports.add(((java.net.InetSocketAddress) address).getPort()); } } @@ -210,45 +220,44 @@ public void failure(Service service) }, MoreExecutors.directExecutor()); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - if (!this.isRunning()) { - log.warn("Server is not running..."); - return; - } + if (!this.isRunning()) + { + log.warn("Server is not running..."); + return; + } - try { + try + { shutdown(); mainThread.join(); - } - catch (InterruptedException ex) { + } catch (InterruptedException ex) + { log.error("Shutdown was interrupted", ex); - } - catch (TimeoutException timeout) { + } catch (TimeoutException timeout) + { log.error("Shutdown timed out", timeout); } })); - buildServer(); undertow.start(); serviceManager.startAsync(); - } public void shutdown() throws TimeoutException { - if (!this.isRunning()) { + + if (!this.isRunning()) + { log.warn("Server is not running..."); return; } - - log.info("Shutting down..."); serviceManager.stopAsync().awaitStopped(1, TimeUnit.SECONDS); @@ -260,101 +269,116 @@ public void shutdown() throws TimeoutException public boolean isRunning() { + return this.running.get(); } public void buildServer() { + for (Class controllerClass : registeredControllers) + { - for (Class controllerClass : registeredControllers) { - HandlerGenerator generator = new HandlerGenerator("io.sinistral.proteus.controllers.handlers", controllerClass); + HandlerGenerator generator = new HandlerGenerator("io.sinistral.proteus.controllers.handlers", controllerClass); - injector.injectMembers(generator); + injector.injectMembers(generator); - try { - Supplier generatedRouteSupplier = injector.getInstance(generator.compileClass()); + try + { + Supplier generatedRouteSupplier = injector.getInstance(generator.compileClass()); - router.addAll(generatedRouteSupplier.get()); - - } catch (Exception e) { - log.error("Exception creating handlers for " + controllerClass.getName() + "!!!\n" + e.getMessage(), e); - } + router.addAll(generatedRouteSupplier.get()); + } catch (Exception e) + { + log.error("Exception creating handlers for " + controllerClass.getName() + "!!!\n" + e.getMessage(), e); } - this.addDefaultRoutes(router); - - HttpHandler handler; + } - if (rootHandlerClass != null) { - handler = injector.getInstance(rootHandlerClass); - } else { - handler = rootHandler; - } + this.addDefaultRoutes(router); - SessionAttachmentHandler sessionAttachmentHandler = null; + HttpHandler handler; - try { - sessionAttachmentHandler = injector.getInstance(SessionAttachmentHandler.class); - } catch (Exception e) { - log.info("No session attachment handler found."); - } + if (rootHandlerClass != null) + { + handler = injector.getInstance(rootHandlerClass); + } + else + { + handler = rootHandler; + } - if (sessionAttachmentHandler != null) { - log.info("Using session attachment handler."); + SessionAttachmentHandler sessionAttachmentHandler = null; - sessionAttachmentHandler.setNext(handler); - handler = sessionAttachmentHandler; - } + try + { + sessionAttachmentHandler = injector.getInstance(SessionAttachmentHandler.class); + } catch (Exception e) + { + log.info("No session attachment handler found."); + } - int httpPort = config.getInt("application.ports.http"); + if (sessionAttachmentHandler != null) + { + log.info("Using session attachment handler."); - if (System.getProperty("http.port") != null) { - httpPort = Integer.parseInt(System.getProperty("http.port")); - } + sessionAttachmentHandler.setNext(handler); + handler = sessionAttachmentHandler; + } - Undertow.Builder undertowBuilder = Undertow.builder().addHttpListener(httpPort, config.getString("application.host")) - - .setBufferSize(Long.valueOf(config.getMemorySize("undertow.bufferSize").toBytes()).intValue()) - .setIoThreads(Runtime.getRuntime().availableProcessors() * config.getInt("undertow.ioThreadsMultiplier")) - .setWorkerThreads(Runtime.getRuntime().availableProcessors() * config.getInt("undertow.workerThreadMultiplier")) - .setDirectBuffers(config.getBoolean("undertow.directBuffers")) - .setSocketOption(org.xnio.Options.BACKLOG, config.getInt("undertow.socket.backlog")) - .setSocketOption(org.xnio.Options.REUSE_ADDRESSES, config.getBoolean("undertow.socket.reuseAddresses")) - .setSocketOption(org.xnio.Options.READ_TIMEOUT, config.getInt("undertow.socket.readTimeout")) - .setSocketOption(org.xnio.Options.WRITE_TIMEOUT, config.getInt("undertow.socket.writeTimeout")) - .setServerOption(UndertowOptions.ENABLE_HTTP2, config.getBoolean("undertow.server.enableHttp2")) - .setServerOption(UndertowOptions.ALWAYS_SET_DATE, config.getBoolean("undertow.server.alwaysSetDate")) - .setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, config.getBoolean("undertow.server.alwaysSetKeepAlive")) - .setServerOption(UndertowOptions.RECORD_REQUEST_START_TIME, config.getBoolean("undertow.server.recordRequestStartTime")) - .setServerOption(UndertowOptions.MAX_ENTITY_SIZE, config.getBytes("undertow.server.maxEntitySize")) - .setHandler(handler); - - if (config.getBoolean("undertow.ssl.enabled")) { - try { - int httpsPort = config.getInt("application.ports.https"); - - if (System.getProperty("https.port") != null) { - httpsPort = Integer.parseInt(System.getProperty("https.port")); - } + int httpPort = config.getInt("application.ports.http"); - KeyStore keyStore = SecurityOps.loadKeyStore(config.getString("undertow.ssl.keystorePath"), config.getString("undertow.ssl.keystorePassword")); - KeyStore trustStore = SecurityOps.loadKeyStore(config.getString("undertow.ssl.truststorePath"), config.getString("undertow.ssl.truststorePassword")); + if (System.getProperty("http.port") != null) + { + httpPort = Integer.parseInt(System.getProperty("http.port")); + } - undertowBuilder.addHttpsListener(httpsPort, config.getString("application.host"), SecurityOps.createSSLContext(keyStore, trustStore, config.getString("undertow.ssl.keystorePassword"))); + Undertow.Builder undertowBuilder = Undertow.builder().addHttpListener(httpPort, config.getString("application.host")) + + .setBufferSize(Long.valueOf(config.getMemorySize("undertow.bufferSize").toBytes()).intValue()) + .setIoThreads(Runtime.getRuntime().availableProcessors() * config.getInt("undertow.ioThreadsMultiplier")) + .setWorkerThreads(Runtime.getRuntime().availableProcessors() * config.getInt("undertow.workerThreadMultiplier")) + .setDirectBuffers(config.getBoolean("undertow.directBuffers")) + .setSocketOption(org.xnio.Options.BACKLOG, config.getInt("undertow.socket.backlog")) + .setSocketOption(org.xnio.Options.REUSE_ADDRESSES, config.getBoolean("undertow.socket.reuseAddresses")) + .setSocketOption(org.xnio.Options.READ_TIMEOUT, config.getInt("undertow.socket.readTimeout")) + .setSocketOption(org.xnio.Options.WRITE_TIMEOUT, config.getInt("undertow.socket.writeTimeout")) + .setServerOption(UndertowOptions.ENABLE_HTTP2, config.getBoolean("undertow.server.enableHttp2")) + .setServerOption(UndertowOptions.ALWAYS_SET_DATE, config.getBoolean("undertow.server.alwaysSetDate")) + .setServerOption(UndertowOptions.ALWAYS_SET_KEEP_ALIVE, config.getBoolean("undertow.server.alwaysSetKeepAlive")) + .setServerOption(UndertowOptions.RECORD_REQUEST_START_TIME, config.getBoolean("undertow.server.recordRequestStartTime")) + .setServerOption(UndertowOptions.MAX_ENTITY_SIZE, config.getBytes("undertow.server.maxEntitySize")) + .setHandler(handler); + + if (config.getBoolean("undertow.ssl.enabled")) + { + try + { + int httpsPort = config.getInt("application.ports.https"); - } catch (Exception e) { - log.error(e.getMessage(), e); + if (System.getProperty("https.port") != null) + { + httpsPort = Integer.parseInt(System.getProperty("https.port")); } - } - if (serverConfigurationFunction != null) { - undertowBuilder = serverConfigurationFunction.apply(undertowBuilder); + KeyStore keyStore = SecurityOps.loadKeyStore(config.getString("undertow.ssl.keystorePath"), config.getString("undertow.ssl.keystorePassword")); + KeyStore trustStore = SecurityOps.loadKeyStore(config.getString("undertow.ssl.truststorePath"), config.getString("undertow.ssl.truststorePassword")); + + undertowBuilder.addHttpsListener(httpsPort, config.getString("application.host"), SecurityOps.createSSLContext(keyStore, trustStore, config.getString("undertow.ssl.keystorePassword"))); + + } catch (Exception e) + { + log.error(e.getMessage(), e); } + } - this.undertow = undertowBuilder.build(); + if (serverConfigurationFunction != null) + { + undertowBuilder = serverConfigurationFunction.apply(undertowBuilder); + } + this.undertow = undertowBuilder.build(); } @@ -366,6 +390,7 @@ public void buildServer() */ public ProteusApplication addService(Class serviceClass) { + registeredServices.add(serviceClass); return this; } @@ -378,11 +403,11 @@ public ProteusApplication addService(Class serviceClass) */ public ProteusApplication addController(Class controllerClass) { + registeredControllers.add(controllerClass); return this; } - /** * Add a module class to the application * @@ -391,11 +416,11 @@ public ProteusApplication addController(Class controllerClass) */ public ProteusApplication addModule(Class moduleClass) { + registeredModules.add(moduleClass); return this; } - /** * Add utility routes the router * @@ -404,8 +429,10 @@ public ProteusApplication addModule(Class moduleClass) public ProteusApplication addDefaultRoutes(RoutingHandler router) { - if (config.hasPath("health.statusPath")) { - try { + if (config.hasPath("health.statusPath")) + { + try + { final String statusPath = config.getString("health.statusPath"); router.add(Methods.GET, statusPath, (final HttpServerExchange exchange) -> @@ -416,27 +443,34 @@ public ProteusApplication addDefaultRoutes(RoutingHandler router) this.registeredEndpoints.add(EndpointInfo.builder().withConsumes("*/*").withProduces("text/plain").withPathTemplate(statusPath).withControllerName("Internal").withMethod(Methods.GET).build()); - } catch (Exception e) { + } catch (Exception e) + { log.error("Error adding health status route.", e); } } - if (config.hasPath("application.favicon")) { - try { + if (config.hasPath("application.favicon")) + { + try + { final ByteBuffer faviconImageBuffer; final File faviconFile = new File(config.getString("application.favicon")); - if (!faviconFile.exists()) { - try (final InputStream stream = this.getClass().getResourceAsStream(config.getString("application.favicon"))) { + if (!faviconFile.exists()) + { + try (final InputStream stream = this.getClass().getResourceAsStream(config.getString("application.favicon"))) + { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int read = 0; - while (read != -1) { + while (read != -1) + { read = stream.read(buffer); - if (read > 0) { + if (read > 0) + { baos.write(buffer, 0, read); } } @@ -444,15 +478,20 @@ public ProteusApplication addDefaultRoutes(RoutingHandler router) faviconImageBuffer = ByteBuffer.wrap(baos.toByteArray()); } - } else { - try (final InputStream stream = Files.newInputStream(Paths.get(config.getString("application.favicon")))) { + } + else + { + try (final InputStream stream = Files.newInputStream(Paths.get(config.getString("application.favicon")))) + { ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[4096]; int read = 0; - while (read != -1) { + while (read != -1) + { read = stream.read(buffer); - if (read > 0) { + if (read > 0) + { baos.write(buffer, 0, read); } } @@ -467,7 +506,8 @@ public ProteusApplication addDefaultRoutes(RoutingHandler router) exchange.getResponseSender().send(faviconImageBuffer); }); - } catch (Exception e) { + } catch (Exception e) + { log.error("Error adding favicon route.", e); } } @@ -483,6 +523,7 @@ public ProteusApplication addDefaultRoutes(RoutingHandler router) */ public ProteusApplication setRootHandlerClass(Class rootHandlerClass) { + this.rootHandlerClass = rootHandlerClass; return this; } @@ -495,6 +536,7 @@ public ProteusApplication setRootHandlerClass(Class rootH */ public ProteusApplication setRootHandler(HttpHandler rootHandler) { + this.rootHandler = rootHandler; return this; } @@ -506,6 +548,7 @@ public ProteusApplication setRootHandler(HttpHandler rootHandler) */ public ProteusApplication setServerConfigurationFunction(Function serverConfigurationFunction) { + this.serverConfigurationFunction = serverConfigurationFunction; return this; } @@ -515,6 +558,7 @@ public ProteusApplication setServerConfigurationFunction(Function getPorts() { + return ports; } - /** * @return The Undertow server */ public Undertow getUndertow() { + return undertow; } - public void printStatus() { + Config globalHeaders = config.getConfig("globalHeaders"); Map globalHeadersParameters = globalHeaders.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().render())); @@ -566,7 +612,7 @@ public void printStatus() List tableHeaders = Arrays.asList("Header", "Value"); List> tableRows = globalHeadersParameters.entrySet().stream().map(e -> Arrays.asList(e.getKey(), e.getValue())) - .collect(Collectors.toList()); + .collect(Collectors.toList()); TablePrinter printer = new TablePrinter(tableHeaders, tableRows); @@ -578,7 +624,7 @@ public void printStatus() tableRows = this.registeredEndpoints.stream().sorted().map(e -> Arrays.asList(e.getMethod().toString(), e.getPathTemplate(), String.format("[%s]", e.getConsumes()), String.format("[%s]", e.getProduces()), String.format("(%s.%s)", e.getControllerName(), e.getControllerMethod()))) - .collect(Collectors.toList()); + .collect(Collectors.toList()); printer = new TablePrinter(tableHeaders, tableRows); @@ -593,7 +639,7 @@ public void printStatus() tableRows = serviceStateMap.asMap().entrySet().stream().flatMap(e -> e.getValue().stream().map(s -> Arrays.asList(s.getClass().getSimpleName(), e.getKey().toString(), DurationFormatUtils.formatDurationHMS(serviceStartupTimeMap.get(s))))) - .collect(Collectors.toList()); + .collect(Collectors.toList()); printer = new TablePrinter(tableHeaders, tableRows); @@ -602,5 +648,4 @@ public void printStatus() log.info(sb.toString()); } - } diff --git a/proteus-core/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java b/proteus-core/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java index 062f89a..e0411ae 100644 --- a/proteus-core/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java +++ b/proteus-core/src/main/java/io/sinistral/proteus/server/handlers/HandlerGenerator.java @@ -21,6 +21,7 @@ import io.undertow.server.handlers.form.MultiPartParserDefinition; import io.undertow.util.Headers; import io.undertow.util.HttpString; +import net.openhft.compiler.CachedCompiler; import net.openhft.compiler.CompilerUtils; import org.apache.commons.lang3.StringUtils; import org.reflections.Reflections; @@ -112,12 +113,12 @@ public Class> compileClass() try { this.generateRoutes(); - log.debug("\n\nGenerated Class Source:\n\n" + this.sourceString); + log.debug("\n\nGenerated Class Source:\n\n{}", this.sourceString); - return CompilerUtils.CACHED_COMPILER.loadFromJava(packageName + "." + className, this.sourceString); + return new CachedCompiler(null,null).loadFromJava(packageName + "." + className, this.sourceString); } catch (Exception e) { - log.error(e.getMessage(), e); + log.error("Failed to compile {}\nSource:\n{}",packageName + "." + className,this.sourceString, e); return null; } } @@ -125,9 +126,8 @@ public Class> compileClass() /** * Generates the routing Java source code */ - protected void generateRoutes() + protected void generateRoutes() throws Exception { - try { TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC) .addSuperinterface(ParameterizedTypeName.get(Supplier.class, RoutingHandler.class)); @@ -183,9 +183,7 @@ protected void generateRoutes() this.sourceString = sb.toString(); - } catch (Exception e) { - log.error(e.getMessage(), e); - } + } protected void addClassMethodHandlers(TypeSpec.Builder typeBuilder, Class clazz) diff --git a/proteus-core/src/test/resources/logback-test.xml b/proteus-core/src/test/resources/logback-test.xml index 3c2ac50..fe9b61b 100644 --- a/proteus-core/src/test/resources/logback-test.xml +++ b/proteus-core/src/test/resources/logback-test.xml @@ -12,7 +12,7 @@ - + @@ -45,7 +45,7 @@ - +