diff --git a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/BookClient.java b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/BookClient.java index d1d36c3b7..b63615eed 100644 --- a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/BookClient.java +++ b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/BookClient.java @@ -44,8 +44,8 @@ interface AuthorClient { @Produces(MediaType.TEXT_PLAIN) interface ProfessionClient { @GET - @Path("/name") - Uni getName(); + @Path("/title") + Uni getTitle(); @Path("/wage") WageClient getWage(); diff --git a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/ReactiveClientBookResource.java b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/ReactiveClientBookResource.java index d84e28016..b903fd094 100644 --- a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/ReactiveClientBookResource.java +++ b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/ReactiveClientBookResource.java @@ -57,7 +57,7 @@ public Uni getSubResource(@QueryParam("author") String author) { @GET @Path("/profession") public Uni getSubSubResource() { - return bookInterface.getAuthor().getProfession().getName(); + return bookInterface.getAuthor().getProfession().getTitle(); } @GET diff --git a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileClient.java b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileClient.java index 3bc403ab6..3dbac6543 100644 --- a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileClient.java +++ b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileClient.java @@ -23,7 +23,7 @@ public interface FileClient { @GET @Path("/hash") @Produces(MediaType.TEXT_PLAIN) - Uni hash(); + String hash(); @GET @Path("/download") @@ -33,7 +33,12 @@ public interface FileClient { @GET @Produces(MediaType.MULTIPART_FORM_DATA) @Path("/download-multipart") - FileWrapper downloadMultipart(); + Uni downloadMultipart(); + + @GET + @Produces(MediaType.MULTIPART_FORM_DATA) + @Path("/download-broken-multipart") + Uni brokenMultipart(); @POST @Consumes(MediaType.APPLICATION_OCTET_STREAM) diff --git a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileClientResource.java b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileClientResource.java index c579a383e..4beaeec5d 100644 --- a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileClientResource.java +++ b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileClientResource.java @@ -2,28 +2,44 @@ import java.io.IOException; import java.nio.file.Files; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; import javax.inject.Inject; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; +import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.jboss.resteasy.reactive.RestResponse; +import io.quarkus.logging.Log; import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Uni; @Path("/file-client") public class FileClientResource { private static final String BIGGER_THAN_TWO_GIGABYTES = OsUtils.SIZE_2049MiB; - private final java.nio.file.Path FILE = Files.createTempFile("upload", ".txt").toAbsolutePath(); + + private final java.nio.file.Path file; + private final List deathRow = new LinkedList<>(); private final FileClient client; private final OsUtils utils; @Inject - public FileClientResource(@RestClient FileClient client) throws IOException { + public FileClientResource(@RestClient FileClient client, + @ConfigProperty(name = "client.filepath") Optional folder) { utils = OsUtils.get(); - utils.createFile(FILE.toString(), BIGGER_THAN_TWO_GIGABYTES); + file = folder + .stream() + .map(existing -> java.nio.file.Path.of(existing).resolve("upload.txt").toAbsolutePath()) + .peek(path -> { + utils.createFile(path.toString(), BIGGER_THAN_TWO_GIGABYTES); + }) + .findFirst().orElse(null); this.client = client; } @@ -31,40 +47,73 @@ public FileClientResource(@RestClient FileClient client) throws IOException { @Path("/client-hash") @Blocking public Uni calculateHash() { - return utils.getSum(FILE.toString()); + return utils.getSum(file.toString()); } @GET @Path("/hash") - public Uni hash() { + public String hash() { return client.hash(); } @GET @Path("/download") public Uni download() { - return client.download().onItem().transformToUni(file -> utils.getSum(file.getAbsolutePath())); + return client.download() + .map(file -> { + java.nio.file.Path path = file.toPath().toAbsolutePath(); + deathRow.add(path); + return path.toString(); + }) + .onItem() + .transformToUni(utils::getSum); } @GET @Path("/download-multipart") public Uni downloadMultipart() { - FileWrapper wrapper = client.downloadMultipart(); - String path = wrapper.file.getAbsolutePath(); - return utils.getSum(path); + return client.downloadMultipart() + .map(wrapper -> wrapper.file.toPath()) + .map(java.nio.file.Path::toAbsolutePath) + .invoke(deathRow::add) + .map(java.nio.file.Path::toString) + .flatMap(utils::getSum); + } + + @GET + @Path("/download-broken-multipart") + public Uni downloadMultipartResponse() { + return client.brokenMultipart() + .map(wrapper -> wrapper.file.getAbsolutePath()) + .flatMap(utils::getSum); } @POST @Path("/multipart") + @Blocking public Uni uploadMultipart() { FileWrapper wrapper = new FileWrapper(); - wrapper.file = FILE.toFile(); + wrapper.file = file.toFile(); + wrapper.name = file.toString(); return client.sendMultipart(wrapper); } @POST @Path("/upload-file") public Uni upload() { - return client.sendFile(FILE.toFile()); + return client.sendFile(file.toFile()); + } + + @DELETE + @Path("/") + public RestResponse removeTemporaryFiles() { + for (java.nio.file.Path path : deathRow) { + try { + Files.delete(path); + } catch (IOException e) { + Log.warn(e); + } + } + return RestResponse.noContent(); } } diff --git a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileResource.java b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileResource.java index 617effbf7..ca000ae97 100644 --- a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileResource.java +++ b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileResource.java @@ -3,40 +3,55 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import org.eclipse.microprofile.config.inject.ConfigProperty; import org.jboss.resteasy.reactive.MultipartForm; import org.jboss.resteasy.reactive.RestResponse; import io.quarkus.logging.Log; +import io.smallrye.common.annotation.Blocking; import io.smallrye.mutiny.Uni; @Path("/file") public class FileResource { private static final String BIGGER_THAN_TWO_GIGABYTES = OsUtils.SIZE_2049MiB; - private final File FILE = Files.createTempFile("server", ".txt").toAbsolutePath().toFile(); + private final File file; private final OsUtils utils; + private final List deathRow = new LinkedList<>(); - public FileResource() throws IOException { + public FileResource(@ConfigProperty(name = "client.filepath") Optional folder) { utils = OsUtils.get(); - utils.createFile(FILE.getAbsolutePath(), BIGGER_THAN_TWO_GIGABYTES); + file = folder + .stream() + .map(existing -> java.nio.file.Path.of(existing).resolve("server.txt").toAbsolutePath()) + .peek(path -> { + utils.createFile(path.toString(), BIGGER_THAN_TWO_GIGABYTES); + }) + .map(java.nio.file.Path::toFile) + .findFirst().orElse(null); } @GET @Path("/download") public Uni download() { - return Uni.createFrom().item(FILE); + return Uni.createFrom().item(file); } @POST @Path("/upload") public Uni upload(File body) { + deathRow.add(body); return utils.getSum(body.getAbsolutePath()); } @@ -44,24 +59,49 @@ public Uni upload(File body) { @Consumes(MediaType.MULTIPART_FORM_DATA) @Produces(MediaType.TEXT_PLAIN) @Path("/upload-multipart") + @Blocking public Uni uploadMultipart(@MultipartForm FileWrapper body) { + deathRow.add(body.file); return utils.getSum(body.file.getAbsolutePath()); } @GET @Produces(MediaType.MULTIPART_FORM_DATA) @Path("/download-multipart") - public RestResponse downloadMultipart() { + @Blocking //https://github.com/quarkusio/quarkus/issues/25909 + public Uni downloadMultipart() { FileWrapper wrapper = new FileWrapper(); - wrapper.file = FILE; - return RestResponse.ok(wrapper); + wrapper.file = file; + wrapper.name = file.getName(); + return Uni.createFrom().item(() -> wrapper); + } + + @GET + @Produces(MediaType.MULTIPART_FORM_DATA) + @Path("/download-broken-multipart") + @Blocking //https://github.com/quarkusio/quarkus/issues/25909 + public Uni brokenMultipart() { + return Uni.createFrom().item(() -> RestResponse.ok("Not a multipart message")); } @GET @Path("/hash") @Produces(MediaType.TEXT_PLAIN) - public Uni hash() { - Log.info("Hashing path " + FILE.getAbsolutePath()); - return utils.getSum(FILE.getAbsolutePath()); + public Uni getHashSum() { + Log.info("Hashing path " + file.getAbsolutePath()); + return utils.getSum(file.getAbsolutePath()); + } + + @DELETE + @Path("/") + public RestResponse removeTemporaryFiles() { + for (File path : deathRow) { + try { + Files.delete(path.toPath().toAbsolutePath()); + } catch (IOException e) { + Log.warn(e); + } + } + return RestResponse.noContent(); } } diff --git a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileWrapper.java b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileWrapper.java index 5f9d7c543..b74a61864 100644 --- a/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileWrapper.java +++ b/http/rest-client-reactive/src/main/java/io/quarkus/ts/http/restclient/reactive/files/FileWrapper.java @@ -2,13 +2,20 @@ import java.io.File; -import javax.ws.rs.FormParam; import javax.ws.rs.core.MediaType; import org.jboss.resteasy.reactive.PartType; +import org.jboss.resteasy.reactive.RestForm; +import io.quarkus.runtime.annotations.RegisterForReflection; + +@RegisterForReflection public class FileWrapper { - @FormParam("file") + @RestForm("file") @PartType(MediaType.APPLICATION_OCTET_STREAM) public File file; + + @RestForm("name") + @PartType(MediaType.TEXT_PLAIN) + public String name; } diff --git a/http/rest-client-reactive/src/test/java/io/quarkus/ts/http/restclient/reactive/LargeFileHandlingIT.java b/http/rest-client-reactive/src/test/java/io/quarkus/ts/http/restclient/reactive/LargeFileHandlingIT.java index 9ed1bc7db..92d260851 100644 --- a/http/rest-client-reactive/src/test/java/io/quarkus/ts/http/restclient/reactive/LargeFileHandlingIT.java +++ b/http/rest-client-reactive/src/test/java/io/quarkus/ts/http/restclient/reactive/LargeFileHandlingIT.java @@ -6,11 +6,15 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.ExecutionException; +import java.util.function.Predicate; import org.apache.http.HttpStatus; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; @@ -18,6 +22,7 @@ import io.quarkus.test.bootstrap.RestService; import io.quarkus.test.scenarios.QuarkusScenario; +import io.quarkus.test.scenarios.annotations.DisabledOnNative; import io.quarkus.test.services.QuarkusApplication; import io.quarkus.ts.http.restclient.reactive.files.OsUtils; import io.restassured.response.Response; @@ -26,17 +31,27 @@ public class LargeFileHandlingIT { private static final String BIGGER_THAN_TWO_GIGABYTES = OsUtils.SIZE_2049MiB; + private static final Path files = getTempDirectory(); private final Path downloaded; private final Path uploaded; private final OsUtils utils = OsUtils.get(); + private static Path getTempDirectory() { + try { + return Files.createTempDirectory("large_files"); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + @QuarkusApplication - static RestService app = new RestService().withProperties("modern.properties"); + static RestService app = new RestService() + .withProperty("client.filepath", () -> files.toAbsolutePath().toString()) + .withProperties("modern.properties"); - public LargeFileHandlingIT() throws IOException { - downloaded = Files.createTempFile("downloaded", ".txt").toAbsolutePath(); - Files.delete(downloaded); - uploaded = Files.createTempFile("uploaded", ".txt").toAbsolutePath(); + public LargeFileHandlingIT() { + downloaded = files.resolve("downloaded.txt"); + uploaded = files.resolve("uploaded.txt"); } @Test @@ -65,7 +80,6 @@ public void downloadDirectly() throws IOException, ExecutionException, Interrupt } @Test - @Disabled("https://github.com/quarkusio/quarkus/issues/24402") @DisabledOnOs(value = OS.WINDOWS, disabledReason = "https://github.com/quarkusio/quarkus/issues/24763") public void downloadThroughClient() { Response hashSum = app.given().get("/file/hash"); @@ -123,8 +137,8 @@ public void uploadFile() throws ExecutionException, InterruptedException { } @Test - @Disabled("https://github.com/quarkusio/quarkus/issues/24405") @DisabledOnOs(value = OS.WINDOWS, disabledReason = "https://github.com/quarkusio/quarkus/issues/24763") + @DisabledOnNative(reason = "https://github.com/quarkusio/quarkus/issues/25973") public void uploadFileThroughClient() { Response hashSum = app.given().get("/file-client/client-hash"); assertEquals(HttpStatus.SC_OK, hashSum.statusCode()); @@ -150,4 +164,25 @@ public void uploadMultipart() { assertEquals(before, after); } + + @Test + @DisabledOnOs(value = OS.WINDOWS, disabledReason = "https://github.com/quarkusio/quarkus/issues/24763") + public void failOnMalformedMultipart() { + Response download = app.given().get("/file-client/download-broken-multipart"); + assertEquals(HttpStatus.SC_INTERNAL_SERVER_ERROR, download.statusCode()); + Predicate containsError = line -> line.contains("Unable to parse multipart response - No delimiter specified"); + Assertions.assertTrue(app.getLogs().stream().anyMatch(containsError)); + } + + @AfterAll + static void afterAll() throws IOException { + try (DirectoryStream paths = Files.newDirectoryStream(files)) { + for (Path path : paths) { + Files.delete(path); + } + } + app.given().delete("/file-client/"); + app.given().delete("/file/"); + Files.delete(files); + } } diff --git a/http/rest-client-reactive/src/test/java/io/quarkus/ts/http/restclient/reactive/ReactiveRestClientIT.java b/http/rest-client-reactive/src/test/java/io/quarkus/ts/http/restclient/reactive/ReactiveRestClientIT.java index ef5869f1c..7289d4e0a 100644 --- a/http/rest-client-reactive/src/test/java/io/quarkus/ts/http/restclient/reactive/ReactiveRestClientIT.java +++ b/http/rest-client-reactive/src/test/java/io/quarkus/ts/http/restclient/reactive/ReactiveRestClientIT.java @@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.apache.http.HttpStatus; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; @@ -96,7 +95,6 @@ public void deepestLevelDirectly() { } @Test - @Disabled("https://github.com/quarkusio/quarkus/issues/25028") public void subResource() { Response response = app.given().get("/client/book/author/?author=Heller"); assertEquals(HttpStatus.SC_OK, response.statusCode()); @@ -108,11 +106,10 @@ public void subResource() { } @Test - @Disabled("https://github.com/quarkusio/quarkus/issues/25028") public void deepLevel() { Response response = app.given().get("/client/book/currency"); assertEquals(HttpStatus.SC_OK, response.statusCode()); - assertEquals("Heller", response.getBody().asString()); + assertEquals("USD", response.getBody().asString()); } @DisabledOnQuarkusVersion(version = DISABLE_IF_NOT_QUARKUS_2_7_6_OR_2_8_3_OR_HIGHER, reason = FIXED_IN_2_7_6_AND_2_8_3)