Skip to content

Commit 37a084b

Browse files
committed
Redirect file upload WiP
Signed-off-by: Maxim Nesen <[email protected]>
1 parent efc5e29 commit 37a084b

File tree

5 files changed

+276
-0
lines changed

5 files changed

+276
-0
lines changed

Diff for: connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/JerseyClientHandler.java

+5
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,19 @@ protected void notifyResponse() {
146146
restrictRedirectRequest(newReq, cr);
147147

148148
final NettyConnector newConnector = new NettyConnector(newReq.getClient());
149+
System.out.println("redirecting");
149150
newConnector.execute(newReq, redirectUriHistory, new CompletableFuture<ClientResponse>() {
150151
@Override
151152
public boolean complete(ClientResponse value) {
153+
System.out.println("Finished OK");
152154
newConnector.close();
153155
return responseAvailable.complete(value);
154156
}
155157

156158
@Override
157159
public boolean completeExceptionally(Throwable ex) {
160+
System.out.println("Finished with exception");
161+
ex.printStackTrace();
158162
newConnector.close();
159163
return responseAvailable.completeExceptionally(ex);
160164
}
@@ -238,6 +242,7 @@ public void exceptionCaught(ChannelHandlerContext ctx, final Throwable cause) {
238242
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
239243
if (evt instanceof IdleStateEvent) {
240244
readTimedOut = true;
245+
System.out.println("Closing from the handler: " + ctx + " by event " + evt);
241246
ctx.close();
242247
} else {
243248
super.userEventTriggered(ctx, evt);

Diff for: connectors/netty-connector/src/main/java/org/glassfish/jersey/netty/connector/NettyConnector.java

+7
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,8 @@ protected void initChannel(SocketChannel ch) throws Exception {
364364
ch.pipeline().addLast(EXPECT_100_CONTINUE_HANDLER, expect100ContinueHandler);
365365
ch.pipeline().addLast(REQUEST_HANDLER, clientHandler);
366366

367+
System.out.println("NETTY CONNECTOR");
368+
367369
responseDone.whenComplete((_r, th) -> {
368370
ch.pipeline().remove(READ_TIMEOUT_HANDLER);
369371
ch.pipeline().remove(clientHandler);
@@ -374,22 +376,27 @@ protected void initChannel(SocketChannel ch) throws Exception {
374376
boolean added = true;
375377
synchronized (connections) {
376378
ArrayList<Channel> conns1 = connections.get(key);
379+
System.out.println(key);
380+
System.out.println(conns1);
377381
if (conns1 == null) {
378382
conns1 = new ArrayList<>(1);
379383
conns1.add(ch);
380384
connections.put(key, conns1);
381385
} else {
382386
synchronized (conns1) {
383387
if ((maxPoolSizeTotal == 0 || connections.size() < maxPoolSizeTotal) && conns1.size() < maxPoolSize) {
388+
System.out.println("Adding " + ch);
384389
conns1.add(ch);
385390
} else { // else do not add the Channel to the idle pool
391+
System.out.println("Not adding " + ch);
386392
added = false;
387393
}
388394
}
389395
}
390396
}
391397

392398
if (!added) {
399+
System.out.println("closing ch " + ch);
393400
ch.close();
394401
}
395402
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package org.glassfish.jersey.netty.connector;
2+
3+
import com.sun.net.httpserver.HttpExchange;
4+
import com.sun.net.httpserver.HttpHandler;
5+
import com.sun.net.httpserver.HttpServer;
6+
7+
import java.io.BufferedReader;
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.io.InputStreamReader;
11+
import java.io.OutputStream;
12+
import java.net.InetSocketAddress;
13+
import java.nio.charset.StandardCharsets;
14+
import java.nio.file.Files;
15+
import java.nio.file.Path;
16+
import java.nio.file.Paths;
17+
import java.nio.file.StandardCopyOption;
18+
import java.util.UUID;
19+
import java.util.concurrent.Executors;
20+
21+
public class RedirectFileTest {
22+
private static final int PORT = 8080;
23+
private static final String UPLOAD_DIRECTORY = "uploads";
24+
private static final String BOUNDARY_PREFIX = "boundary=";
25+
26+
public static void main(String[] args) throws IOException {
27+
// Create upload directory if it doesn't exist
28+
Path uploadDir = Paths.get(UPLOAD_DIRECTORY);
29+
if (!Files.exists(uploadDir)) {
30+
Files.createDirectories(uploadDir);
31+
}
32+
33+
// Create HTTP server
34+
HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0);
35+
36+
// Create contexts for different endpoints
37+
server.createContext("/submit", new SubmitHandler());
38+
server.createContext("/upload", new UploadHandler());
39+
40+
// Set executor and start server
41+
server.setExecutor(Executors.newFixedThreadPool(10));
42+
server.start();
43+
44+
System.out.println("Server started on port " + PORT);
45+
}
46+
47+
// Handler for /submit endpoint that redirects to /upload
48+
static class SubmitHandler implements HttpHandler {
49+
@Override
50+
public void handle(HttpExchange exchange) throws IOException {
51+
System.out.println(exchange.getRequestMethod());
52+
exchange.getRequestHeaders().forEach((a, b) -> {
53+
System.out.print(a + ": ");
54+
b.stream().forEach(System.out::println);
55+
});
56+
System.out.println("***");
57+
58+
try {
59+
if (!"POST".equals(exchange.getRequestMethod())) {
60+
sendResponse(exchange, 405, "Method Not Allowed. Only POST is supported.");
61+
return;
62+
}
63+
64+
// Send a 307 Temporary Redirect to /upload
65+
// This preserves the POST method and body in the redirect
66+
exchange.getResponseHeaders().add("Location", "/upload");
67+
exchange.sendResponseHeaders(307, -1);
68+
} finally {
69+
exchange.close();
70+
}
71+
}
72+
}
73+
74+
// Handler for /upload endpoint that processes file uploads
75+
static class UploadHandler implements HttpHandler {
76+
@Override
77+
public void handle(HttpExchange exchange) throws IOException {
78+
try {
79+
if (!"POST".equals(exchange.getRequestMethod())) {
80+
sendResponse(exchange, 405, "Method Not Allowed. Only POST is supported.");
81+
return;
82+
}
83+
84+
// Check if the request contains multipart form data
85+
String contentType = exchange.getRequestHeaders().getFirst("Content-Type");
86+
System.out.println(contentType);
87+
System.out.println("***");
88+
if (contentType == null || !contentType.startsWith("multipart/form-data")) {
89+
sendResponse(exchange, 400, "Bad Request. Content type must be multipart/form-data.");
90+
return;
91+
}
92+
93+
// Extract boundary from content type
94+
String boundary = extractBoundary(contentType);
95+
if (boundary == null) {
96+
System.out.println("***boundary not found***");
97+
sendResponse(exchange, 400, "Bad Request. Could not determine boundary.");
98+
return;
99+
}
100+
101+
// Process the multipart request and save the file
102+
String fileName = processMultipartRequest(exchange, boundary);
103+
104+
if (fileName != null) {
105+
sendResponse(exchange, 200, "File uploaded successfully: " + fileName);
106+
} else {
107+
System.out.println("***File not found***");
108+
sendResponse(exchange, 400, "Bad Request. No file found in request.");
109+
}
110+
} catch (Exception e) {
111+
e.printStackTrace();
112+
sendResponse(exchange, 500, "Internal Server Error: " + e.getMessage());
113+
} finally {
114+
exchange.close();
115+
}
116+
}
117+
118+
private String extractBoundary(String contentType) {
119+
int boundaryIndex = contentType.indexOf(BOUNDARY_PREFIX);
120+
if (boundaryIndex != -1) {
121+
return "--" + contentType.substring(boundaryIndex + BOUNDARY_PREFIX.length());
122+
}
123+
return null;
124+
}
125+
126+
private String processMultipartRequest(HttpExchange exchange, String boundary) throws IOException {
127+
InputStream requestBody = exchange.getRequestBody();
128+
BufferedReader reader = new BufferedReader(new InputStreamReader(requestBody, StandardCharsets.UTF_8));
129+
130+
String line;
131+
String fileName = null;
132+
Path tempFile = null;
133+
boolean isFileContent = false;
134+
135+
// Generate a random filename for the temporary file
136+
String tempFileName = UUID.randomUUID().toString();
137+
tempFile = Files.createTempFile(tempFileName, ".tmp");
138+
139+
try (OutputStream fileOut = Files.newOutputStream(tempFile)) {
140+
while ((line = reader.readLine()) != null) {
141+
System.out.println("1: " + line);
142+
// Check for the boundary
143+
if (line.startsWith(boundary)) {
144+
if (isFileContent) {
145+
// We've reached the end of the file content
146+
break;
147+
}
148+
149+
// Read the next line (Content-Disposition)
150+
line = reader.readLine();
151+
if (line != null && line.startsWith("Content-Type")) {
152+
line = reader.readLine();
153+
}
154+
System.out.println("2: " + line);
155+
if (line != null && line.contains("filename=")) {
156+
// Extract filename
157+
int filenameStart = line.indexOf("filename=\"") + 10;
158+
int filenameEnd = line.indexOf("\"", filenameStart);
159+
fileName = line.substring(filenameStart, filenameEnd);
160+
161+
// Skip Content-Type line and empty line
162+
System.out.println(reader.readLine()); // Content-Type
163+
System.out.println(reader.readLine()); // Empty line
164+
isFileContent = true;
165+
}
166+
} else if (isFileContent) {
167+
// If we're reading file content and this line is not a boundary,
168+
// write it to the file (append a newline unless it's the first line)
169+
fileOut.write(line.getBytes(StandardCharsets.UTF_8));
170+
fileOut.write('\n');
171+
}
172+
}
173+
}
174+
175+
// If we found a file, move it from the temp location to the uploads directory
176+
if (fileName != null && !fileName.isEmpty()) {
177+
Path targetPath = Paths.get(UPLOAD_DIRECTORY, fileName);
178+
Files.move(tempFile, targetPath, StandardCopyOption.REPLACE_EXISTING);
179+
return fileName;
180+
} else {
181+
// If no file was found, delete the temp file
182+
Files.deleteIfExists(tempFile);
183+
return null;
184+
}
185+
}
186+
}
187+
188+
// Helper method to send HTTP responses
189+
private static void sendResponse(HttpExchange exchange, int statusCode, String response) throws IOException {
190+
exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=UTF-8");
191+
exchange.sendResponseHeaders(statusCode, response.length());
192+
try (OutputStream os = exchange.getResponseBody()) {
193+
os.write(response.getBytes(StandardCharsets.UTF_8));
194+
}
195+
}
196+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.glassfish.jersey.tests.e2e.client;
2+
3+
import jakarta.ws.rs.client.Client;
4+
import jakarta.ws.rs.client.Entity;
5+
import jakarta.ws.rs.core.Application;
6+
import jakarta.ws.rs.core.EntityPart;
7+
import jakarta.ws.rs.core.MediaType;
8+
import jakarta.ws.rs.core.Response;
9+
import org.glassfish.jersey.client.ClientConfig;
10+
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
11+
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
12+
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
13+
import org.glassfish.jersey.netty.connector.NettyConnectorProvider;
14+
import org.glassfish.jersey.server.ResourceConfig;
15+
import org.glassfish.jersey.test.JerseyTest;
16+
import org.junit.jupiter.api.Assertions;
17+
import org.junit.jupiter.api.Test;
18+
19+
import java.io.ByteArrayInputStream;
20+
import java.io.InputStream;
21+
import java.util.List;
22+
23+
public class RedirectLargeFileTest extends JerseyTest {
24+
25+
@Override
26+
protected Application configure() {
27+
return new ResourceConfig();
28+
}
29+
30+
@Override
31+
protected void configureClient(ClientConfig config) {
32+
config.connectorProvider(new NettyConnectorProvider());
33+
}
34+
35+
@Test
36+
void sendFileTest() throws Exception {
37+
38+
try (Client client = client()) {
39+
final byte[] content;
40+
try (InputStream in = RedirectLargeFileTest.class.getResourceAsStream("/bigFile/bigFile.txt")) {
41+
Assertions.assertNotNull(in, "Could not find /bigFile/bigFile.txt");
42+
content = in.readAllBytes();
43+
}
44+
45+
final EntityPart file = EntityPart.withFileName("bigFile.txt")
46+
.content("bigFile.txt", new ByteArrayInputStream(content))
47+
// .header("Content-Length", String.valueOf(content.length))
48+
// .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
49+
.build();
50+
51+
52+
final FormDataMultiPart mp = new FormDataMultiPart();
53+
mp.bodyPart(new FormDataBodyPart(FormDataContentDisposition.name("bean").fileName("bean").build(),
54+
new ByteArrayInputStream(content),
55+
MediaType.APPLICATION_OCTET_STREAM_TYPE));
56+
57+
System.out.println(file.getFileName());
58+
59+
try (Response response = client.target("http://localhost:8080/submit").request()
60+
.post(Entity.entity(file, MediaType.MULTIPART_FORM_DATA_TYPE))) {
61+
System.out.println(response);
62+
System.out.println(response.getStatus());
63+
}
64+
}
65+
}
66+
67+
}

Diff for: tests/e2e-client/src/test/resources/bigFile/bigFile.txt

+1
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)