/* * JBoss, Home of Professional Open Source. * Copyright 2014 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.undertow.server; import io.undertow.predicate.Predicate; import io.undertow.server.handlers.ResponseCodeHandler; import io.undertow.util.CopyOnWriteMap; import io.undertow.util.HttpString; import io.undertow.util.Methods; import io.undertow.util.PathTemplate; import io.undertow.util.PathTemplateMatch; import io.undertow.util.PathTemplateMatcher; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CopyOnWriteArrayList; /** * A Handler that handles the common case of routing via path template and method name. * * @author Stuart Douglas */ public class RoutingHandler implements HttpHandler { // Matcher objects grouped by http methods. private final Map<HttpString, PathTemplateMatcher<RoutingMatch>> 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<RoutingMatch> 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. private final boolean rewriteQueryParameters; public RoutingHandler(boolean rewriteQueryParameters) { this.rewriteQueryParameters = rewriteQueryParameters; } public RoutingHandler() { this.rewriteQueryParameters = true; } @Override public void handleRequest(HttpServerExchange exchange) throws Exception { PathTemplateMatcher<RoutingMatch> matcher = matches.get(exchange.getRequestMethod()); if (matcher == null) { handleNoMatch(exchange); return; } PathTemplateMatcher.PathMatchResult<RoutingMatch> match = matcher.match(exchange.getRelativePath()); if (match == null) { handleNoMatch(exchange); return; } exchange.putAttachment(PathTemplateMatch.ATTACHMENT_KEY, match); if (rewriteQueryParameters) { for (Map.Entry<String, String> 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); } } /** * Handles the case in with a match was not found for the http method but might exist for another http method. * For example: POST not matched for a path but at least one match exists for same path. * * @param exchange The object for which its handled the "no match" case. * @throws Exception */ 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 RoutingHandler add(final String method, final String template, HttpHandler handler) { return add(new HttpString(method), template, handler); } public synchronized RoutingHandler add(HttpString method, String template, HttpHandler handler) { PathTemplateMatcher<RoutingMatch> 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 RoutingHandler get(final String template, HttpHandler handler) { return add(Methods.GET, template, handler); } public synchronized RoutingHandler post(final String template, HttpHandler handler) { return add(Methods.POST, template, handler); } public synchronized RoutingHandler put(final String template, HttpHandler handler) { return add(Methods.PUT, template, handler); } public synchronized RoutingHandler delete(final String template, HttpHandler handler) { return add(Methods.DELETE, template, handler); } public synchronized RoutingHandler add(final String method, final String template, Predicate predicate, HttpHandler handler) { return add(new HttpString(method), template, predicate, handler); } public synchronized RoutingHandler add(HttpString method, String template, Predicate predicate, HttpHandler handler) { PathTemplateMatcher<RoutingMatch> 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 RoutingHandler get(final String template, Predicate predicate, HttpHandler handler) { return add(Methods.GET, template, predicate, handler); } public synchronized RoutingHandler post(final String template, Predicate predicate, HttpHandler handler) { return add(Methods.POST, template, predicate, handler); } public synchronized RoutingHandler put(final String template, Predicate predicate, HttpHandler handler) { return add(Methods.PUT, template, predicate, handler); } public synchronized RoutingHandler delete(final String template, Predicate predicate, HttpHandler handler) { return add(Methods.DELETE, template, predicate, handler); } public synchronized RoutingHandler addAll(RoutingHandler routingHandler) { for (Entry<HttpString, PathTemplateMatcher<RoutingMatch>> entry : routingHandler.getMatches().entrySet()) { HttpString method = entry.getKey(); PathTemplateMatcher<RoutingMatch> 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 RoutingHandler remove(HttpString method, String path) { PathTemplateMatcher<RoutingMatch> 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 RoutingHandler remove(String path) { allMethodsMatcher.remove(path); return this; } Map<HttpString, PathTemplateMatcher<RoutingMatch>> 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 RoutingHandler 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 RoutingHandler setInvalidMethodHandler(HttpHandler invalidMethodHandler) { this.invalidMethodHandler = invalidMethodHandler; return this; } private static class RoutingMatch { final List<HandlerHolder> 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; } } }