[undertow-dev] Docs for templates

Oliver Dain oliver at analyticspot.com
Wed Sep 7 20:58:37 EDT 2016


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 at analyticspot.com> wrote:

> Thanks Greg and Bill!
>
> On Wed, Sep 7, 2016 at 6:20 AM Bill O'Neil <bill at dartalley.com> wrote:
>
>> I did something similar to Greg by delegating to a RoutingHandler which
>> has a PathHandler fallback.
>>
>> https://gist.github.com/billoneil/08b1648a3b2a849e02c57e133bd6d45c
>>
>> 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 at 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 at 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.com • 425-296-6556
>>> > www.linkedin.com/in/oliverdain
>>> >
>>> > _______________________________________________
>>> > undertow-dev mailing list
>>> > undertow-dev at lists.jboss.org
>>> > https://lists.jboss.org/mailman/listinfo/undertow-dev
>>>
>>> _______________________________________________
>>> undertow-dev mailing list
>>> undertow-dev at 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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/undertow-dev/attachments/20160908/8af5ff27/attachment.html 


More information about the undertow-dev mailing list