diff --git a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java index 8102a84ca82d6..384539170f0dd 100644 --- a/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java +++ b/extensions/resteasy/deployment/src/main/java/io/quarkus/resteasy/deployment/ResteasyStandaloneBuildStep.java @@ -42,6 +42,8 @@ public class ResteasyStandaloneBuildStep { protected static final String META_INF_RESOURCES_SLASH = "META-INF/resources/"; protected static final String META_INF_RESOURCES = "META-INF/resources"; + private static final int URI_INFO_CACHE_MAX_SIZE = 100; // TODO we should set this via config + public static final class ResteasyStandaloneBuildItem extends SimpleBuildItem { final String deploymentRootPath; @@ -88,10 +90,10 @@ public void staticInit(ResteasyStandaloneRecorder recorder, } rootPath += deploymentRootPath; } - recorder.staticInit(deployment.getDeployment(), rootPath, knownPaths); + recorder.staticInit(deployment.getDeployment(), rootPath, knownPaths, URI_INFO_CACHE_MAX_SIZE); } else if (!knownPaths.isEmpty()) { - recorder.staticInit(null, rootPath, knownPaths); + recorder.staticInit(null, rootPath, knownPaths, URI_INFO_CACHE_MAX_SIZE); } if (deployment != null || !knownPaths.isEmpty()) { diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java index 312a91fcc1224..7881826abdd0b 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java +++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/ResteasyStandaloneRecorder.java @@ -78,14 +78,16 @@ public static void setHotDeploymentResources(List resources) { private static ResteasyDeployment deployment; private static Set knownPaths; private static String contextPath; + private static int resteasyUriInfoCacheMaxSize; - public void staticInit(ResteasyDeployment dep, String path, Set known) { + public void staticInit(ResteasyDeployment dep, String path, Set known, int uriInfoCacheMaxSize) { if (dep != null) { deployment = dep; deployment.start(); } knownPaths = known; contextPath = path; + resteasyUriInfoCacheMaxSize = uriInfoCacheMaxSize; } public Consumer start(RuntimeValue vertx, @@ -163,7 +165,8 @@ public void accept(Route route) { public Handler vertxRequestHandler(RuntimeValue vertx, BeanContainer beanContainer) { if (deployment != null) { - return new VertxRequestHandler(vertx.getValue(), beanContainer, deployment, contextPath, ALLOCATOR); + return new VertxRequestHandler(vertx.getValue(), beanContainer, deployment, contextPath, + resteasyUriInfoCacheMaxSize, ALLOCATOR); } return null; } diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java index f9d97aee3b939..340e42900d5a2 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java +++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxRequestHandler.java @@ -2,6 +2,8 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import javax.enterprise.inject.Instance; import javax.enterprise.inject.spi.CDI; @@ -35,20 +37,25 @@ public class VertxRequestHandler implements Handler { protected final Vertx vertx; protected final RequestDispatcher dispatcher; protected final String rootPath; + protected final int resteasyUriInfoCacheMaxSize; protected final BufferAllocator allocator; protected final BeanContainer beanContainer; protected final CurrentIdentityAssociation association; + private final Map resteasyUriInfoInitDataMap = new ConcurrentHashMap<>(); + public VertxRequestHandler(Vertx vertx, BeanContainer beanContainer, ResteasyDeployment deployment, String rootPath, + int resteasyUriInfoCacheMaxSize, BufferAllocator allocator) { this.vertx = vertx; this.beanContainer = beanContainer; this.dispatcher = new RequestDispatcher((SynchronousDispatcher) deployment.getDispatcher(), deployment.getProviderFactory(), null); this.rootPath = rootPath; + this.resteasyUriInfoCacheMaxSize = resteasyUriInfoCacheMaxSize; this.allocator = allocator; Instance association = CDI.current().select(CurrentIdentityAssociation.class); this.association = association.isResolvable() ? association.get() : null; @@ -90,7 +97,23 @@ private void dispatch(RoutingContext routingContext, InputStream is, VertxOutput try { Context ctx = vertx.getOrCreateContext(); HttpServerRequest request = routingContext.request(); - ResteasyUriInfo uriInfo = VertxUtil.extractUriInfo(request, rootPath); + + String uriString = VertxUtil.getUriString(request); + ResteasyUriInfo.InitData resteasyUriInfoCachedInitData = null; + boolean setInitDataUponSuccess = false; + if (ResteasyUriInfo.InitData.canBeCached(uriString) && resteasyUriInfoCacheMaxSize > 0) { + String cacheKey = ResteasyUriInfo.InitData.getCacheKey(uriString, rootPath); + resteasyUriInfoCachedInitData = resteasyUriInfoInitDataMap.get(cacheKey); + if (resteasyUriInfoCachedInitData == null) { + setInitDataUponSuccess = true; + } + } + if (resteasyUriInfoCachedInitData == null) { + resteasyUriInfoCachedInitData = new ResteasyUriInfo.InitData(uriString, rootPath); + } + + ResteasyUriInfo uriInfo = new ResteasyUriInfo(uriString, rootPath, resteasyUriInfoCachedInitData); + ResteasyHttpHeaders headers = VertxUtil.extractHttpHeaders(request); HttpServerResponse response = request.response(); VertxHttpResponse vertxResponse = new VertxHttpResponse(request, dispatcher.getProviderFactory(), @@ -114,6 +137,19 @@ private void dispatch(RoutingContext routingContext, InputStream is, VertxOutput if (!vertxRequest.getAsyncContext().isSuspended()) { try { vertxResponse.finish(); + // the reason we only cache successful responses is to ensure that a torrent of erroneous URLs + // doesn't fill up the cache and cause a DoS + if (setInitDataUponSuccess && vertxResponse.getStatus() >= 200 && vertxResponse.getStatus() <= 300) { + // we don't want the cache to grow unbounded since values path params could potentially be infinite + // and if left unchecked would take up all memory if the application is up for long enough + if (resteasyUriInfoInitDataMap.size() > resteasyUriInfoCacheMaxSize) { + resteasyUriInfoInitDataMap.clear(); // this is super lame and should probably be revisited + } + // this could potentially be written multiple times for initial requests but it doesn't matter + // since the data is always the same + resteasyUriInfoInitDataMap.put(ResteasyUriInfo.InitData.getCacheKey(uriString, rootPath), + resteasyUriInfoCachedInitData); + } } catch (IOException e) { log.error("Unexpected failure", e); } diff --git a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxUtil.java b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxUtil.java index 72b62c7f43f63..28ddc772b1451 100644 --- a/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxUtil.java +++ b/extensions/resteasy/runtime/src/main/java/io/quarkus/resteasy/runtime/standalone/VertxUtil.java @@ -27,7 +27,7 @@ public class VertxUtil { private static final Pattern COMMA_PATTERN = Pattern.compile(","); - public static ResteasyUriInfo extractUriInfo(HttpServerRequest req, String contextPath) { + public static String getUriString(HttpServerRequest req) { String uri = req.absoluteURI(); String protocol = req.scheme(); @@ -44,12 +44,16 @@ public static ResteasyUriInfo extractUriInfo(HttpServerRequest req, String conte uriString = protocol + "://" + host + uri; } - // ResteasyUriInfo expects a context path to start with a "/" character - if (!contextPath.startsWith("/")) { - contextPath = "/" + contextPath; - } + return uriString; + } - return new ResteasyUriInfo(uriString, contextPath); + public static ResteasyUriInfo.InitData extractUriInfoInitData(String uriString, String contextPath, + Map resteasyUriInfoInitDataMap) { + if (ResteasyUriInfo.InitData.canBeCached(uriString)) { + String cacheKey = ResteasyUriInfo.InitData.getCacheKey(uriString, contextPath); + return resteasyUriInfoInitDataMap.get(cacheKey); + } + return null; } public static ResteasyHttpHeaders extractHttpHeaders(HttpServerRequest request) {