Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle static resources in vertx-http #10912

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,10 @@
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;

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;
import io.quarkus.deployment.Feature;
Expand All @@ -31,32 +20,25 @@
import io.quarkus.resteasy.common.deployment.ResteasyInjectionReadyBuildItem;
import io.quarkus.resteasy.runtime.standalone.ResteasyStandaloneRecorder;
import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentBuildItem;
import io.quarkus.runtime.util.ClassPathUtils;
import io.quarkus.vertx.core.deployment.CoreVertxBuildItem;
import io.quarkus.vertx.http.deployment.DefaultRouteBuildItem;
import io.quarkus.vertx.http.deployment.RequireVirtualHttpBuildItem;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.quarkus.vertx.http.runtime.BasicRoute;
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;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.RoutingContext;

public class ResteasyStandaloneBuildStep {

protected static final String META_INF_RESOURCES_SLASH = "META-INF/resources/";
protected static final String META_INF_RESOURCES = "META-INF/resources";

public static final class ResteasyStandaloneBuildItem extends SimpleBuildItem {

final String deploymentRootPath;

public ResteasyStandaloneBuildItem(String deploymentRootPath) {
if (deploymentRootPath != null) {
this.deploymentRootPath = deploymentRootPath.startsWith("/") ? deploymentRootPath : "/" + deploymentRootPath;
} else {
this.deploymentRootPath = null;
}
this.deploymentRootPath = deploymentRootPath.startsWith("/") ? deploymentRootPath : "/" + deploymentRootPath;
}

}
Expand All @@ -74,7 +56,6 @@ public void staticInit(ResteasyStandaloneRecorder recorder,
return;
}

Set<String> knownPaths = getClasspathResources(applicationArchivesBuildItem);
String deploymentRootPath = null;
// The context path + the resources path
String rootPath = httpConfig.rootPath;
Expand All @@ -93,64 +74,11 @@ public void staticInit(ResteasyStandaloneRecorder recorder,
}
rootPath += deploymentRootPath;
}
recorder.staticInit(deployment.getDeployment(), rootPath, knownPaths);

} else if (!knownPaths.isEmpty()) {
recorder.staticInit(null, rootPath, knownPaths);
}

if (deployment != null || !knownPaths.isEmpty()) {
recorder.staticInit(deployment.getDeployment(), rootPath);
standalone.produce(new ResteasyStandaloneBuildItem(deploymentRootPath));
}
}

/**
* Find all static file resources that are available from classpath.
*
* @param applicationArchivesBuildItem
* @return
* @throws Exception
*/
private Set<String> getClasspathResources(ApplicationArchivesBuildItem applicationArchivesBuildItem) throws Exception {
Set<String> knownPaths = new HashSet<>();
for (ApplicationArchive i : applicationArchivesBuildItem.getAllApplicationArchives()) {
Path resource = i.getChildPath(META_INF_RESOURCES);
if (resource != null && Files.exists(resource)) {
collectKnownPaths(resource, knownPaths);
}
}

ClassPathUtils.consumeAsPaths(META_INF_RESOURCES, resource -> {
collectKnownPaths(resource, knownPaths);
});

return knownPaths;
}

