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(a)analyticspot.com> wrote:
Thanks Greg and Bill!
On Wed, Sep 7, 2016 at 6:20 AM Bill O'Neil <bill(a)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(a)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/theh...
>>
>>
https://github.com/greg-hellings/gully/blob/master/src/main/java/com/theh...
>>
>> 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(a)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(a)lists.jboss.org
>> >
https://lists.jboss.org/mailman/listinfo/undertow-dev
>>
>> _______________________________________________
>> undertow-dev mailing list
>> undertow-dev(a)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