Hey all,

I added my own router handler that does everything I need it to do. It's pretty well documented and very well tested. I'd be willing to submit a pull request to have this added to Undertow if that'd be valuable to anyone. Here's a taste of the code (the core stuff is in PathTrie):

package com.analyticspot.uservices.server.router;

import com.analyticspot.httputils.HttpVerb;
import com.analyticspot.httputils.ResponseCodes;
import com.memoizrlabs.retrooptional.Optional;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;

import java.util.Map;
import java.util.TreeMap;

/**
 * An HttpHandler that routes requests to other handlers based on paths and path templates. It is thread safe to
 * search this class concurrently (match paths) but adding paths is not thread safe. Thus the expected use-case is
 * that all paths are set up before the handler is added as an Undertow listener.
 *
 * <p>The rules for path matching are:
 * <ul>
 *   <li>All paths must begin with a "/"</li>
 *   <li>Wildcard matches are allowed by adding "{X}" to the path. Then {@code X} and what matched it are attached
 *   to the request in a {@code Map} from {@code X} to the value that matched. For example, if the added path was
 *   "/foo/{name}/stuff/{age}" and the observed path was "/foo/bar/stuff/22" then the attachment would have a map
 *   like "{name: bar, age: 22}.</li>
 *   <li>Prefix paths are allowed. Such paths will match anything that begins with the provided prefix.</li>
 *   <li>Exact matches and/or longer matches take precedence over both prefix and wildcard matches. Thus, given the
 *   wildcard path "/foo/{name}", the prefix path "/foo/baz", and the exact path "/foo/bar", we would expect the
 *   following matches:
 *   <table>
 *     <tr><td>Observed Path</td><td>Matched Path</td></tr>
 *     <tr><td>/foo/bar</td><td>/foo/bar</td></tr>
 *     <tr><td>/foo/baz</td><td>/foo/baz</td></tr>
 *     <tr><td>/foo/baz/bump</td><td>/foo/baz</td></tr>
 *     <tr><td>/foo/gourdy</td><td>/foo/{name}</td></tr>
 *   </table>
 *   </li>
 *   <li>This is not a "backtracking" matcher. Thus, given a prefix path of "/foo/bar" and an exact path
 *   "/foo/bar/baz", the observed path "/foo/bar/baz/bizzle" would have <b>no match</b> because "/foo/bar/baz"
 *   doesn't match and we do not backtrack to the shorter "/foo/bar" prefix to see if it matches.</li>
 *   <li>If both a prefix and a wildcard match, the wildcard takes precendence. Thus given prefix path "/foo" and
 *   wildcard path "/foo/{name}", the match for obeserved path "/foo/bar" would be the wildcard path.</li>
 * </ul>
 *
 * <p>We added our own router due to limitations in the Undertow PathTemplateHandler and RoutingHandler. See
 * http://lists.jboss.org/pipermail/undertow-dev/2016-September/001685.html for details.
 */
public class PathRoutingHandler implements HttpHandler {
  public static AttachmentKey<PathMatch> PATH_MATCH_KEY = AttachmentKey.create(PathMatch.class);

  // Map from HTTP verb to the PathTrie for the handlers for that route.
  private final Map<HttpVerb, PathTrie> verbToPathTrie = new TreeMap<>();
  // If present and none of the paths match this handler will be called.
  Optional<HttpHandler> fallbackHandler;

  public PathRoutingHandler() {
    fallbackHandler = Optional.empty();
  }

  /**
   * Provides a fallback handler which will handle the request if nothing in the trie matches.
   */
  public PathRoutingHandler(HttpHandler fallbackHandler) {
    this.fallbackHandler = Optional.of(fallbackHandler);
  }

  /**
   * Adds a handler for the given URL/verb combination. The path can be a template as per the class comment.
   */
  public void addHandler(HttpVerb verb, String path, HttpHandler handler) {
    PathTrie trie = getTrie(verb);
    trie.addPath(path, handler);
  }

  /**
   * Like {@link #addHandler(HttpVerb, String, HttpHandler)}but add a prefix handler.
   */
  public void addPrefixHandler(HttpVerb verb, String path, HttpHandler handler) {
    PathTrie trie = getTrie(verb);
    trie.addPrefixPath(path, handler);
  }