private void collectKnownPaths(Path resource, Set<String> knownPaths) {
try {
Files.walkFileTree(resource, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path p, BasicFileAttributes attrs)
throws IOException {
String file = resource.relativize(p).toString();
if (file.equals("index.html") || file.equals("index.htm")) {
knownPaths.add("/");
}
if (!file.startsWith("/")) {
file = "/" + file;
}
// Windows has a backslash
file = file.replace('\\', '/');
knownPaths.add(file);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

@BuildStep
@Record(RUNTIME_INIT)
public void boot(ShutdownContextBuildItem shutdown,
Expand All @@ -170,31 +98,23 @@ public void boot(ShutdownContextBuildItem shutdown,
}
feature.produce(new FeatureBuildItem(Feature.RESTEASY));

boolean isDefaultOrNullDeploymentPath = standalone.deploymentRootPath == null
|| standalone.deploymentRootPath.equals("/");
if (!isDefaultOrNullDeploymentPath) {
// We need to register a special handler for non-default deployment path (specified as application path or resteasyConfig.path)
Handler<RoutingContext> handler = recorder.vertxRequestHandler(vertx.getVertx(), beanContainer.getValue(),
executorBuildItem.getExecutorProxy(), httpConfiguration);
// Exact match for resources matched to the root path
routes.produce(new RouteBuildItem(standalone.deploymentRootPath, handler, false));
String matchPath = standalone.deploymentRootPath;
if (matchPath.endsWith("/")) {
matchPath += "*";
} else {
matchPath += "/*";
}
// Match paths that begin with the deployment path
routes.produce(new RouteBuildItem(matchPath, handler, false));
// Handler used for both the default and non-default deployment path (specified as application path or resteasyConfig.path)
// Routes use the order VertxHttpRecorder.DEFAULT_ROUTE_ORDER + 1 to ensure the default route is called before the resteasy one
Handler<RoutingContext> handler = recorder.vertxRequestHandler(vertx.getVertx(), beanContainer.getValue(),
executorBuildItem.getExecutorProxy(), httpConfiguration);
// Exact match for resources matched to the root path
routes.produce(new RouteBuildItem(
new BasicRoute(standalone.deploymentRootPath, VertxHttpRecorder.DEFAULT_ROUTE_ORDER + 1), handler));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order value of VertxHttpRecorder.DEFAULT_ROUTE_ORDER + 1 means that resteasy route (resource methods) are called after the default route. Unlike custom routes and @Route methods which are called before the default route. The problem with resteasy route is that it returns 404 for unknown resource and does not call RoutingContext.next(). And since we handle the static resources in the default route it would always return 404 for a request to a static route. @cescoffier @stuartwdouglas Do you think this approach is OK? If yes, I'm going to merge this PR...

String matchPath = standalone.deploymentRootPath;
if (matchPath.endsWith("/")) {
matchPath += "*";
} else {
matchPath += "/*";
}
// Match paths that begin with the deployment path
routes.produce(new RouteBuildItem(new BasicRoute(matchPath, VertxHttpRecorder.DEFAULT_ROUTE_ORDER + 1), handler));

boolean isVirtual = requireVirtual.isPresent();
Consumer<Route> ut = recorder.start(vertx.getVertx(),
shutdown,
beanContainer.getValue(),
isVirtual, isDefaultOrNullDeploymentPath, executorBuildItem.getExecutorProxy(), httpConfiguration);

defaultRoutes.produce(new DefaultRouteBuildItem(ut));
recorder.start(shutdown, requireVirtual.isPresent());
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
package io.quarkus.resteasy.runtime.standalone;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.jboss.resteasy.spi.ResteasyDeployment;
Expand All @@ -16,12 +11,9 @@
import io.quarkus.runtime.ShutdownContext;
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.vertx.http.runtime.HttpConfiguration;
import io.quarkus.vertx.http.runtime.ThreadLocalHandler;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.StaticHandler;

/**
* Provides the runtime methods to bootstrap Resteasy in standalone mode.
Expand Down Expand Up @@ -70,30 +62,18 @@ public int getBufferSize() {
}
};

private static volatile List<Path> hotDeploymentResourcePaths;

public static void setHotDeploymentResources(List<Path> resources) {
hotDeploymentResourcePaths = resources;
}

private static ResteasyDeployment deployment;
private static Set<String> knownPaths;
private static String contextPath;

public void staticInit(ResteasyDeployment dep, String path, Set<String> known) {
public void staticInit(ResteasyDeployment dep, String path) {
if (dep != null) {
deployment = dep;
deployment.start();
}
knownPaths = known;
contextPath = path;
}

public Consumer<Route> start(Supplier<Vertx> vertx,
ShutdownContext shutdown,
BeanContainer beanContainer,
boolean isVirtual, boolean isDefaultResourcesPath,
Executor executor, HttpConfiguration httpConfiguration) {
public void start(ShutdownContext shutdown, boolean isVirtual) {

shutdown.addShutdownTask(new Runnable() {
@Override
Expand All @@ -104,64 +84,6 @@ public void run() {
}
});
useDirect = !isVirtual;
List<Handler<RoutingContext>> handlers = new ArrayList<>();

if (hotDeploymentResourcePaths != null && !hotDeploymentResourcePaths.isEmpty()) {
for (Path resourcePath : hotDeploymentResourcePaths) {
String root = resourcePath.toAbsolutePath().toString();
ThreadLocalHandler staticHandler = new ThreadLocalHandler(new Supplier<Handler<RoutingContext>>() {
@Override
public Handler<RoutingContext> get() {
StaticHandler staticHandler = StaticHandler.create();
staticHandler.setCachingEnabled(false);
staticHandler.setAllowRootFileSystemAccess(true);
staticHandler.setWebRoot(root);
staticHandler.setDefaultContentEncoding("UTF-8");
return staticHandler;
}
});
handlers.add(event -> {
try {
staticHandler.handle(event);
} catch (Exception e) {
// on Windows, the drive in file path screws up cache lookup
// so just punt to next handler
event.next();
}
});
}
}
if (!knownPaths.isEmpty()) {
ThreadLocalHandler staticHandler = new ThreadLocalHandler(new Supplier<Handler<RoutingContext>>() {
@Override
public Handler<RoutingContext> get() {
return StaticHandler.create(META_INF_RESOURCES)
.setDefaultContentEncoding("UTF-8");
}
});
handlers.add(ctx -> {
String rel = ctx.mountPoint() == null ? ctx.normalisedPath()
: ctx.normalisedPath().substring(ctx.mountPoint().length());
if (knownPaths.contains(rel)) {
staticHandler.handle(ctx);
} else {
ctx.next();
}
});
}

if (deployment != null && isDefaultResourcesPath) {
handlers.add(vertxRequestHandler(vertx, beanContainer, executor, httpConfiguration));
}
return new Consumer<Route>() {

@Override
public void accept(Route route) {
for (Handler<RoutingContext> i : handlers) {
route.handler(i);
}
}
};
}

public Handler<RoutingContext> vertxRequestHandler(Supplier<Vertx> vertx,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.quarkus.smallrye.graphql.runtime;

import java.util.function.Supplier;

import javax.enterprise.inject.Instance;
import javax.enterprise.inject.spi.CDI;

Expand All @@ -10,7 +8,6 @@
import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.security.identity.CurrentIdentityAssociation;
import io.quarkus.smallrye.graphql.runtime.spi.QuarkusClassloadingService;
import io.quarkus.vertx.http.runtime.ThreadLocalHandler;
import io.smallrye.graphql.cdi.producer.GraphQLProducer;
import io.smallrye.graphql.schema.model.Schema;
import io.vertx.core.Handler;
Expand Down Expand Up @@ -45,14 +42,9 @@ public Handler<RoutingContext> schemaHandler() {

public Handler<RoutingContext> uiHandler(String graphqlUiFinalDestination, String graphqlUiPath) {

Handler<RoutingContext> handler = new ThreadLocalHandler(new Supplier<Handler<RoutingContext>>() {
@Override
public Handler<RoutingContext> get() {
return StaticHandler.create().setAllowRootFileSystemAccess(true)
.setWebRoot(graphqlUiFinalDestination)
.setDefaultContentEncoding("UTF-8");
}
});
StaticHandler staticHandler = StaticHandler.create().setAllowRootFileSystemAccess(true)
.setWebRoot(graphqlUiFinalDestination)
.setDefaultContentEncoding("UTF-8");

return new Handler<RoutingContext>() {
@Override
Expand All @@ -68,7 +60,7 @@ public void handle(RoutingContext event) {
return;
}

handler.handle(event);
staticHandler.handle(event);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package io.quarkus.smallrye.health.runtime;

import java.util.function.Supplier;

import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.spi.HealthCheckResponseProvider;

import io.quarkus.runtime.annotations.Recorder;
import io.quarkus.vertx.http.runtime.ThreadLocalHandler;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpHeaders;
import io.vertx.ext.web.RoutingContext;
Expand All @@ -26,14 +23,9 @@ public void registerHealthCheckResponseProvider(Class<? extends HealthCheckRespo

public Handler<RoutingContext> uiHandler(String healthUiFinalDestination, String healthUiPath) {

Handler<RoutingContext> handler = new ThreadLocalHandler(new Supplier<Handler<RoutingContext>>() {
@Override
public Handler<RoutingContext> get() {
return StaticHandler.create().setAllowRootFileSystemAccess(true)
.setWebRoot(healthUiFinalDestination)
.setDefaultContentEncoding("UTF-8");
}
});
StaticHandler staticHandler = StaticHandler.create().setAllowRootFileSystemAccess(true)
.setWebRoot(healthUiFinalDestination)
.setDefaultContentEncoding("UTF-8");

return new Handler<RoutingContext>() {
@Override
Expand All @@ -49,7 +41,7 @@ public void handle(RoutingContext event) {
return;
}

handler.handle(event);
staticHandler.handle(event);
}
};
}
Expand Down
Loading