From 3dda470f35c8596e55534b5aa1b92f4db894862f Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 26 Apr 2021 11:02:56 +0300 Subject: [PATCH] Fix dev-mode CL issue with multipart form handling Fixes: #16794 --- .../MultipartFormInputDevModeTest.java | 79 +++++++++++++++++++ .../server/runtime/MultipartFormHandler.java | 16 +++- 2 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartFormInputDevModeTest.java diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartFormInputDevModeTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartFormInputDevModeTest.java new file mode 100644 index 0000000000000..5f92c7a06d224 --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/multipart/MultipartFormInputDevModeTest.java @@ -0,0 +1,79 @@ +package io.quarkus.resteasy.reactive.server.test.multipart; + +import static org.hamcrest.CoreMatchers.equalTo; + +import java.io.File; +import java.util.function.Function; +import java.util.function.Supplier; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.resteasy.reactive.server.test.multipart.other.OtherPackageFormDataBase; +import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; + +public class MultipartFormInputDevModeTest { + + @RegisterExtension + static QuarkusDevModeTest TEST = new QuarkusDevModeTest() + .setArchiveProducer(new Supplier() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(FormDataBase.class, OtherPackageFormDataBase.class, FormData.class, Status.class, + OtherFormData.class, OtherFormDataBase.class, MultipartResource.class); + } + + }); + + private final File HTML_FILE = new File("./src/test/resources/test.html"); + private final File XML_FILE = new File("./src/test/resources/test.html"); + private final File TXT_FILE = new File("./src/test/resources/lorem.txt"); + + @Test + public void test() { + doTest("simple"); + doTest("simple"); + + TEST.modifySourceFile(MultipartResource.class.getSimpleName() + ".java", new Function() { + @Override + public String apply(String s) { + return s.replace("/simple", "/simple2"); + } + }); + + doTest("simple2"); + doTest("simple2"); + + TEST.modifySourceFile(MultipartResource.class.getSimpleName() + ".java", new Function() { + @Override + public String apply(String s) { + return s.replace("/simple2", "/simple"); + } + }); + + doTest("simple"); + doTest("simple"); + } + + private void doTest(String path) { + RestAssured.given() + .multiPart("name", "Alice") + .multiPart("active", "true") + .multiPart("num", "25") + .multiPart("status", "WORKING") + .multiPart("htmlFile", HTML_FILE, "text/html") + .multiPart("xmlFile", XML_FILE, "text/xml") + .multiPart("txtFile", TXT_FILE, "text/plain") + .accept("text/plain") + .when() + .post("/multipart/" + path + "/2") + .then() + .statusCode(200) + .body(equalTo("Alice - true - 50 - WORKING - text/html - true - true")); + } + +} diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java index 97488c5c0da95..6d440315e0fca 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/runtime/src/main/java/io/quarkus/resteasy/reactive/server/runtime/MultipartFormHandler.java @@ -47,12 +47,15 @@ public class MultipartFormHandler implements RuntimeConfigurableServerRestHandle private volatile String uploadsDirectory; private volatile boolean deleteUploadedFilesOnEnd; private volatile Optional maxBodySize; + private volatile ClassLoader tccl; @Override public void configure(RuntimeConfiguration configuration) { uploadsDirectory = configuration.body().uploadsDirectory(); deleteUploadedFilesOnEnd = configuration.body().deleteUploadedFilesOnEnd(); maxBodySize = configuration.limits().maxBodySize(); + // capture the proper TCCL in order to avoid losing it to Vert.x in dev-mode + tccl = Thread.currentThread().getContextClassLoader(); try { Files.createDirectories(Paths.get(uploadsDirectory)); @@ -80,7 +83,7 @@ public void handle(ResteasyReactiveRequestContext context) throws Exception { httpServerRequest.setExpectMultipart(true); httpServerRequest.pause(); context.suspend(); - MultipartFormVertxHandler handler = new MultipartFormVertxHandler(context, uploadsDirectory, + MultipartFormVertxHandler handler = new MultipartFormVertxHandler(context, tccl, uploadsDirectory, deleteUploadedFilesOnEnd, maxBodySize); httpServerRequest.handler(handler); httpServerRequest.endHandler(new Handler() { @@ -96,6 +99,7 @@ public void handle(Void event) { private static class MultipartFormVertxHandler implements Handler { private final ResteasyReactiveRequestContext rrContext; private final RoutingContext context; + private final ClassLoader tccl; private final String uploadsDirectory; private final boolean deleteUploadedFilesOnEnd; @@ -107,10 +111,11 @@ private static class MultipartFormVertxHandler implements Handler { boolean ended; long uploadSize = 0L; - public MultipartFormVertxHandler(ResteasyReactiveRequestContext rrContext, String uploadsDirectory, + public MultipartFormVertxHandler(ResteasyReactiveRequestContext rrContext, ClassLoader tccl, String uploadsDirectory, boolean deleteUploadedFilesOnEnd, Optional maxBodySize) { this.rrContext = rrContext; this.context = rrContext.serverRequest().unwrap(RoutingContext.class); + this.tccl = tccl; this.uploadsDirectory = uploadsDirectory; this.deleteUploadedFilesOnEnd = deleteUploadedFilesOnEnd; this.maxBodySize = maxBodySize; @@ -134,6 +139,7 @@ public void handle(HttpServerFileUpload upload) { long size = uploadSize + upload.size(); if (size > MultipartFormVertxHandler.this.maxBodySize.get()) { failed = true; + restoreProperTCCL(); rrContext.resume(new WebApplicationException(Response.Status.REQUEST_ENTITY_TOO_LARGE)); return; } @@ -191,10 +197,15 @@ public void handle(Void v) { MultipartFormVertxHandler.this.deleteFileUploads(); } }); + restoreProperTCCL(); rrContext.resume(new WebApplicationException(Response.Status.REQUEST_ENTITY_TOO_LARGE)); } } + private void restoreProperTCCL() { + Thread.currentThread().setContextClassLoader(tccl); + } + void uploadEnded() { int count = uploadCount.decrementAndGet(); // only if parsing is done and count is 0 then all files have been processed @@ -222,6 +233,7 @@ void doEnd() { context.addBodyEndHandler(x -> deleteFileUploads()); } rrContext.setInputStream(NO_BYTES_INPUT_STREAM); + restoreProperTCCL(); rrContext.resume(); }