Skip to content

Commit

Permalink
Merge pull request #19501 from gsmet/fix-19492
Browse files Browse the repository at this point in the history
Normalize the quarkus.http.root-path at config level
  • Loading branch information
gsmet authored Aug 24, 2021
2 parents 4f0745b + 4a66f79 commit ab3a4f8
Show file tree
Hide file tree
Showing 15 changed files with 161 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,18 @@ public static URI toURI(String path, boolean trailingSlash) {
* </ul>
*
* @param base URI to resolve relative paths. Use {@link #toURI(String, boolean)} to construct this parameter.
*
*
* @param segment Relative or absolute path
* @param trailingSlash true if resulting URI must end with a '/'
* @throws IllegalArgumentException if the path contains invalid characters or path segments.
*/
public static URI normalizeWithBase(URI base, String segment, boolean trailingSlash) {
if (segment == null || segment.trim().isEmpty()) {
return base;
if ("/".equals(base.getPath())) {
return base;
}
// otherwise, make sure trailingSlash is honored
return toURI(base.getPath(), trailingSlash);
}
URI segmentUri = toURI(segment, trailingSlash);
URI resolvedUri = base.resolve(segmentUri);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.quarkus.runtime.configuration;

import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY;

import javax.annotation.Priority;

import org.eclipse.microprofile.config.spi.Converter;

/**
* A converter to normalize paths that are considered root of something.
* <p>
* Any path coming out of this converter will have a leading and ending '/'.
* <p>
* Do NOT use this converter for paths that could be relative.
*/
@Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY)
public class NormalizeRootHttpPathConverter implements Converter<String> {

private static final String SLASH = "/";

@Override
public String convert(String value) throws IllegalArgumentException, NullPointerException {
if (value == null) {
return SLASH;
}

value = value.trim();
if (SLASH.equals(value)) {
return value;
}
if (!value.startsWith(SLASH)) {
value = SLASH + value;
}
if (!value.endsWith(SLASH)) {
value = value + SLASH;
}

return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
import io.quarkus.builder.item.SimpleBuildItem;

public final class ResteasyDeploymentBuildItem extends SimpleBuildItem {
private ResteasyDeployment deployment;
private String rootPath;
private ResteasyDeployment deployment;

public ResteasyDeploymentBuildItem(String rootPath, ResteasyDeployment deployment) {
this.rootPath = rootPath.startsWith("/") ? rootPath : "/" + rootPath;
this.deployment = deployment;
this.rootPath = rootPath;
}

public ResteasyDeployment getDeployment() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
import io.quarkus.resteasy.server.common.spi.AllowedJaxRsAnnotationPrefixBuildItem;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.quarkus.runtime.annotations.ConvertWith;
import io.quarkus.runtime.configuration.NormalizeRootHttpPathConverter;

/**
* Processor that builds the RESTEasy server configuration.
Expand Down Expand Up @@ -135,9 +137,17 @@ static final class ResteasyConfig {

/**
* Set this to override the default path for JAX-RS resources if there are no
* annotated application classes.
* annotated application classes. This path is specified with a leading {@literal /}, but is resolved relative
* to {@literal quarkus.http.root-path}.
* <ul>
* <li>If {@literal quarkus.http.root-path=/} and {@code quarkus.resteasy.path=/bar}, the JAX-RS resource path will be
* {@literal /bar}</li>
* <li>If {@literal quarkus.http.root-path=/foo} and {@code quarkus.resteasy.path=/bar}, the JAX-RS resource path will
* be {@literal /foo/bar}</li>
* </ul>
*/
@ConfigItem(defaultValue = "/")
@ConvertWith(NormalizeRootHttpPathConverter.class)
String path;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public final class ResteasyServerConfigBuildItem extends SimpleBuildItem {
*/
public ResteasyServerConfigBuildItem(String rootPath, String path, Map<String, String> initParameters) {
this.rootPath = rootPath;
this.path = path;
this.path = path.startsWith("/") ? path : "/" + path;
this.initParameters = initParameters;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,8 @@ public void jaxrsConfig(
BuildProducer<io.quarkus.resteasy.server.common.spi.ResteasyJaxrsConfigBuildItem> resteasyJaxrsConfig,
HttpRootPathBuildItem httpRootPathBuildItem) {
if (resteasyServerConfig.isPresent()) {
String rp = resteasyServerConfig.get().getRootPath();
String rootPath = httpRootPathBuildItem.resolvePath(rp.startsWith("/") ? rp.substring(1) : rp);
String defaultPath = httpRootPathBuildItem.resolvePath(resteasyServerConfig.get().getPath());
String rootPath = httpRootPathBuildItem.relativePath(resteasyServerConfig.get().getRootPath());
String defaultPath = resteasyServerConfig.get().getPath();

deprecatedResteasyJaxrsConfig.produce(new ResteasyJaxrsConfigBuildItem(defaultPath));
resteasyJaxrsConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentBuildItem;
import io.quarkus.vertx.core.deployment.CoreVertxBuildItem;
import io.quarkus.vertx.http.deployment.DefaultRouteBuildItem;
import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RequireVirtualHttpBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.HttpConfiguration;
import io.quarkus.vertx.http.runtime.VertxHttpRecorder;
import io.vertx.core.Handler;
Expand All @@ -50,32 +50,17 @@ public void staticInit(ResteasyStandaloneRecorder recorder,
ResteasyDeploymentBuildItem deployment,
ApplicationArchivesBuildItem applicationArchivesBuildItem,
ResteasyInjectionReadyBuildItem resteasyInjectionReady,
HttpBuildTimeConfig httpConfig,
HttpRootPathBuildItem httpRootPathBuildItem,
BuildProducer<ResteasyStandaloneBuildItem> standalone) throws Exception {
if (capabilities.isPresent(Capability.SERVLET)) {
return;
}

String deploymentRootPath = null;
// The context path + the resources path
String rootPath = httpConfig.rootPath;

if (deployment != null) {
deploymentRootPath = deployment.getRootPath();
if (rootPath.endsWith("/")) {
if (deploymentRootPath.startsWith("/")) {
rootPath += deploymentRootPath.substring(1);
} else {
rootPath += deploymentRootPath;
}
} else if (!deploymentRootPath.equals("/")) {
if (!deploymentRootPath.startsWith("/")) {
rootPath += "/";
}
rootPath += deploymentRootPath;
}
recorder.staticInit(deployment.getDeployment(), rootPath);
standalone.produce(new ResteasyStandaloneBuildItem(deploymentRootPath));
// the deployment path is always relative to the HTTP root path
recorder.staticInit(deployment.getDeployment(),
httpRootPathBuildItem.relativePath(deployment.getRootPath()));
standalone.produce(new ResteasyStandaloneBuildItem(deployment.getRootPath()));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
package io.quarkus.undertow.deployment;

import static io.quarkus.runtime.configuration.ConverterSupport.DEFAULT_QUARKUS_CONVERTER_PRIORITY;

import java.util.Optional;

import javax.annotation.Priority;

import org.eclipse.microprofile.config.spi.Converter;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.quarkus.runtime.annotations.ConvertWith;

@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
public class ServletConfig {

/**
* The context path to serve all Servlet context from. This will also affect any resources
* that run as a Servlet, e.g. JAX-RS.
*
* Note that this is relative to the HTTP root path set in quarkus.http.root-path, so if the context path
* is /bar and the http root is /foo then the actual Servlet path will be /foo/bar.
* The context path for Servlet content. This will determine the path used to
* resolve all Servlet-based resources, including JAX-RS resources - when using the Undertow extension in conjunction with
* RESTEasy.
* <p>
* This path is specified with a leading {@literal /}, but is resolved relative
* to {@literal quarkus.http.root-path}.
* <ul>
* <li>If {@literal quarkus.http.root-path=/} and {@code quarkus.servlet.context-path=/bar}, the servlet path will be
* {@literal /bar}</li>
* <li>If {@literal quarkus.http.root-path=/foo} and {@code quarkus.servlet.context-path=/bar}, the servlet path will be
* {@literal /foo/bar}</li>
* </ul>
*/
@ConfigItem
@ConvertWith(ContextPathConverter.class)
Optional<String> contextPath;

/**
Expand All @@ -25,4 +40,33 @@ public class ServletConfig {
@ConfigItem(defaultValue = "UTF-8")
public String defaultCharset;

/**
* This converter adds a '/' at the beginning of the context path but does not add one at the end, given we want to support
* binding to a context without an ending '/'.
* <p>
* See ContextPathTestCase for an example.
*/
@Priority(DEFAULT_QUARKUS_CONVERTER_PRIORITY)
public static class ContextPathConverter implements Converter<String> {

private static final String SLASH = "/";

@Override
public String convert(String value) throws IllegalArgumentException, NullPointerException {
if (value == null) {
return SLASH;
}

value = value.trim();
if (SLASH.equals(value)) {
return value;
}
if (!value.startsWith(SLASH)) {
value = SLASH + value;
}

return value;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
import io.quarkus.undertow.runtime.UndertowDeploymentRecorder;
import io.quarkus.undertow.runtime.UndertowHandlersConfServletExtension;
import io.quarkus.vertx.http.deployment.DefaultRouteBuildItem;
import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.quarkus.vertx.http.runtime.HttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.HttpConfiguration;
Expand Down Expand Up @@ -319,11 +320,7 @@ public ServletContextPathBuildItem contextPath(
WebMetadataBuildItem webMetadataBuildItem) {
String contextPath;
if (servletConfig.contextPath.isPresent()) {
if (!servletConfig.contextPath.get().startsWith("/")) {
contextPath = "/" + servletConfig.contextPath;
} else {
contextPath = servletConfig.contextPath.get();
}
contextPath = servletConfig.contextPath.get();
} else if (webMetadataBuildItem.getWebMetaData().getDefaultContextPath() != null) {
contextPath = webMetadataBuildItem.getWebMetaData().getDefaultContextPath();
} else {
Expand Down Expand Up @@ -351,6 +348,7 @@ public ServletDeploymentManagerBuildItem build(List<ServletBuildItem> servlets,
ShutdownContextBuildItem shutdownContext,
KnownPathsBuildItem knownPaths,
HttpBuildTimeConfig httpBuildTimeConfig,
HttpRootPathBuildItem httpRootPath,
ServletConfig servletConfig) throws Exception {

ObjectSubstitutionBuildItem.Holder holder = new ObjectSubstitutionBuildItem.Holder(ServletSecurityInfo.class,
Expand All @@ -365,7 +363,7 @@ public ServletDeploymentManagerBuildItem build(List<ServletBuildItem> servlets,
String contextPath = servletContextPathBuildItem.getServletContextPath();
RuntimeValue<DeploymentInfo> deployment = recorder.createDeployment("test", knownPaths.knownFiles,
knownPaths.knownDirectories,
launchMode.getLaunchMode(), shutdownContext, contextPath, httpBuildTimeConfig.rootPath,
launchMode.getLaunchMode(), shutdownContext, httpRootPath.relativePath(contextPath),
servletConfig.defaultCharset, webMetaData.getRequestCharacterEncoding(),
webMetaData.getResponseCharacterEncoding(), httpBuildTimeConfig.auth.proactive,
webMetaData.getWelcomeFileList() != null ? webMetaData.getWelcomeFileList().getWelcomeFiles() : null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,26 +156,17 @@ public static void setHotDeploymentResources(List<Path> resources) {
}

public RuntimeValue<DeploymentInfo> createDeployment(String name, Set<String> knownFile, Set<String> knownDirectories,
LaunchMode launchMode, ShutdownContext context, String contextPath, String httpRootPath, String defaultCharset,
LaunchMode launchMode, ShutdownContext context, String mountPoint, String defaultCharset,
String requestCharacterEncoding, String responseCharacterEncoding, boolean proactiveAuth,
List<String> welcomeFiles) {
String realMountPoint;
if (contextPath.equals("/")) {
realMountPoint = httpRootPath;
} else if (httpRootPath.equals("/")) {
realMountPoint = contextPath;
} else {
realMountPoint = httpRootPath + contextPath;
}

DeploymentInfo d = new DeploymentInfo();
d.setDefaultRequestEncoding(requestCharacterEncoding);
d.setDefaultResponseEncoding(responseCharacterEncoding);
d.setDefaultEncoding(defaultCharset);
d.setSessionIdGenerator(new QuarkusSessionIdGenerator());
d.setClassLoader(getClass().getClassLoader());
d.setDeploymentName(name);
d.setContextPath(realMountPoint);
d.setContextPath(mountPoint);
d.setEagerFilterInit(true);
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,33 @@ public String resolvePath(String path) {
return UriNormalizationUtil.normalizeWithBase(rootPath, path, false).getPath();
}

/**
* Resolve path that is always relative into an absolute path.
* Whether the path is relative or absolute, it will be resolved against `quarkus.http.root-path`,
* by removing the '/' in the latter case.
* <p>
* Given {@literal quarkus.http.root-path=/}
* <ul>
* <li>{@code relativePath("foo")} will return {@literal /foo}</li>
* <li>{@code relativePath("/foo")} will return {@literal /foo}</li>
* </ul>
* Given {@literal quarkus.http.root-path=/app}
* <ul>
* <li>{@code relativePath("foo")} will return {@literal /app/foo}</li>
* <li>{@code relativePath("/foo")} will return {@literal /app/foo}</li>
* </ul>
* <p>
* The returned path will not end with a slash.
*
* @param path Path to be resolved to an absolute path.
* @return An absolute path not ending with a slash
* @see UriNormalizationUtil#normalizeWithBase(URI, String, boolean)
*/
public String relativePath(String path) {
String relativePath = path.startsWith("/") ? path.substring(1) : path;
return UriNormalizationUtil.normalizeWithBase(rootPath, relativePath, false).getPath();
}

public HttpRootPathBuildItem.Builder routeBuilder() {
return new HttpRootPathBuildItem.Builder(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ ServiceStartBuildItem finalizeRouter(
LaunchModeBuildItem launchMode,
List<DefaultRouteBuildItem> defaultRoutes, List<FilterBuildItem> filters,
VertxWebRouterBuildItem httpRouteRouter,
HttpRootPathBuildItem httpRootPathBuildItem,
NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem,
HttpBuildTimeConfig httpBuildTimeConfig, HttpConfiguration httpConfiguration,
List<RequireBodyHandlerBuildItem> requireBodyHandlerBuildItems,
Expand Down Expand Up @@ -256,7 +257,8 @@ ServiceStartBuildItem finalizeRouter(

recorder.finalizeRouter(beanContainer.getValue(),
defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null),
listOfFilters, vertx.getVertx(), lrc, mainRouter, httpRouteRouter.getHttpRouter(), httpBuildTimeConfig.rootPath,
listOfFilters, vertx.getVertx(), lrc, mainRouter, httpRouteRouter.getHttpRouter(),
httpRootPathBuildItem.getRootPath(),
launchMode.getLaunchMode(),
!requireBodyHandlerBuildItems.isEmpty(), bodyHandler, httpConfiguration, gracefulShutdownFilter,
shutdownConfig, executorBuildItem.getExecutorProxy());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ void testResolvePathWithSlash() {
void testResolvePathWithSlashApp() {
HttpRootPathBuildItem buildItem = new HttpRootPathBuildItem("/app");

Assertions.assertEquals("/app/", buildItem.resolvePath(""));
Assertions.assertEquals("/app", buildItem.resolvePath(""));
Assertions.assertEquals("/app/foo", buildItem.resolvePath("foo"));
Assertions.assertEquals("/foo", buildItem.resolvePath("/foo"));
}
Expand Down
Loading

0 comments on commit ab3a4f8

Please sign in to comment.