From 3a819480aab3d207c7c1c1f974e1c5be597f99dd Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Fri, 6 Dec 2019 13:43:27 +1100 Subject: [PATCH] Use body handler to allow request buffering Fixes #5959 This change allows the body handler to be used for Undertow and RESTEasy standalone. When the request is fully buffered it can be consumed multiple times, which allows keycloak to also process it. --- bom/runtime/pom.xml | 2 +- .../KeycloakPolicyEnforcerBuildStep.java | 7 ++++ .../keycloak/pep/runtime/VertxHttpFacade.java | 19 ++++------ .../standalone/VertxRequestHandler.java | 8 ++-- .../runtime/UndertowDeploymentRecorder.java | 3 +- .../http/deployment/BodyHandlerBuildItem.java | 17 +++++++++ .../RequireBodyHandlerBuildItem.java | 10 +++++ .../http/deployment/VertxHttpProcessor.java | 18 ++++++++- .../vertx/http/runtime/VertxHttpRecorder.java | 37 ++++++++++++++++++- .../vertx/http/runtime/VertxInputStream.java | 8 ++-- .../web/deployment/BodyHandlerBuildItem.java | 4 ++ .../web/deployment/VertxWebProcessor.java | 12 +++--- .../vertx/web/runtime/VertxWebRecorder.java | 30 ++------------- 13 files changed, 118 insertions(+), 57 deletions(-) create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java create mode 100644 extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 32eff33ff677c9..211a464ecf3321 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -24,7 +24,7 @@ 0.2.0 0.0.12 0.34.0 - 3.0.0.Final + 3.0.1.Final 1.0.0.Final 1.3 1.0.1 diff --git a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java index f9775d62682b08..3dd6edb748e88f 100644 --- a/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java +++ b/extensions/keycloak-authorization/deployment/src/main/java/io/quarkus/keycloak/pep/deployment/KeycloakPolicyEnforcerBuildStep.java @@ -13,6 +13,7 @@ import io.quarkus.oidc.OIDCException; import io.quarkus.oidc.runtime.OidcBuildTimeConfig; import io.quarkus.oidc.runtime.OidcConfig; +import io.quarkus.vertx.http.deployment.RequireBodyHandlerBuildItem; public class KeycloakPolicyEnforcerBuildStep { @@ -21,6 +22,12 @@ FeatureBuildItem featureBuildItem() { return new FeatureBuildItem(FeatureBuildItem.KEYCLOAK_AUTHORIZATION); } + @BuildStep + RequireBodyHandlerBuildItem requireBody() { + //TODO: this should only be produced if required, ask Pedro about config for this + return new RequireBodyHandlerBuildItem(); + } + @BuildStep public AdditionalBeanBuildItem beans(KeycloakPolicyEnforcerConfig config) { if (config.policyEnforcer.enable) { diff --git a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java index 825d0993b1306e..eabdb995a42a38 100644 --- a/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java +++ b/extensions/keycloak-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/runtime/VertxHttpFacade.java @@ -1,6 +1,5 @@ package io.quarkus.keycloak.pep.runtime; -import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; @@ -11,8 +10,6 @@ import javax.net.ssl.SSLPeerUnverifiedException; import javax.security.cert.X509Certificate; -import io.quarkus.vertx.http.runtime.VertxInputStream; -import io.vertx.core.http.HttpHeaders; import org.keycloak.KeycloakSecurityContext; import org.keycloak.adapters.OIDCHttpFacade; import org.keycloak.adapters.spi.AuthenticationError; @@ -25,8 +22,10 @@ import io.quarkus.oidc.AccessTokenCredential; import io.quarkus.security.credential.TokenCredential; import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.vertx.http.runtime.VertxInputStream; import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; import io.vertx.core.buffer.Buffer; +import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.HttpServerResponse; import io.vertx.core.http.impl.CookieImpl; @@ -133,15 +132,13 @@ public InputStream getInputStream() { @Override public InputStream getInputStream(boolean buffered) { try { - if (routingContext.get("quarkus.request.inputstream") != null) { - return routingContext.get("quarkus.request.inputstream"); + if (routingContext.getBody() != null) { + return new ByteArrayInputStream(routingContext.getBody().getBytes()); } - - BufferedInputStream stream = new BufferedInputStream(new VertxInputStream(request)); - - routingContext.put("quarkus.request.inputstream", stream); - - return stream; + if (routingContext.request().isEnded()) { + return new ByteArrayInputStream(new byte[0]); + } + return new VertxInputStream(request); } catch (Exception e) { throw new RuntimeException(e); } 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 fb81b825c1ba09..ccd63565410d40 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 @@ -1,5 +1,6 @@ package io.quarkus.resteasy.runtime.standalone; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -7,7 +8,6 @@ import javax.enterprise.inject.spi.CDI; import javax.ws.rs.core.SecurityContext; -import io.quarkus.vertx.http.runtime.VertxInputStream; import org.jboss.logging.Logger; import org.jboss.resteasy.core.ResteasyContext; import org.jboss.resteasy.core.SynchronousDispatcher; @@ -20,6 +20,7 @@ import io.quarkus.arc.runtime.BeanContainer; import io.quarkus.security.identity.CurrentIdentityAssociation; import io.quarkus.vertx.http.runtime.CurrentVertxRequest; +import io.quarkus.vertx.http.runtime.VertxInputStream; import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser; import io.vertx.core.Context; import io.vertx.core.Handler; @@ -64,9 +65,8 @@ public void handle(RoutingContext request) { // otherwise request handlers may not get set up before request ends InputStream is; try { - if (request.get("quarkus.request.inputstream") != null) { - is = request.get("quarkus.request.inputstream"); - is.mark(0); + if (request.getBody() != null) { + is = new ByteArrayInputStream(request.getBody().getBytes()); } else { is = new VertxInputStream(request.request()); } diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java index f9e7394440a1bf..d66f8d71f3e487 100644 --- a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowDeploymentRecorder.java @@ -353,7 +353,8 @@ public void run() { return new Handler() { @Override public void handle(RoutingContext event) { - VertxHttpExchange exchange = new VertxHttpExchange(event.request(), allocator, executorService, event); + VertxHttpExchange exchange = new VertxHttpExchange(event.request(), allocator, executorService, event, + event.getBody()); Optional maxBodySize = httpConfiguration.limits.maxBodySize; if (maxBodySize.isPresent()) { exchange.setMaxEntitySize(maxBodySize.get().asLongValue()); diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java new file mode 100644 index 00000000000000..c5743fcd303850 --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/BodyHandlerBuildItem.java @@ -0,0 +1,17 @@ +package io.quarkus.vertx.http.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +public final class BodyHandlerBuildItem extends SimpleBuildItem { + private final Handler handler; + + public BodyHandlerBuildItem(Handler handler) { + this.handler = handler; + } + + public Handler getHandler() { + return handler; + } +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java new file mode 100644 index 00000000000000..dbfd8541499a3d --- /dev/null +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/RequireBodyHandlerBuildItem.java @@ -0,0 +1,10 @@ +package io.quarkus.vertx.http.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * This is a marker that indicates that the body handler should be installed + * on all routes, as an extension requires the request to be fully buffered. + */ +public final class RequireBodyHandlerBuildItem extends MultiBuildItem { +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java index 2dcebfca9208d3..344f6a18cfd907 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/VertxHttpProcessor.java @@ -35,8 +35,10 @@ import io.quarkus.vertx.http.runtime.VertxHttpRecorder; import io.quarkus.vertx.http.runtime.cors.CORSRecorder; import io.quarkus.vertx.http.runtime.filters.Filter; +import io.vertx.core.Handler; import io.vertx.core.impl.VertxImpl; import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; class VertxHttpProcessor { @@ -97,6 +99,12 @@ VertxWebRouterBuildItem initializeRouter(VertxHttpRecorder recorder, return new VertxWebRouterBuildItem(router); } + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + BodyHandlerBuildItem bodyHandler(VertxHttpRecorder recorder, HttpConfiguration httpConfiguration) { + return new BodyHandlerBuildItem(recorder.createBodyHandler(httpConfiguration)); + } + @BuildStep @Record(ExecutionTime.RUNTIME_INIT) ServiceStartBuildItem finalizeRouter( @@ -106,7 +114,9 @@ ServiceStartBuildItem finalizeRouter( List defaultRoutes, List filters, VertxWebRouterBuildItem router, EventLoopCountBuildItem eventLoopCount, HttpBuildTimeConfig httpBuildTimeConfig, HttpConfiguration httpConfiguration, - BuildProducer reflectiveClass, List websocketSubProtocols) + BuildProducer reflectiveClass, List websocketSubProtocols, + List requireBodyHandlerBuildItems, + BodyHandlerBuildItem bodyHandlerBuildItem) throws BuildException, IOException { Optional defaultRoute; if (defaultRoutes == null || defaultRoutes.isEmpty()) { @@ -124,9 +134,13 @@ ServiceStartBuildItem finalizeRouter( .filter(f -> f.getHandler() != null) .map(FilterBuildItem::toFilter).collect(Collectors.toList()); + //if the body handler is required then we know it is installed for all routes, so we don't need to register it here + Handler bodyHandler = requireBodyHandlerBuildItems.isEmpty() ? bodyHandlerBuildItem.getHandler() : null; + recorder.finalizeRouter(beanContainer.getValue(), defaultRoute.map(DefaultRouteBuildItem::getRoute).orElse(null), - listOfFilters, vertx.getVertx(), router.getRouter(), httpBuildTimeConfig.rootPath, launchMode.getLaunchMode()); + listOfFilters, vertx.getVertx(), router.getRouter(), httpBuildTimeConfig.rootPath, launchMode.getLaunchMode(), + !requireBodyHandlerBuildItems.isEmpty(), bodyHandler); boolean startVirtual = requireVirtual.isPresent() || httpBuildTimeConfig.virtual; if (startVirtual) { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index 4b7b13dfbc37aa..69568666ea191e 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -35,6 +35,7 @@ import io.quarkus.runtime.Timing; import io.quarkus.runtime.annotations.Recorder; import io.quarkus.runtime.configuration.ConfigInstantiator; +import io.quarkus.runtime.configuration.MemorySize; import io.quarkus.vertx.core.runtime.VertxCoreRecorder; import io.quarkus.vertx.core.runtime.config.VertxConfiguration; import io.quarkus.vertx.http.runtime.filters.Filter; @@ -61,6 +62,7 @@ import io.vertx.ext.web.Route; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; +import io.vertx.ext.web.handler.BodyHandler; @Recorder public class VertxHttpRecorder { @@ -169,7 +171,8 @@ public void startServer(RuntimeValue vertxRuntimeValue, ShutdownContext s public void finalizeRouter(BeanContainer container, Consumer defaultRouteHandler, List filterList, RuntimeValue vertx, - RuntimeValue runtimeValue, String rootPath, LaunchMode launchMode) { + RuntimeValue runtimeValue, String rootPath, LaunchMode launchMode, boolean requireBodyHandler, + Handler bodyHandler) { // install the default route at the end Router router = runtimeValue.getValue(); @@ -200,6 +203,18 @@ public void finalizeRouter(BeanContainer container, Consumer defaultRoute container.instance(RouterProducer.class).initialize(resumingRouter); router.route().last().failureHandler(new QuarkusErrorHandler(launchMode.isDevOrTest())); + if (requireBodyHandler) { + //if this is set then everything needs the body handler installed + //TODO: config etc + router.route().order(Integer.MIN_VALUE).handler(new Handler() { + @Override + public void handle(RoutingContext routingContext) { + routingContext.request().resume(); + bodyHandler.handle(routingContext); + } + }); + } + if (rootPath.equals("/")) { if (hotReplacementHandler != null) { router.route().order(-1).handler(hotReplacementHandler); @@ -626,4 +641,24 @@ public static Handler getRootHandler() { return ACTUAL_ROOT; } + public Handler createBodyHandler(HttpConfiguration httpConfiguration) { + BodyHandler bodyHandler = BodyHandler.create(); + Optional maxBodySize = httpConfiguration.limits.maxBodySize; + if (maxBodySize.isPresent()) { + bodyHandler.setBodyLimit(maxBodySize.get().asLongValue()); + } + final BodyConfig bodyConfig = httpConfiguration.body; + bodyHandler.setHandleFileUploads(bodyConfig.handleFileUploads); + bodyHandler.setUploadsDirectory(bodyConfig.uploadsDirectory); + bodyHandler.setDeleteUploadedFilesOnEnd(bodyConfig.deleteUploadedFilesOnEnd); + bodyHandler.setMergeFormAttributes(bodyConfig.mergeFormAttributes); + bodyHandler.setPreallocateBodyBuffer(bodyConfig.preallocateBodyBuffer); + return new Handler() { + @Override + public void handle(RoutingContext event) { + event.request().resume(); + bodyHandler.handle(event); + } + }; + } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java index 00cd2a23063863..357f77de923aac 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxInputStream.java @@ -81,7 +81,7 @@ public int available() throws IOException { if (finished) { return -1; } - + return exchange.readBytesAvailable(); } @@ -201,13 +201,13 @@ public int readBytesAvailable() { if (input1 != null) { return input1.getByteBuf().readableBytes(); } - + String length = request.getHeader(HttpHeaders.CONTENT_LENGTH); - + if (length == null) { return 0; } - + return Integer.parseInt(length); } } diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java index 715dbafcde91bb..d8e3e968eef7ba 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/BodyHandlerBuildItem.java @@ -4,6 +4,10 @@ import io.vertx.core.Handler; import io.vertx.ext.web.RoutingContext; +/** + * use {@link io.quarkus.vertx.http.deployment.BodyHandlerBuildItem} instead + */ +@Deprecated public final class BodyHandlerBuildItem extends SimpleBuildItem { private final Handler handler; diff --git a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java index 8b98af3df949c8..ed69c72f5731eb 100644 --- a/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java +++ b/extensions/vertx-web/deployment/src/main/java/io/quarkus/vertx/web/deployment/VertxWebProcessor.java @@ -48,9 +48,9 @@ import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.vertx.http.deployment.FilterBuildItem; +import io.quarkus.vertx.http.deployment.RequireBodyHandlerBuildItem; import io.quarkus.vertx.http.deployment.RouteBuildItem; import io.quarkus.vertx.http.runtime.HandlerType; -import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.quarkus.vertx.web.Route; import io.quarkus.vertx.web.RouteBase; import io.quarkus.vertx.web.RouteFilter; @@ -149,9 +149,8 @@ void validateBeanDeployment( } @BuildStep - @Record(ExecutionTime.RUNTIME_INIT) - BodyHandlerBuildItem bodyHandler(VertxWebRecorder recorder, HttpConfiguration httpConfiguration) { - return new BodyHandlerBuildItem(recorder.createBodyHandler(httpConfiguration)); + BodyHandlerBuildItem bodyHandler(io.quarkus.vertx.http.deployment.BodyHandlerBuildItem realOne) { + return new BodyHandlerBuildItem(realOne.getHandler()); } @BuildStep @@ -163,9 +162,10 @@ void addAdditionalRoutes( BuildProducer generatedClass, AnnotationProxyBuildItem annotationProxy, BuildProducer reflectiveClasses, - BodyHandlerBuildItem bodyHandler, + io.quarkus.vertx.http.deployment.BodyHandlerBuildItem bodyHandler, BuildProducer routeProducer, - BuildProducer filterProducer) throws IOException { + BuildProducer filterProducer, + List bodyHandlerRequired) throws IOException { ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClass, true); diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java index ffc08969aa4fb1..e7b3051717b6b5 100644 --- a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/VertxWebRecorder.java @@ -1,20 +1,15 @@ package io.quarkus.vertx.web.runtime; import java.lang.reflect.InvocationTargetException; -import java.util.Optional; import java.util.function.Function; import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.runtime.configuration.MemorySize; -import io.quarkus.vertx.http.runtime.BodyConfig; -import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.quarkus.vertx.http.runtime.RouterProducer; import io.quarkus.vertx.web.Route; import io.vertx.core.Handler; import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; -import io.vertx.ext.web.handler.BodyHandler; @Recorder public class VertxWebRecorder { @@ -66,33 +61,14 @@ public io.vertx.ext.web.Route apply(Router router) { route.consumes(consumes); } } - route.handler(bodyHandler); + if (bodyHandler != null) { + route.handler(bodyHandler); + } return route; } }; } - public Handler createBodyHandler(HttpConfiguration httpConfiguration) { - BodyHandler bodyHandler = BodyHandler.create(); - Optional maxBodySize = httpConfiguration.limits.maxBodySize; - if (maxBodySize.isPresent()) { - bodyHandler.setBodyLimit(maxBodySize.get().asLongValue()); - } - final BodyConfig bodyConfig = httpConfiguration.body; - bodyHandler.setHandleFileUploads(bodyConfig.handleFileUploads); - bodyHandler.setUploadsDirectory(bodyConfig.uploadsDirectory); - bodyHandler.setDeleteUploadedFilesOnEnd(bodyConfig.deleteUploadedFilesOnEnd); - bodyHandler.setMergeFormAttributes(bodyConfig.mergeFormAttributes); - bodyHandler.setPreallocateBodyBuffer(bodyConfig.preallocateBodyBuffer); - return new Handler() { - @Override - public void handle(RoutingContext event) { - event.request().resume(); - bodyHandler.handle(event); - } - }; - } - private String ensureStartWithSlash(String path) { if (path.startsWith("/")) { return path;