All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.runtime.rest.handler.router.Router Maven / Gradle / Ivy

There is a newer version: 1.13.6
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.flink.runtime.rest.handler.router;

import org.apache.flink.shaded.netty4.io.netty.handler.codec.http.HttpMethod;
import org.apache.flink.shaded.netty4.io.netty.handler.codec.http.QueryStringDecoder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * This is adopted and simplified code from tv.cntt:netty-router library. Compared to original version this one
 * defines and guarantees an order of pattern matching routes, drops reverse routing feature and restores
 * {@link RouterHandler} which was dropped in tv.cntt:netty-router 2.X.X. Original code:
 * https://github.com/sinetja/netty-router/blob/2.2.0/src/main/java/io/netty/handler/codec/http/router/Router.java
 *
 * 

Router that contains information about both route matching orders and * HTTP request methods. * *

Routes are guaranteed to be matched in order of their addition. * *

Route targets can be any type. In the below example, targets are classes: * *

 * {@code
 * Router router = new Router()
 *   .GET      ("/articles",     IndexHandler.class)
 *   .GET      ("/articles/:id", ShowHandler.class)
 *   .POST     ("/articles",     CreateHandler.class)
 *   .GET      ("/download/:*",  DownloadHandler.class)  // ":*" must be the last token
 *   .GET_FIRST("/articles/new", NewHandler.class);      // This will be matched first
 * }
 * 
* *

Slashes at both ends are ignored. These are the same: * *

 * {@code
 * router.GET("articles",   IndexHandler.class);
 * router.GET("/articles",  IndexHandler.class);
 * router.GET("/articles/", IndexHandler.class);
 * }
 * 
* *

You can remove routes by target or by path pattern: * *

 * {@code
 * router.removePathPattern("/articles");
 * }
 * 
* *

