diff --git a/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/AdditionalStaticResourceBuildItem.java b/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/AdditionalStaticResourceBuildItem.java
index 94ec70a8809f3..2fdbe78cae532 100644
--- a/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/AdditionalStaticResourceBuildItem.java
+++ b/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/AdditionalStaticResourceBuildItem.java
@@ -9,7 +9,7 @@
*
* The value of {@code path} should be prefixed with {@code '/'} and is assumed to be a path under {@code 'META-INF/resources'}.
*
- * @deprecated Use {@link GeneratedStaticResourceBuildItem} instead.
+ * @deprecated Use {@link GeneratedStaticResourceBuildItem} instead (the goal is to make this BuildItem internal).
*/
@Deprecated
public final class AdditionalStaticResourceBuildItem extends MultiBuildItem {
diff --git a/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/GeneratedStaticResourceBuildItem.java b/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/GeneratedStaticResourceBuildItem.java
index 21580b8bf8d90..ffa37294d0314 100644
--- a/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/GeneratedStaticResourceBuildItem.java
+++ b/extensions/vertx-http/deployment-spi/src/main/java/io/quarkus/vertx/http/deployment/spi/GeneratedStaticResourceBuildItem.java
@@ -2,40 +2,79 @@
import static java.util.Objects.requireNonNull;
+import java.nio.file.Path;
+
import io.quarkus.builder.item.MultiBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
/**
* This build item aims to be used by extensions to generate static resources.
*
- * Those resources will be served on the given {@link GeneratedStaticResourceBuildItem#path}. It is NOT necessary to create the
+ * Those resources will be served on the given {@link GeneratedStaticResourceBuildItem#endpoint}. It is NOT necessary to create
+ * the
* file on disk.
*
* Behind the scenes the build step will take care of add those resources to the final build, through
* {@link AdditionalStaticResourceBuildItem}, {@link NativeImageResourceBuildItem}
* and {@link io.quarkus.deployment.builditem.GeneratedResourceBuildItem} build items.
*
- * The value of {@code path} should be prefixed with {@code '/'}.
+ * The value of {@code endpoint} should be prefixed with {@code '/'}.
*/
public final class GeneratedStaticResourceBuildItem extends MultiBuildItem {
- private final String path;
+ private final String endpoint;
+
+ private final Path file;
private final byte[] content;
- public GeneratedStaticResourceBuildItem(final String path, final byte[] content) {
- if (!requireNonNull(path, "path is required").startsWith("/")) {
- throw new IllegalArgumentException("path must start with '/'");
+ private GeneratedStaticResourceBuildItem(final String endpoint, final byte[] content, final Path file) {
+ if (!requireNonNull(endpoint, "endpoint is required").startsWith("/")) {
+ throw new IllegalArgumentException("endpoint must start with '/'");
}
- this.path = path;
+ this.endpoint = endpoint;
+ this.file = file;
this.content = content;
}
- public String getPath() {
- return this.path;
+ /**
+ * The resource will be served at {@code '{quarkus.http.root-path}{endpoint}'}
+ *
+ * @param endpoint the endpoint from the {@code '{quarkus.http.root-path}'} for this generated static resource. It should be
+ * prefixed with {@code '/'}
+ * @param content the content of this generated static resource
+ */
+ public GeneratedStaticResourceBuildItem(final String endpoint, final byte[] content) {
+ this(endpoint, content, null);
+ }
+
+ /**
+ * The resource will be served at {root-path}{path}
+ *
+ * @param endpoint the endpoint from the {@code '{quarkus.http.root-path}'} for this generated static resource. It should be
+ * prefixed with {@code '/'}
+ * @param file the file Path on the local filesystem
+ */
+ public GeneratedStaticResourceBuildItem(final String endpoint, final Path file) {
+ this(endpoint, null, file);
+ }
+
+ public String getEndpoint() {
+ return this.endpoint;
}
+ public boolean isFile() {
+ return file != null;
+ };
+
public byte[] getContent() {
return this.content;
}
+ public Path getFile() {
+ return file;
+ }
+
+ public String getFileAbsolutePath() {
+ return file.toAbsolutePath().toString();
+ }
}
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/GeneratedStaticResourcesProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/GeneratedStaticResourcesProcessor.java
index bcd7cd7089c44..d6316b11e86ed 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/GeneratedStaticResourcesProcessor.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/GeneratedStaticResourcesProcessor.java
@@ -14,6 +14,7 @@
import io.quarkus.bootstrap.classloading.ClassPathElement;
import io.quarkus.bootstrap.classloading.QuarkusClassLoader;
import io.quarkus.deployment.IsDevelopment;
+import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
@@ -25,22 +26,22 @@
import io.quarkus.paths.FilteredPathTree;
import io.quarkus.paths.PathFilter;
import io.quarkus.paths.PathVisitor;
+import io.quarkus.runtime.LaunchMode;
import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem;
import io.quarkus.vertx.http.deployment.spi.AdditionalStaticResourceBuildItem;
import io.quarkus.vertx.http.deployment.spi.GeneratedStaticResourceBuildItem;
import io.quarkus.vertx.http.runtime.GeneratedStaticResourcesRecorder;
import io.quarkus.vertx.http.runtime.RouteConstants;
-import io.quarkus.vertx.http.runtime.handlers.DevClasspathStaticHandler;
+import io.quarkus.vertx.http.runtime.handlers.DevStaticHandler;
/**
* {@link GeneratedStaticResourcesProcessor} is responsible for dealing {@link GeneratedStaticResourceBuildItem}
- * creating a {@link DevClasspathStaticHandler} to handle all static resources
+ * creating a {@link DevStaticHandler} to handle all static resources
* generated from extensions through {@link GeneratedStaticResourceBuildItem} build item.
*/
public class GeneratedStaticResourcesProcessor {
private static final int ROUTE_ORDER = RouteConstants.ROUTE_ORDER_BEFORE_DEFAULT + 60;
- private static final String META_INF_GENERATED_RESOURCES = "META-INF/generated-resources";
@BuildStep
public void produceResources(List generatedStaticResources,
@@ -48,54 +49,87 @@ public void produceResources(List generatedSta
BuildProducer nativeImageResourcesProducer,
LaunchModeBuildItem launchModeBuildItem,
BuildProducer additionalStaticResourcesProducer) {
-
for (GeneratedStaticResourceBuildItem generatedStaticResource : generatedStaticResources) {
String generatedStaticResourceLocation = buildGeneratedStaticResourceLocation(generatedStaticResource);
-
- generatedResourceBuildItem.produce(
- new GeneratedResourceBuildItem(generatedStaticResourceLocation,
- generatedStaticResource.getContent(), false));
+ if (!generatedStaticResource.isFile()) {
+ generatedResourceBuildItem.produce(
+ new GeneratedResourceBuildItem(generatedStaticResourceLocation,
+ generatedStaticResource.getContent(), false));
+ } else if (launchModeBuildItem.getLaunchMode() != LaunchMode.DEVELOPMENT) {
+ // For files, we need to read it and add it in the classpath for normal and test mode
+ try {
+ final byte[] content = Files.readAllBytes(generatedStaticResource.getFile());
+ generatedResourceBuildItem.produce(
+ new GeneratedResourceBuildItem(generatedStaticResourceLocation,
+ content, false));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
// We can't use the vert.x StaticHandler for tests as it doesn't support 'quarkus' protocol
- if (!launchModeBuildItem.getLaunchMode().isDevOrTest()) {
+ if (launchModeBuildItem.getLaunchMode() == LaunchMode.NORMAL) {
additionalStaticResourcesProducer.produce(
- new AdditionalStaticResourceBuildItem(generatedStaticResource.getPath(), false));
+ new AdditionalStaticResourceBuildItem(generatedStaticResource.getEndpoint(), false));
nativeImageResourcesProducer.produce(new NativeImageResourceBuildItem(generatedStaticResourceLocation));
}
}
}
- @BuildStep
+ @BuildStep(onlyIfNot = IsNormal.class)
@Record(ExecutionTime.RUNTIME_INIT)
public void process(List generatedStaticResources,
LaunchModeBuildItem launchModeBuildItem,
BuildProducer routes, GeneratedStaticResourcesRecorder generatedStaticResourcesRecorder,
BuildProducer notFoundPageProducer) {
- if (!launchModeBuildItem.getLaunchMode().isDevOrTest() || generatedStaticResources.isEmpty()) {
+ if (generatedStaticResources.isEmpty()) {
return;
}
- Set paths = generatedStaticResources.stream().map(GeneratedStaticResourceBuildItem::getPath)
- .peek(path -> notFoundPageProducer.produce(new NotFoundPageDisplayableEndpointBuildItem(path)))
+ Map generatedFilesResources = generatedStaticResources.stream()
+ .peek(path -> notFoundPageProducer.produce(new NotFoundPageDisplayableEndpointBuildItem(path.getEndpoint())))
+ .filter(GeneratedStaticResourceBuildItem::isFile)
+ .collect(Collectors.toMap(GeneratedStaticResourceBuildItem::getEndpoint,
+ GeneratedStaticResourceBuildItem::getFileAbsolutePath));
+ Set generatedClassPathResources = generatedStaticResources.stream()
+ .map(GeneratedStaticResourceBuildItem::getEndpoint)
.collect(Collectors.toSet());
routes.produce(RouteBuildItem.builder()
.orderedRoute("/*", ROUTE_ORDER, generatedStaticResourcesRecorder.createRouteCustomizer())
- .handler(generatedStaticResourcesRecorder.createHandler(paths))
+ .handler(generatedStaticResourcesRecorder.createHandler(generatedClassPathResources, generatedFilesResources))
.build());
}
+ private static String buildGeneratedStaticResourceLocation(
+ GeneratedStaticResourceBuildItem generatedStaticResourceBuildItem) {
+ return META_INF_RESOURCES +
+ generatedStaticResourceBuildItem.getEndpoint();
+ }
+
+ // THIS IS TO TEST DEV MODE
+
+ private static final String META_INF_GENERATED_RESOURCES_TEST = "META-INF/generated-resources-test";
+
@BuildStep(onlyIf = IsDevelopment.class)
public void devMode(
BuildProducer hotDeployment,
- BuildProducer generatedStaticResourceProducer) throws IOException {
+ BuildProducer generatedStaticResourceProducer,
+ LaunchModeBuildItem launchMode) throws IOException {
+ // this is only for dev-mode tests
+ if (!launchMode.isTest()) {
+ return;
+ }
+
hotDeployment.produce(HotDeploymentWatchedFileBuildItem.builder()
.setRestartNeeded(true)
- .setLocationPredicate(l -> l.startsWith(META_INF_GENERATED_RESOURCES))
+ .setLocationPredicate(l -> l.startsWith(META_INF_GENERATED_RESOURCES_TEST + "/bytes"))
.build());
Map classpathResources = getClasspathResources();
- for (Map.Entry entries : classpathResources.entrySet()) {
- byte[] bytes = Files.readAllBytes(entries.getValue());
- generatedStaticResourceProducer.produce(new GeneratedStaticResourceBuildItem(
- "/" + entries.getKey(), bytes));
+ for (Map.Entry entry : classpathResources.entrySet()) {
+ final String key = "/" + entry.getKey();
+ final GeneratedStaticResourceBuildItem item = key.startsWith("/bytes")
+ ? new GeneratedStaticResourceBuildItem(key, Files.readAllBytes(entry.getValue()))
+ : new GeneratedStaticResourceBuildItem(key, entry.getValue());
+ generatedStaticResourceProducer.produce(item);
}
}
@@ -104,7 +138,7 @@ private Map getClasspathResources() {
visitRuntimeMetaInfResources(visit -> {
if (!Files.isDirectory(visit.getPath())) {
String resourcePath = visit.getRelativePath("/")
- .substring("/".concat(META_INF_GENERATED_RESOURCES).length());
+ .substring("/".concat(META_INF_GENERATED_RESOURCES_TEST).length());
knownPaths.put(resourcePath, visit.getPath());
}
});
@@ -112,12 +146,12 @@ private Map getClasspathResources() {
}
private static void visitRuntimeMetaInfResources(PathVisitor visitor) {
- final List elements = QuarkusClassLoader.getElements(META_INF_GENERATED_RESOURCES,
+ final List elements = QuarkusClassLoader.getElements(META_INF_GENERATED_RESOURCES_TEST,
false);
if (!elements.isEmpty()) {
final PathFilter filter = PathFilter.forIncludes(List.of(
- META_INF_GENERATED_RESOURCES + "/**",
- META_INF_GENERATED_RESOURCES));
+ META_INF_GENERATED_RESOURCES_TEST + "/**",
+ META_INF_GENERATED_RESOURCES_TEST));
for (var element : elements) {
if (element.isRuntime()) {
element.apply(tree -> {
@@ -128,10 +162,4 @@ private static void visitRuntimeMetaInfResources(PathVisitor visitor) {
}
}
}
-
- private static String buildGeneratedStaticResourceLocation(
- GeneratedStaticResourceBuildItem generatedStaticResourceBuildItem) {
- return META_INF_RESOURCES +
- generatedStaticResourceBuildItem.getPath();
- }
}
diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java
index 7c6a362204199..3b64ea89e1c7c 100644
--- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java
+++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/StaticResourcesProcessor.java
@@ -43,10 +43,9 @@ void collectStaticResources(Capabilities capabilities,
return;
}
Set paths = getClasspathResources();
- if (!launchModeBuildItem.getLaunchMode().isDevOrTest()) {
- for (AdditionalStaticResourceBuildItem bi : additionalStaticResources) {
- paths.add(new StaticResourcesBuildItem.Entry(bi.getPath(), bi.isDirectory()));
- }
+ // We shouldn't add them in test and dev-mode (as they are handled by the GeneratedStaticResourcesProcessor), but for backward compatibility we keep it for now
+ for (AdditionalStaticResourceBuildItem bi : additionalStaticResources) {
+ paths.add(new StaticResourcesBuildItem.Entry(bi.getPath(), bi.isDirectory()));
}
if (!paths.isEmpty()) {
diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/GeneratedStaticResourcesTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/GeneratedStaticClasspathResourcesTest.java
similarity index 98%
rename from extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/GeneratedStaticResourcesTest.java
rename to extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/GeneratedStaticClasspathResourcesTest.java
index 324565a2cf177..612927e4bf484 100644
--- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/GeneratedStaticResourcesTest.java
+++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/GeneratedStaticClasspathResourcesTest.java
@@ -16,7 +16,7 @@
import io.quarkus.vertx.http.deployment.spi.GeneratedStaticResourceBuildItem;
import io.restassured.RestAssured;
-public class GeneratedStaticResourcesTest {
+public class GeneratedStaticClasspathResourcesTest {
@RegisterExtension
final static QuarkusUnitTest test = new QuarkusUnitTest().withApplicationRoot(
diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/GeneratedStaticFileResourcesTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/GeneratedStaticFileResourcesTest.java
new file mode 100644
index 0000000000000..d4e6ddea85ebc
--- /dev/null
+++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/GeneratedStaticFileResourcesTest.java
@@ -0,0 +1,125 @@
+package io.quarkus.vertx.http;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+
+import org.hamcrest.Matchers;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.builder.BuildChainBuilder;
+import io.quarkus.builder.BuildContext;
+import io.quarkus.builder.BuildStep;
+import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
+import io.quarkus.runtime.util.ClassPathUtils;
+import io.quarkus.test.QuarkusUnitTest;
+import io.quarkus.vertx.http.deployment.spi.GeneratedStaticResourceBuildItem;
+import io.restassured.RestAssured;
+
+public class GeneratedStaticFileResourcesTest {
+
+ @RegisterExtension
+ final static QuarkusUnitTest test = new QuarkusUnitTest().withApplicationRoot(
+ (jar) -> jar
+ .addAsResource("static-file.html", "static-file.html")
+ .add(new StringAsset(
+ "quarkus.http.enable-compression=true\nquarkus.http.static-resources.index-page=default.html"),
+ "application.properties"))
+ .addBuildChainCustomizer(new Consumer() {
+ @Override
+ public void accept(BuildChainBuilder buildChainBuilder) {
+
+ buildChainBuilder.addBuildStep(new BuildStep() {
+ @Override
+ public void execute(BuildContext context) {
+ final Path file = resolveResource("/static-file.html");
+ context.produce(new GeneratedStaticResourceBuildItem(
+ "/default.html", file));
+ context.produce(new GeneratedStaticResourceBuildItem("/hello-from-generated-static-resource.html",
+ file));
+
+ context.produce(new GeneratedStaticResourceBuildItem("/static-file.html",
+ file));
+ context.produce(new GeneratedStaticResourceBuildItem(
+ "/.nojekyll", resolveResource("/.nojekyll")));
+ context.produce(new GeneratedStaticResourceBuildItem("/quarkus-openapi-generator/default.html",
+ file));
+ }
+ }).produces(GeneratedStaticResourceBuildItem.class).produces(GeneratedResourceBuildItem.class).build();
+ }
+ });
+
+ private static Path resolveResource(String name) {
+ return ClassPathUtils.toLocalPath(GeneratedStaticFileResourcesTest.class.getResource(name));
+ }
+
+ @Test
+ public void shouldGetStaticFileHtmlPageWhenThereIsAGeneratedStaticResource() throws IOException {
+ final String result = Files.readString(resolveResource("/static-file.html"));
+ RestAssured.get("/static-file.html").then()
+ .body(Matchers.is(result))
+ .statusCode(Matchers.is(200));
+
+ RestAssured.get("hello-from-generated-static-resource.html").then()
+ .body(Matchers.is(result))
+ .statusCode(Matchers.is(200));
+
+ RestAssured.get("/").then()
+ .body(Matchers.is(result))
+ .statusCode(200);
+ }
+
+ @Test
+ public void shouldCompress() throws IOException {
+ final String result = Files.readString(resolveResource("/static-file.html"));
+ RestAssured.get("/static-file.html").then()
+ .header("Content-Encoding", "gzip")
+ .body(Matchers.is(result))
+ .statusCode(Matchers.is(200));
+ }
+
+ @Test
+ public void shouldGetHiddenFiles() {
+ RestAssured.get("/.nojekyll")
+ .then()
+ .body(Matchers.containsString("{empty}"))
+ .statusCode(200);
+ }
+
+ @Test
+ public void shouldGetTheIndexPageCorrectly() throws IOException {
+ final String result = Files.readString(resolveResource("/static-file.html"));
+ RestAssured.get("/quarkus-openapi-generator/")
+ .then()
+ .body(Matchers.is(result))
+ .statusCode(200);
+ }
+
+ @Test
+ public void shouldNotGetWhenPathEndsWithoutSlash() {
+ RestAssured.get("/quarkus-openapi-generator")
+ .then()
+ .statusCode(404); // We are using next()
+ }
+
+ @Test
+ public void shouldGetAllowHeaderWhenUsingOptions() {
+ RestAssured.options("/quarkus-openapi-generator/")
+ .then()
+ .header("Allow", Matchers.is("HEAD,GET,OPTIONS"))
+ .statusCode(204);
+ }
+
+ @Test
+ public void shouldGetHeadersFromHeadRequest() throws IOException {
+ final byte[] result = Files.readAllBytes(resolveResource("/static-file.html"));
+ RestAssured.head("/static-file.html")
+ .then()
+ .header("Content-Length", Integer::parseInt, Matchers.is(result.length))
+ .header("Content-Type", Matchers.is("text/html;charset=UTF-8"))
+ .statusCode(200);
+ }
+}
diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devmode/GeneratedStaticResourcesDevModeTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devmode/GeneratedStaticResourcesDevModeTest.java
index 89c88ee71dd84..77b51020560ec 100644
--- a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devmode/GeneratedStaticResourcesDevModeTest.java
+++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/devmode/GeneratedStaticResourcesDevModeTest.java
@@ -15,20 +15,57 @@ public class GeneratedStaticResourcesDevModeTest {
.withApplicationRoot((jar) -> jar
.add(new StringAsset("quarkus.http.enable-compression=true\n"),
"application.properties")
- .addAsResource("static-file.html", "META-INF/generated-resources/static-file.html")
- .addAsResource("static-file.html", "META-INF/generated-resources/.hidden-file.html")
- .addAsResource("static-file.html", "META-INF/generated-resources/index.html")
- .addAsResource("static-file.html", "META-INF/generated-resources/image.svg"));
+ .addAsResource("static-file.html", "META-INF/generated-resources-test/bytes/static-file.html")
+ .addAsResource("static-file.html", "META-INF/generated-resources-test/bytes/.hidden-file.html")
+ .addAsResource("static-file.html", "META-INF/generated-resources-test/bytes/index.html")
+ .addAsResource("static-file.html", "META-INF/generated-resources-test/bytes/image.svg")
+ .addAsResource("static-file.html", "META-INF/generated-resources-test/static-file.html")
+ .addAsResource("static-file.html", "META-INF/generated-resources-test/.hidden-file.html")
+ .addAsResource("static-file.html", "META-INF/generated-resources-test/index.html")
+ .addAsResource("static-file.html", "META-INF/generated-resources-test/image.svg"));
@Test
void shouldUpdateResourceIndexHtmlOnUserChange() {
+ RestAssured.given()
+ .get("/bytes/")
+ .then()
+ .statusCode(200)
+ .body(Matchers.containsString("This is the title of the webpage!"));
+
+ devMode.modifyResourceFile("META-INF/generated-resources-test/bytes/index.html", s -> s.replace("webpage", "Matheus"));
+
+ RestAssured.given()
+ .get("/bytes/")
+ .then()
+ .statusCode(200)
+ .body(Matchers.containsString("This is the title of the Matheus!"));
+ }
+
+ @Test
+ void shouldUpdateHiddenResourceOnUserChange() {
+ RestAssured.given()
+ .get("/bytes/.hidden-file.html")
+ .then()
+ .statusCode(200)
+ .body(Matchers.containsString("This is the title of the webpage!"));
+ devMode.modifyResourceFile("META-INF/generated-resources-test/bytes/.hidden-file.html",
+ s -> s.replace("webpage", "Matheus"));
+ RestAssured.given()
+ .get("/bytes/.hidden-file.html")
+ .then()
+ .statusCode(200)
+ .body(Matchers.containsString("This is the title of the Matheus!"));
+ }
+
+ @Test
+ void shouldUpdateFileResourceIndexHtmlOnUserChange() {
RestAssured.given()
.get("/")
.then()
.statusCode(200)
.body(Matchers.containsString("This is the title of the webpage!"));
- devMode.modifyResourceFile("META-INF/generated-resources/index.html", s -> s.replace("webpage", "Matheus"));
+ devMode.modifyResourceFile("META-INF/generated-resources-test/index.html", s -> s.replace("webpage", "Matheus"));
RestAssured.given()
.get("/")
@@ -38,15 +75,16 @@ void shouldUpdateResourceIndexHtmlOnUserChange() {
}
@Test
- void shouldUpdateHiddenResourceOnUserChange() {
+ void shouldUpdateHiddenFileResourceOnUserChange() {
RestAssured.given()
.get("/.hidden-file.html")
.then()
.statusCode(200)
.body(Matchers.containsString("This is the title of the webpage!"));
- devMode.modifyResourceFile("META-INF/generated-resources/.hidden-file.html", s -> s.replace("webpage", "Matheus"));
+ devMode.modifyResourceFile("META-INF/generated-resources-test/.hidden-file.html",
+ s -> s.replace("webpage", "Matheus"));
RestAssured.given()
- .get(".hidden-file.html")
+ .get("/.hidden-file.html")
.then()
.statusCode(200)
.body(Matchers.containsString("This is the title of the Matheus!"));
diff --git a/extensions/vertx-http/deployment/src/test/resources/.nojekyll b/extensions/vertx-http/deployment/src/test/resources/.nojekyll
new file mode 100644
index 0000000000000..06130e8de130d
--- /dev/null
+++ b/extensions/vertx-http/deployment/src/test/resources/.nojekyll
@@ -0,0 +1 @@
+{empty}
\ No newline at end of file
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/GeneratedStaticResourcesRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/GeneratedStaticResourcesRecorder.java
index 87df8dd36b0e8..394657aa62211 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/GeneratedStaticResourcesRecorder.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/GeneratedStaticResourcesRecorder.java
@@ -1,12 +1,13 @@
package io.quarkus.vertx.http.runtime;
+import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
-import io.quarkus.vertx.http.runtime.handlers.DevClasspathStaticHandler;
import io.quarkus.vertx.http.runtime.handlers.DevClasspathStaticHandlerOptions;
+import io.quarkus.vertx.http.runtime.handlers.DevStaticHandler;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.Route;
@@ -26,7 +27,8 @@ public GeneratedStaticResourcesRecorder(RuntimeValue httpConf
this.httpBuildTimeConfig = httpBuildTimeConfig;
}
- public Handler createHandler(Set generatedResources) {
+ public Handler createHandler(Set generatedClasspathResources,
+ Map generatedFilesResources) {
if (httpBuildTimeConfig.enableCompression && httpBuildTimeConfig.compressMediaTypes.isPresent()) {
this.compressMediaTypes = Set.copyOf(httpBuildTimeConfig.compressMediaTypes.get());
@@ -38,7 +40,8 @@ public Handler createHandler(Set generatedResources) {
.enableCompression(httpBuildTimeConfig.enableCompression)
.compressMediaTypes(compressMediaTypes)
.defaultEncoding(config.contentEncoding).build();
- return new DevClasspathStaticHandler(generatedResources,
+ return new DevStaticHandler(generatedClasspathResources,
+ generatedFilesResources,
options);
}
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/handlers/DevClasspathStaticHandler.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/handlers/DevStaticHandler.java
similarity index 81%
rename from extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/handlers/DevClasspathStaticHandler.java
rename to extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/handlers/DevStaticHandler.java
index fda217224b8d8..08e0a8826a3ed 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/handlers/DevClasspathStaticHandler.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/handlers/DevStaticHandler.java
@@ -4,6 +4,7 @@
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
+import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
@@ -21,22 +22,25 @@
* {@link StaticHandler} implementation to handle static resources using the Classpath.
* This is meant to be used on {@link io.quarkus.runtime.LaunchMode#DEVELOPMENT} mode.
*/
-public class DevClasspathStaticHandler implements Handler {
+public class DevStaticHandler implements Handler {
- private static final Logger LOG = Logger.getLogger(DevClasspathStaticHandler.class);
+ private static final Logger LOG = Logger.getLogger(DevStaticHandler.class);
private static final int HTTP_STATUS_OK = 200;
private static final int HTTP_STATUS_NO_CONTENT = 204;
private static final String ALLOW_HEADER = "Allow";
private static final String ALLOW_HEADER_VALUE = "HEAD,GET,OPTIONS";
- private final Set generatedResources;
+ private final Set generatedClasspathResources;
+ private final Map generatedFilesResources;
private final Set compressedMediaTypes;
private final ClassLoader currentClassLoader;
private final boolean enableCompression;
private final String indexPage;
private final Charset defaultEncoding;
- public DevClasspathStaticHandler(Set generatedResources, DevClasspathStaticHandlerOptions options) {
- this.generatedResources = generatedResources;
+ public DevStaticHandler(Set generatedClasspathResources, Map generatedFilesResources,
+ DevClasspathStaticHandlerOptions options) {
+ this.generatedClasspathResources = generatedClasspathResources;
+ this.generatedFilesResources = generatedFilesResources;
this.compressedMediaTypes = options.getCompressMediaTypes();
this.currentClassLoader = Thread.currentThread().getContextClassLoader();
this.enableCompression = options.isEnableCompression();
@@ -54,7 +58,8 @@ public void handle(RoutingContext context) {
LOG.debugf("Handling request for path '%s'", path);
}
- boolean containsGeneratedResource = this.generatedResources.contains(path);
+ boolean containsGeneratedResource = this.generatedClasspathResources.contains(path)
+ || this.generatedFilesResources.containsKey(path);
if (!containsGeneratedResource) {
beforeNextHandler(this.currentClassLoader, context);
@@ -69,6 +74,17 @@ public void handle(RoutingContext context) {
compressIfNeeded(context, path);
+ if (generatedFilesResources.containsKey(path)) {
+ context.vertx().fileSystem().readFile(generatedFilesResources.get(path), r -> {
+ if (r.succeeded()) {
+ handleAsyncResultSucceeded(context, r.result(), path);
+ } else {
+ context.fail(r.cause());
+ }
+ });
+ return;
+ }
+
context.vertx().executeBlocking(future -> {
try {
byte[] content = getClasspathResourceContent(path);
@@ -79,14 +95,14 @@ public void handle(RoutingContext context) {
}, asyncResult -> {
if (asyncResult.succeeded()) {
byte[] result = (byte[]) asyncResult.result();
- handleAsyncResultSucceeded(context, result, path);
+ handleAsyncResultSucceeded(context, result == null ? null : Buffer.buffer(result), path);
} else {
context.fail(asyncResult.cause());
}
});
}
- private void handleAsyncResultSucceeded(RoutingContext context, byte[] result, String path) {
+ private void handleAsyncResultSucceeded(RoutingContext context, Buffer result, String path) {
if (result == null) {
LOG.warnf("The '%s' file does not contain any content. Proceeding to the next handler if it exists", path);
@@ -104,12 +120,12 @@ private void handleAsyncResultSucceeded(RoutingContext context, byte[] result, S
if (context.request().method().equals(HttpMethod.HEAD)) {
handleHeadMethod(context, result);
} else {
- context.response().send(Buffer.buffer(result));
+ context.response().send(result);
}
}
- private void handleHeadMethod(RoutingContext context, byte[] content) {
- context.response().putHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(content.length));
+ private void handleHeadMethod(RoutingContext context, Buffer content) {
+ context.response().putHeader(HttpHeaders.CONTENT_LENGTH, Long.toString(content.length()));
context.response().setStatusCode(HTTP_STATUS_OK).end();
}