All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.http.MultiPartByteRanges Maven / Gradle / Ivy

There is a newer version: 12.0.13
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.http;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.IOResources;
import org.eclipse.jetty.io.content.ContentSourceCompletableFuture;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.thread.AutoLock;

/**
 * 

A {@link CompletableFuture} that is completed when a multipart/byteranges * has been parsed asynchronously from a {@link Content.Source}.

*

Once the parsing of the multipart/byteranges content completes successfully, * objects of this class are completed with a {@link MultiPartByteRanges.Parts} * object.

*

Typical usage:

*
{@code
 * // Some headers that include Content-Type.
 * HttpFields headers = ...;
 * String boundary = MultiPart.extractBoundary(headers.get(HttpHeader.CONTENT_TYPE));
 *
 * // Some multipart/byteranges content.
 * Content.Source content = ...;
 *
 * // Create and configure MultiPartByteRanges.
 * MultiPartByteRanges.Parser byteRanges = new MultiPartByteRanges.Parser(boundary);
 *
 * // Parse the content.
 * byteRanges.parse(content)
 *     // When complete, use the parts.
 *     .thenAccept(parts -> ...);
 * }
* * @see Parts */ public class MultiPartByteRanges { private MultiPartByteRanges() { } /** *

An ordered list of {@link MultiPart.Part}s that can * be accessed by index, or iterated over.

*/ public static class Parts implements Iterable { private final String boundary; private final List parts; private Parts(String boundary, List parts) { this.boundary = boundary; this.parts = parts; } /** * @return the boundary string */ public String getBoundary() { return boundary; } /** *

Returns the {@link MultiPart.Part} at the given index, a number * between {@code 0} included and the value returned by {@link #size()} * excluded.

* * @param index the index of the {@code MultiPart.Part} to return * @return the {@code MultiPart.Part} at the given index */ public MultiPart.Part get(int index) { return parts.get(index); } /** * @return the number of parts * @see #get(int) */ public int size() { return parts.size(); } @Override public Iterator iterator() { return parts.iterator(); } } /** *

The multipart/byteranges specific content source.

* * @see MultiPart.AbstractContentSource */ public static class ContentSource extends MultiPart.AbstractContentSource { public ContentSource(String boundary) { super(boundary); } @Override public boolean addPart(MultiPart.Part part) { if (part instanceof Part) return super.addPart(part); return false; } } /** *

A specialized {@link org.eclipse.jetty.io.content.InputStreamContentSource} * whose content is sliced by a byte range.

*/ public static class InputStreamContentSource extends org.eclipse.jetty.io.content.InputStreamContentSource { private long toRead; public InputStreamContentSource(InputStream inputStream, ByteRange byteRange) throws IOException { super(inputStream); inputStream.skipNBytes(byteRange.first()); this.toRead = byteRange.getLength(); } @Override protected int fillBufferFromInputStream(InputStream inputStream, byte[] buffer) throws IOException { if (toRead == 0) return -1; int toReadInt = (int)Math.min(Integer.MAX_VALUE, toRead); int len = Math.min(toReadInt, buffer.length); int read = inputStream.read(buffer, 0, len); toRead -= read; return read; } } /** *

A specialized {@link Content.Source} * whose {@link Path} content is sliced by a byte range.

* * @deprecated use {@link Content.Source#from(ByteBufferPool.Sized, Path, long, long)} */ @Deprecated(forRemoval = true, since = "12.0.11") public static class PathContentSource implements Content.Source { private final Content.Source contentSource; public PathContentSource(Path path, ByteRange byteRange) { contentSource = Content.Source.from(null, path, byteRange.first(), byteRange.getLength()); } @Override public void demand(Runnable demandCallback) { contentSource.demand(demandCallback); } @Override public void fail(Throwable failure) { contentSource.fail(failure); } @Override public void fail(Throwable failure, boolean last) { contentSource.fail(failure, last); } @Override public long getLength() { return contentSource.getLength(); } @Override public Content.Chunk read() { return contentSource.read(); } @Override public boolean rewind() { return contentSource.rewind(); } } /** *

A {@link MultiPart.Part} whose content is a byte range of a {@link Resource}.

*/ public static class Part extends MultiPart.Part { private final Resource resource; private final ByteRange byteRange; private final ByteBufferPool bufferPool; public Part(String contentType, Resource resource, ByteRange byteRange, long contentLength) { this(HttpFields.build().put(HttpHeader.CONTENT_TYPE, contentType) .put(HttpHeader.CONTENT_RANGE, byteRange.toHeaderValue(contentLength)), resource, byteRange, null); } public Part(String contentType, Resource resource, ByteRange byteRange, long contentLength, ByteBufferPool bufferPool) { this(HttpFields.build().put(HttpHeader.CONTENT_TYPE, contentType) .put(HttpHeader.CONTENT_RANGE, byteRange.toHeaderValue(contentLength)), resource, byteRange, bufferPool); } public Part(HttpFields headers, Resource resource, ByteRange byteRange) { this(headers, resource, byteRange, null); } public Part(HttpFields headers, Resource resource, ByteRange byteRange, ByteBufferPool bufferPool) { super(null, null, headers); this.resource = resource; this.byteRange = byteRange; this.bufferPool = bufferPool == null ? ByteBufferPool.NON_POOLING : bufferPool; } @Override public Content.Source newContentSource() { return IOResources.asContentSource(resource, bufferPool, 0, false, byteRange.first(), byteRange.getLength()); } } public static class Parser { private final PartsListener listener = new PartsListener(); private final MultiPart.Parser parser; private Parts parts; public Parser(String boundary) { parser = new MultiPart.Parser(boundary, listener); } public CompletableFuture parse(Content.Source content) { ContentSourceCompletableFuture futureParts = new ContentSourceCompletableFuture<>(content) { @Override protected MultiPartByteRanges.Parts parse(Content.Chunk chunk) throws Throwable { if (listener.isFailed()) throw listener.failure; parser.parse(chunk); if (listener.isFailed()) throw listener.failure; return parts; } @Override public boolean completeExceptionally(Throwable failure) { boolean failed = super.completeExceptionally(failure); if (failed) listener.fail(failure); return failed; } }; futureParts.parse(); return futureParts; } /** * @return the boundary string */ public String getBoundary() { return parser.getBoundary(); } private class PartsListener extends MultiPart.AbstractPartsListener { private final AutoLock lock = new AutoLock(); private final List partChunks = new ArrayList<>(); private final List parts = new ArrayList<>(); private Throwable failure; private boolean isFailed() { try (AutoLock ignored = lock.lock()) { return failure != null; } } @Override public void onPartContent(Content.Chunk chunk) { try (AutoLock ignored = lock.lock()) { // Retain the chunk because it is stored for later use. chunk.retain(); partChunks.add(chunk); } } @Override public void onPart(String name, String fileName, HttpFields headers) { try (AutoLock ignored = lock.lock()) { parts.add(new MultiPart.ChunksPart(name, fileName, headers, List.copyOf(partChunks))); partChunks.forEach(Content.Chunk::release); partChunks.clear(); } } @Override public void onComplete() { super.onComplete(); List copy; try (AutoLock ignored = lock.lock()) { copy = List.copyOf(parts); Parser.this.parts = new Parts(getBoundary(), copy); } } @Override public void onFailure(Throwable failure) { fail(failure); } private void fail(Throwable cause) { List partsToFail; try (AutoLock ignored = lock.lock()) { if (failure != null) return; failure = cause; partsToFail = List.copyOf(parts); parts.clear(); partChunks.forEach(Content.Chunk::release); partChunks.clear(); } partsToFail.forEach(p -> p.fail(cause)); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy