blob: 44cea0c8ed30bafbb13a873981271514fcdf8d5f [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
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;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
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;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import org.eclipse.jetty.client.AbstractHttpClientServerTest;
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.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Test;
public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
{
public MultiPartContentProviderTest(SslContextFactory sslContextFactory)
{
super(sslContextFactory);
}
@Test
public void testEmptyMultiPart() throws Exception
{
start(new AbstractMultiPartHandler()
{
@Override
protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Collection<Part> parts = request.getParts();
Assert.assertEquals(0, parts.size());
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
multiPart.close();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.POST)
.content(multiPart)
.send();
Assert.assertEquals(200, response.getStatus());
}
@Test
public void testSimpleField() throws Exception
{
String name = "field";
String value = "value";
start(new AbstractMultiPartHandler()
{
@Override
protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Collection<Part> parts = request.getParts();
Assert.assertEquals(1, parts.size());
Part part = parts.iterator().next();
Assert.assertEquals(name, part.getName());
Assert.assertEquals(value, IO.toString(part.getInputStream()));
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
multiPart.addFieldPart(name, new StringContentProvider(value), null);
multiPart.close();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.POST)
.content(multiPart)
.send();
Assert.assertEquals(200, response.getStatus());
}
@Test
public void testFieldWithOverridenContentType() throws Exception
{
String name = "field";
String value = "\u00e8";
Charset encoding = StandardCharsets.ISO_8859_1;
start(new AbstractMultiPartHandler()
{
@Override
protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Collection<Part> parts = request.getParts();
Assert.assertEquals(1, parts.size());
Part part = parts.iterator().next();
Assert.assertEquals(name, part.getName());
String contentType = part.getContentType();
Assert.assertNotNull(contentType);
int equal = contentType.lastIndexOf('=');
Charset charset = Charset.forName(contentType.substring(equal + 1));
Assert.assertEquals(encoding, charset);
Assert.assertEquals(value, IO.toString(part.getInputStream(), charset));
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
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);
multiPart.close();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.POST)
.content(multiPart)
.send();
Assert.assertEquals(200, response.getStatus());
}
@Test
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<Part> 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);
multiPart.close();
CountDownLatch responseLatch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.POST)
.content(multiPart)
.send(result ->
{
Assert.assertTrue(String.valueOf(result.getFailure()), 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<Part> 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);
multiPart.close();
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";
Path tmpDir = MavenTestingUtils.getTargetTestingPath();
Path tmpPath = Files.createTempFile(tmpDir, "multipart_", ".txt");
Charset encoding = StandardCharsets.UTF_8;
try (BufferedWriter writer = Files.newBufferedWriter(tmpPath, encoding, StandardOpenOption.CREATE))
{
writer.write(data);
}
String name = "file";
String contentType = "text/plain; charset=" + encoding.name();
start(new AbstractMultiPartHandler()
{
@Override
protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Collection<Part> 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(tmpPath.getFileName().toString(), part.getSubmittedFileName());
Assert.assertEquals(Files.size(tmpPath), part.getSize());
Assert.assertEquals(data, IO.toString(part.getInputStream(), encoding));
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
PathContentProvider content = new PathContentProvider(contentType, tmpPath);
content.setByteBufferPool(client.getByteBufferPool());
multiPart.addFilePart(name, tmpPath.getFileName().toString(), content, null);
multiPart.close();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.POST)
.content(multiPart)
.send();
Assert.assertEquals(200, response.getStatus());
Files.delete(tmpPath);
}
@Test
public void testFieldWithFile() throws Exception
{
// Prepare a file to upload.
byte[] data = new byte[1024];
new Random().nextBytes(data);
Path tmpDir = MavenTestingUtils.getTargetTestingPath();
Path tmpPath = Files.createTempFile(tmpDir, "multipart_", ".txt");
try (OutputStream output = Files.newOutputStream(tmpPath, StandardOpenOption.CREATE))
{
output.write(data);
}
String field = "field";
String value = "\u20ac";
String fileField = "file";
Charset encoding = StandardCharsets.UTF_8;
String contentType = "text/plain;charset=" + encoding.name();
String headerName = "foo";
String headerValue = "bar";
start(new AbstractMultiPartHandler()
{
@Override
protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
List<Part> 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(field, fieldPart.getName());
Assert.assertEquals(contentType, fieldPart.getContentType());
Assert.assertEquals(value, IO.toString(fieldPart.getInputStream(), encoding));
Assert.assertEquals(headerValue, fieldPart.getHeader(headerName));
Assert.assertEquals(fileField, filePart.getName());
Assert.assertEquals("application/octet-stream", filePart.getContentType());
Assert.assertEquals(tmpPath.getFileName().toString(), filePart.getSubmittedFileName());
Assert.assertEquals(Files.size(tmpPath), filePart.getSize());
Assert.assertArrayEquals(data, IO.readBytes(filePart.getInputStream()));
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
HttpFields fields = new HttpFields();
fields.put(headerName, headerValue);
multiPart.addFieldPart(field, new StringContentProvider(value, encoding), fields);
multiPart.addFilePart(fileField, tmpPath.getFileName().toString(), new PathContentProvider(tmpPath), null);
multiPart.close();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.POST)
.content(multiPart)
.send();
Assert.assertEquals(200, response.getStatus());
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<Part> 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 ->
{
Assert.assertTrue(String.valueOf(result.getFailure()), 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();
multiPart.close();
Assert.assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
}
private static abstract class AbstractMultiPartHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
File tmpDir = MavenTestingUtils.getTargetTestingDir();
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, new MultipartConfigElement(tmpDir.getAbsolutePath()));
handle(request, response);
}
protected abstract void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
}