Merge pull request #3889 from eclipse/jetty-9.4.x-3888-huge-resources

Issue #3888 - Huge file Resources behaviors
This commit is contained in:
Joakim Erdfelt 2019-07-30 20:03:34 -05:00 committed by GitHub
commit dc4b7b1791
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 556 additions and 68 deletions

View File

@ -125,7 +125,7 @@ public class ResourceHttpContent implements HttpContent
@Override
public ByteBuffer getDirectBuffer()
{
if (_resource.length() <= 0 || _maxBuffer > 0 && _maxBuffer < _resource.length())
if (_resource.length() <= 0 || _maxBuffer > 0 && _resource.length() > _maxBuffer)
return null;
try
{
@ -152,7 +152,7 @@ public class ResourceHttpContent implements HttpContent
@Override
public ByteBuffer getIndirectBuffer()
{
if (_resource.length() <= 0 || _maxBuffer > 0 && _maxBuffer < _resource.length())
if (_resource.length() <= 0 || _maxBuffer > 0 && _resource.length() > _maxBuffer)
return null;
try
{

View File

@ -335,13 +335,14 @@ public class CachedContentFactory implements HttpContent.ContentFactory
{
try
{
return BufferUtil.toBuffer(resource, true);
return BufferUtil.toBuffer(resource, false);
}
catch (IOException | IllegalArgumentException e)
{
LOG.warn(e);
return null;
if (LOG.isDebugEnabled())
LOG.debug(e);
}
return null;
}
protected ByteBuffer getMappedBuffer(Resource resource)
@ -355,7 +356,8 @@ public class CachedContentFactory implements HttpContent.ContentFactory
}
catch (IOException | IllegalArgumentException e)
{
LOG.warn(e);
if (LOG.isDebugEnabled())
LOG.debug(e);
}
return null;
}
@ -368,7 +370,8 @@ public class CachedContentFactory implements HttpContent.ContentFactory
}
catch (IOException | IllegalArgumentException e)
{
LOG.warn(e);
if (LOG.isDebugEnabled())
LOG.debug(e);
}
return null;
}
@ -386,7 +389,7 @@ public class CachedContentFactory implements HttpContent.ContentFactory
{
private final String _key;
private final Resource _resource;
private final int _contentLengthValue;
private final long _contentLengthValue;
private final HttpField _contentType;
private final String _characterEncoding;
private final MimeTypes.Type _mimeType;
@ -415,7 +418,7 @@ public class CachedContentFactory implements HttpContent.ContentFactory
_lastModified = _lastModifiedValue == -1 ? null
: new PreEncodedHttpField(HttpHeader.LAST_MODIFIED, DateGenerator.formatDate(_lastModifiedValue));
_contentLengthValue = exists ? (int)resource.length() : 0;
_contentLengthValue = exists ? resource.length() : 0;
_contentLength = new PreEncodedHttpField(HttpHeader.CONTENT_LENGTH, Long.toString(_contentLengthValue));
if (_cachedFiles.incrementAndGet() > _maxCachedFiles)
@ -552,25 +555,34 @@ public class CachedContentFactory implements HttpContent.ContentFactory
@Override
public ByteBuffer getIndirectBuffer()
{
if (_resource.length() > _maxCachedFileSize)
{
return null;
}
ByteBuffer buffer = _indirectBuffer.get();
if (buffer == null)
{
ByteBuffer buffer2 = CachedContentFactory.this.getIndirectBuffer(_resource);
if (buffer2 == null)
LOG.warn("Could not load " + this);
else if (_indirectBuffer.compareAndSet(null, buffer2))
{
if (LOG.isDebugEnabled())
LOG.debug("Could not load indirect buffer from " + this);
return null;
}
if (_indirectBuffer.compareAndSet(null, buffer2))
{
buffer = buffer2;
if (_cachedSize.addAndGet(BufferUtil.length(buffer)) > _maxCacheSize)
shrinkCache();
}
else
{
buffer = _indirectBuffer.get();
}
if (buffer == null)
return null;
return buffer.slice();
}
return buffer == null ? null : buffer.asReadOnlyBuffer();
}
@Override
@ -589,7 +601,8 @@ public class CachedContentFactory implements HttpContent.ContentFactory
else
buffer = _mappedBuffer.get();
}
else
// Since MappedBuffers don't use heap, we don't care about the resource.length
else if (_resource.length() < _maxCachedFileSize)
{
ByteBuffer direct = CachedContentFactory.this.getDirectBuffer(_resource);
if (direct != null)
@ -607,7 +620,8 @@ public class CachedContentFactory implements HttpContent.ContentFactory
}
else
{
LOG.warn("Could not load " + this);
if (LOG.isDebugEnabled())
LOG.debug("Could not load " + this);
}
}
}

