org.eclipse.jetty.client.util.MultiPartContentProvider Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// 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.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.client.AsyncContentProvider;
import org.eclipse.jetty.client.Synchronizable;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* A {@link ContentProvider} for form uploads with the {@code "multipart/form-data"}
* content type.
* Example usage:
*
* MultiPartContentProvider multiPart = new MultiPartContentProvider();
* multiPart.addFieldPart("field", new StringContentProvider("foo"), null);
* multiPart.addFilePart("icon", "img.png", new PathContentProvider(Paths.get("/tmp/img.png")), null);
* multiPart.close();
* ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
* .method(HttpMethod.POST)
* .content(multiPart)
* .send();
*
* The above example would be the equivalent of submitting this form:
*
* <form method="POST" enctype="multipart/form-data" accept-charset="UTF-8">
* <input type="text" name="field" value="foo" />
* <input type="file" name="icon" />
* </form>
*
*
* @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported.
*/
@Deprecated(since = "2021-05-27")
public class MultiPartContentProvider extends AbstractTypedContentProvider implements AsyncContentProvider, Closeable {
private static final Logger LOG = Log.getLogger(MultiPartContentProvider.class);
private static final byte[] COLON_SPACE_BYTES = new byte[] { ':', ' ' };
private static final byte[] CR_LF_BYTES = new byte[] { '\r', '\n' };
private final List parts = new ArrayList<>();
private final ByteBuffer firstBoundary;
private final ByteBuffer middleBoundary;
private final ByteBuffer onlyBoundary;
private final ByteBuffer lastBoundary;
private final AtomicBoolean closed = new AtomicBoolean();
private Listener listener;
private long length = -1;
public MultiPartContentProvider() {
this(makeBoundary());
}
public MultiPartContentProvider(String boundary) {
super("multipart/form-data; boundary=" + boundary);
String firstBoundaryLine = "--" + boundary + "\r\n";
this.firstBoundary = ByteBuffer.wrap(firstBoundaryLine.getBytes(StandardCharsets.US_ASCII));
String middleBoundaryLine = "\r\n" + firstBoundaryLine;
this.middleBoundary = ByteBuffer.wrap(middleBoundaryLine.getBytes(StandardCharsets.US_ASCII));
String onlyBoundaryLine = "--" + boundary + "--\r\n";
this.onlyBoundary = ByteBuffer.wrap(onlyBoundaryLine.getBytes(StandardCharsets.US_ASCII));
String lastBoundaryLine = "\r\n" + onlyBoundaryLine;
this.lastBoundary = ByteBuffer.wrap(lastBoundaryLine.getBytes(StandardCharsets.US_ASCII));
}
private static String makeBoundary() {
StringBuilder builder = new StringBuilder("JettyHttpClientBoundary");
builder.append(Long.toString(System.identityHashCode(builder), 36));
builder.append(Long.toString(System.identityHashCode(Thread.currentThread()), 36));
builder.append(Long.toString(System.nanoTime(), 36));
return builder.toString();
}
/**
* Adds a field part with the given {@code name} as field name, and the given
* {@code content} as part content.
* The {@code Content-Type} of this part will be obtained from:
*
* - the {@code Content-Type} header in the {@code fields} parameter; otherwise
* - the {@link org.eclipse.jetty.client.api.ContentProvider.Typed#getContentType()} method if the {@code content} parameter
* implements {@link org.eclipse.jetty.client.api.ContentProvider.Typed}; otherwise
* - "text/plain"
*
*
* @param name the part name
* @param content the part content
* @param fields the headers associated with this part
*/
public void addFieldPart(String name, ContentProvider content, HttpFields fields) {
addPart(new Part(name, null, "text/plain", content, fields));
}
/**
* Adds a file part with the given {@code name} as field name, the given
* {@code fileName} as file name, and the given {@code content} as part content.
* The {@code Content-Type} of this part will be obtained from:
*
* - the {@code Content-Type} header in the {@code fields} parameter; otherwise
* - the {@link org.eclipse.jetty.client.api.ContentProvider.Typed#getContentType()} method if the {@code content} parameter
* implements {@link org.eclipse.jetty.client.api.ContentProvider.Typed}; otherwise
* - "application/octet-stream"
*
*
* @param name the part name
* @param fileName the file name associated to this part
* @param content the part content
* @param fields the headers associated with this part
*/
public void addFilePart(String name, String fileName, ContentProvider content, HttpFields fields) {
addPart(new Part(name, fileName, "application/octet-stream", content, fields));
}
private void addPart(Part part) {
parts.add(part);
if (LOG.isDebugEnabled())
LOG.debug("Added {}", part);
}
@Override
public void setListener(Listener listener) {
this.listener = listener;
if (closed.get())
this.length = calculateLength();
}
private long calculateLength() {
// Compute the length, if possible.
if (parts.isEmpty()) {
return onlyBoundary.remaining();
} else {
long result = 0;
for (int i = 0; i < parts.size(); ++i) {
result += (i == 0) ? firstBoundary.remaining() : middleBoundary.remaining();
Part part = parts.get(i);
long partLength = part.length;
result += partLength;
if (partLength < 0) {
result = -1;
break;
}
}
if (result > 0)
result += lastBoundary.remaining();
return result;
}
}
@Override
public long getLength() {
return length;
}
@Override
public Iterator iterator() {
return new MultiPartIterator();
}
@Override
public void close() {
closed.compareAndSet(false, true);
}
// @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported.
@Deprecated(since = "2021-05-27")
private static class Part {
private final String name;
private final String fileName;
private final String contentType;
private final ContentProvider content;
private final HttpFields fields;
private final ByteBuffer headers;
private final long length;
private Part(String name, String fileName, String contentType, ContentProvider content, HttpFields fields) {
this.name = name;
this.fileName = fileName;
this.contentType = contentType;
this.content = content;
this.fields = fields;
this.headers = headers();
this.length = content.getLength() < 0 ? -1 : headers.remaining() + content.getLength();
}
private ByteBuffer headers() {
try {
// Compute the Content-Disposition.
String contentDisposition = "Content-Disposition: form-data; name=\"" + name + "\"";
if (fileName != null)
contentDisposition += "; filename=\"" + fileName + "\"";
contentDisposition += "\r\n";
// Compute the Content-Type.
String contentType = fields == null ? null : fields.get(HttpHeader.CONTENT_TYPE);
if (contentType == null) {
if (content instanceof Typed)
contentType = ((Typed) content).getContentType();
else
contentType = this.contentType;
}
contentType = "Content-Type: " + contentType + "\r\n";
if (fields == null || fields.size() == 0) {
String headers = contentDisposition;
headers += contentType;
headers += "\r\n";
return ByteBuffer.wrap(headers.getBytes(StandardCharsets.UTF_8));
}
ByteArrayOutputStream buffer = new ByteArrayOutputStream((fields.size() + 1) * contentDisposition.length());
buffer.write(contentDisposition.getBytes(StandardCharsets.UTF_8));
buffer.write(contentType.getBytes(StandardCharsets.UTF_8));
for (HttpField field : fields) {
if (HttpHeader.CONTENT_TYPE.equals(field.getHeader()))
continue;
buffer.write(field.getName().getBytes(StandardCharsets.US_ASCII));
buffer.write(COLON_SPACE_BYTES);
String value = field.getValue();
if (value != null)
buffer.write(value.getBytes(StandardCharsets.UTF_8));
buffer.write(CR_LF_BYTES);
}
buffer.write(CR_LF_BYTES);
return ByteBuffer.wrap(buffer.toByteArray());
} catch (IOException x) {
throw new RuntimeIOException(x);
}
}
@Override
public String toString() {
return String.format("%s@%x[name=%s,fileName=%s,length=%d,headers=%s]", getClass().getSimpleName(), hashCode(), name, fileName, content.getLength(), fields);
}
}
// @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported.
@Deprecated(since = "2021-05-27")
private class MultiPartIterator implements Iterator, Synchronizable, Callback, Closeable {
private Iterator iterator;
private int index;
private State state = State.FIRST_BOUNDARY;
@Override
public boolean hasNext() {
return state != State.COMPLETE;
}
@Override
public ByteBuffer next() {
while (true) {
switch(state) {
case FIRST_BOUNDARY:
{
if (parts.isEmpty()) {
state = State.COMPLETE;
return onlyBoundary.slice();
} else {
state = State.HEADERS;
return firstBoundary.slice();
}
}
case HEADERS:
{
Part part = parts.get(index);
ContentProvider content = part.content;
if (content instanceof AsyncContentProvider)
((AsyncContentProvider) content).setListener(listener);
iterator = content.iterator();
state = State.CONTENT;
return part.headers.slice();
}
case CONTENT:
{
if (iterator.hasNext())
return iterator.next();
++index;
if (index < parts.size()) {
state = State.MIDDLE_BOUNDARY;
if (iterator instanceof Closeable)
IO.close((Closeable) iterator);
} else {
state = State.LAST_BOUNDARY;
}
break;
}
case MIDDLE_BOUNDARY:
{
state = State.HEADERS;
return middleBoundary.slice();
}
case LAST_BOUNDARY:
{
state = State.COMPLETE;
return lastBoundary.slice();
}
case COMPLETE:
{
throw new NoSuchElementException();
}
}
}
}
@Override
public Object getLock() {
if (iterator instanceof Synchronizable)
return ((Synchronizable) iterator).getLock();
return this;
}
@Override
public void succeeded() {
if (state == State.CONTENT && iterator instanceof Callback)
((Callback) iterator).succeeded();
}
@Override
public void failed(Throwable x) {
if (state == State.CONTENT && iterator instanceof Callback)
((Callback) iterator).failed(x);
}
@Override
public void close() throws IOException {
if (iterator instanceof Closeable)
((Closeable) iterator).close();
}
}
private enum State {
FIRST_BOUNDARY,
HEADERS,
CONTENT,
MIDDLE_BOUNDARY,
LAST_BOUNDARY,
COMPLETE
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy