Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import org.eclipse.aether.transfer.TransferCancelledException;
Expand Down Expand Up @@ -87,11 +89,42 @@ public void get(GetTask task) throws Exception {
* download starts at the first byte of the resource.
* @throws IOException If the transfer encountered an I/O error.
* @throws TransferCancelledException If the transfer was cancelled.
* @deprecated Use {@link #utilGet(GetTask, InputStream, boolean, long, boolean, Map)} instead.
*/
@Deprecated
protected void utilGet(GetTask task, InputStream is, boolean close, long length, boolean resume)
throws IOException, TransferCancelledException {
utilGet(task, is, close, length, resume, Collections.emptyMap());
}

/**
* Performs stream-based I/O for the specified download task and notifies the configured transport listener.
* Subclasses might want to invoke this utility method from within their {@link #implGet(GetTask)} to avoid
* boilerplate I/O code.
*
* @param task The download to perform, must not be {@code null}.
* @param is The input stream to download the data from, must not be {@code null}.
* @param close {@code true} if the supplied input stream should be automatically closed, {@code false} to leave the
* stream open.
* @param length The size in bytes of the downloaded resource or {@code -1} if unknown, not to be confused with the
* length of the supplied input stream which might be smaller if the download is resumed.
* @param resume {@code true} if the download resumes from {@link GetTask#getResumeOffset()}, {@code false} if the
* download starts at the first byte of the resource.
* @param transportProperties the transport properties connected with this download. May be empty.
* @throws IOException If the transfer encountered an I/O error.
* @throws TransferCancelledException If the transfer was cancelled.
* @since NEXT
*/
protected void utilGet(
GetTask task,
InputStream is,
boolean close,
long length,
boolean resume,
Map<TransportListener.TransportPropertyKey, Object> transportProperties)
throws IOException, TransferCancelledException {
try (OutputStream os = task.newOutputStream(resume)) {
task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length);
task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length, transportProperties);
copy(os, is, task.getListener());
} finally {
if (close) {
Expand Down Expand Up @@ -126,11 +159,36 @@ public void put(PutTask task) throws Exception {
* the stream open.
* @throws IOException If the transfer encountered an I/O error.
* @throws TransferCancelledException If the transfer was cancelled.
* @deprecated Use {@link #utilPut(PutTask, OutputStream, boolean, Map)} instead.
*/
@Deprecated
protected void utilPut(PutTask task, OutputStream os, boolean close)
throws IOException, TransferCancelledException {
utilPut(task, os, close, Collections.emptyMap());
}

/**
* Performs stream-based I/O for the specified upload task and notifies the configured transport listener.
* Subclasses might want to invoke this utility method from within their {@link #implPut(PutTask)} to avoid
* boilerplate I/O code.
*
* @param task The upload to perform, must not be {@code null}.
* @param os The output stream to upload the data to, must not be {@code null}.
* @param close {@code true} if the supplied output stream should be automatically closed, {@code false} to leave
* the stream open.
* @param transportProperties the transport properties connected with this upload. May be empty.
* @throws IOException If the transfer encountered an I/O error.
* @throws TransferCancelledException If the transfer was cancelled.
* @since NEXT
*/
protected void utilPut(
PutTask task,
OutputStream os,
boolean close,
Map<TransportListener.TransportPropertyKey, Object> transportProperties)
throws IOException, TransferCancelledException {
try (InputStream is = task.newInputStream()) {
task.getListener().transportStarted(0, task.getDataLength());
task.getListener().transportStarted(0, task.getDataLength(), transportProperties);
copy(os, is, task.getListener());
} finally {
if (close) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.eclipse.aether.spi.connector.transport;

import java.nio.ByteBuffer;
import java.util.Map;

import org.eclipse.aether.transfer.TransferCancelledException;

Expand All @@ -34,19 +35,39 @@
*/
public abstract class TransportListener {

public interface TransportPropertyKey {}

/**
* Enables subclassing.
*/
protected TransportListener() {}

/**
* Notifies the listener about the start of the data transfer. This event may arise more than once if the transfer
* needs to be restarted (e.g. after an authentication failure).
*
* @param dataOffset The byte offset in the resource at which the transfer starts, must not be negative.
* @param dataLength The total number of bytes in the resource or {@code -1} if the length is unknown.
* @param transportProperties The transport properties associated with this transfer, may be empty. The keys are transporter specific and the value types are key specific.
* @throws TransferCancelledException If the transfer should be aborted.
* @since NEXT
*/
public void transportStarted(
long dataOffset, long dataLength, Map<TransportPropertyKey, Object> transportProperties)
throws TransferCancelledException {
transportStarted(dataOffset, dataLength);
}

/**
* Notifies the listener about the start of the data transfer. This event may arise more than once if the transfer
* needs to be restarted (e.g. after an authentication failure).
*
* @param dataOffset The byte offset in the resource at which the transfer starts, must not be negative.
* @param dataLength The total number of bytes in the resource or {@code -1} if the length is unknown.
* @throws TransferCancelledException If the transfer should be aborted.
* @deprecated use {@link #transportStarted(long, long, Map)} instead
*/
@Deprecated
public void transportStarted(long dataOffset, long dataLength) throws TransferCancelledException {}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,42 @@
*/
package org.eclipse.aether.spi.connector.transport.http;

import org.eclipse.aether.spi.connector.transport.TransportListener;
import org.eclipse.aether.spi.connector.transport.Transporter;

/**
* A transporter using HTTP protocol.
*
* @since 2.0.0
*/
public interface HttpTransporter extends Transporter {}
public interface HttpTransporter extends Transporter {

/**
* Transport property keys specific to HTTP transporters.
* @see org.eclipse.aether.spi.connector.transport.TransporterListener#transportStarted(long, long, java.util.Map)
*/
enum HttpTransportPropertyKey implements TransportListener.TransportPropertyKey {
/**
* Transport property key for HTTP version. Value is a String representing the HTTP version used (e.g., "HTTP/1.1", "HTTP/2").
*/
HTTP_VERSION,
/**
* Transport property key for SSL protocol. Value is a String representing the SSL protocol used (e.g., "TLSv1.2", "TLSv1.3").
*/
SSL_PROTOCOL,
/**
* Transport property key for SSL cipher suite. Value is a String representing the SSL cipher suite used (e.g., "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256").
*/
SSL_CIPHER_SUITE,
/**
* Transport property key for content coding (usually compression). Value is a String representing the compression algorithm used (e.g., "gzip", "br", or "zstd")
* @see <a href="https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding">Content Coding Values</a>
*/
CONTENT_CODING,
/**
* Transport property key for number of bytes transferred. Value is a Long representing the total number of bytes transferred during the transport operation.
* This may be less than the content length in case of compression.
*/
NUM_BYTES_TRANSFERRED;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.ByteArrayOutputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.Map;

import org.eclipse.aether.spi.connector.transport.TransportListener;
import org.eclipse.aether.transfer.TransferCancelledException;
Expand All @@ -41,8 +42,12 @@ public class RecordingTransportListener extends TransportListener {

private boolean cancelProgress;

private Map<TransportPropertyKey, Object> transportProperties;

@Override
public void transportStarted(long dataOffset, long dataLength) throws TransferCancelledException {
public void transportStarted(
long dataOffset, long dataLength, Map<TransportPropertyKey, Object> transportProperties)
throws TransferCancelledException {
startedCount++;
progressedCount = 0;
this.dataLength = dataLength;
Expand All @@ -51,6 +56,7 @@ public void transportStarted(long dataOffset, long dataLength) throws TransferCa
if (cancelStart) {
throw new TransferCancelledException();
}
this.transportProperties = transportProperties;
}

@Override
Expand Down Expand Up @@ -104,4 +110,8 @@ public void cancelStart() {
public void cancelProgress() {
this.cancelProgress = true;
}

public Map<TransportPropertyKey, Object> getTransportProperties() {
return transportProperties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.nio.file.StandardCopyOption;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -92,6 +93,7 @@
import org.eclipse.aether.spi.connector.transport.GetTask;
import org.eclipse.aether.spi.connector.transport.PeekTask;
import org.eclipse.aether.spi.connector.transport.PutTask;
import org.eclipse.aether.spi.connector.transport.TransportListener;
import org.eclipse.aether.spi.connector.transport.TransportTask;
import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor;
import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
Expand Down Expand Up @@ -674,11 +676,13 @@ public void handle(CloseableHttpResponse response) throws IOException, TransferC
}
}

Map<TransportListener.TransportPropertyKey, Object> transportProperties =
createTransportProperties(response);
final boolean resume = offset > 0L;
final Path dataFile = task.getDataPath();
if (dataFile == null) {
try (InputStream is = entity.getContent()) {
utilGet(task, is, true, length, resume);
utilGet(task, is, true, length, resume, transportProperties);
extractChecksums(response);
}
} else {
Expand All @@ -690,7 +694,7 @@ public void handle(CloseableHttpResponse response) throws IOException, TransferC
}
}
try (InputStream is = entity.getContent()) {
utilGet(task, is, true, length, resume);
utilGet(task, is, true, length, resume, transportProperties);
}
tempFile.move();
} finally {
Expand Down Expand Up @@ -718,6 +722,16 @@ private void extractChecksums(CloseableHttpResponse response) {
}
}

private static Map<TransportListener.TransportPropertyKey, Object> createTransportProperties(
CloseableHttpResponse response) {
Map<TransportListener.TransportPropertyKey, Object> properties = new HashMap<>();
properties.put(
HttpTransporter.HttpTransportPropertyKey.HTTP_VERSION,
response.getProtocolVersion().toString());
// https://stackoverflow.com/questions/13273305/apache-httpclient-get-server-certificate
return properties;
}

private static Function<String, String> headerGetter(CloseableHttpResponse closeableHttpResponse) {
return s -> {
Header header = closeableHttpResponse.getFirstHeader(s);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
import org.eclipse.aether.spi.connector.transport.GetTask;
import org.eclipse.aether.spi.connector.transport.PeekTask;
import org.eclipse.aether.spi.connector.transport.PutTask;
import org.eclipse.aether.spi.connector.transport.TransportListener;
import org.eclipse.aether.spi.connector.transport.TransportListener.TransportPropertyKey;
import org.eclipse.aether.spi.connector.transport.TransportTask;
import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor;
import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
Expand Down Expand Up @@ -372,9 +374,11 @@ protected void implGet(GetTask task) throws Exception {

final boolean downloadResumed = offset > 0L;
final Path dataFile = task.getDataPath();
final Map<TransportListener.TransportPropertyKey, Object> transportProperties =
createTransportProperties(response);
if (dataFile == null) {
try (InputStream is = response.body()) {
utilGet(task, is, true, length, downloadResumed);
utilGet(task, is, true, length, downloadResumed, transportProperties);
}
} else {
try (PathProcessor.CollocatedTempFile tempFile = pathProcessor.newTempFile(dataFile)) {
Expand All @@ -385,7 +389,7 @@ protected void implGet(GetTask task) throws Exception {
}
}
try (InputStream is = response.body()) {
utilGet(task, is, true, length, downloadResumed);
utilGet(task, is, true, length, downloadResumed, transportProperties);
}
tempFile.move();
} finally {
Expand Down Expand Up @@ -417,6 +421,21 @@ protected void implGet(GetTask task) throws Exception {
}
}

private Map<TransportPropertyKey, Object> createTransportProperties(HttpResponse<?> response) {
Map<TransportPropertyKey, Object> props = new HashMap<>();
props.put(HttpTransportPropertyKey.HTTP_VERSION, response.version().toString());
response.sslSession().ifPresent(ssl -> {
props.put(
HttpTransportPropertyKey.SSL_PROTOCOL,
response.sslSession().get().getProtocol());
props.put(
HttpTransportPropertyKey.SSL_CIPHER_SUITE,
response.sslSession().get().getCipherSuite());
});
// TODO: add compression algorithm if any (https://github.com/mizosoft/methanol/issues/182)
return props;
}

private static Function<String, String> headerGetter(HttpResponse<?> response) {
return s -> response.headers().firstValue(s).orElse(null);
}
Expand All @@ -438,7 +457,8 @@ protected void implPut(PutTask task) throws Exception {
}
headers.forEach(request::setHeader);
try (PathProcessor.TempFile tempFile = pathProcessor.newTempFile()) {
utilPut(task, Files.newOutputStream(tempFile.getPath()), true);
// TODO: add properties
utilPut(task, Files.newOutputStream(tempFile.getPath()), true, Collections.emptyMap());
request.PUT(HttpRequest.BodyPublishers.ofFile(tempFile.getPath()));

prepare(request);
Expand Down
Loading