View File

@ -682,9 +682,21 @@ public class Request implements HttpServletRequest
MetaData.Request metadata = _metaData;
if (metadata == null)
return -1;
if (metadata.getContentLength() != Long.MIN_VALUE)
return (int)metadata.getContentLength();
return (int)metadata.getFields().getLongField(HttpHeader.CONTENT_LENGTH.toString());
long contentLength = metadata.getContentLength();
if (contentLength != Long.MIN_VALUE)
{
if (contentLength > Integer.MAX_VALUE)
{
// Per ServletRequest#getContentLength() javadoc this must return -1 for values exceeding Integer.MAX_VALUE
return -1;
}
else
{
return (int)contentLength;
}
}
return (int)metadata.getFields().getLongField(HttpHeader.CONTENT_LENGTH.asString());
}
/*
@ -698,7 +710,7 @@ public class Request implements HttpServletRequest
return -1L;
if (metadata.getContentLength() != Long.MIN_VALUE)
return metadata.getContentLength();
return metadata.getFields().getLongField(HttpHeader.CONTENT_LENGTH.toString());
return metadata.getFields().getLongField(HttpHeader.CONTENT_LENGTH.asString());
}
public long getContentRead()

View File

@ -639,6 +639,56 @@ public class RequestTest
assertThat(responses, startsWith("HTTP/1.1 200"));
}
@Test
public void testContentLength_ExceedsMaxInteger() throws Exception
{
final long HUGE_LENGTH = (long) Integer.MAX_VALUE * 10L;
_handler._checker = (request, response) ->
request.getContentLength() == (-1) // per HttpServletRequest javadoc this must return (-1);
&& request.getContentLengthLong() == HUGE_LENGTH;
//Send a request with encoded form content
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: application/octet-stream\n"+
"Content-Length: " + HUGE_LENGTH + "\n"+
"Connection: close\n"+
"\n"+
"<insert huge amount of content here>\n";
System.out.println(request);
String responses=_connector.getResponse(request);
assertThat(responses,startsWith("HTTP/1.1 200"));
}
/**
* The Servlet spec and API cannot parse Content-Length that exceeds Long.MAX_VALUE
*/
@Test
public void testContentLength_ExceedsMaxLong() throws Exception
{
String HUGE_LENGTH = Long.MAX_VALUE + "0";
_handler._checker = (request, response) ->
request.getHeader("Content-Length").equals(HUGE_LENGTH)
&& request.getContentLength() == (-1) // per HttpServletRequest javadoc this must return (-1);
&& request.getContentLengthLong() == (-1); // exact behavior here not specified in Servlet javadoc
//Send a request with encoded form content
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: application/octet-stream\n"+
"Content-Length: " + HUGE_LENGTH + "\n"+
"Connection: close\n"+
"\n"+
"<insert huge amount of content here>\n";
String responses=_connector.getResponse(request);
assertThat(responses, startsWith("HTTP/1.1 400"));
}
@Test
public void testIdentityParamExtraction() throws Exception
{

View File

@ -18,34 +18,106 @@
package org.eclipse.jetty.server;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import org.eclipse.jetty.http.CompressedContentFormat;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.ResourceHttpContent;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(WorkDirExtension.class)
public class ResourceCacheTest
{
public WorkDir workDir;
public Path createUtilTestResources(Path basePath) throws IOException
{
// root
makeFile(basePath.resolve("resource.txt"), "this is test data");
// - one/
Path one = basePath.resolve("one");
FS.ensureDirExists(one);
makeFile(one.resolve("1.txt"), "1 - one");
// - one/dir/
Path oneDir = one.resolve("dir");
FS.ensureDirExists(oneDir);
makeFile(oneDir.resolve("1.txt"), "1 - one");
// - two/
Path two = basePath.resolve("two");
FS.ensureDirExists(two);
makeFile(two.resolve("1.txt"), "1 - two");
makeFile(two.resolve("2.txt"), "2 - two");
// - two/dir/
Path twoDir = two.resolve("dir");
FS.ensureDirExists(twoDir);
makeFile(twoDir.resolve("2.txt"), "2 - two");
// - three/
Path three = basePath.resolve("three");
FS.ensureDirExists(three);
makeFile(three.resolve("2.txt"), "2 - three");
makeFile(three.resolve("3.txt"), "3 - three");
// - three/dir/
Path threeDir = three.resolve("dir");
FS.ensureDirExists(threeDir);
makeFile(threeDir.resolve("3.txt"), "3 - three");
// - four/
Path four = basePath.resolve("four");
FS.ensureDirExists(four);
makeFile(four.resolve("four"), "4 - four (no extension)");
makeFile(four.resolve("four.txt"), "4 - four");
return basePath;
}
private void makeFile(Path file, String contents) throws IOException
{
try (BufferedWriter writer = Files.newBufferedWriter(file, UTF_8, StandardOpenOption.CREATE_NEW))
{
writer.write(contents);
writer.flush();
}
}
@Test
public void testMutlipleSources1() throws Exception
{
ResourceCollection rc = new ResourceCollection(new String[]{
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/one/",
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/two/",
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/three/"
});
Path basePath = createUtilTestResources(workDir.getEmptyPathDir());
ResourceCollection rc = new ResourceCollection(
new PathResource(basePath.resolve("one")),
new PathResource(basePath.resolve("two")),
new PathResource(basePath.resolve("three")));
Resource[] r = rc.getResources();
MimeTypes mime = new MimeTypes();
@ -70,11 +142,12 @@ public class ResourceCacheTest
@Test
public void testUncacheable() throws Exception
{
ResourceCollection rc = new ResourceCollection(new String[]{
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/one/",
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/two/",
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/three/"
});
Path basePath = createUtilTestResources(workDir.getEmptyPathDir());
ResourceCollection rc = new ResourceCollection(
new PathResource(basePath.resolve("one")),
new PathResource(basePath.resolve("two")),
new PathResource(basePath.resolve("three")));
Resource[] r = rc.getResources();
MimeTypes mime = new MimeTypes();
@ -112,19 +185,21 @@ public class ResourceCacheTest
String[] names = new String[files.length];
CachedContentFactory cache;
Path basePath = workDir.getEmptyPathDir();
for (int i = 0; i < files.length; i++)
{
files[i] = File.createTempFile("R-" + i + "-", ".txt");
files[i].deleteOnExit();
names[i] = files[i].getName();
try (OutputStream out = new FileOutputStream(files[i]))
Path tmpFile = basePath.resolve("R-" + i + ".txt");
try (BufferedWriter writer = Files.newBufferedWriter(tmpFile, UTF_8, StandardOpenOption.CREATE_NEW))
{
for (int j = 0; j < (i * 10 - 1); j++)
{
out.write(' ');
writer.write(' ');
}
out.write('\n');
writer.write('\n');
}
files[i] = tmpFile.toFile();
names[i] = tmpFile.getFileName().toString();
}
directory = Resource.newResource(files[0].getParentFile().getAbsolutePath());
@ -141,7 +216,7 @@ public class ResourceCacheTest
HttpContent content;
content = cache.getContent(names[8], 4096);
assertTrue(content != null);
assertThat(content, is(not(nullValue())));
assertEquals(80, content.getContentLengthValue());
assertEquals(0, cache.getCachedSize());
@ -274,36 +349,20 @@ public class ResourceCacheTest
@Test
public void testNoextension() throws Exception
{
ResourceCollection rc = new ResourceCollection(new String[]{
"../jetty-util/src/test/resources/org/eclipse/jetty/util/resource/four/"
});
Path basePath = createUtilTestResources(workDir.getEmptyPathDir());
Resource[] resources = rc.getResources();
Resource resource = new PathResource(basePath.resolve("four"));
MimeTypes mime = new MimeTypes();
CachedContentFactory cache = new CachedContentFactory(null, resources[0], mime, false, false, CompressedContentFormat.NONE);
CachedContentFactory cache = new CachedContentFactory(null, resource, mime, false, false, CompressedContentFormat.NONE);
assertEquals(getContent(cache, "four.txt"), "4 - four");
assertEquals(getContent(cache, "four"), "4 - four (no extension)");
}
static String getContent(Resource r, String path) throws Exception
{
StringBuilder buffer = new StringBuilder();
String line = null;
try (BufferedReader br = new BufferedReader(new InputStreamReader(r.addPath(path).getURL().openStream())))
{
while ((line = br.readLine()) != null)
{
buffer.append(line);
}
}
return buffer.toString();
}
static String getContent(CachedContentFactory rc, String path) throws Exception
{
HttpContent content = rc.lookup(path);
HttpContent content = rc.getContent(path, rc.getMaxCachedFileSize());
if (content == null)
return null;

View File

@ -971,11 +971,19 @@ public class BufferUtil
public static ByteBuffer toBuffer(Resource resource, boolean direct) throws IOException
{
int len = (int)resource.length();
long len = resource.length();
if (len < 0)
throw new IllegalArgumentException("invalid resource: " + resource + " len=" + len);
ByteBuffer buffer = direct ? BufferUtil.allocateDirect(len) : BufferUtil.allocate(len);
if (len > Integer.MAX_VALUE)
{
// This method cannot handle resources of this size.
return null;
}
int ilen = (int)len;
ByteBuffer buffer = direct ? BufferUtil.allocateDirect(ilen) : BufferUtil.allocate(ilen);
int pos = BufferUtil.flipToFill(buffer);
if (resource.getFile() != null)
@ -984,7 +992,7 @@ public class BufferUtil
{
try (InputStream is = resource.getInputStream())
{
BufferUtil.readFrom(is, len, buffer);
BufferUtil.readFrom(is, ilen, buffer);
}
}
BufferUtil.flipToFlush(buffer, pos);

View File

@ -52,11 +52,6 @@
<artifactId>jetty-xml</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
@ -68,6 +63,17 @@
<version>${project.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>

View File

@ -0,0 +1,339 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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.webapp;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.client.util.MultiPartContentProvider;
import org.eclipse.jetty.client.util.PathContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.resource.PathResource;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
public class HugeResourceTest
{
private static final long KB = 1024;
private static final long MB = 1024 * KB;
private static final long GB = 1024 * MB;
public static Path staticBase;
public static Path outputDir;
public static Path multipartTempDir;
public Server server;
public HttpClient client;
@BeforeAll
public static void prepareStaticFiles() throws IOException
{
staticBase = MavenTestingUtils.getTargetTestingPath(HugeResourceTest.class.getSimpleName() + "-static-base");
FS.ensureDirExists(staticBase);
makeStaticFile(staticBase.resolve("test-1g.dat"), 1 * GB);
makeStaticFile(staticBase.resolve("test-4g.dat"), 4 * GB);
makeStaticFile(staticBase.resolve("test-10g.dat"), 10 * GB);
outputDir = MavenTestingUtils.getTargetTestingPath(HugeResourceTest.class.getSimpleName() + "-outputdir");
FS.ensureEmpty(outputDir);
multipartTempDir = MavenTestingUtils.getTargetTestingPath(HugeResourceTest.class.getSimpleName() + "-multipart-tmp");
FS.ensureEmpty(multipartTempDir);
}
public static Stream<Arguments> staticFiles()
{
ArrayList<Arguments> ret = new ArrayList<>();
ret.add(Arguments.of("test-1g.dat", 1 * GB));
ret.add(Arguments.of("test-4g.dat", 4 * GB));
ret.add(Arguments.of("test-10g.dat", 10 * GB));
return ret.stream();
}
@AfterAll
public static void cleanupStaticFiles()
{
FS.ensureDeleted(staticBase);
FS.ensureDeleted(outputDir);
}
private static void makeStaticFile(Path staticFile, long size) throws IOException
{
byte[] buf = new byte[(int)(1 * MB)];
Arrays.fill(buf, (byte)'x');
ByteBuffer src = ByteBuffer.wrap(buf);
if (Files.exists(staticFile) && Files.size(staticFile) == size)
{
// all done, nothing left to do.
return;
}
System.err.printf("Creating %,d byte file: %s ...%n", size, staticFile.getFileName());
try (SeekableByteChannel channel = Files.newByteChannel(staticFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING))
{
long remaining = size;
while (remaining > 0)
{
ByteBuffer slice = src.slice();
int len = buf.length;
if (remaining < Integer.MAX_VALUE)
{
len = Math.min(buf.length, (int)remaining);
slice.limit(len);
}
channel.write(slice);
remaining -= len;
}
}
System.err.println(" Done");
}
@BeforeEach
public void startServer() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(0);
server.addConnector(connector);
WebAppContext context = new WebAppContext();
context.setContextPath("/");
context.setBaseResource(new PathResource(staticBase));
context.addServlet(PostServlet.class, "/post");
String location = multipartTempDir.toString();
long maxFileSize = Long.MAX_VALUE;
long maxRequestSize = Long.MAX_VALUE;
int fileSizeThreshold = (int)(2 * MB);
MultipartConfigElement multipartConfig = new MultipartConfigElement(location, maxFileSize, maxRequestSize, fileSizeThreshold);
ServletHolder holder = context.addServlet(MultipartServlet.class, "/multipart");
holder.getRegistration().setMultipartConfig(multipartConfig);
HandlerList handlers = new HandlerList();
handlers.addHandler(context);
handlers.addHandler(new DefaultHandler());
server.setHandler(handlers);
server.start();
}
@AfterEach
public void stopServer() throws Exception
{
server.stop();
}
@BeforeEach
public void startClient() throws Exception
{
client = new HttpClient();
client.start();
}
@AfterEach
public void stopClient() throws Exception
{
client.stop();
}
@ParameterizedTest
@MethodSource("staticFiles")
public void testDownload(String filename, long expectedSize) throws Exception
{
URI destUri = server.getURI().resolve("/" + filename);
InputStreamResponseListener responseListener = new InputStreamResponseListener();
Request request = client.newRequest(destUri)
.method(HttpMethod.GET);
request.send(responseListener);
Response response = responseListener.get(5, TimeUnit.SECONDS);
assertThat("HTTP Response Code", response.getStatus(), is(200));
dumpResponse(response);
String contentLength = response.getHeaders().get(HttpHeader.CONTENT_LENGTH);
long contentLengthLong = Long.parseLong(contentLength);
assertThat("Http Response Header: \"Content-Length: " + contentLength + "\"", contentLengthLong, is(expectedSize));
Path outputFile = outputDir.resolve(filename);
try (OutputStream out = Files.newOutputStream(outputFile);
InputStream in = responseListener.getInputStream())
{
IO.copy(in, out);
}
assertThat("Downloaded Files Size: " + filename, Files.size(outputFile), is(expectedSize));
}
@ParameterizedTest
@MethodSource("staticFiles")
public void testUpload(String filename, long expectedSize) throws Exception
{
Path inputFile = staticBase.resolve(filename);
PathContentProvider pathContentProvider = new PathContentProvider(inputFile);
URI destUri = server.getURI().resolve("/post");
Request request = client.newRequest(destUri).method(HttpMethod.POST).content(pathContentProvider);
ContentResponse response = request.send();
assertThat("HTTP Response Code", response.getStatus(), is(200));
dumpResponse(response);
String responseBody = response.getContentAsString();
assertThat("Response", responseBody, containsString("bytes-received=" + expectedSize));
}
@ParameterizedTest
@MethodSource("staticFiles")
public void testUpload_Multipart(String filename, long expectedSize) throws Exception
{
MultiPartContentProvider multipart = new MultiPartContentProvider();
Path inputFile = staticBase.resolve(filename);
String name = String.format("file-%d", expectedSize);
multipart.addFilePart(name, filename, new PathContentProvider(inputFile), null);
URI destUri = server.getURI().resolve("/multipart");
Request request = client.newRequest(destUri).method(HttpMethod.POST).content(multipart);
ContentResponse response = request.send();
assertThat("HTTP Response Code", response.getStatus(), is(200));
dumpResponse(response);
String responseBody = response.getContentAsString();
String expectedResponse = String.format("part[%s].size=%d", name, expectedSize);
assertThat("Response", responseBody, containsString(expectedResponse));
}
private void dumpResponse(Response response)
{
System.out.printf(" %s %d %s%n", response.getVersion(), response.getStatus(), response.getReason());
response.getHeaders().forEach((field) -> System.out.printf(" %s%n", field));
}
public static class ByteCountingOutputStream extends OutputStream
{
private long count = 0;
public long getCount()
{
return count;
}
@Override
public void write(int b)
{
count++;
}
@Override
public void write(byte[] b)
{
count += b.length;
}
@Override
public void write(byte[] b, int off, int len)
{
count += len;
}
}
public static class PostServlet extends HttpServlet
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
ByteCountingOutputStream byteCounting = new ByteCountingOutputStream();
IO.copy(req.getInputStream(), byteCounting);
resp.setContentType("text/plain");
resp.setCharacterEncoding("utf-8");
resp.getWriter().printf("bytes-received=%d%n", byteCounting.getCount());
}
}
public static class MultipartServlet extends HttpServlet
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setContentType("text/plain");
resp.setCharacterEncoding("utf-8");
PrintWriter out = resp.getWriter();
req.getParts().forEach((part) ->
{
out.printf("part[%s].filename=%s%n", part.getName(), part.getSubmittedFileName());
out.printf("part[%s].size=%d%n", part.getName(), part.getSize());
try (InputStream inputStream = part.getInputStream();
ByteCountingOutputStream byteCounting = new ByteCountingOutputStream())
{
IO.copy(inputStream, byteCounting);
out.printf("part[%s].inputStream.length=%d%n", part.getName(), byteCounting.getCount());
}
catch (IOException e)
{
e.printStackTrace(out);
}
});
}
}
}