improvements to multipart

Signed-off-by: Lachlan Roberts <lachlan@webtide.com>
This commit is contained in:
Lachlan Roberts 2023-01-17 14:09:45 +11:00
parent 1e20abc902
commit 5bcfeaac09
15 changed files with 191 additions and 146 deletions

View File

@ -70,7 +70,7 @@ public class MultiPartRequestContent extends MultiPartFormData.ContentSource imp
if (headers.contains(HttpHeader.CONTENT_TYPE)) if (headers.contains(HttpHeader.CONTENT_TYPE))
return headers; return headers;
Content.Source partContent = part.getNewContent(); Content.Source partContent = part.newContentSource();
if (partContent instanceof Request.Content requestContent) if (partContent instanceof Request.Content requestContent)
{ {
String contentType = requestContent.getContentType(); String contentType = requestContent.getContentType();

View File

@ -129,7 +129,7 @@ public class MultiPartRequestContentTest extends AbstractHttpClientServerTest
int equal = contentType.lastIndexOf('='); int equal = contentType.lastIndexOf('=');
Charset charset = Charset.forName(contentType.substring(equal + 1)); Charset charset = Charset.forName(contentType.substring(equal + 1));
assertEquals(encoding, charset); assertEquals(encoding, charset);
assertEquals(value, Content.Source.asString(part.getNewContent(), charset)); assertEquals(value, Content.Source.asString(part.newContentSource(), charset));
} }
}); });
@ -163,7 +163,7 @@ public class MultiPartRequestContentTest extends AbstractHttpClientServerTest
MultiPart.Part part = parts.iterator().next(); MultiPart.Part part = parts.iterator().next();
assertEquals(name, part.getName()); assertEquals(name, part.getName());
assertEquals("text/plain", part.getHeaders().get(HttpHeader.CONTENT_TYPE)); assertEquals("text/plain", part.getHeaders().get(HttpHeader.CONTENT_TYPE));
assertArrayEquals(data, Content.Source.asByteBuffer(part.getNewContent()).array()); assertArrayEquals(data, Content.Source.asByteBuffer(part.newContentSource()).array());
} }
}); });
@ -215,8 +215,8 @@ public class MultiPartRequestContentTest extends AbstractHttpClientServerTest
assertEquals(name, part.getName()); assertEquals(name, part.getName());
assertEquals(contentType, part.getHeaders().get(HttpHeader.CONTENT_TYPE)); assertEquals(contentType, part.getHeaders().get(HttpHeader.CONTENT_TYPE));
assertEquals(fileName, part.getFileName()); assertEquals(fileName, part.getFileName());
assertEquals(data.length, part.getNewContent().getLength()); assertEquals(data.length, part.newContentSource().getLength());
assertArrayEquals(data, Content.Source.asByteBuffer(part.getNewContent()).array()); assertArrayEquals(data, Content.Source.asByteBuffer(part.newContentSource()).array());
} }
}); });
@ -271,8 +271,8 @@ public class MultiPartRequestContentTest extends AbstractHttpClientServerTest
assertEquals(name, part.getName()); assertEquals(name, part.getName());
assertEquals(contentType, part.getHeaders().get(HttpHeader.CONTENT_TYPE)); assertEquals(contentType, part.getHeaders().get(HttpHeader.CONTENT_TYPE));
assertEquals(tmpPath.getFileName().toString(), part.getFileName()); assertEquals(tmpPath.getFileName().toString(), part.getFileName());
assertEquals(Files.size(tmpPath), part.getNewContent().getLength()); assertEquals(Files.size(tmpPath), part.newContentSource().getLength());
assertEquals(data, Content.Source.asString(part.getNewContent(), encoding)); assertEquals(data, Content.Source.asString(part.newContentSource(), encoding));
} }
}); });
@ -323,14 +323,14 @@ public class MultiPartRequestContentTest extends AbstractHttpClientServerTest
assertEquals(field, fieldPart.getName()); assertEquals(field, fieldPart.getName());
assertEquals(contentType, fieldPart.getHeaders().get(HttpHeader.CONTENT_TYPE)); assertEquals(contentType, fieldPart.getHeaders().get(HttpHeader.CONTENT_TYPE));
assertEquals(value, Content.Source.asString(fieldPart.getNewContent(), encoding)); assertEquals(value, Content.Source.asString(fieldPart.newContentSource(), encoding));
assertEquals(headerValue, fieldPart.getHeaders().get(headerName)); assertEquals(headerValue, fieldPart.getHeaders().get(headerName));
assertEquals(fileField, filePart.getName()); assertEquals(fileField, filePart.getName());
assertEquals("application/octet-stream", filePart.getHeaders().get(HttpHeader.CONTENT_TYPE)); assertEquals("application/octet-stream", filePart.getHeaders().get(HttpHeader.CONTENT_TYPE));
assertEquals(tmpPath.getFileName().toString(), filePart.getFileName()); assertEquals(tmpPath.getFileName().toString(), filePart.getFileName());
assertEquals(Files.size(tmpPath), filePart.getNewContent().getLength()); assertEquals(Files.size(tmpPath), filePart.newContentSource().getLength());
assertArrayEquals(data, Content.Source.asByteBuffer(filePart.getNewContent()).array()); assertArrayEquals(data, Content.Source.asByteBuffer(filePart.newContentSource()).array());
} }
}); });
@ -367,11 +367,11 @@ public class MultiPartRequestContentTest extends AbstractHttpClientServerTest
MultiPart.Part fieldPart = parts.get(0); MultiPart.Part fieldPart = parts.get(0);
MultiPart.Part filePart = parts.get(1); MultiPart.Part filePart = parts.get(1);
assertEquals(value, Content.Source.asString(fieldPart.getNewContent(), encoding)); assertEquals(value, Content.Source.asString(fieldPart.newContentSource(), encoding));
assertEquals("file", filePart.getName()); assertEquals("file", filePart.getName());
assertEquals("application/octet-stream", filePart.getHeaders().get(HttpHeader.CONTENT_TYPE)); assertEquals("application/octet-stream", filePart.getHeaders().get(HttpHeader.CONTENT_TYPE));
assertEquals("fileName", filePart.getFileName()); assertEquals("fileName", filePart.getFileName());
assertArrayEquals(fileData, Content.Source.asByteBuffer(filePart.getNewContent()).array()); assertArrayEquals(fileData, Content.Source.asByteBuffer(filePart.newContentSource()).array());
} }
}); });

