io.vertx.ext.web.impl.RouteState Maven / Gradle / Ivy
/*
* Copyright 2019 Red Hat, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.ext.web.impl;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.net.impl.URIDecoder;
import io.vertx.core.net.HostAndPort;
import io.vertx.ext.web.MIMEHeader;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This class encapsulates the route state, all mutations are atomic and return a new state with the mutation.
*
* This class is thread-safe
*
* @author Paulo Lopes
*/
final class RouteState {
private static final Logger LOG = LoggerFactory.getLogger(RouteState.class);
enum Priority {
PLATFORM,
SECURITY_POLICY,
PROTOCOL_UPGRADE,
BODY,
MULTI_TENANT,
AUTHENTICATION,
INPUT_TRUST,
AUTHORIZATION,
USER
}
private static Priority weight(Handler handler) {
if (handler instanceof PlatformHandler) {
return Priority.PLATFORM;
}
if (handler instanceof SecurityPolicyHandler) {
return Priority.SECURITY_POLICY;
}
if (handler instanceof ProtocolUpgradeHandler) {
return Priority.PROTOCOL_UPGRADE;
}
if (handler instanceof BodyHandler) {
return Priority.BODY;
}
if (handler instanceof MultiTenantHandler) {
return Priority.MULTI_TENANT;
}
if (handler instanceof AuthenticationHandler) {
return Priority.AUTHENTICATION;
}
if (handler instanceof InputTrustHandler) {
return Priority.INPUT_TRUST;
}
if (handler instanceof AuthorizationHandler) {
return Priority.AUTHORIZATION;
}
return Priority.USER;
}
private final RouteImpl route;
private final Map metadata;
private final String path;
private final String name;
private final int order;
private final boolean enabled;
private final Set methods;
private final Set consumes;
private final boolean emptyBodyPermittedWithConsumes;
private final Set produces;
private final List> contextHandlers;
private final List> failureHandlers;
private final boolean added;
private final Pattern pattern;
private final List groups;
private final boolean useNormalizedPath;
private final Set namedGroupsInRegex;
private final Pattern virtualHostPattern;
private final boolean pathEndsWithSlash;
private final boolean exclusive;
private final boolean exactPath;
private RouteState(RouteImpl route, Map metadata, String path, String name, int order, boolean enabled, Set methods, Set consumes, boolean emptyBodyPermittedWithConsumes, Set produces, List> contextHandlers, List> failureHandlers, boolean added, Pattern pattern, List groups, boolean useNormalizedPath, Set namedGroupsInRegex, Pattern virtualHostPattern, boolean pathEndsWithSlash, boolean exclusive, boolean exactPath) {
this.route = route;
this.metadata = metadata;
this.path = path;
this.name = name;
this.order = order;
this.enabled = enabled;
this.methods = methods;
this.consumes = consumes;
this.emptyBodyPermittedWithConsumes = emptyBodyPermittedWithConsumes;
this.produces = produces;
this.contextHandlers = contextHandlers;
this.failureHandlers = failureHandlers;
this.added = added;
this.pattern = pattern;
this.groups = groups;
this.useNormalizedPath = useNormalizedPath;
this.namedGroupsInRegex = namedGroupsInRegex;
this.virtualHostPattern = virtualHostPattern;
this.pathEndsWithSlash = pathEndsWithSlash;
this.exclusive = exclusive;
this.exactPath = exactPath;
}
RouteState(RouteImpl route, int order) {
this(
route,
null,
null,
null,
order,
true,
null,
null,
false,
null,
null,
null,
false,
null,
null,
true,
null,
null,
false,
false,
true);
}
public RouteImpl getRoute() {
return route;
}
public RouterImpl getRouter() {
return route.router();
}
public RouteState putMetadata(String key, Object value) {
Map metadata = this.metadata == null ? new HashMap<>() : new HashMap<>(this.metadata);
if (value == null) {
metadata.remove(key);
} else {
metadata.put(key, value);
}
return new RouteState(
this.route,
Collections.unmodifiableMap(metadata),
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public Map getMetadata() {
return metadata;
}
public String getPath() {
return path;
}
RouteState setPath(String path) {
return new RouteState(
this.route,
this.metadata,
path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public int getOrder() {
return order;
}
RouteState setOrder(int order) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public boolean isEnabled() {
return enabled;
}
RouteState setEnabled(boolean enabled) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public Set getMethods() {
return methods;
}
RouteState setMethods(Set methods) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public RouteState addMethod(HttpMethod method) {
RouteState newState = new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods == null ? new HashSet<>() : new HashSet<>(this.methods),
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
newState.methods.add(method);
return newState;
}
public Set getConsumes() {
return consumes;
}
RouteState setConsumes(Set consumes) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
RouteState addConsume(MIMEHeader mime) {
RouteState newState = new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes == null ? new LinkedHashSet<>() : new LinkedHashSet<>(this.consumes),
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
newState.consumes.add(mime);
return newState;
}
public boolean isEmptyBodyPermittedWithConsumes() {
return emptyBodyPermittedWithConsumes;
}
RouteState setEmptyBodyPermittedWithConsumes(boolean emptyBodyPermittedWithConsumes) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public Set getProduces() {
return produces;
}
RouteState setProduces(Set produces) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
RouteState addProduce(MIMEHeader mime) {
RouteState newState = new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces == null ? new LinkedHashSet<>() : new LinkedHashSet<>(this.produces),
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
newState.produces.add(mime);
return newState;
}
public List> getContextHandlers() {
return contextHandlers;
}
public int getContextHandlersLength() {
return contextHandlers == null ? 0 : contextHandlers.size();
}
RouteState setContextHandlers(List> contextHandlers) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
RouteState addContextHandler(Handler contextHandler) {
RouteState newState = new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers == null ? new ArrayList<>() : new ArrayList<>(this.contextHandlers),
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
int len = newState.contextHandlers.size();
final Priority weight = weight(contextHandler);
final Priority lastWeight;
if (len > 0) {
lastWeight = weight(newState.contextHandlers.get(len - 1));
if (lastWeight.ordinal() > weight.ordinal()) {
String message = "Cannot add [" + weight.name() + "] handler to route with [" + lastWeight.name() + "] handler at index " + (len - 1);
// when lenient mode is disabled, throw IllegalStateException to signal that the setup is incorrect
if (!Boolean.getBoolean("io.vertx.web.router.setup.lenient")) {
throw new IllegalStateException(message);
}
LOG.warn(message);
}
} else {
lastWeight = null;
}
if (lastWeight == Priority.PROTOCOL_UPGRADE) {
// when lenient mode is disabled, don't log to signal that the setup might be incorrect
if (!Boolean.getBoolean("io.vertx.web.router.setup.lenient")) {
LOG.warn("Adding an handler after PROTOCOL_UPGRADE handler may not be reachable");
}
}
newState.contextHandlers.add(contextHandler);
return newState;
}
public List> getFailureHandlers() {
return failureHandlers;
}
public int getFailureHandlersLength() {
return failureHandlers == null ? 0 : failureHandlers.size();
}
RouteState setFailureHandlers(List> failureHandlers) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
RouteState addFailureHandler(Handler failureHandler) {
RouteState newState = new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers == null ? new ArrayList<>() : new ArrayList<>(this.failureHandlers),
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
newState.failureHandlers.add(failureHandler);
return newState;
}
public boolean isAdded() {
return added;
}
RouteState setAdded(boolean added) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public Pattern getPattern() {
return pattern;
}
RouteState setPattern(Pattern pattern) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public List getGroups() {
return groups;
}
RouteState setGroups(List groups) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
RouteState addGroup(String group) {
RouteState newState = new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups == null ? new ArrayList<>() : new ArrayList<>(groups),
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
newState.groups.add(group);
return newState;
}
public boolean isUseNormalizedPath() {
return useNormalizedPath;
}
RouteState setUseNormalizedPath(boolean useNormalizedPath) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public Set getNamedGroupsInRegex() {
return namedGroupsInRegex;
}
RouteState setNamedGroupsInRegex(Set namedGroupsInRegex) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
RouteState addNamedGroupInRegex(String namedGroupInRegex) {
RouteState newState = new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex == null ? new HashSet<>() : new HashSet<>(this.namedGroupsInRegex),
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
newState.namedGroupsInRegex.add(namedGroupInRegex);
return newState;
}
public Pattern getVirtualHostPattern() {
return virtualHostPattern;
}
RouteState setVirtualHostPattern(Pattern virtualHostPattern) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public boolean isPathEndsWithSlash() {
return pathEndsWithSlash;
}
RouteState setPathEndsWithSlash(boolean pathEndsWithSlash) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
public boolean isExclusive() {
return exclusive;
}
RouteState setExclusive(boolean exclusive) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
exclusive,
this.exactPath);
}
public boolean isExactPath() {
return exactPath;
}
RouteState setExactPath(boolean exactPath) {
return new RouteState(
this.route,
this.metadata,
this.path,
this.name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
exactPath);
}
RouteState setName(String name) {
return new RouteState(
this.route,
this.metadata,
this.path,
name,
this.order,
this.enabled,
this.methods,
this.consumes,
this.emptyBodyPermittedWithConsumes,
this.produces,
this.contextHandlers,
this.failureHandlers,
this.added,
this.pattern,
this.groups,
this.useNormalizedPath,
this.namedGroupsInRegex,
this.virtualHostPattern,
this.pathEndsWithSlash,
this.exclusive,
this.exactPath);
}
private boolean containsMethod(HttpServerRequest request) {
if (!isEmpty(methods)) {
return methods.contains(request.method());
}
return false;
}
private static boolean isEmpty(Collection collection) {
return collection == null || collection.isEmpty();
}
/**
* @return 0 if route matches, otherwise it return the status code
*/
public int matches(RoutingContextImplBase context, String mountPoint, boolean failure) {
if (failure && !hasNextFailureHandler(context) || !failure && !hasNextContextHandler(context)) {
return 404;
}
if (!enabled) {
return 404;
}
HttpServerRequest request = context.request();
if (path != null && pattern == null && !pathMatches(mountPoint, context)) {
return 404;
}
if (pattern != null) {
// need to reset "rest"
context.pathParams()
.remove("*");
String path = useNormalizedPath ? context.normalizedPath() : context.request().path();
if (mountPoint != null) {
int strip = mountPoint.length();
// mount point can have significant slash
if (mountPoint.charAt(strip - 1)== '/') {
strip--;
}
if (path != null) {
path = path.substring(strip);
}
}
Matcher m;
if (path != null && (m = pattern.matcher(path)).matches()) {
if (!isEmpty(methods) && !containsMethod(request)) {
// If I'm here path or path pattern matches, but the method is wrong
return 405;
}
context.matchRest = -1;
context.normalizedMatch = useNormalizedPath;
if (m.groupCount() > 0) {
if (!exactPath) {
context.matchRest = m.start("rest");
// always replace
context.pathParams()
.put("*", path.substring(context.matchRest));
}
if (!isEmpty(groups)) {
// Pattern - named params
// decode the path as it could contain escaped chars.
final int len = Math.min(groups.size(), m.groupCount());
for (int i = 0; i < len; i++) {
final String k = groups.get(i);
String undecodedValue;
// We try to take value in three ways:
// 1. group name of type p0, p1, pN (most frequent and used by vertx params)
// 2. group name inside the regex
// 3. No group name
try {
undecodedValue = m.group("p" + i);
} catch (IllegalArgumentException e) {
try {
undecodedValue = m.group(k);
} catch (IllegalArgumentException e1) {
// Groups starts from 1 (0 group is total match)
undecodedValue = m.group(i + 1);
}
}
if (undecodedValue != null) {
addPathParam(context, k, undecodedValue);
}
}
} else {
// Straight regex - un-named params
// decode the path as it could contain escaped chars.
if (!isEmpty(namedGroupsInRegex)) {
for (String namedGroup : namedGroupsInRegex) {
String namedGroupValue = m.group(namedGroup);
if (namedGroupValue != null) {
addPathParam(context, namedGroup, namedGroupValue);
}
}
}
for (int i = 0; i < m.groupCount(); i++) {
String group = m.group(i + 1);
if (group != null) {
final String k = "param" + i;
addPathParam(context, k, group);
}
}
}
}
} else {
return 404;
}
} else {
// no pattern check for wrong method
if (!isEmpty(methods) && !containsMethod(request)) {
// If I'm here path or path pattern matches, but the method is wrong
return 405;
}
}
if (!isEmpty(consumes)) {
// Can this route consume the specified content type
MIMEHeader contentType = context.parsedHeaders().contentType();
MIMEHeader consumal = contentType.findMatchedBy(consumes);
if (consumal == null && !(contentType.rawValue().isEmpty() && emptyBodyPermittedWithConsumes)) {
if (contentType.rawValue().isEmpty()) {
return 400;
} else {
return 415;
}
}
}
if (!isEmpty(produces)) {
List acceptableTypes = context.parsedHeaders().accept();
if(!acceptableTypes.isEmpty()) {
MIMEHeader selectedAccept = context.parsedHeaders().findBestUserAcceptedIn(acceptableTypes, produces);
if (selectedAccept != null) {
context.setAcceptableContentType(selectedAccept.rawValue());
} else {
return 406;
}
}
}
if (!virtualHostMatches(context.request())) {
return 404;
}
return 0;
}
private boolean pathMatches(String mountPoint, RoutingContext ctx) {
final boolean rootRouter = mountPoint == null;
final boolean pathEndsWithSlash;
final String thePath;
if (rootRouter) {
thePath = path;
pathEndsWithSlash = this.pathEndsWithSlash;
} else {
boolean mountPointEndsWithSlash = mountPoint.charAt(mountPoint.length() - 1) == '/';
// path is "/"
if (path.length() == 1) {
// mount point is always assumed to be a directory so
// we must ignore the final slash
thePath = mountPoint;
// so this is a special case we can't consider the configured route but the mount point itself
pathEndsWithSlash = mountPointEndsWithSlash;
} else {
// solve the double slash when mount point ends with slash
if (mountPointEndsWithSlash) {
thePath = mountPoint + path.substring(1);
} else {
thePath = mountPoint + path;
}
pathEndsWithSlash = this.pathEndsWithSlash;
}
}
String requestPath;
if (useNormalizedPath) {
// never null
requestPath = ctx.normalizedPath();
} else {
requestPath = ctx.request().path();
// can be null
if (requestPath == null) {
requestPath = "/";
}
}
if (exactPath) {
// exact path has no "rest"
ctx.pathParams()
.remove("*");
return pathMatchesExact(thePath, requestPath, pathEndsWithSlash);
} else {
if (pathEndsWithSlash) {
// the route expects a path that ends in "/*". This is a special case
// we need to optionally allow any request just like if it was a "*" but
// treat the slash
final int pathLen = thePath.length();
final int reqLen = requestPath.length();
if (reqLen < pathLen - 2) {
// we miss at least 2 characters
return false;
}
if (reqLen == pathLen - 1) {
// request misses 1 character, there is the chance that this request doesn't include the final slash
// because the mount path ended with a wildcard we are relaxed in the check
if (thePath.regionMatches(0, requestPath, 0, pathLen - 1)) {
// handle the "rest" as path param *, always known to be empty
ctx.pathParams()
.put("*", "/");
return true;
}
}
}
if (requestPath.startsWith(thePath)) {
// handle the "rest" as path param *
ctx.pathParams()
.put("*", URIDecoder.decodeURIComponent(requestPath.substring(thePath.length()), false));
return true;
}
return false;
}
}
private boolean virtualHostMatches(HttpServerRequest request) {
if (virtualHostPattern == null) {
return true;
}
HostAndPort authority = request.authority();
if (authority == null) {
return false;
}
String host = authority.host();
int len = host.length();
// knowing that the shortest IPv6 is [::]
if (len > 3 && host.charAt(0) == '[') {
// attempt to parse IPv6
int delim = host.indexOf(']');
if (delim != -1) {
// the delim must be the terminal character OR right before a ':'
if (delim == len - 1 || host.charAt(delim + 1) == ':') {
// OK
host = host.substring(1, delim);
return virtualHostPattern.matcher(host).matches();
}
}
}
// assume IPv4 or name
int portSeparatorIdx = host.lastIndexOf(':');
if (portSeparatorIdx != -1) {
host = host.substring(0, portSeparatorIdx);
}
return virtualHostPattern.matcher(host).matches();
}
private static boolean pathMatchesExact(String base, String other, boolean significantSlash) {
// Ignore trailing slash when matching paths
int len = other.length();
if (significantSlash) {
if (other.charAt(len -1) != '/') {
// final slash is significant but missing
return false;
}
} else {
if (other.charAt(len -1) == '/') {
// final slash is not significant, ignore it
len--;
if (base.length() != len) {
return false;
}
// content must match
return other.regionMatches(0, base, 0, len);
}
}
// content must match
return other.equals(base);
}
private void addPathParam(RoutingContext context, String name, String value) {
HttpServerRequest request = context.request();
final String decodedValue = URIDecoder.decodeURIComponent(value, false);
if (!request.params().contains(name)) {
request.params().add(name, decodedValue);
}
context.pathParams().put(name, decodedValue);
}
boolean hasNextContextHandler(RoutingContextImplBase context) {
return context.currentRouteNextHandlerIndex() < getContextHandlersLength();
}
boolean hasNextFailureHandler(RoutingContextImplBase context) {
return context.currentRouteNextFailureHandlerIndex() < getFailureHandlersLength();
}
void handleContext(RoutingContextImplBase context) {
contextHandlers
.get(context.currentRouteNextHandlerIndex() - 1)
.handle(context);
}
void handleFailure(RoutingContextImplBase context) {
failureHandlers
.get(context.currentRouteNextFailureHandlerIndex() - 1)
.handle(context);
}
public String getName() {
if (name != null) {
return name;
}
if (path != null) {
return path;
}
if (pattern != null) {
return pattern.pattern();
}
return null;
}
@Override
public String toString() {
return "RouteState{" +
"metadata=" + metadata +
", path='" + path + '\'' +
", name=" + name +
", order=" + order +
", enabled=" + enabled +
", methods=" + methods +
", consumes=" + consumes +
", emptyBodyPermittedWithConsumes=" + emptyBodyPermittedWithConsumes +
", produces=" + produces +
", contextHandlers=" + contextHandlers +
", failureHandlers=" + failureHandlers +
", added=" + added +
", pattern=" + pattern +
", groups=" + groups +
", useNormalizedPath=" + useNormalizedPath +
", namedGroupsInRegex=" + namedGroupsInRegex +
", virtualHostPattern=" + virtualHostPattern +
", pathEndsWithSlash=" + pathEndsWithSlash +
", exclusive=" + exclusive +
", exactPath=" + exactPath +
'}';
}
}