  private PathTrie getTrie(HttpVerb verb) {
    if (!verbToPathTrie.containsKey(verb)) {
      PathTrie trie = new PathTrie();
      verbToPathTrie.put(verb, trie);
      return trie;
    } else {
      return verbToPathTrie.get(verb);
    }
  }

  @Override
  public void handleRequest(HttpServerExchange exchange) throws Exception {
    HttpVerb verb = HttpVerb.fromString(exchange.getRequestMethod().toString());
    PathTrie trie = verbToPathTrie.get(verb);
    if (trie == null) {
      handleNoMatch(exchange);
    } else {
      Optional<PathMatch> optMatch = trie.findMatch(exchange.getRelativePath());
      if (optMatch.isPresent()) {
        exchange.putAttachment(PATH_MATCH_KEY, optMatch.get());
        optMatch.get().getHandler().handleRequest(exchange);
      } else {
        handleNoMatch(exchange);
      }
    }
  }

  private void handleNoMatch(HttpServerExchange exchange) throws Exception {
    if (fallbackHandler.isPresent()) {
      fallbackHandler.get().handleRequest(exchange);
    } else {
      exchange.setStatusCode(ResponseCodes.NOT_FOUND.getCode());
      exchange.endExchange();
    }
  }
}

-- Oliver

On Wed, Sep 7, 2016 at 11:35 AM Oliver Dain <oliver@analyticspot.com> wrote:
Thanks Greg and Bill!

On Wed, Sep 7, 2016 at 6:20 AM Bill O'Neil <bill@dartalley.com> wrote:
I did something similar to Greg by delegating to a RoutingHandler which has a PathHandler fallback.


This allows me to add prefix routes but still not flexible enough for all uses cases. The main drawback with this approach is the prefix handler doesn't route based on Verb but it has worked for all of my use cases so far.

I would also like to know if there is a way the RoutingHandler can be extended to have wildcard functionality.

On Wed, Sep 7, 2016 at 8:08 AM, Greg Hellings <greg.hellings@gmail.com> wrote:
Oliver,

I found it very difficult to work with the PathTemplateHandler and its
set of matching operations seemed very minimal to me. I also found no
efficient way to mix-and-match between full Paths and PathTemplate
entries. So I created my own handler class that gave the ability to
match based on both HTTP Verb and various path components.

https://github.com/greg-hellings/gully/blob/master/src/main/java/com/thehellings/gully/Router.java
https://github.com/greg-hellings/gully/blob/master/src/main/java/com/thehellings/gully/PlainRouter.java

I haven't used it extensively, but in my own local testing it seems to
work exactly the way I wanted - it's still just an HttpHandler
underneath, so it can be placed anywhere in a handler chain. And it
operates on the result of getRelativePath, so you can nest them within
each other, or place them within other components in a handler chain
and the class should operate properly.

The whole set of handler and its dependent classes should be available
as a Maven artifact if you want to play with it. If this sort of
functionality exists somewhere in core Undertow, I've been entirely
unable to decipher it and locate its functioning.

--Greg

On Tue, Sep 6, 2016 at 9:41 PM, Oliver Dain <oliver@analyticspot.com> wrote:
> Hey all,
>
> I haven't seen any documentation on the format of a template accepted by a
> PathTemplateHandler. I know the basics: "/foo", "/foo/{userId}", etc. but
> are wildcards allowed? Is there a way to specify a handler for anything with
> a certain prefix? If two handlers would match how is the tie broken, etc.
>
> Thanks,
> Oliver
> --
> CTO, Analytic Spot
> 44 West Broadway #222
> Eugene, OR 97401
> analyticspot.com425-296-6556
> www.linkedin.com/in/oliverdain
>
> _______________________________________________
> undertow-dev mailing list
> undertow-dev@lists.jboss.org
> https://lists.jboss.org/mailman/listinfo/undertow-dev

_______________________________________________
undertow-dev mailing list
undertow-dev@lists.jboss.org
https://lists.jboss.org/mailman/listinfo/undertow-dev

--
CTO, Analytic Spot
44 West Broadway #222
Eugene, OR 97401
analyticspot.com • 425-296-6556
www.linkedin.com/in/oliverdain
--
CTO, Analytic Spot
44 West Broadway #222
Eugene, OR 97401
analyticspot.com • 425-296-6556
www.linkedin.com/in/oliverdain