View File

@ -176,7 +176,7 @@ public class MultiPart
public Content.Source getContentSource() public Content.Source getContentSource()
{ {
if (content == null) if (content == null)
content = getNewContent(); content = newContentSource();
return content; return content;
} }
@ -192,11 +192,11 @@ public class MultiPart
* *
* @return the content of this part as a new {@link Content.Source} * @return the content of this part as a new {@link Content.Source}
*/ */
public abstract Content.Source getNewContent(); public abstract Content.Source newContentSource();
public long getLength() public long getLength()
{ {
return getNewContent().getLength(); return newContentSource().getLength();
} }
/** /**
@ -221,7 +221,7 @@ public class MultiPart
Charset charset = defaultCharset != null ? defaultCharset : UTF_8; Charset charset = defaultCharset != null ? defaultCharset : UTF_8;
if (charsetName != null) if (charsetName != null)
charset = Charset.forName(charsetName); charset = Charset.forName(charsetName);
return Content.Source.asString(getNewContent(), charset); return Content.Source.asString(newContentSource(), charset);
} }
catch (IOException x) catch (IOException x)
{ {
@ -237,11 +237,6 @@ public class MultiPart
return fields; return fields;
} }
public Path getPath()
{
return path;
}
/** /**
* <p>Writes the content of this part to the given path.</p> * <p>Writes the content of this part to the given path.</p>
* *
@ -254,7 +249,7 @@ public class MultiPart
{ {
try (OutputStream out = Files.newOutputStream(path)) try (OutputStream out = Files.newOutputStream(path))
{ {
IO.copy(Content.Source.asInputStream(getNewContent()), out); IO.copy(Content.Source.asInputStream(newContentSource()), out);
} }
this.path = path; this.path = path;
} }
@ -306,7 +301,7 @@ public class MultiPart
} }
@Override @Override
public Content.Source getNewContent() public Content.Source newContentSource()
{ {
return new ByteBufferContentSource(content); return new ByteBufferContentSource(content);
} }
@ -346,7 +341,7 @@ public class MultiPart
} }
@Override @Override
public Content.Source getNewContent() public Content.Source newContentSource()
{ {
return new ChunksContentSource(content.stream().map(Content.Chunk::slice).toList()); return new ChunksContentSource(content.stream().map(Content.Chunk::slice).toList());
} }
@ -395,8 +390,13 @@ public class MultiPart
} }
} }
public Path getPath()
{
return path;
}
@Override @Override
public Content.Source getNewContent() public Content.Source newContentSource()
{ {
return new PathContentSource(path); return new PathContentSource(path);
} }
@ -409,7 +409,7 @@ public class MultiPart
hashCode(), hashCode(),
getName(), getName(),
getFileName(), getFileName(),
getPath() path
); );
} }
} }
@ -428,7 +428,7 @@ public class MultiPart
} }
@Override @Override
public Content.Source getNewContent() public Content.Source newContentSource()
{ {
Content.Source c = content; Content.Source c = content;
content = null; content = null;
@ -630,7 +630,7 @@ public class MultiPart
else else
{ {
part = parts.poll(); part = parts.poll();
partContent = part.getNewContent(); partContent = part.newContentSource();
state = State.HEADERS; state = State.HEADERS;
yield Content.Chunk.from(firstBoundary.slice(), false); yield Content.Chunk.from(firstBoundary.slice(), false);
} }
@ -657,7 +657,7 @@ public class MultiPart
else else
{ {
part = parts.poll(); part = parts.poll();
partContent = part.getNewContent(); partContent = part.newContentSource();
state = State.HEADERS; state = State.HEADERS;
yield Content.Chunk.from(middleBoundary.slice(), false); yield Content.Chunk.from(middleBoundary.slice(), false);
} }
@ -743,7 +743,7 @@ public class MultiPart
if (state == State.CONTENT) if (state == State.CONTENT)
{ {
partContent.demand(() -> part.getContentSource().demand(() ->
{ {
try (AutoLock ignoredAgain = lock.lock()) try (AutoLock ignoredAgain = lock.lock())
{ {
@ -850,6 +850,8 @@ public class MultiPart
private int trailingWhiteSpaces; private int trailingWhiteSpaces;
private String fieldName; private String fieldName;
private String fieldValue; private String fieldValue;
private long maxParts;
private int numParts = 0;
public Parser(String boundary, Listener listener) public Parser(String boundary, Listener listener)
{ {
@ -881,6 +883,22 @@ public class MultiPart
this.partHeadersMaxLength = partHeadersMaxLength; this.partHeadersMaxLength = partHeadersMaxLength;
} }
/**
* @return the maximum number of parts that can be parsed from the multipart content.
*/
public long getMaxParts()
{
return maxParts;
}
/**
* @param maxParts the maximum number of parts that can be parsed from the multipart content.
*/
public void setMaxParts(long maxParts)
{
this.maxParts = maxParts;
}
/** /**
* <p>Resets this parser to make it ready to parse again a multipart/form-data content.</p> * <p>Resets this parser to make it ready to parse again a multipart/form-data content.</p>
*/ */
@ -939,6 +957,10 @@ public class MultiPart
} }
else if (type == HttpTokens.Type.LF) else if (type == HttpTokens.Type.LF)
{ {
numParts++;
if (numParts >= maxParts)
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", numParts, maxParts));
notifyPartBegin(); notifyPartBegin();
state = State.HEADER_START; state = State.HEADER_START;
trailingWhiteSpaces = 0; trailingWhiteSpaces = 0;
@ -1343,37 +1365,93 @@ public class MultiPart
private void notifyPartBegin() private void notifyPartBegin()
{ {
listener.onPartBegin(); try
{
listener.onPartBegin();
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
}
} }
private void notifyPartHeader(String name, String value) private void notifyPartHeader(String name, String value)
{ {
listener.onPartHeader(name, value); try
{
listener.onPartHeader(name, value);
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
}
} }
private void notifyPartHeaders() private void notifyPartHeaders()
{ {
listener.onPartHeaders(); try
{
listener.onPartHeaders();
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
}
} }
private void notifyPartContent(Content.Chunk chunk) private void notifyPartContent(Content.Chunk chunk)
{ {
listener.onPartContent(chunk); try
{
listener.onPartContent(chunk);
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
}
} }
private void notifyPartEnd() private void notifyPartEnd()
{ {
listener.onPartEnd(); try
{
listener.onPartEnd();
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
}
} }
private void notifyComplete() private void notifyComplete()
{ {
listener.onComplete(); try
{
listener.onComplete();
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
}
} }
private void notifyFailure(Throwable failure) private void notifyFailure(Throwable failure)
{ {
listener.onFailure(failure); try
{
listener.onFailure(failure);
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug("failure while notifying listener {}", listener, x);
}
} }
/** /**

View File

@ -265,7 +265,7 @@ public class MultiPartByteRanges extends CompletableFuture<MultiPartByteRanges.P
} }
@Override @Override
public Content.Source getNewContent() public Content.Source newContentSource()
{ {
return new PathContentSource(path, byteRange); return new PathContentSource(path, byteRange);
} }

View File

@ -68,7 +68,7 @@ import static java.nio.charset.StandardCharsets.US_ASCII;
* *
* @see Parts * @see Parts
*/ */
public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts> implements Closeable public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts>
{ {
private static final Logger LOG = LoggerFactory.getLogger(MultiPartFormData.class); private static final Logger LOG = LoggerFactory.getLogger(MultiPartFormData.class);
@ -80,8 +80,6 @@ public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts
private long maxMemoryFileSize; private long maxMemoryFileSize;
private long maxLength = -1; private long maxLength = -1;
private long length; private long length;
private int numParts = 0;
private long maxParts = 1000;
public MultiPartFormData(String boundary) public MultiPartFormData(String boundary)
{ {
@ -281,7 +279,7 @@ public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts
*/ */
public long getMaxParts() public long getMaxParts()
{ {
return maxLength; return parser.getMaxParts();
} }
/** /**
@ -289,7 +287,7 @@ public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts
*/ */
public void setMaxParts(long maxParts) public void setMaxParts(long maxParts)
{ {
this.maxParts = maxParts; parser.setMaxParts(maxParts);
} }
@Override @Override
@ -309,23 +307,18 @@ public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts
* <p>An ordered list of {@link MultiPart.Part}s that can * <p>An ordered list of {@link MultiPart.Part}s that can
* be accessed by index or by name, or iterated over.</p> * be accessed by index or by name, or iterated over.</p>
*/ */
public static class Parts implements Iterable<MultiPart.Part> public class Parts implements Iterable<MultiPart.Part>
{ {
private final String boundary;
private final List<MultiPart.Part> parts; private final List<MultiPart.Part> parts;
private Parts(String boundary, List<MultiPart.Part> parts) private Parts(List<MultiPart.Part> parts)
{ {
this.boundary = boundary;
this.parts = parts; this.parts = parts;
} }
/** public MultiPartFormData getMultiPartFormData()
* @return the boundary string
*/
public String getBoundary()
{ {
return boundary; return MultiPartFormData.this;
} }
/** /**
@ -383,6 +376,21 @@ public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts
{ {
return parts.iterator(); return parts.iterator();
} }
public void close()
{
for (MultiPart.Part p : parts)
{
try
{
p.close();
}
catch (Throwable e)
{
LOG.warn("Errors deleting multipart tmp files", e);
}
}
}
} }
/** /**
@ -502,15 +510,6 @@ public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts
} }
} }
@Override
public void onPartBegin()
{
// TODO: Move to parser.
numParts++;
if (numParts >= maxParts)
throw new IllegalStateException(String.format("Form with too many keys [%d > %d]", numParts, maxParts));
}
@Override @Override
public void onPart(String name, String fileName, HttpFields headers) public void onPart(String name, String fileName, HttpFields headers)
{ {
@ -536,7 +535,7 @@ public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts
public void onComplete() public void onComplete()
{ {
super.onComplete(); super.onComplete();
complete(new Parts(getBoundary(), getParts())); complete(new Parts(getParts()));
} }
private List<MultiPart.Part> getParts() private List<MultiPart.Part> getParts()
@ -629,25 +628,4 @@ public class MultiPartFormData extends CompletableFuture<MultiPartFormData.Parts
} }
} }
} }
@Override
public void close()
{
// TODO: Can we do this async?
MultiPartFormData.Parts parts = getNow(null);
if (parts != null)
{
for (MultiPart.Part p : parts)
{
try
{
p.close();
}
catch (Throwable e)
{
LOG.warn("Errors deleting multipart tmp files", e);
}
}
}
}
} }

View File

@ -245,7 +245,7 @@ public class MultiPartCaptureTest
List<MultiPart.Part> charSetParts = allParts.get("_charset_"); List<MultiPart.Part> charSetParts = allParts.get("_charset_");
if (charSetParts != null) if (charSetParts != null)
{ {
defaultCharset = Promise.Completable.<String>with(p -> Content.Source.asString(charSetParts.get(0).getNewContent(), StandardCharsets.US_ASCII, p)) defaultCharset = Promise.Completable.<String>with(p -> Content.Source.asString(charSetParts.get(0).newContentSource(), StandardCharsets.US_ASCII, p))
.get(); .get();
} }
@ -255,8 +255,8 @@ public class MultiPartCaptureTest
assertThat("Part[" + expected.name + "]", parts, is(notNullValue())); assertThat("Part[" + expected.name + "]", parts, is(notNullValue()));
MultiPart.Part part = parts.get(0); MultiPart.Part part = parts.get(0);
String charset = getCharsetFromContentType(part.getHeaders().get(HttpHeader.CONTENT_TYPE), defaultCharset); String charset = getCharsetFromContentType(part.getHeaders().get(HttpHeader.CONTENT_TYPE), defaultCharset);
assertTrue(part.getNewContent().rewind()); assertTrue(part.newContentSource().rewind());
String partContent = Content.Source.asString(part.getNewContent(), Charset.forName(charset)); String partContent = Content.Source.asString(part.newContentSource(), Charset.forName(charset));
assertThat("Part[" + expected.name + "].contents", partContent, containsString(expected.value)); assertThat("Part[" + expected.name + "].contents", partContent, containsString(expected.value));
} }
@ -276,8 +276,8 @@ public class MultiPartCaptureTest
assertThat("Part[" + expected.name + "]", parts, is(notNullValue())); assertThat("Part[" + expected.name + "]", parts, is(notNullValue()));
MultiPart.Part part = parts.get(0); MultiPart.Part part = parts.get(0);
MessageDigest digest = MessageDigest.getInstance("SHA1"); MessageDigest digest = MessageDigest.getInstance("SHA1");
assertTrue(part.getNewContent().rewind()); assertTrue(part.newContentSource().rewind());
try (InputStream partInputStream = Content.Source.asInputStream(part.getNewContent()); try (InputStream partInputStream = Content.Source.asInputStream(part.newContentSource());
DigestOutputStream digester = new DigestOutputStream(OutputStream.nullOutputStream(), digest)) DigestOutputStream digester = new DigestOutputStream(OutputStream.nullOutputStream(), digest))
{ {
IO.copy(partInputStream, digester); IO.copy(partInputStream, digester);

View File

@ -189,25 +189,25 @@ public class MultiPartFormDataTest
MultiPart.Part fileName = parts.getFirst("fileName"); MultiPart.Part fileName = parts.getFirst("fileName");
assertThat(fileName, notNullValue()); assertThat(fileName, notNullValue());
Content.Source partContent = fileName.getNewContent(); Content.Source partContent = fileName.newContentSource();
assertThat(partContent.getLength(), is(3L)); assertThat(partContent.getLength(), is(3L));
assertThat(Content.Source.asString(partContent), is("abc")); assertThat(Content.Source.asString(partContent), is("abc"));
MultiPart.Part desc = parts.getFirst("desc"); MultiPart.Part desc = parts.getFirst("desc");
assertThat(desc, notNullValue()); assertThat(desc, notNullValue());
partContent = desc.getNewContent(); partContent = desc.newContentSource();
assertThat(partContent.getLength(), is(3L)); assertThat(partContent.getLength(), is(3L));
assertThat(Content.Source.asString(partContent), is("123")); assertThat(Content.Source.asString(partContent), is("123"));
MultiPart.Part title = parts.getFirst("title"); MultiPart.Part title = parts.getFirst("title");
assertThat(title, notNullValue()); assertThat(title, notNullValue());
partContent = title.getNewContent(); partContent = title.newContentSource();
assertThat(partContent.getLength(), is(3L)); assertThat(partContent.getLength(), is(3L));
assertThat(Content.Source.asString(partContent), is("ttt")); assertThat(Content.Source.asString(partContent), is("ttt"));
MultiPart.Part datafile = parts.getFirst("datafile5239138112980980385.txt"); MultiPart.Part datafile = parts.getFirst("datafile5239138112980980385.txt");
assertThat(datafile, notNullValue()); assertThat(datafile, notNullValue());
partContent = datafile.getNewContent(); partContent = datafile.newContentSource();
assertThat(partContent.getLength(), is(3L)); assertThat(partContent.getLength(), is(3L));
assertThat(Content.Source.asString(partContent), is("000")); assertThat(Content.Source.asString(partContent), is("000"));
} }
@ -275,11 +275,11 @@ public class MultiPartFormDataTest
assertThat(parts.size(), is(2)); assertThat(parts.size(), is(2));
MultiPart.Part part1 = parts.getFirst("field1"); MultiPart.Part part1 = parts.getFirst("field1");
assertThat(part1, notNullValue()); assertThat(part1, notNullValue());
Content.Source partContent = part1.getNewContent(); Content.Source partContent = part1.newContentSource();
assertThat(Content.Source.asString(partContent), is("Joe Blow")); assertThat(Content.Source.asString(partContent), is("Joe Blow"));
MultiPart.Part part2 = parts.getFirst("stuff"); MultiPart.Part part2 = parts.getFirst("stuff");
assertThat(part2, notNullValue()); assertThat(part2, notNullValue());
partContent = part2.getNewContent(); partContent = part2.newContentSource();
assertThat(Content.Source.asString(partContent), is("aaaabbbbb")); assertThat(Content.Source.asString(partContent), is("aaaabbbbb"));
} }
@ -312,7 +312,7 @@ public class MultiPartFormDataTest
assertThat(parts.size(), is(1)); assertThat(parts.size(), is(1));
MultiPart.Part part2 = parts.getFirst("stuff"); MultiPart.Part part2 = parts.getFirst("stuff");
assertThat(part2, notNullValue()); assertThat(part2, notNullValue());
Content.Source partContent = part2.getNewContent(); Content.Source partContent = part2.newContentSource();
assertThat(Content.Source.asString(partContent), is("aaaabbbbb")); assertThat(Content.Source.asString(partContent), is("aaaabbbbb"));
} }
@ -340,7 +340,7 @@ public class MultiPartFormDataTest
assertThat(part, instanceOf(MultiPart.PathPart.class)); assertThat(part, instanceOf(MultiPart.PathPart.class));
MultiPart.PathPart pathPart = (MultiPart.PathPart)part; MultiPart.PathPart pathPart = (MultiPart.PathPart)part;
assertTrue(Files.exists(pathPart.getPath())); assertTrue(Files.exists(pathPart.getPath()));
assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", Content.Source.asString(part.getNewContent())); assertEquals("ABCDEFGHIJKLMNOPQRSTUVWXYZ", Content.Source.asString(part.newContentSource()));
} }
@Test @Test
@ -422,13 +422,13 @@ public class MultiPartFormDataTest
MultiPart.Part part1 = parts.get(0); MultiPart.Part part1 = parts.get(0);
assertThat(part1, instanceOf(MultiPart.ChunksPart.class)); assertThat(part1, instanceOf(MultiPart.ChunksPart.class));
assertEquals(chunk, Content.Source.asString(part1.getNewContent())); assertEquals(chunk, Content.Source.asString(part1.newContentSource()));
MultiPart.Part part2 = parts.get(1); MultiPart.Part part2 = parts.get(1);
assertThat(part2, instanceOf(MultiPart.PathPart.class)); assertThat(part2, instanceOf(MultiPart.PathPart.class));
MultiPart.PathPart pathPart2 = (MultiPart.PathPart)part2; MultiPart.PathPart pathPart2 = (MultiPart.PathPart)part2;
assertTrue(Files.exists(pathPart2.getPath())); assertTrue(Files.exists(pathPart2.getPath()));
assertEquals(chunk.repeat(4), Content.Source.asString(part2.getNewContent())); assertEquals(chunk.repeat(4), Content.Source.asString(part2.newContentSource()));
} }
@Test @Test

View File

@ -361,11 +361,11 @@ public class MultiPartTest
MultiPart.Part part1 = listener.parts.get(0); MultiPart.Part part1 = listener.parts.get(0);
assertEquals("value", part1.getHeaders().get("name")); assertEquals("value", part1.getHeaders().get("name"));
assertEquals("Hello", Content.Source.asString(part1.getNewContent())); assertEquals("Hello", Content.Source.asString(part1.newContentSource()));
MultiPart.Part part2 = listener.parts.get(1); MultiPart.Part part2 = listener.parts.get(1);
assertEquals("9001", part2.getHeaders().get("powerLevel")); assertEquals("9001", part2.getHeaders().get("powerLevel"));
assertEquals("secondary\r\ncontent", Content.Source.asString(part2.getNewContent())); assertEquals("secondary\r\ncontent", Content.Source.asString(part2.newContentSource()));
assertEquals(0, data.remaining()); assertEquals(0, data.remaining());
} }
@ -397,11 +397,11 @@ public class MultiPartTest
MultiPart.Part part1 = listener.parts.get(0); MultiPart.Part part1 = listener.parts.get(0);
assertEquals("value", part1.getHeaders().get("name")); assertEquals("value", part1.getHeaders().get("name"));
assertEquals("Hello", Content.Source.asString(part1.getNewContent())); assertEquals("Hello", Content.Source.asString(part1.newContentSource()));
MultiPart.Part part2 = listener.parts.get(1); MultiPart.Part part2 = listener.parts.get(1);
assertEquals("9001", part2.getHeaders().get("powerLevel")); assertEquals("9001", part2.getHeaders().get("powerLevel"));
assertEquals("secondary\ncontent", Content.Source.asString(part2.getNewContent())); assertEquals("secondary\ncontent", Content.Source.asString(part2.newContentSource()));
assertEquals(0, data.remaining()); assertEquals(0, data.remaining());
} }
@ -457,7 +457,7 @@ public class MultiPartTest
assertEquals(1, listener.parts.size()); assertEquals(1, listener.parts.size());
MultiPart.Part part = listener.parts.get(0); MultiPart.Part part = listener.parts.get(0);
assertEquals("value", part.getHeaders().get("name")); assertEquals("value", part.getHeaders().get("name"));
assertEquals("", Content.Source.asString(part.getNewContent())); assertEquals("", Content.Source.asString(part.newContentSource()));
} }
@Test @Test
@ -477,7 +477,7 @@ public class MultiPartTest
assertEquals(1, listener.parts.size()); assertEquals(1, listener.parts.size());
MultiPart.Part part = listener.parts.get(0); MultiPart.Part part = listener.parts.get(0);
assertEquals("value", part.getHeaders().get("name")); assertEquals("value", part.getHeaders().get("name"));
assertEquals("", Content.Source.asString(part.getNewContent())); assertEquals("", Content.Source.asString(part.newContentSource()));
} }
@Test @Test
@ -508,7 +508,7 @@ public class MultiPartTest
assertEquals(1, listener.parts.size()); assertEquals(1, listener.parts.size());
MultiPart.Part part = listener.parts.get(0); MultiPart.Part part = listener.parts.get(0);
assertEquals("value", part.getHeaders().get("name")); assertEquals("value", part.getHeaders().get("name"));
assertThat(Content.Source.asString(part.getNewContent()), is(""" assertThat(Content.Source.asString(part.newContentSource()), is("""
Hello\r Hello\r
this is not a --BOUNDARY\r this is not a --BOUNDARY\r
that's a boundary""")); that's a boundary"""));
@ -532,7 +532,7 @@ public class MultiPartTest
assertThat(epilogueBuffer.remaining(), is(0)); assertThat(epilogueBuffer.remaining(), is(0));
assertEquals(1, listener.parts.size()); assertEquals(1, listener.parts.size());
MultiPart.Part part = listener.parts.get(0); MultiPart.Part part = listener.parts.get(0);
assertThat(Content.Source.asByteBuffer(part.getNewContent()), is(ByteBuffer.wrap(random))); assertThat(Content.Source.asByteBuffer(part.newContentSource()), is(ByteBuffer.wrap(random)));
} }
@Test @Test
@ -556,7 +556,7 @@ public class MultiPartTest
assertEquals(1, listener.parts.size()); assertEquals(1, listener.parts.size());
MultiPart.Part part = listener.parts.get(0); MultiPart.Part part = listener.parts.get(0);
assertEquals("value", part.getHeaders().get("name")); assertEquals("value", part.getHeaders().get("name"));
assertEquals("Hello", Content.Source.asString(part.getNewContent())); assertEquals("Hello", Content.Source.asString(part.newContentSource()));
} }
@Test @Test

View File

@ -269,13 +269,13 @@ public class DelayedHandler extends Handler.Wrapper
super(handler, wrapped, response, callback); super(handler, wrapped, response, callback);
String boundary = MultiPart.extractBoundary(contentType); String boundary = MultiPart.extractBoundary(contentType);
_formData = boundary == null ? null : new MultiPartFormData(boundary); _formData = boundary == null ? null : new MultiPartFormData(boundary);
getRequest().setAttribute(MultiPartFormData.class.getName(), _formData);
} }
private void process(MultiPartFormData.Parts parts, Throwable x) private void process(MultiPartFormData.Parts parts, Throwable x)
{ {
if (x == null) if (x == null)
{ {
getRequest().setAttribute(MultiPartFormData.Parts.class.getName(), parts);
super.process(); super.process();
} }
else else

View File

@ -34,7 +34,7 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MultiPartFormData; import org.eclipse.jetty.http.MultiPartFormData.Parts;
import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.http.Trailers; import org.eclipse.jetty.http.Trailers;
import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.http.UriCompliance;
@ -649,9 +649,9 @@ public class HttpChannelState implements HttpChannel, Components
} }
// Clean up any multipart tmp files and release any associated resources. // Clean up any multipart tmp files and release any associated resources.
MultiPartFormData multiParts = (MultiPartFormData)_request.getAttribute(MultiPartFormData.class.getName()); Parts parts = (Parts)_request.getAttribute(Parts.class.getName());
if (multiParts != null) if (parts != null)
multiParts.close(); parts.close();
} }
finally finally
{ {

View File

@ -119,11 +119,11 @@ public class MultiPartByteRangesTest
assertEquals(3, parts.size()); assertEquals(3, parts.size());
MultiPart.Part part1 = parts.get(0); MultiPart.Part part1 = parts.get(0);
assertEquals("12", Content.Source.asString(part1.getNewContent())); assertEquals("12", Content.Source.asString(part1.newContentSource()));
MultiPart.Part part2 = parts.get(1); MultiPart.Part part2 = parts.get(1);
assertEquals("456", Content.Source.asString(part2.getNewContent())); assertEquals("456", Content.Source.asString(part2.newContentSource()));
MultiPart.Part part3 = parts.get(2); MultiPart.Part part3 = parts.get(2);
assertEquals("CDEF", Content.Source.asString(part3.getNewContent())); assertEquals("CDEF", Content.Source.asString(part3.newContentSource()));
} }
} }
} }

View File

@ -80,7 +80,7 @@ public class MultiPartFormDataHandlerTest
.whenComplete((parts, failure) -> .whenComplete((parts, failure) ->
{ {
if (parts != null) if (parts != null)
Content.copy(parts.get(0).getNewContent(), response, callback); Content.copy(parts.get(0).newContentSource(), response, callback);
else else
Response.writeError(request, response, callback, failure); Response.writeError(request, response, callback, failure);
}); });
@ -126,10 +126,10 @@ public class MultiPartFormDataHandlerTest
public boolean process(Request request, Response response, Callback callback) throws Exception public boolean process(Request request, Response response, Callback callback) throws Exception
{ {
processLatch.countDown(); processLatch.countDown();
MultiPartFormData formData = (MultiPartFormData)request.getAttribute(MultiPartFormData.class.getName()); MultiPartFormData.Parts parts = (MultiPartFormData.Parts)request.getAttribute(MultiPartFormData.Parts.class.getName());
assertNotNull(formData); assertNotNull(parts);
MultiPart.Part part = formData.get().get(0); MultiPart.Part part = parts.get(0);
Content.copy(part.getNewContent(), response, callback); Content.copy(part.newContentSource(), response, callback);
return true; return true;
} }
}); });
@ -195,8 +195,8 @@ public class MultiPartFormDataHandlerTest
{ {
if (parts != null) if (parts != null)
{ {
response.getHeaders().put(HttpHeader.CONTENT_TYPE, "multipart/form-data; boundary=\"%s\"".formatted(parts.getBoundary())); response.getHeaders().put(HttpHeader.CONTENT_TYPE, "multipart/form-data; boundary=\"%s\"".formatted(parts.getMultiPartFormData().getBoundary()));
MultiPartFormData.ContentSource source = new MultiPartFormData.ContentSource(parts.getBoundary()); MultiPartFormData.ContentSource source = new MultiPartFormData.ContentSource(parts.getMultiPartFormData().getBoundary());
source.setPartHeadersMaxLength(1024); source.setPartHeadersMaxLength(1024);
parts.forEach(source::addPart); parts.forEach(source::addPart);
source.close(); source.close();
@ -321,7 +321,7 @@ public class MultiPartFormDataHandlerTest
HttpFields headers2 = part2.getHeaders(); HttpFields headers2 = part2.getHeaders();
assertEquals(2, headers2.size()); assertEquals(2, headers2.size());
assertEquals("application/octet-stream", headers2.get(HttpHeader.CONTENT_TYPE)); assertEquals("application/octet-stream", headers2.get(HttpHeader.CONTENT_TYPE));
assertEquals(32, part2.getNewContent().getLength()); assertEquals(32, part2.newContentSource().getLength());
} }
} }
} }

View File

@ -181,10 +181,10 @@ public class ResourceHandlerByteRangesTest
assertEquals(2, parts.size()); assertEquals(2, parts.size());
MultiPart.Part part1 = parts.get(0); MultiPart.Part part1 = parts.get(0);
assertEquals("text/plain", part1.getHeaders().get(HttpHeader.CONTENT_TYPE)); assertEquals("text/plain", part1.getHeaders().get(HttpHeader.CONTENT_TYPE));
assertEquals("234", Content.Source.asString(part1.getNewContent())); assertEquals("234", Content.Source.asString(part1.newContentSource()));
MultiPart.Part part2 = parts.get(1); MultiPart.Part part2 = parts.get(1);
assertEquals("text/plain", part2.getHeaders().get(HttpHeader.CONTENT_TYPE)); assertEquals("text/plain", part2.getHeaders().get(HttpHeader.CONTENT_TYPE));
assertEquals("xyz", Content.Source.asString(part2.getNewContent())); assertEquals("xyz", Content.Source.asString(part2.newContentSource()));
} }
} }
} }

View File

@ -77,9 +77,9 @@ public class ServletMultiPartFormData
try try
{ {
// Look for a previously read and parsed MultiPartFormData from the DelayedHandler. // Look for a previously read and parsed MultiPartFormData from the DelayedHandler.
MultiPartFormData formData = (MultiPartFormData)request.getAttribute(MultiPartFormData.class.getName()); MultiPartFormData.Parts parts = (MultiPartFormData.Parts)request.getAttribute(MultiPartFormData.Parts.class.getName());
if (formData != null) if (parts != null)
return new Parts(formData); return new Parts(parts);
// TODO set the files directory // TODO set the files directory
return new ServletMultiPartFormData().parse(request, maxParts); return new ServletMultiPartFormData().parse(request, maxParts);
@ -102,7 +102,6 @@ public class ServletMultiPartFormData
// Store MultiPartFormData as attribute on request so it is released by the HttpChannel. // Store MultiPartFormData as attribute on request so it is released by the HttpChannel.
MultiPartFormData formData = new MultiPartFormData(boundary); MultiPartFormData formData = new MultiPartFormData(boundary);
request.setAttribute(MultiPartFormData.class.getName(), formData);
formData.setMaxParts(maxParts); formData.setMaxParts(maxParts);
File tmpDirFile = (File)request.getServletContext().getAttribute(ServletContext.TEMPDIR); File tmpDirFile = (File)request.getServletContext().getAttribute(ServletContext.TEMPDIR);
@ -123,7 +122,7 @@ public class ServletMultiPartFormData
Connection connection = connectionMetaData.getConnection(); Connection connection = connectionMetaData.getConnection();
int bufferSize = connection instanceof AbstractConnection c ? c.getInputBufferSize() : 2048; int bufferSize = connection instanceof AbstractConnection c ? c.getInputBufferSize() : 2048;
InputStream input = request.getInputStream(); InputStream input = request.getInputStream();
while (true) while (!formData.isDone())
{ {
ByteBuffer buffer = byteBufferPool.newByteBuffer(bufferSize, false); ByteBuffer buffer = byteBufferPool.newByteBuffer(bufferSize, false);
boolean readEof = false; boolean readEof = false;
@ -145,7 +144,9 @@ public class ServletMultiPartFormData
} }
} }
return new Parts(formData); Parts parts = new Parts(formData.join());
request.setAttribute(Parts.class.getName(), parts);
return parts;
} }
/** /**
@ -155,9 +156,9 @@ public class ServletMultiPartFormData
{ {
private final List<Part> parts = new ArrayList<>(); private final List<Part> parts = new ArrayList<>();
public Parts(MultiPartFormData formData) public Parts(MultiPartFormData.Parts parts)
{ {
formData.join().forEach(part -> parts.add(new ServletPart(formData, part))); parts.forEach(part -> this.parts.add(new ServletPart(parts.getMultiPartFormData(), part)));
} }
public Part getPart(String name) public Part getPart(String name)
@ -190,7 +191,7 @@ public class ServletMultiPartFormData
@Override @Override
public InputStream getInputStream() throws IOException public InputStream getInputStream() throws IOException
{ {
return Content.Source.asInputStream(_part.getNewContent()); return Content.Source.asInputStream(_part.newContentSource());
} }
@Override @Override

View File

@ -129,10 +129,6 @@ public class MultiPartServletTest
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{ {
req.getParameterMap(); req.getParameterMap();
req.getParts();
resp.setStatus(200);
resp.getWriter().print("success");
resp.getWriter().close();
} }
}, new MultipartConfigElement(tmpDirString)); }, new MultipartConfigElement(tmpDirString));
@ -174,10 +170,6 @@ public class MultiPartServletTest
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{ {
req.getParameterMap(); req.getParameterMap();
req.getParts();
resp.setStatus(200);
resp.getWriter().print("success");
resp.getWriter().close();
} }
}, new MultipartConfigElement(tmpDirString)); }, new MultipartConfigElement(tmpDirString));
@ -216,10 +208,6 @@ public class MultiPartServletTest
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{ {
req.getParameterMap(); req.getParameterMap();
req.getParts();
resp.setStatus(200);
resp.getWriter().print("success");
resp.getWriter().close();
} }
}, new MultipartConfigElement(tmpDirString, -1, 1024, 1024 * 1024 * 8)); }, new MultipartConfigElement(tmpDirString, -1, 1024, 1024 * 1024 * 8));