To match requests use {@link #route(HttpMethod, String)}. * *

From the {@link RouteResult} you can extract params embedded in * the path and from the query part of the request URI. * *

{@link #notFound(Object)} will be used as the target when there's no match. * *

 * {@code
 * router.notFound(My404Handler.class);
 * }
 * 
*/ public class Router { private final Map> routers = new HashMap>(); private final MethodlessRouter anyMethodRouter = new MethodlessRouter(); private T notFound; //-------------------------------------------------------------------------- // Design decision: // We do not allow access to routers and anyMethodRouter, because we don't // want to expose MethodlessRouter, OrderlessRouter, and PathPattern. // Exposing those will complicate the use of this package. /** * Helper for toString. */ private static void aggregateRoutes( String method, Map routes, List accMethods, List accPatterns, List accTargets) { for (Map.Entry entry : routes.entrySet()) { accMethods.add(method); accPatterns.add("/" + entry.getKey().pattern()); accTargets.add(targetToString(entry.getValue())); } } /** * Helper for toString. */ private static int maxLength(List coll) { int max = 0; for (String e : coll) { int length = e.length(); if (length > max) { max = length; } } return max; } /** * Helper for toString. * *

For example, returns * "io.netty.example.http.router.HttpRouterServerHandler" instead of * "class io.netty.example.http.router.HttpRouterServerHandler" */ private static String targetToString(Object target) { if (target instanceof Class) { return ((Class) target).getName(); } else { return target.toString(); } } /** * Returns the fallback target for use when there's no match at * {@link #route(HttpMethod, String)}. */ public T notFound() { return notFound; } /** * Returns the number of routes in this router. */ public int size() { int ret = anyMethodRouter.size(); for (MethodlessRouter router : routers.values()) { ret += router.size(); } return ret; } //-------------------------------------------------------------------------- /** * Add route. * *

A path pattern can only point to one target. This method does nothing if the pattern * has already been added. */ public Router addRoute(HttpMethod method, String pathPattern, T target) { getMethodlessRouter(method).addRoute(pathPattern, target); return this; } //-------------------------------------------------------------------------- /** * Sets the fallback target for use when there's no match at * {@link #route(HttpMethod, String)}. */ public Router notFound(T target) { this.notFound = target; return this; } private MethodlessRouter getMethodlessRouter(HttpMethod method) { if (method == null) { return anyMethodRouter; } MethodlessRouter router = routers.get(method); if (router == null) { router = new MethodlessRouter(); routers.put(method, router); } return router; } /** * Removes the route specified by the path pattern. */ public void removePathPattern(String pathPattern) { for (MethodlessRouter router : routers.values()) { router.removePathPattern(pathPattern); } anyMethodRouter.removePathPattern(pathPattern); } /** * If there's no match, returns the result with {@link #notFound(Object) notFound} * as the target if it is set, otherwise returns {@code null}. */ public RouteResult route(HttpMethod method, String path) { return route(method, path, Collections.emptyMap()); } public RouteResult route(HttpMethod method, String path, Map> queryParameters) { MethodlessRouter router = routers.get(method); if (router == null) { router = anyMethodRouter; } String[] tokens = decodePathTokens(path); RouteResult ret = router.route(path, path, queryParameters, tokens); if (ret != null) { return new RouteResult(path, path, ret.pathParams(), queryParameters, ret.target()); } if (router != anyMethodRouter) { ret = anyMethodRouter.route(path, path, queryParameters, tokens); if (ret != null) { return new RouteResult(path, path, ret.pathParams(), queryParameters, ret.target()); } } if (notFound != null) { return new RouteResult(path, path, Collections.emptyMap(), queryParameters, notFound); } return null; } //-------------------------------------------------------------------------- private String[] decodePathTokens(String uri) { // Need to split the original URI (instead of QueryStringDecoder#path) then decode the tokens (components), // otherwise /test1/123%2F456 will not match /test1/:p1 int qPos = uri.indexOf("?"); String encodedPath = (qPos >= 0) ? uri.substring(0, qPos) : uri; String[] encodedTokens = PathPattern.removeSlashesAtBothEnds(encodedPath).split("/"); String[] decodedTokens = new String[encodedTokens.length]; for (int i = 0; i < encodedTokens.length; i++) { String encodedToken = encodedTokens[i]; decodedTokens[i] = QueryStringDecoder.decodeComponent(encodedToken); } return decodedTokens; } /** * Returns allowed methods for a specific URI. * *

For {@code OPTIONS *}, use {@link #allAllowedMethods()} instead of this method. */ public Set allowedMethods(String uri) { QueryStringDecoder decoder = new QueryStringDecoder(uri); String[] tokens = PathPattern.removeSlashesAtBothEnds(decoder.path()).split("/"); if (anyMethodRouter.anyMatched(tokens)) { return allAllowedMethods(); } Set ret = new HashSet(routers.size()); for (Map.Entry> entry : routers.entrySet()) { MethodlessRouter router = entry.getValue(); if (router.anyMatched(tokens)) { HttpMethod method = entry.getKey(); ret.add(method); } } return ret; } /** * Returns all methods that this router handles. For {@code OPTIONS *}. */ public Set allAllowedMethods() { if (anyMethodRouter.size() > 0) { Set ret = new HashSet(9); ret.add(HttpMethod.CONNECT); ret.add(HttpMethod.DELETE); ret.add(HttpMethod.GET); ret.add(HttpMethod.HEAD); ret.add(HttpMethod.OPTIONS); ret.add(HttpMethod.PATCH); ret.add(HttpMethod.POST); ret.add(HttpMethod.PUT); ret.add(HttpMethod.TRACE); return ret; } else { return new HashSet(routers.keySet()); } } /** * Returns visualized routing rules. */ @Override public String toString() { // Step 1/2: Dump routers and anyMethodRouter in order int numRoutes = size(); List methods = new ArrayList(numRoutes); List patterns = new ArrayList(numRoutes); List targets = new ArrayList(numRoutes); // For router for (Entry> e : routers.entrySet()) { HttpMethod method = e.getKey(); MethodlessRouter router = e.getValue(); aggregateRoutes(method.toString(), router.routes(), methods, patterns, targets); } // For anyMethodRouter aggregateRoutes("*", anyMethodRouter.routes(), methods, patterns, targets); // For notFound if (notFound != null) { methods.add("*"); patterns.add("*"); targets.add(targetToString(notFound)); } // Step 2/2: Format the List into aligned columns: int maxLengthMethod = maxLength(methods); int maxLengthPattern = maxLength(patterns); String format = "%-" + maxLengthMethod + "s %-" + maxLengthPattern + "s %s\n"; int initialCapacity = (maxLengthMethod + 1 + maxLengthPattern + 1 + 20) * methods.size(); StringBuilder b = new StringBuilder(initialCapacity); for (int i = 0; i < methods.size(); i++) { String method = methods.get(i); String pattern = patterns.get(i); String target = targets.get(i); b.append(String.format(format, method, pattern, target)); } return b.toString(); } //-------------------------------------------------------------------------- public Router addConnect(String path, T target) { return addRoute(HttpMethod.CONNECT, path, target); } public Router addDelete(String path, T target) { return addRoute(HttpMethod.DELETE, path, target); } public Router addGet(String path, T target) { return addRoute(HttpMethod.GET, path, target); } public Router addHead(String path, T target) { return addRoute(HttpMethod.HEAD, path, target); } public Router addOptions(String path, T target) { return addRoute(HttpMethod.OPTIONS, path, target); } public Router addPatch(String path, T target) { return addRoute(HttpMethod.PATCH, path, target); } public Router addPost(String path, T target) { return addRoute(HttpMethod.POST, path, target); } public Router addPut(String path, T target) { return addRoute(HttpMethod.PUT, path, target); } public Router addTrace(String path, T target) { return addRoute(HttpMethod.TRACE, path, target); } public Router addAny(String path, T target) { return addRoute(null, path, target); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy