-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Work to fix path handling and non-application endpoints.
Changes include: - Created `UriNormalizationUtil` for normalizing URI paths - Added `NonApplicationRootPathBuildItem.routeBuilder()` for constructing non-application endpoint routes - Modified extensions to utilize the new builder to create routes so any path munging happens inside the builder and not in extensions. - Added lots of tests - Added section to extension writing guide for adding non-application endpoints Co-authored-by: Ken Finnigan <[email protected]> Co-authored-by: Erin Schnabel <[email protected]> Co-authored-by: Stuart Douglas <[email protected]>
- Loading branch information
1 parent
e2398cd
commit e0f8ec9
Showing
102 changed files
with
2,592 additions
and
491 deletions.
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
core/deployment/src/main/java/io/quarkus/deployment/util/UriNormalizationUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package io.quarkus.deployment.util; | ||
|
||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
|
||
/** | ||
* Common URI path resolution | ||
*/ | ||
public class UriNormalizationUtil { | ||
private UriNormalizationUtil() { | ||
} | ||
|
||
/** | ||
* Create a URI path from a string. The specified path can not contain | ||
* relative {@literal ..} segments or {@literal %} characters. | ||
* <p> | ||
* Examples: | ||
* <ul> | ||
* <li>{@code toUri("/", true)} will return a URI with path {@literal /}</li> | ||
* <li>{@code toUri("/", false)} will return a URI with an empty path {@literal /}</li> | ||
* <li>{@code toUri("./", true)} will return a URI with path {@literal /}</li> | ||
* <li>{@code toUri("./", false)} will return a URI with an empty path {@literal /}</li> | ||
* <li>{@code toUri("foo/", true)} will return a URI with path {@literal foo/}</li> | ||
* <li>{@code toUri("foo/", false)} will return a URI with an empty path {@literal foo}</li> | ||
* </ul> | ||
* | ||
* | ||
* @param path String to convert into a URI | ||
* @param trailingSlash true if resulting URI must end with a '/' | ||
* @throws IllegalArgumentException if the path contains invalid characters or path segments. | ||
*/ | ||
public static URI toURI(String path, boolean trailingSlash) { | ||
try { | ||
// replace inbound // with / | ||
path = path.replaceAll("//", "/"); | ||
// remove trailing slash if result shouldn't have one | ||
if (!trailingSlash && path.endsWith("/")) { | ||
path = path.substring(0, path.length() - 1); | ||
} | ||
|
||
if (path.contains("..") || path.contains("%")) { | ||
throw new IllegalArgumentException("Specified path can not contain '..' or '%'. Path was " + path); | ||
} | ||
URI uri = new URI(path).normalize(); | ||
if (uri.getPath().equals("")) { | ||
return trailingSlash ? new URI("/") : new URI(""); | ||
} else if (trailingSlash && !path.endsWith("/")) { | ||
uri = new URI(uri.getPath() + "/"); | ||
} | ||
return uri; | ||
} catch (URISyntaxException e) { | ||
throw new IllegalArgumentException("Specified path is an invalid URI. Path was " + path, e); | ||
} | ||
} | ||
|
||
/** | ||
* Resolve a string path against a URI base. The specified path can not contain | ||
* relative {@literal ..} segments or {@literal %} characters. | ||
* | ||
* Relative paths will be resolved against the specified base URI. | ||
* Absolute paths will be normalized and returned. | ||
* <p> | ||
* Examples: | ||
* <ul> | ||
* <li>{@code normalizeWithBase(new URI("/"), "example", true)} | ||
* will return a URI with path {@literal /example/}</li> | ||
* <li>{@code normalizeWithBase(new URI("/"), "example", false)} | ||
* will return a URI with an empty path {@literal /example}</li> | ||
* <li>{@code normalizeWithBase(new URI("/"), "/example", true)} | ||
* will return a URI with path {@literal /example/}</li> | ||
* <li>{@code normalizeWithBase(new URI("/"), "/example", false)} | ||
* will return a URI with an empty {@literal /example</li> | ||
* | ||
* <li>{@code normalizeWithBase(new URI("/prefix/"), "example", true)} | ||
* will return a URI with path {@literal /prefix/example/}</li> | ||
* <li>{@code normalizeWithBase(new URI("/prefix/"), "example", false)} | ||
* will return a URI with an empty path {@literal /prefix/example}</li> | ||
* <li>{@code normalizeWithBase(new URI("/prefix/"), "/example", true)} | ||
* will return a URI with path {@literal /example/}</li> | ||
* <li>{@code normalizeWithBase(new URI("/prefix/"), "/example", false)} | ||
* will return a URI with an empty path {@literal /example}</li> | ||
* | ||
* <li>{@code normalizeWithBase(new URI("foo/"), "example", true)} | ||
* will return a URI with path {@literal foo/example/}</li> | ||
* <li>{@code normalizeWithBase(new URI("foo/"), "example", false)} | ||
* will return a URI with an empty path {@literal foo/example}</li> | ||
* <li>{@code normalizeWithBase(new URI("foo/"), "/example", true)} | ||
* will return a URI with path {@literal /example/}</li> | ||
* <li>{@code normalizeWithBase(new URI("foo/"), "/example", false)} | ||
* will return a URI with an empty path {@literal /example}</li> | ||
* </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; | ||
} | ||
URI segmentUri = toURI(segment, trailingSlash); | ||
URI resolvedUri = base.resolve(segmentUri); | ||
return resolvedUri; | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
core/deployment/src/test/java/io/quarkus/deployment/util/UriNormalizationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package io.quarkus.deployment.util; | ||
|
||
import java.net.URI; | ||
|
||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
|
||
public class UriNormalizationTest { | ||
@Test | ||
void testToUri() { | ||
Assertions.assertEquals("/", UriNormalizationUtil.toURI("/", true).getPath()); | ||
Assertions.assertEquals("", UriNormalizationUtil.toURI("/", false).getPath()); | ||
|
||
Assertions.assertEquals("/", UriNormalizationUtil.toURI("./", true).getPath()); | ||
Assertions.assertEquals("", UriNormalizationUtil.toURI("./", false).getPath()); | ||
|
||
Assertions.assertEquals("bob/", UriNormalizationUtil.toURI("bob/", true).getPath()); | ||
Assertions.assertEquals("bob", UriNormalizationUtil.toURI("bob/", false).getPath()); | ||
} | ||
|
||
@Test | ||
void testExamples() { | ||
URI root = UriNormalizationUtil.toURI("/", true); | ||
URI prefix = UriNormalizationUtil.toURI("/prefix", true); | ||
URI foo = UriNormalizationUtil.toURI("foo", true); | ||
|
||
Assertions.assertEquals("/example/", UriNormalizationUtil.normalizeWithBase(root, "example", true).getPath()); | ||
Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(root, "example", false).getPath()); | ||
Assertions.assertEquals("/example/", UriNormalizationUtil.normalizeWithBase(root, "/example", true).getPath()); | ||
Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(root, "/example", false).getPath()); | ||
|
||
Assertions.assertEquals("/prefix/example/", UriNormalizationUtil.normalizeWithBase(prefix, "example", true).getPath()); | ||
Assertions.assertEquals("/prefix/example", UriNormalizationUtil.normalizeWithBase(prefix, "example", false).getPath()); | ||
Assertions.assertEquals("/example/", UriNormalizationUtil.normalizeWithBase(prefix, "/example", true).getPath()); | ||
Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(prefix, "/example", false).getPath()); | ||
|
||
Assertions.assertEquals("foo/example/", UriNormalizationUtil.normalizeWithBase(foo, "example", true).getPath()); | ||
Assertions.assertEquals("foo/example", UriNormalizationUtil.normalizeWithBase(foo, "example", false).getPath()); | ||
Assertions.assertEquals("/example/", UriNormalizationUtil.normalizeWithBase(foo, "/example", true).getPath()); | ||
Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(foo, "/example", false).getPath()); | ||
} | ||
|
||
@Test | ||
void testDubiousUriPaths() { | ||
URI root = UriNormalizationUtil.toURI("/", true); | ||
|
||
Assertions.assertEquals("/", UriNormalizationUtil.normalizeWithBase(root, "#example", false).getPath()); | ||
Assertions.assertEquals("/", UriNormalizationUtil.normalizeWithBase(root, "?example", false).getPath()); | ||
|
||
Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(root, "./example", false).getPath()); | ||
Assertions.assertEquals("/example", UriNormalizationUtil.normalizeWithBase(root, "//example", false).getPath()); | ||
|
||
Assertions.assertThrows(IllegalArgumentException.class, | ||
() -> UriNormalizationUtil.normalizeWithBase(root, "/%2fexample", false)); | ||
Assertions.assertThrows(IllegalArgumentException.class, | ||
() -> UriNormalizationUtil.normalizeWithBase(root, "junk/../example", false)); | ||
Assertions.assertThrows(IllegalArgumentException.class, | ||
() -> UriNormalizationUtil.normalizeWithBase(root, "junk/../../example", false)); | ||
Assertions.assertThrows(IllegalArgumentException.class, | ||
() -> UriNormalizationUtil.normalizeWithBase(root, "../example", false)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
...oyment/src/test/java/io/quarkus/micrometer/deployment/export/JsonRegistryEnabledTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package io.quarkus.micrometer.deployment.export; | ||
|
||
import java.util.Set; | ||
|
||
import javax.inject.Inject; | ||
|
||
import org.jboss.shrinkwrap.api.ShrinkWrap; | ||
import org.jboss.shrinkwrap.api.spec.JavaArchive; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.RegisterExtension; | ||
|
||
import io.micrometer.core.instrument.MeterRegistry; | ||
import io.micrometer.core.instrument.composite.CompositeMeterRegistry; | ||
import io.quarkus.micrometer.runtime.registry.json.JsonMeterRegistry; | ||
import io.quarkus.test.QuarkusUnitTest; | ||
import io.restassured.RestAssured; | ||
|
||
public class JsonRegistryEnabledTest { | ||
@RegisterExtension | ||
static final QuarkusUnitTest config = new QuarkusUnitTest() | ||
.withConfigurationResource("test-logging.properties") | ||
.overrideConfigKey("quarkus.http.root-path", "/app") | ||
.overrideConfigKey("quarkus.http.non-application-root-path", "relative") | ||
.overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") | ||
.overrideConfigKey("quarkus.micrometer.export.json.enabled", "true") | ||
.overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") | ||
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) | ||
.addClass(PrometheusRegistryProcessor.REGISTRY_CLASS)); | ||
|
||
@Inject | ||
MeterRegistry registry; | ||
|
||
@Inject | ||
JsonMeterRegistry jsonMeterRegistry; | ||
|
||
@Test | ||
public void testMeterRegistryPresent() { | ||
// Prometheus is enabled (only registry) | ||
Assertions.assertNotNull(registry, "A registry should be configured"); | ||
Set<MeterRegistry> subRegistries = ((CompositeMeterRegistry) registry).getRegistries(); | ||
JsonMeterRegistry subPromRegistry = (JsonMeterRegistry) subRegistries.iterator().next(); | ||
Assertions.assertEquals(JsonMeterRegistry.class, subPromRegistry.getClass(), "Should be JsonMeterRegistry"); | ||
Assertions.assertEquals(subPromRegistry, jsonMeterRegistry, | ||
"The only MeterRegistry should be the same bean as the JsonMeterRegistry"); | ||
} | ||
|
||
@Test | ||
public void metricsEndpoint() { | ||
// RestAssured prepends /app for us | ||
RestAssured.given() | ||
.contentType("application/json") | ||
.get("/relative/metrics") | ||
.then() | ||
.statusCode(200); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.