Skip to content

Commit

Permalink
Merge pull request #30068 from brunolmfg/allow-static-resources-config
Browse files Browse the repository at this point in the history
Allow tuning some static resources serving properties
  • Loading branch information
geoand authored Jan 3, 2023
2 parents 33d5c2b + 615cb02 commit f927729
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 119 deletions.
7 changes: 7 additions & 0 deletions docs/src/main/asciidoc/http-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ TIP: By default, the following list of media types is compressed: `text/html`, `

NOTE: If the client does not support HTTP compression then the response body is not compressed.

[[static-resources-config]]
=== Other Configurations

Additionally, the index page for static resources can be changed from default `index.html`, the hidden files (e.g. dot files) can be indicated as not served, the range requests can be disabled, and the caching support (e.g. caching headers and file properties cache) can be configured.

include::{generated-dir}/config/quarkus-vertx-http-config-group-static-resources-config.adoc[leveloffset=+1, opts=optional]

[[context-path]]
== Configuring the Context path

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.builder.item.SimpleBuildItem;
import io.quarkus.deployment.ApplicationArchive;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
Expand All @@ -30,115 +28,48 @@
import io.quarkus.runtime.util.ClassPathUtils;
import io.quarkus.vertx.core.deployment.CoreVertxBuildItem;
import io.quarkus.vertx.http.deployment.spi.AdditionalStaticResourceBuildItem;
import io.quarkus.vertx.http.runtime.HttpConfiguration;
import io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem;
import io.quarkus.vertx.http.runtime.StaticResourcesRecorder;

/**
* Handles all static file resources found in {@code META-INF/resources} unless the servlet container is present.
*/
public class StaticResourcesProcessor {

@Deprecated
public static final class StaticResourcesBuildItem extends SimpleBuildItem {

private final Set<Entry> entries;

public StaticResourcesBuildItem(Set<Entry> entries) {
this.entries = entries;
}

public Set<Entry> getEntries() {
return entries;
}

public Set<String> getPaths() {
Set<String> paths = new HashSet<>(entries.size());
for (Entry entry : entries) {
paths.add(entry.getPath());
}
return paths;
}

public static class Entry {
private final String path;
private final boolean isDirectory;

public Entry(String path, boolean isDirectory) {
this.path = path;
this.isDirectory = isDirectory;
}

public String getPath() {
return path;
}

public boolean isDirectory() {
return isDirectory;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Entry entry = (Entry) o;
return isDirectory == entry.isDirectory && path.equals(entry.path);
}

@Override
public int hashCode() {
return Objects.hash(path, isDirectory);
}
}

}

@BuildStep
void collectStaticResources(Capabilities capabilities, ApplicationArchivesBuildItem applicationArchivesBuildItem,
List<AdditionalStaticResourceBuildItem> additionalStaticResources,
Optional<StaticResourcesBuildItem> deprecatedStaticResources,
BuildProducer<io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem> staticResources) throws Exception {
BuildProducer<StaticResourcesBuildItem> staticResources) throws Exception {
if (capabilities.isPresent(Capability.SERVLET)) {
// Servlet container handles static resources
return;
}
// Copy deprecated build item
Set<io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry> paths = getClasspathResources(
applicationArchivesBuildItem);
if (deprecatedStaticResources.isPresent()) {
Set<StaticResourcesBuildItem.Entry> deprecatedEntries = deprecatedStaticResources.get().getEntries();
for (StaticResourcesBuildItem.Entry deprecatedEntry : deprecatedEntries) {
paths.add(new io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry(deprecatedEntry.getPath(),
deprecatedEntry.isDirectory()));
}
}
Set<StaticResourcesBuildItem.Entry> paths = getClasspathResources(applicationArchivesBuildItem);
for (AdditionalStaticResourceBuildItem bi : additionalStaticResources) {
paths.add(new io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry(bi.getPath(), bi.isDirectory()));
paths.add(new StaticResourcesBuildItem.Entry(bi.getPath(), bi.isDirectory()));
}
if (!paths.isEmpty()) {
staticResources.produce(new io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem(paths));
staticResources.produce(new StaticResourcesBuildItem(paths));
}
}

@BuildStep
@Record(RUNTIME_INIT)
public void runtimeInit(Optional<io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem> staticResources,
StaticResourcesRecorder recorder, CoreVertxBuildItem vertx, BeanContainerBuildItem beanContainer,
BuildProducer<DefaultRouteBuildItem> defaultRoutes, HttpConfiguration config) {
public void runtimeInit(Optional<StaticResourcesBuildItem> staticResources, StaticResourcesRecorder recorder,
CoreVertxBuildItem vertx, BeanContainerBuildItem beanContainer,
BuildProducer<DefaultRouteBuildItem> defaultRoutes) {
if (staticResources.isPresent()) {
defaultRoutes.produce(new DefaultRouteBuildItem(recorder.start(staticResources.get().getPaths())));
}
}

@BuildStep(onlyIf = NativeOrNativeSourcesBuild.class)
public void nativeImageResource(Optional<io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem> staticResources,
public void nativeImageResource(Optional<StaticResourcesBuildItem> staticResources,
BuildProducer<NativeImageResourceBuildItem> producer) {
if (staticResources.isPresent()) {
Set<io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry> entries = staticResources.get()
.getEntries();
Set<StaticResourcesBuildItem.Entry> entries = staticResources.get().getEntries();
List<String> metaInfResources = new ArrayList<>(entries.size());
for (io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry entry : entries) {
for (StaticResourcesBuildItem.Entry entry : entries) {
if (entry.isDirectory()) {
// TODO: do we perhaps want to register the whole directory?
continue;
Expand All @@ -157,10 +88,9 @@ public void nativeImageResource(Optional<io.quarkus.vertx.http.deployment.spi.St
* @return the set of static resources
* @throws Exception
*/
private Set<io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry> getClasspathResources(
ApplicationArchivesBuildItem applicationArchivesBuildItem)
private Set<StaticResourcesBuildItem.Entry> getClasspathResources(ApplicationArchivesBuildItem applicationArchivesBuildItem)
throws Exception {
Set<io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry> knownPaths = new HashSet<>();
Set<StaticResourcesBuildItem.Entry> knownPaths = new HashSet<>();

for (ApplicationArchive i : applicationArchivesBuildItem.getAllApplicationArchives()) {
i.accept(tree -> {
Expand All @@ -178,34 +108,19 @@ private Set<io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry>
return knownPaths;
}

private void collectKnownPaths(Path resource,
Set<io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry> knownPaths) {
private void collectKnownPaths(Path resource, Set<StaticResourcesBuildItem.Entry> knownPaths) {
try {
Files.walkFileTree(resource, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path p, BasicFileAttributes attrs)
throws IOException {
String simpleName = p.getFileName().toString();
String file = resource.relativize(p).toString();
if (simpleName.equals("index.html") || simpleName.equals("index.htm")) {
Path parent = resource.relativize(p).getParent();
if (parent == null) {
knownPaths.add(new io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry("/", true));
} else {
String parentString = parent.toString();
if (!parentString.startsWith("/")) {
parentString = "/" + parentString;
}
knownPaths.add(new io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry(
parentString + "/", true));
}
}
if (!file.startsWith("/")) {
file = "/" + file;
}
// Windows has a backslash
file = file.replace('\\', '/');
knownPaths.add(new io.quarkus.vertx.http.deployment.spi.StaticResourcesBuildItem.Entry(file, false));
knownPaths.add(new StaticResourcesBuildItem.Entry(file, false));
return FileVisitResult.CONTINUE;
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,17 @@ public abstract class AbstractStaticResourcesTest {

@Test
public void shouldEncodeHtmlPage() {
RestAssured.when().get("/static-file.html")
.then()
.header("Content-Encoding", "gzip")
.header("Transfer-Encoding", "chunked")
.body(Matchers.containsString("This is the title of the webpage!"))
.statusCode(200);
assertEncodedResponse("/static-file.html");
}

@Test
public void shouldEncodeRootPage() {
RestAssured.when().get("/")
.then()
.header("Content-Encoding", "gzip")
.header("Transfer-Encoding", "chunked")
.body(Matchers.containsString("This is the title of the webpage!"))
.statusCode(200);
assertEncodedResponse("/");
}

@Test
public void shouldEncodeHiddenHtmlPage() {
assertEncodedResponse("/.hidden-file.html");
}

@Test
Expand All @@ -36,4 +31,22 @@ public void shouldNotEncodeSVG() {
.statusCode(200);
}

@Test
public void shouldReturnRangeSupport() {
RestAssured.when().head("/")
.then()
.header("Accept-Ranges", "bytes")
.header("Content-Length", Integer::parseInt, Matchers.greaterThan(0))
.statusCode(200);
}

protected void assertEncodedResponse(String path) {
RestAssured.when().get(path)
.then()
.header("Content-Encoding", "gzip")
.header("Transfer-Encoding", "chunked")
.body(Matchers.containsString("This is the title of the webpage!"))
.statusCode(200);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.vertx.http;

import static org.hamcrest.Matchers.nullValue;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;

public class StaticResourcesCachingDisabledTest {

@RegisterExtension
final static QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.add(new StringAsset(
"quarkus.http.static-resources.caching-enabled=false\n"),
"application.properties")
.addAsResource("static-file.html", "META-INF/resources/index.html"));

@Test
public void shouldNotContainCachingHeaders() {
RestAssured.when().get("/")
.then()
.header("Cache-Control", nullValue())
.header("Last-Modified", nullValue())
.statusCode(200);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.quarkus.vertx.http;

import static org.hamcrest.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;

import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;

public class StaticResourcesCustomizedPagesTest {

@RegisterExtension
final static QuarkusUnitTest test = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.add(new StringAsset("" +
"quarkus.http.static-resources.index-page=default.html\n" +
"quarkus.http.static-resources.include-hidden=false\n" +
"quarkus.http.static-resources.enable-range-support=false\n"),
"application.properties")
.addAsResource("static-file.html", "META-INF/resources/.hidden-file.html")
.addAsResource("static-file.html", "META-INF/resources/default.html"));

@Test
public void shouldContainCachingHeaders() {
RestAssured.when().get("/")
.then()
.header("Cache-Control", containsStringIgnoringCase("max-age="))
.header("Last-Modified", notNullValue())
.statusCode(200);
}

@Test
public void shouldNotReturnHiddenHtmlPage() {
RestAssured.when().get("/.hidden-file.html")
.then()
.statusCode(404);
}

@Test
public void shouldNotReturnRangeSupport() {
RestAssured.when().head("/")
.then()
.header("Accept-Ranges", nullValue())
.header("Content-Length", nullValue())
.statusCode(200);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class StaticResourcesTest extends AbstractStaticResourcesTest {
.add(new StringAsset("quarkus.http.enable-compression=true\n"),
"application.properties")
.addAsResource("static-file.html", "META-INF/resources/static-file.html")
.addAsResource("static-file.html", "META-INF/resources/.hidden-file.html")
.addAsResource("static-file.html", "META-INF/resources/index.html")
.addAsResource("static-file.html", "META-INF/resources/image.svg"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public class StaticResourcesDevModeTest extends AbstractStaticResourcesTest {
.add(new StringAsset("quarkus.http.enable-compression=true\n"),
"application.properties")
.addAsResource("static-file.html", "META-INF/resources/static-file.html")
.addAsResource("static-file.html", "META-INF/resources/.hidden-file.html")
.addAsResource("static-file.html", "META-INF/resources/index.html")
.addAsResource("static-file.html", "META-INF/resources/image.svg"));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ public class HttpConfiguration {
*/
public ServerSslConfig ssl;

/**
* The Static Resources config
*/
public StaticResourcesConfig staticResources;

/**
* When set to {@code true}, the HTTP server automatically sends `100 CONTINUE`
* response when the request expects it (with the `Expect: 100-Continue` header).
Expand Down
Loading

0 comments on commit f927729

Please sign in to comment.