From d641ead14bc8d7ad84f939f7fa7178ad69f1617c Mon Sep 17 00:00:00 2001 From: joshua bauer Date: Wed, 27 Sep 2017 13:08:23 -0700 Subject: [PATCH] Removed default application.conf. --- conf/application.conf | 118 ------ pom.xml | 19 +- .../proteus/modules/ConfigModule.java | 3 +- .../server/handlers/ProteusHandler.java | 374 ++++++++++++++++++ .../proteus/server/DefaultServer.java | 1 + 5 files changed, 395 insertions(+), 120 deletions(-) delete mode 100644 conf/application.conf create mode 100644 src/main/java/io/sinistral/proteus/server/handlers/ProteusHandler.java diff --git a/conf/application.conf b/conf/application.conf deleted file mode 100644 index cbbb48b..0000000 --- a/conf/application.conf +++ /dev/null @@ -1,118 +0,0 @@ - -application { - - env = dev - - version = "1.0" - - name="proteus" - - path = "/v1" - - host = "localhost" - - ports { - http = 8090 - https = 8443 - } - - charset = UTF-8 - - fallbackHandler = "io.sinistral.proteus.server.handlers.ServerFallbackHandler" - - defaultResponseListener = "io.sinistral.proteus.server.handlers.ServerDefaultResponseListener" - - tmpdir = ${java.io.tmpdir}/${application.name} - - favicon = "/io/sinistral/proteus/favicon.ico" - -} - -api.version="v1" - -globalHeaders -{ -# Access-Control-Allow-Origin: "*" -# Access-Control-Allow-Methods: "*" -# Access-Control-Allow-Headers: "*" - Server = ${application.name} -} - -health { - statusPath = "/internal/status" -} - - - -assets { - # the base path assets will be server from - path = "/public" - # the directory to load the assets from - dir = "./assets" - cache { - # cache timeout for the assets - time = 500 - } -} - - - -swagger { - # the path that has an index.html template and theme css files - resourcePrefix="io/sinistral/proteus/swagger" - # swagger version - swagger="2.0" - info { - # swagger info title - title = ${application.name} - # swagger info version - version = ${application.version} - } - # swagger-ui theme from ostranme's swagger-ui-themes, the following are built-in [feeling-blue, flattop, material, monokai, muted, newspaper, outline] - # specifying a different name causes the SwaggerService to search in {swagger.resourcePrefix}/themes for a file named "theme-{swagger.theme}.css" - theme="default" - # where the swagger endpoints will be mounted - basePath= ${application.path}"/swagger" - # where redoc will be mounted relative to swagger base path - redocPath= "redoc" - #the name of the spec file - specFilename="swagger.json" - consumes = ["application/json"] - produces = ["application/json"] - port = ${application.ports.http} - -} - -undertow -{ - server { - enableHttp2 = false - alwaysSetDate = true - alwaysSetKeepAlive = false - recordRequestStartTime = false - maxEntitySize = 100M - bufferPipelinedData = false - } - - socket { - backlog = 10000 - } - - - ssl { - enabled=false - keystorePath="development.jks" - truststorePath="development.ts" - keystorePassword="password" - truststorePassword="password" - } - - enableHttp2=false - # x AvailableProcessors - ioThreads = 16 - workerThreads = 200 - bufferSize = 16K - directBuffers = true -} - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 9953d55..226d44e 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 io.sinistral proteus-core - 0.1.7.1-SNAPSHOT + 0.1.7.2-SNAPSHOT proteus core Proteus is an extremely light, fast, and flexible Java REST API framework built atop Undertow. http://github.com/noboomu/proteus @@ -116,6 +116,21 @@ + + maven-surefire-plugin + 2.20.1 + + + + org.apache.maven.surefire + surefire-junit47 + 2.20.1 + + + + -Dconfig.file=src/main/resources/reference.conf + + org.apache.maven.plugins maven-gpg-plugin @@ -184,6 +199,8 @@ test + + io.undertow undertow-core diff --git a/src/main/java/io/sinistral/proteus/modules/ConfigModule.java b/src/main/java/io/sinistral/proteus/modules/ConfigModule.java index 62580a6..84f6285 100644 --- a/src/main/java/io/sinistral/proteus/modules/ConfigModule.java +++ b/src/main/java/io/sinistral/proteus/modules/ConfigModule.java @@ -41,7 +41,7 @@ public class ConfigModule extends AbstractModule public ConfigModule() { - this.configFile = System.getenv("config.file"); + this.configFile = System.getProperty("config.file"); if(this.configFile == null) { @@ -57,6 +57,7 @@ public ConfigModule(String configFile) public ConfigModule(URL configURL) { this.configURL = configURL; + } diff --git a/src/main/java/io/sinistral/proteus/server/handlers/ProteusHandler.java b/src/main/java/io/sinistral/proteus/server/handlers/ProteusHandler.java new file mode 100644 index 0000000..c5639f8 --- /dev/null +++ b/src/main/java/io/sinistral/proteus/server/handlers/ProteusHandler.java @@ -0,0 +1,374 @@ +/** + * + */ +package io.sinistral.proteus.server.handlers; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.CopyOnWriteArrayList; + +import io.undertow.Handlers; +import io.undertow.predicate.Predicate; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.ResponseCodeHandler; +import io.undertow.server.handlers.cache.LRUCache; +import io.undertow.util.CopyOnWriteMap; +import io.undertow.util.HttpString; +import io.undertow.util.Methods; +import io.undertow.util.PathMatcher; +import io.undertow.util.PathTemplate; +import io.undertow.util.PathTemplateMatch; +import io.undertow.util.PathTemplateMatcher; + +/** + * @author jbauer + * + */ +public class ProteusHandler implements HttpHandler +{ + private final PathMatcher pathMatcher = new PathMatcher<>(); + + private final LRUCache> cache; + + private final Map> matches = new CopyOnWriteMap<>(); + // Matcher used to find if this instance contains matches for any http method for a path. + // This matcher is used to report if this instance can match a path for one of the http methods. + private final PathTemplateMatcher allMethodsMatcher = new PathTemplateMatcher<>(); + + // Handler called when no match was found and invalid method handler can't be invoked. + private volatile HttpHandler fallbackHandler = ResponseCodeHandler.HANDLE_404; + // Handler called when this instance can not match the http method but can match another http method. + // For example: For an exchange the POST method is not matched by this instance but at least one http method is + // matched for the same exchange. + // If this handler is null the fallbackHandler will be used. + private volatile HttpHandler invalidMethodHandler = ResponseCodeHandler.HANDLE_405; + + // If this is true then path matches will be added to the query parameters for easy access by later handlers. + + + public ProteusHandler(final HttpHandler defaultHandler) { + this(0); + pathMatcher.addPrefixPath("/", defaultHandler); + } + + public ProteusHandler(final HttpHandler defaultHandler, int cacheSize) { + this(cacheSize); + pathMatcher.addPrefixPath("/", defaultHandler); + } + + public ProteusHandler() { + this(0); + } + + public ProteusHandler(int cacheSize) { + if(cacheSize > 0) { + cache = new LRUCache<>(cacheSize, -1, true); + } else { + cache = null; + } + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + PathMatcher.PathMatch match = null; + boolean hit = false; + if(cache != null) { + match = cache.get(exchange.getRelativePath()); + hit = true; + } + if(match == null) { + match = pathMatcher.match(exchange.getRelativePath()); + } + if (match.getValue() == null) { + handleRouterRequest(exchange); + return; + } + if(hit) { + cache.add(exchange.getRelativePath(), match); + } + exchange.setRelativePath(match.getRemaining()); + if(exchange.getResolvedPath().isEmpty()) { + //first path handler, we can just use the matched part + exchange.setResolvedPath(match.getMatched()); + } else { + //already something in the resolved path + StringBuilder sb = new StringBuilder(exchange.getResolvedPath().length() + match.getMatched().length()); + sb.append(exchange.getResolvedPath()); + sb.append(match.getMatched()); + exchange.setResolvedPath(sb.toString()); + } + match.getValue().handleRequest(exchange); + } + + public void handleRouterRequest(HttpServerExchange exchange) throws Exception { + + PathTemplateMatcher matcher = matches.get(exchange.getRequestMethod()); + if (matcher == null) { + handleNoMatch(exchange); + return; + } + PathTemplateMatcher.PathMatchResult match = matcher.match(exchange.getRelativePath()); + if (match == null) { + handleNoMatch(exchange); + return; + } + exchange.putAttachment(PathTemplateMatch.ATTACHMENT_KEY, match); + + for (Map.Entry entry : match.getParameters().entrySet()) { + exchange.addQueryParam(entry.getKey(), entry.getValue()); + + } + for (HandlerHolder handler : match.getValue().predicatedHandlers) { + if (handler.predicate.resolve(exchange)) { + handler.handler.handleRequest(exchange); + return; + } + } + if (match.getValue().defaultHandler != null) { + match.getValue().defaultHandler.handleRequest(exchange); + } else { + fallbackHandler.handleRequest(exchange); + } + } + + + /** + * Adds a path prefix and a handler for that path. If the path does not start + * with a / then one will be prepended. + *

+ * The match is done on a prefix bases, so registering /foo will also match /foo/bar. + * Though exact path matches are taken into account before prefix path matches. So + * if an exact path match exists it's handler will be triggered. + *

+ * If / is specified as the path then it will replace the default handler. + * + * @param path If the request contains this prefix, run handler. + * @param handler The handler which is activated upon match. + * @return The resulting PathHandler after this path has been added to it. + */ + public synchronized ProteusHandler addPrefixPath(final String path, final HttpHandler handler) { + Handlers.handlerNotNull(handler); + pathMatcher.addPrefixPath(path, handler); + return this; + } + + /** + * If the request path is exactly equal to the given path, run the handler. + *

+ * Exact paths are prioritized higher than prefix paths. + * + * @param path If the request path is exactly this, run handler. + * @param handler Handler run upon exact path match. + * @return The resulting PathHandler after this path has been added to it. + */ + public synchronized ProteusHandler addExactPath(final String path, final HttpHandler handler) { + Handlers.handlerNotNull(handler); + pathMatcher.addExactPath(path, handler); + return this; + } + + public synchronized ProteusHandler removePrefixPath(final String path) { + pathMatcher.removePrefixPath(path); + return this; + } + + public synchronized ProteusHandler removeExactPath(final String path) { + pathMatcher.removeExactPath(path); + return this; + } + + public synchronized ProteusHandler clearPaths() { + pathMatcher.clearPaths(); + return this; + } + + private void handleNoMatch(final HttpServerExchange exchange) throws Exception { + // if invalidMethodHandler is null we fail fast without matching with allMethodsMatcher + if (invalidMethodHandler != null && allMethodsMatcher.match(exchange.getRelativePath()) != null) { + invalidMethodHandler.handleRequest(exchange); + return; + } + fallbackHandler.handleRequest(exchange); + } + + public synchronized ProteusHandler add(final String method, final String template, HttpHandler handler) { + return add(new HttpString(method), template, handler); + } + + public synchronized ProteusHandler add(HttpString method, String template, HttpHandler handler) { + PathTemplateMatcher matcher = matches.get(method); + if (matcher == null) { + matches.put(method, matcher = new PathTemplateMatcher<>()); + } + RoutingMatch res = matcher.get(template); + if (res == null) { + matcher.add(template, res = new RoutingMatch()); + } + if (allMethodsMatcher.get(template) == null) { + allMethodsMatcher.add(template, res); + } + res.defaultHandler = handler; + return this; + } + + public synchronized ProteusHandler get(final String template, HttpHandler handler) { + return add(Methods.GET, template, handler); + } + + public synchronized ProteusHandler post(final String template, HttpHandler handler) { + return add(Methods.POST, template, handler); + } + + public synchronized ProteusHandler put(final String template, HttpHandler handler) { + return add(Methods.PUT, template, handler); + } + + public synchronized ProteusHandler delete(final String template, HttpHandler handler) { + return add(Methods.DELETE, template, handler); + } + + public synchronized ProteusHandler add(final String method, final String template, Predicate predicate, HttpHandler handler) { + return add(new HttpString(method), template, predicate, handler); + } + + public synchronized ProteusHandler add(HttpString method, String template, Predicate predicate, HttpHandler handler) { + PathTemplateMatcher matcher = matches.get(method); + if (matcher == null) { + matches.put(method, matcher = new PathTemplateMatcher<>()); + } + RoutingMatch res = matcher.get(template); + if (res == null) { + matcher.add(template, res = new RoutingMatch()); + } + if (allMethodsMatcher.get(template) == null) { + allMethodsMatcher.add(template, res); + } + res.predicatedHandlers.add(new HandlerHolder(predicate, handler)); + return this; + } + + public synchronized ProteusHandler get(final String template, Predicate predicate, HttpHandler handler) { + return add(Methods.GET, template, predicate, handler); + } + + public synchronized ProteusHandler post(final String template, Predicate predicate, HttpHandler handler) { + return add(Methods.POST, template, predicate, handler); + } + + public synchronized ProteusHandler put(final String template, Predicate predicate, HttpHandler handler) { + return add(Methods.PUT, template, predicate, handler); + } + + public synchronized ProteusHandler delete(final String template, Predicate predicate, HttpHandler handler) { + return add(Methods.DELETE, template, predicate, handler); + } + + public synchronized ProteusHandler addAll(ProteusHandler routingHandler) { + for (Entry> entry : routingHandler.getMatches().entrySet()) { + HttpString method = entry.getKey(); + PathTemplateMatcher matcher = matches.get(method); + if (matcher == null) { + matches.put(method, matcher = new PathTemplateMatcher<>()); + } + matcher.addAll(entry.getValue()); + // If we use allMethodsMatcher.addAll() we can have duplicate + // PathTemplates which we want to ignore here so it does not crash. + for (PathTemplate template : entry.getValue().getPathTemplates()) { + if (allMethodsMatcher.get(template.getTemplateString()) == null) { + allMethodsMatcher.add(template, new RoutingMatch()); + } + } + } + return this; + } + + /** + * + * Removes the specified route from the handler + * + * @param method The method to remove + * @param path the path tempate to remove + * @return this handler + */ + public ProteusHandler remove(HttpString method, String path) { + PathTemplateMatcher handler = matches.get(method); + if(handler != null) { + handler.remove(path); + } + return this; + } + + + /** + * + * Removes the specified route from the handler + * + * @param path the path tempate to remove + * @return this handler + */ + public ProteusHandler remove(String path) { + allMethodsMatcher.remove(path); + return this; + } + + Map> getMatches() { + return matches; + } + + /** + * @return Handler called when no match was found and invalid method handler can't be invoked. + */ + public HttpHandler getFallbackHandler() { + return fallbackHandler; + } + + /** + * @param fallbackHandler Handler that will be called when no match was found and invalid method handler can't be + * invoked. + * @return This instance. + */ + public ProteusHandler setFallbackHandler(HttpHandler fallbackHandler) { + this.fallbackHandler = fallbackHandler; + return this; + } + + /** + * @return Handler called when this instance can not match the http method but can match another http method. + */ + public HttpHandler getInvalidMethodHandler() { + return invalidMethodHandler; + } + + /** + * Sets the handler called when this instance can not match the http method but can match another http method. + * For example: For an exchange the POST method is not matched by this instance but at least one http method matched + * for the exchange. + * If this handler is null the fallbackHandler will be used. + * + * @param invalidMethodHandler Handler that will be called when this instance can not match the http method but can + * match another http method. + * @return This instance. + */ + public ProteusHandler setInvalidMethodHandler(HttpHandler invalidMethodHandler) { + this.invalidMethodHandler = invalidMethodHandler; + return this; + } + + private static class RoutingMatch { + + final List predicatedHandlers = new CopyOnWriteArrayList<>(); + volatile HttpHandler defaultHandler; + + } + + private static class HandlerHolder { + final Predicate predicate; + final HttpHandler handler; + + private HandlerHolder(Predicate predicate, HttpHandler handler) { + this.predicate = predicate; + this.handler = handler; + } + } +} diff --git a/src/test/java/io/sinistral/proteus/server/DefaultServer.java b/src/test/java/io/sinistral/proteus/server/DefaultServer.java index d0bb850..e8e4eb1 100644 --- a/src/test/java/io/sinistral/proteus/server/DefaultServer.java +++ b/src/test/java/io/sinistral/proteus/server/DefaultServer.java @@ -61,6 +61,7 @@ public void testFinished(Description description) throws Exception private static void runInternal(final RunNotifier notifier) { + System.out.println(System.getenv("config.file")); if (first) { first = false;