From b102bd507a4e1cf0e6f72abc8a536d0f5809ebb9 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 2 Nov 2015 18:17:51 +0100 Subject: [PATCH] 419966 - Add ContentProvider that submits multipart/form-data. Second implementation after feedback from the community. --- .../client/util/MultiPartContentProvider.java | Bin 15796 -> 14026 bytes .../util/MultiPartContentProviderTest.java | 195 ++++++++++++++++-- 2 files changed, 182 insertions(+), 13 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java index 2d60c97fa31b4e401e893f6b0bb95be29a6f9adf..61a22f3086fd4f8bd7913f5252fbd5832fecda17 100644 GIT binary patch literal 14026 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 LT_L{RxZU^KlJClhs9&xwaDb`7fqytmJ|K8`> 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 diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentProviderTest.java index daa91cf7d4f..4ae83b23b88 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentProviderTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/MultiPartContentProviderTest.java @@ -19,9 +19,11 @@ package org.eclipse.jetty.client.util; import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.OutputStream; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -31,6 +33,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import javax.servlet.MultipartConfigElement; import javax.servlet.ServletException; @@ -39,12 +43,14 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import org.eclipse.jetty.client.AbstractHttpClientServerTest; +import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.Assert; @@ -99,7 +105,7 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); - multiPart.addPart(new MultiPartContentProvider.FieldPart(name, value, null)); + multiPart.addFieldPart(name, new StringContentProvider(value), null); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) @@ -110,11 +116,11 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest } @Test - public void testFieldWithContentType() throws Exception + public void testFieldWithOverridenContentType() throws Exception { String name = "field"; - String value = "\u20ac"; - Charset encoding = StandardCharsets.UTF_8; + String value = "\u00e8"; + Charset encoding = StandardCharsets.ISO_8859_1; start(new AbstractMultiPartHandler() { @Override @@ -134,7 +140,10 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); - multiPart.addPart(new MultiPartContentProvider.FieldPart(name, value, encoding)); + HttpFields fields = new HttpFields(); + fields.put(HttpHeader.CONTENT_TYPE, "text/plain;charset=" + encoding.name()); + BytesContentProvider content = new BytesContentProvider(value.getBytes(encoding)); + multiPart.addFieldPart(name, content, fields); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) @@ -145,7 +154,101 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest } @Test - public void testOnlyFile() throws Exception + public void testFieldDeferred() throws Exception + { + String name = "field"; + byte[] data = "Hello, World".getBytes(StandardCharsets.US_ASCII); + start(new AbstractMultiPartHandler() + { + @Override + protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + Collection parts = request.getParts(); + Assert.assertEquals(1, parts.size()); + Part part = parts.iterator().next(); + Assert.assertEquals(name, part.getName()); + Assert.assertEquals("text/plain", part.getContentType()); + Assert.assertArrayEquals(data, IO.readBytes(part.getInputStream())); + } + }); + + MultiPartContentProvider multiPart = new MultiPartContentProvider(); + DeferredContentProvider content = new DeferredContentProvider(); + multiPart.addFieldPart(name, content, null); + CountDownLatch responseLatch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .method(HttpMethod.POST) + .content(multiPart) + .send(result -> + { + if (result.isSucceeded()) + { + Assert.assertEquals(200, result.getResponse().getStatus()); + responseLatch.countDown(); + } + }); + + // Wait until the request has been sent. + Thread.sleep(1000); + + // Provide the content. + content.offer(ByteBuffer.wrap(data)); + content.close(); + + Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testFileFromInputStream() throws Exception + { + String name = "file"; + String fileName = "upload.png"; + String contentType = "image/png"; + byte[] data = new byte[512]; + new Random().nextBytes(data); + start(new AbstractMultiPartHandler() + { + @Override + protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + Collection parts = request.getParts(); + Assert.assertEquals(1, parts.size()); + Part part = parts.iterator().next(); + Assert.assertEquals(name, part.getName()); + Assert.assertEquals(contentType, part.getContentType()); + Assert.assertEquals(fileName, part.getSubmittedFileName()); + Assert.assertEquals(data.length, part.getSize()); + Assert.assertArrayEquals(data, IO.readBytes(part.getInputStream())); + } + }); + + CountDownLatch closeLatch = new CountDownLatch(1); + MultiPartContentProvider multiPart = new MultiPartContentProvider(); + InputStreamContentProvider content = new InputStreamContentProvider(new ByteArrayInputStream(data) + { + @Override + public void close() throws IOException + { + super.close(); + closeLatch.countDown(); + } + }); + HttpFields fields = new HttpFields(); + fields.put(HttpHeader.CONTENT_TYPE, contentType); + multiPart.addFilePart(name, fileName, content, fields); + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .method(HttpMethod.POST) + .content(multiPart) + .send(); + + Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); + Assert.assertEquals(200, response.getStatus()); + } + + @Test + public void testFileFromPath() throws Exception { // Prepare a file to upload. String data = "multipart_test_\u20ac"; @@ -176,7 +279,8 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); - multiPart.addPart(new MultiPartContentProvider.PathPart(name, tmpPath, contentType)); + ContentProvider content = new PathContentProvider(contentType, tmpPath); + multiPart.addFilePart(name, tmpPath.getFileName().toString(), content, null); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) @@ -205,7 +309,7 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest String value = "\u20ac"; String fileField = "file"; Charset encoding = StandardCharsets.UTF_8; - String contentType = "text/plain; charset=" + encoding.name(); + String contentType = "text/plain;charset=" + encoding.name(); String headerName = "foo"; String headerValue = "bar"; start(new AbstractMultiPartHandler() @@ -238,11 +342,10 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest }); MultiPartContentProvider multiPart = new MultiPartContentProvider(); - Fields fields = new Fields(); - fields.put("Content-Type", contentType); + HttpFields fields = new HttpFields(); fields.put(headerName, headerValue); - multiPart.addPart(new MultiPartContentProvider.FieldPart(field, encoding.encode(value), fields)); - multiPart.addPart(new MultiPartContentProvider.PathPart(fileField, tmpPath)); + multiPart.addFieldPart(field, new StringContentProvider(value, encoding), fields); + multiPart.addFilePart(fileField, tmpPath.getFileName().toString(), new PathContentProvider(tmpPath), null); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) .method(HttpMethod.POST) @@ -254,6 +357,72 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest Files.delete(tmpPath); } + @Test + public void testFieldDeferredAndFileDeferred() throws Exception + { + String value = "text"; + Charset encoding = StandardCharsets.US_ASCII; + byte[] fileData = new byte[1024]; + new Random().nextBytes(fileData); + start(new AbstractMultiPartHandler() + { + @Override + protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + List parts = new ArrayList<>(request.getParts()); + Assert.assertEquals(2, parts.size()); + Part fieldPart = parts.get(0); + Part filePart = parts.get(1); + if (!"field".equals(fieldPart.getName())) + { + Part swap = filePart; + filePart = fieldPart; + fieldPart = swap; + } + + Assert.assertEquals(value, IO.toString(fieldPart.getInputStream(), encoding)); + + Assert.assertEquals("file", filePart.getName()); + Assert.assertEquals("application/octet-stream", filePart.getContentType()); + Assert.assertEquals("fileName", filePart.getSubmittedFileName()); + Assert.assertArrayEquals(fileData, IO.readBytes(filePart.getInputStream())); + } + }); + + MultiPartContentProvider multiPart = new MultiPartContentProvider(); + DeferredContentProvider fieldContent = new DeferredContentProvider(); + multiPart.addFieldPart("field", fieldContent, null); + DeferredContentProvider fileContent = new DeferredContentProvider(); + multiPart.addFilePart("file", "fileName", fileContent, null); + CountDownLatch responseLatch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .method(HttpMethod.POST) + .content(multiPart) + .send(result -> + { + if (result.isSucceeded()) + { + Assert.assertEquals(200, result.getResponse().getStatus()); + responseLatch.countDown(); + } + }); + + // Wait until the request has been sent. + Thread.sleep(1000); + + // Provide the content, in reversed part order. + fileContent.offer(ByteBuffer.wrap(fileData)); + fileContent.close(); + + Thread.sleep(1000); + + fieldContent.offer(encoding.encode(value)); + fieldContent.close(); + + Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); + } + private static abstract class AbstractMultiPartHandler extends AbstractHandler { @Override