From 480d61b67fedc286465fdcda6bba82399b79a30b Mon Sep 17 00:00:00 2001 From: Maxim Nesen Date: Tue, 3 Jun 2025 13:34:52 +0200 Subject: [PATCH] Upload file after redirect Signed-off-by: Maxim Nesen --- .../FileAttachingRedirectController.java | 74 +++++++++++++++++++ .../client/RedirectFileUploadServerTest.java | 9 +-- .../e2e/client/RedirectLargeFileTest.java | 52 +++++++++++-- 3 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/FileAttachingRedirectController.java diff --git a/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/FileAttachingRedirectController.java b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/FileAttachingRedirectController.java new file mode 100644 index 0000000000..c3cdc620a4 --- /dev/null +++ b/connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/FileAttachingRedirectController.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.netty.connector; + +import org.glassfish.jersey.client.ClientRequest; +import org.glassfish.jersey.client.ClientResponse; + +import javax.ws.rs.core.MediaType; +import java.lang.annotation.Annotation; + +/** + * A NettyHttpRedirectController implementation that allows attaching a file only after a redirect. + * This controller can be configured to hold a file entity that will only be attached to the request + * after a redirect has occurred. + * + * @since 2.47 + */ +public class FileAttachingRedirectController extends NettyHttpRedirectController { + + /** + * Property name for the file entity to be attached after a redirect. + */ + public static final String FILE_ENTITY_AFTER_REDIRECT + = "jersey.config.client.netty.file.entity.after.redirect"; + + /** + * Property name for the file entity media type to be used after a redirect. + */ + public static final String FILE_ENTITY_MEDIA_TYPE_AFTER_REDIRECT + = "jersey.config.client.netty.file.entity.media.type.after.redirect"; + + /** + * Property name for the file entity annotations to be used after a redirect. + */ + public static final String FILE_ENTITY_ANNOTATIONS_AFTER_REDIRECT + = "jersey.config.client.netty.file.entity.annotations.after.redirect"; + + @Override + public boolean prepareRedirect(ClientRequest request, ClientResponse response) { + boolean result = super.prepareRedirect(request, response); + + if (result) { + final Object fileEntity = request.getProperty(FILE_ENTITY_AFTER_REDIRECT); + if (fileEntity != null) { + final MediaType mediaType = (MediaType) request.getProperty(FILE_ENTITY_MEDIA_TYPE_AFTER_REDIRECT); + final Annotation[] annotations = (Annotation[]) request.getProperty(FILE_ENTITY_ANNOTATIONS_AFTER_REDIRECT); + request.setEntity(fileEntity, annotations); + if (mediaType != null) { + request.setMediaType(mediaType); + } + + request.removeProperty(FILE_ENTITY_AFTER_REDIRECT); + request.removeProperty(FILE_ENTITY_MEDIA_TYPE_AFTER_REDIRECT); + request.removeProperty(FILE_ENTITY_ANNOTATIONS_AFTER_REDIRECT); + } + } + + return result; + } +} diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/RedirectFileUploadServerTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/RedirectFileUploadServerTest.java index eddcba59e6..33d33df684 100644 --- a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/RedirectFileUploadServerTest.java +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/RedirectFileUploadServerTest.java @@ -19,6 +19,7 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; +import org.glassfish.jersey.message.internal.ReaderWriter; import java.io.BufferedReader; import java.io.IOException; @@ -80,12 +81,8 @@ public void handle(HttpExchange exchange) throws IOException { return; } - final BufferedReader reader - = new BufferedReader(new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8)); - while (reader.readLine() != null) { - //discard payload - required for JDK 1.8 - } - reader.close(); + //discard payload - required for JDK 1.8 + ReaderWriter.readFromAsBytes(exchange.getRequestBody()); // Send a 307 Temporary Redirect to /upload // This preserves the POST method and body in the redirect diff --git a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/RedirectLargeFileTest.java b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/RedirectLargeFileTest.java index 56e5d71fa1..dc6b9c34ec 100644 --- a/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/RedirectLargeFileTest.java +++ b/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/RedirectLargeFileTest.java @@ -29,6 +29,8 @@ import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataMultiPart; import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.glassfish.jersey.netty.connector.FileAttachingRedirectController; +import org.glassfish.jersey.netty.connector.NettyClientProperties; import org.glassfish.jersey.netty.connector.NettyConnectorProvider; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; @@ -36,8 +38,6 @@ import org.junit.jupiter.api.Test; import java.io.FileWriter; -import java.io.IOException; -import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -47,13 +47,22 @@ public class RedirectLargeFileTest { private static final int SERVER_PORT = 9997; private static final String SERVER_ADDR = String.format("http://localhost:%d/submit", SERVER_PORT); - Client client() { + Client client(boolean registerControler) { final ClientConfig config = new ClientConfig(); config.connectorProvider(new NettyConnectorProvider()); config.register(MultiPartFeature.class); + if (registerControler) { + registerFileUploadController(config); + } return ClientBuilder.newClient(config); } + void registerFileUploadController(ClientConfig config) { + // Register custom redirect controller + FileAttachingRedirectController redirectController = new FileAttachingRedirectController(); + config.property(NettyClientProperties.HTTP_REDIRECT_CONTROLLER, redirectController); + } + @BeforeAll static void startServer() throws Exception{ RedirectFileUploadServerTest.start(SERVER_PORT); @@ -78,13 +87,46 @@ void sendFileTest() throws Exception { final byte[] content = Files.readAllBytes(realFilePath); + final FormDataMultiPart mp = new FormDataMultiPart(); + mp.bodyPart(new FormDataBodyPart(FormDataContentDisposition.name(fileName).fileName(fileName).build(), + content, + MediaType.TEXT_PLAIN_TYPE)); + + try (final Response response = client(false).target(SERVER_ADDR).request() + .post(Entity.entity(mp, MediaType.MULTIPART_FORM_DATA_TYPE))) { + Assertions.assertEquals(200, response.getStatus()); + } + } finally { + Files.deleteIfExists(pathResource); + } + } + + @Test + void sendFileAfterRedirectTest() throws Exception { + final String fileName = "bigFile.json"; + final String path = "target/" + fileName; + + final Path pathResource = Paths.get(path); + try { + final Path realFilePath = Files.createFile(pathResource.toAbsolutePath()); + + generateJson(realFilePath.toString(), 1000000); // 33Mb real file size + + final byte[] content = Files.readAllBytes(realFilePath); + + // Create the multipart form data final FormDataMultiPart mp = new FormDataMultiPart(); mp.bodyPart(new FormDataBodyPart(FormDataContentDisposition.name(fileName).fileName(fileName).build(), content, MediaType.TEXT_PLAIN_TYPE)); - try (final Response response = client().target(SERVER_ADDR).request() - .post(Entity.entity(mp, MediaType.MULTIPART_FORM_DATA_TYPE))) { + // Set the file entity to be attached after the redirect on the request properties + final Response response = client(true).target(SERVER_ADDR).request() + .property(FileAttachingRedirectController.FILE_ENTITY_AFTER_REDIRECT, mp) + .property(FileAttachingRedirectController.FILE_ENTITY_MEDIA_TYPE_AFTER_REDIRECT, + MediaType.MULTIPART_FORM_DATA_TYPE) + .post(Entity.entity("", MediaType.TEXT_PLAIN_TYPE)); + if (response != null){ Assertions.assertEquals(200, response.getStatus()); } } finally {