Skip to content

Commit 865b07b

Browse files
committed
Expose additional transport details to TransportListener
For HTTP Transporters this is: - HTTP Version - SSL Protocol (only HTTPS) - SSL Cipher Suite (only HTTPS) - Compression Algorithm (if used) - Transport Size (if it differs from data size) This closes #1761
1 parent ec52693 commit 865b07b

7 files changed

Lines changed: 211 additions & 13 deletions

File tree

maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/AbstractTransporter.java

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.io.InputStream;
2323
import java.io.OutputStream;
2424
import java.nio.ByteBuffer;
25+
import java.util.Collections;
26+
import java.util.Map;
2527
import java.util.concurrent.atomic.AtomicBoolean;
2628

2729
import org.eclipse.aether.transfer.TransferCancelledException;
@@ -87,11 +89,42 @@ public void get(GetTask task) throws Exception {
8789
* download starts at the first byte of the resource.
8890
* @throws IOException If the transfer encountered an I/O error.
8991
* @throws TransferCancelledException If the transfer was cancelled.
92+
* @deprecated Use {@link #utilGet(GetTask, InputStream, boolean, long, boolean, Map)} instead.
9093
*/
94+
@Deprecated
9195
protected void utilGet(GetTask task, InputStream is, boolean close, long length, boolean resume)
9296
throws IOException, TransferCancelledException {
97+
utilGet(task, is, close, length, resume, Collections.emptyMap());
98+
}
99+
100+
/**
101+
* Performs stream-based I/O for the specified download task and notifies the configured transport listener.
102+
* Subclasses might want to invoke this utility method from within their {@link #implGet(GetTask)} to avoid
103+
* boilerplate I/O code.
104+
*
105+
* @param task The download to perform, must not be {@code null}.
106+
* @param is The input stream to download the data from, must not be {@code null}.
107+
* @param close {@code true} if the supplied input stream should be automatically closed, {@code false} to leave the
108+
* stream open.
109+
* @param length The size in bytes of the downloaded resource or {@code -1} if unknown, not to be confused with the
110+
* length of the supplied input stream which might be smaller if the download is resumed.
111+
* @param resume {@code true} if the download resumes from {@link GetTask#getResumeOffset()}, {@code false} if the
112+
* download starts at the first byte of the resource.
113+
* @param transportProperties the transport properties connected with this download. May be empty.
114+
* @throws IOException If the transfer encountered an I/O error.
115+
* @throws TransferCancelledException If the transfer was cancelled.
116+
* @since NEXT
117+
*/
118+
protected void utilGet(
119+
GetTask task,
120+
InputStream is,
121+
boolean close,
122+
long length,
123+
boolean resume,
124+
Map<TransportListener.TransportPropertyKey, Object> transportProperties)
125+
throws IOException, TransferCancelledException {
93126
try (OutputStream os = task.newOutputStream(resume)) {
94-
task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length);
127+
task.getListener().transportStarted(resume ? task.getResumeOffset() : 0L, length, transportProperties);
95128
copy(os, is, task.getListener());
96129
} finally {
97130
if (close) {
@@ -126,11 +159,36 @@ public void put(PutTask task) throws Exception {
126159
* the stream open.
127160
* @throws IOException If the transfer encountered an I/O error.
128161
* @throws TransferCancelledException If the transfer was cancelled.
162+
* @deprecated Use {@link #utilPut(PutTask, OutputStream, boolean, Map)} instead.
129163
*/
164+
@Deprecated
130165
protected void utilPut(PutTask task, OutputStream os, boolean close)
131166
throws IOException, TransferCancelledException {
167+
utilPut(task, os, close, Collections.emptyMap());
168+
}
169+
170+
/**
171+
* Performs stream-based I/O for the specified upload task and notifies the configured transport listener.
172+
* Subclasses might want to invoke this utility method from within their {@link #implPut(PutTask)} to avoid
173+
* boilerplate I/O code.
174+
*
175+
* @param task The upload to perform, must not be {@code null}.
176+
* @param os The output stream to upload the data to, must not be {@code null}.
177+
* @param close {@code true} if the supplied output stream should be automatically closed, {@code false} to leave
178+
* the stream open.
179+
* @param transportProperties the transport properties connected with this upload. May be empty.
180+
* @throws IOException If the transfer encountered an I/O error.
181+
* @throws TransferCancelledException If the transfer was cancelled.
182+
* @since NEXT
183+
*/
184+
protected void utilPut(
185+
PutTask task,
186+
OutputStream os,
187+
boolean close,
188+
Map<TransportListener.TransportPropertyKey, Object> transportProperties)
189+
throws IOException, TransferCancelledException {
132190
try (InputStream is = task.newInputStream()) {
133-
task.getListener().transportStarted(0, task.getDataLength());
191+
task.getListener().transportStarted(0, task.getDataLength(), transportProperties);
134192
copy(os, is, task.getListener());
135193
} finally {
136194
if (close) {

maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/TransportListener.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.eclipse.aether.spi.connector.transport;
2020

2121
import java.nio.ByteBuffer;
22+
import java.util.Map;
2223

2324
import org.eclipse.aether.transfer.TransferCancelledException;
2425

@@ -34,19 +35,39 @@
3435
*/
3536
public abstract class TransportListener {
3637

38+
public interface TransportPropertyKey {}
39+
3740
/**
3841
* Enables subclassing.
3942
*/
4043
protected TransportListener() {}
4144

45+
/**
46+
* Notifies the listener about the start of the data transfer. This event may arise more than once if the transfer
47+
* needs to be restarted (e.g. after an authentication failure).
48+
*
49+
* @param dataOffset The byte offset in the resource at which the transfer starts, must not be negative.
50+
* @param dataLength The total number of bytes in the resource or {@code -1} if the length is unknown.
51+
* @param transportProperties The transport properties associated with this transfer, may be empty. The keys are transporter specific and the value types are key specific.
52+
* @throws TransferCancelledException If the transfer should be aborted.
53+
* @since NEXT
54+
*/
55+
public void transportStarted(
56+
long dataOffset, long dataLength, Map<TransportPropertyKey, Object> transportProperties)
57+
throws TransferCancelledException {
58+
transportStarted(dataOffset, dataLength);
59+
}
60+
4261
/**
4362
* Notifies the listener about the start of the data transfer. This event may arise more than once if the transfer
4463
* needs to be restarted (e.g. after an authentication failure).
4564
*
4665
* @param dataOffset The byte offset in the resource at which the transfer starts, must not be negative.
4766
* @param dataLength The total number of bytes in the resource or {@code -1} if the length is unknown.
4867
* @throws TransferCancelledException If the transfer should be aborted.
68+
* @deprecated use {@link #transportStarted(long, long, Map)} instead
4969
*/
70+
@Deprecated
5071
public void transportStarted(long dataOffset, long dataLength) throws TransferCancelledException {}
5172

5273
/**

maven-resolver-spi/src/main/java/org/eclipse/aether/spi/connector/transport/http/HttpTransporter.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,42 @@
1818
*/
1919
package org.eclipse.aether.spi.connector.transport.http;
2020

21+
import org.eclipse.aether.spi.connector.transport.TransportListener;
2122
import org.eclipse.aether.spi.connector.transport.Transporter;
2223

2324
/**
2425
* A transporter using HTTP protocol.
2526
*
2627
* @since 2.0.0
2728
*/
28-
public interface HttpTransporter extends Transporter {}
29+
public interface HttpTransporter extends Transporter {
30+
31+
/**
32+
* Transport property keys specific to HTTP transporters.
33+
* @see org.eclipse.aether.spi.connector.transport.TransporterListener#transportStarted(long, long, java.util.Map)
34+
*/
35+
enum HttpTransportPropertyKey implements TransportListener.TransportPropertyKey {
36+
/**
37+
* Transport property key for HTTP version. Value is a String representing the HTTP version used (e.g., "HTTP/1.1", "HTTP/2").
38+
*/
39+
HTTP_VERSION,
40+
/**
41+
* Transport property key for SSL protocol. Value is a String representing the SSL protocol used (e.g., "TLSv1.2", "TLSv1.3").
42+
*/
43+
SSL_PROTOCOL,
44+
/**
45+
* 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").
46+
*/
47+
SSL_CIPHER_SUITE,
48+
/**
49+
* Transport property key for content coding (usually compression). Value is a String representing the compression algorithm used (e.g., "gzip", "br", or "zstd")
50+
* @see <a href="https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding">Content Coding Values</a>
51+
*/
52+
CONTENT_CODING,
53+
/**
54+
* Transport property key for number of bytes transferred. Value is a Long representing the total number of bytes transferred during the transport operation.
55+
* This may be less than the content length in case of compression.
56+
*/
57+
NUM_BYTES_TRANSFERRED;
58+
}
59+
}

maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/RecordingTransportListener.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.io.ByteArrayOutputStream;
2222
import java.nio.Buffer;
2323
import java.nio.ByteBuffer;
24+
import java.util.Map;
2425

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

4243
private boolean cancelProgress;
4344

45+
private Map<TransportPropertyKey, Object> transportProperties;
46+
4447
@Override
45-
public void transportStarted(long dataOffset, long dataLength) throws TransferCancelledException {
48+
public void transportStarted(
49+
long dataOffset, long dataLength, Map<TransportPropertyKey, Object> transportProperties)
50+
throws TransferCancelledException {
4651
startedCount++;
4752
progressedCount = 0;
4853
this.dataLength = dataLength;
@@ -51,6 +56,7 @@ public void transportStarted(long dataOffset, long dataLength) throws TransferCa
5156
if (cancelStart) {
5257
throw new TransferCancelledException();
5358
}
59+
this.transportProperties = transportProperties;
5460
}
5561

5662
@Override
@@ -104,4 +110,8 @@ public void cancelStart() {
104110
public void cancelProgress() {
105111
this.cancelProgress = true;
106112
}
113+
114+
public Map<TransportPropertyKey, Object> getTransportProperties() {
115+
return transportProperties;
116+
}
107117
}

maven-resolver-transport-apache/src/main/java/org/eclipse/aether/transport/apache/ApacheTransporter.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.nio.file.StandardCopyOption;
3434
import java.util.Collections;
3535
import java.util.Date;
36+
import java.util.HashMap;
3637
import java.util.HashSet;
3738
import java.util.List;
3839
import java.util.Map;
@@ -92,6 +93,7 @@
9293
import org.eclipse.aether.spi.connector.transport.GetTask;
9394
import org.eclipse.aether.spi.connector.transport.PeekTask;
9495
import org.eclipse.aether.spi.connector.transport.PutTask;
96+
import org.eclipse.aether.spi.connector.transport.TransportListener;
9597
import org.eclipse.aether.spi.connector.transport.TransportTask;
9698
import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor;
9799
import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
@@ -674,11 +676,13 @@ public void handle(CloseableHttpResponse response) throws IOException, TransferC
674676
}
675677
}
676678

679+
Map<TransportListener.TransportPropertyKey, Object> transportProperties =
680+
createTransportProperties(response);
677681
final boolean resume = offset > 0L;
678682
final Path dataFile = task.getDataPath();
679683
if (dataFile == null) {
680684
try (InputStream is = entity.getContent()) {
681-
utilGet(task, is, true, length, resume);
685+
utilGet(task, is, true, length, resume, transportProperties);
682686
extractChecksums(response);
683687
}
684688
} else {
@@ -690,7 +694,7 @@ public void handle(CloseableHttpResponse response) throws IOException, TransferC
690694
}
691695
}
692696
try (InputStream is = entity.getContent()) {
693-
utilGet(task, is, true, length, resume);
697+
utilGet(task, is, true, length, resume, transportProperties);
694698
}
695699
tempFile.move();
696700
} finally {
@@ -718,6 +722,16 @@ private void extractChecksums(CloseableHttpResponse response) {
718722
}
719723
}
720724

725+
private static Map<TransportListener.TransportPropertyKey, Object> createTransportProperties(
726+
CloseableHttpResponse response) {
727+
Map<TransportListener.TransportPropertyKey, Object> properties = new HashMap<>();
728+
properties.put(
729+
HttpTransporter.HttpTransportPropertyKey.HTTP_VERSION,
730+
response.getProtocolVersion().toString());
731+
// https://stackoverflow.com/questions/13273305/apache-httpclient-get-server-certificate
732+
return properties;
733+
}
734+
721735
private static Function<String, String> headerGetter(CloseableHttpResponse closeableHttpResponse) {
722736
return s -> {
723737
Header header = closeableHttpResponse.getFirstHeader(s);

maven-resolver-transport-jdk-parent/maven-resolver-transport-jdk11/src/main/java/org/eclipse/aether/transport/jdk/JdkTransporter.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@
7777
import org.eclipse.aether.spi.connector.transport.GetTask;
7878
import org.eclipse.aether.spi.connector.transport.PeekTask;
7979
import org.eclipse.aether.spi.connector.transport.PutTask;
80+
import org.eclipse.aether.spi.connector.transport.TransportListener;
81+
import org.eclipse.aether.spi.connector.transport.TransportListener.TransportPropertyKey;
8082
import org.eclipse.aether.spi.connector.transport.TransportTask;
8183
import org.eclipse.aether.spi.connector.transport.http.ChecksumExtractor;
8284
import org.eclipse.aether.spi.connector.transport.http.HttpTransporter;
@@ -372,9 +374,11 @@ protected void implGet(GetTask task) throws Exception {
372374

373375
final boolean downloadResumed = offset > 0L;
374376
final Path dataFile = task.getDataPath();
377+
final Map<TransportListener.TransportPropertyKey, Object> transportProperties =
378+
createTransportProperties(response);
375379
if (dataFile == null) {
376380
try (InputStream is = response.body()) {
377-
utilGet(task, is, true, length, downloadResumed);
381+
utilGet(task, is, true, length, downloadResumed, transportProperties);
378382
}
379383
} else {
380384
try (PathProcessor.CollocatedTempFile tempFile = pathProcessor.newTempFile(dataFile)) {
@@ -385,7 +389,7 @@ protected void implGet(GetTask task) throws Exception {
385389
}
386390
}
387391
try (InputStream is = response.body()) {
388-
utilGet(task, is, true, length, downloadResumed);
392+
utilGet(task, is, true, length, downloadResumed, transportProperties);
389393
}
390394
tempFile.move();
391395
} finally {
@@ -417,6 +421,21 @@ protected void implGet(GetTask task) throws Exception {
417421
}
418422
}
419423

424+
private Map<TransportPropertyKey, Object> createTransportProperties(HttpResponse<?> response) {
425+
Map<TransportPropertyKey, Object> props = new HashMap<>();
426+
props.put(HttpTransportPropertyKey.HTTP_VERSION, response.version().toString());
427+
response.sslSession().ifPresent(ssl -> {
428+
props.put(
429+
HttpTransportPropertyKey.SSL_PROTOCOL,
430+
response.sslSession().get().getProtocol());
431+
props.put(
432+
HttpTransportPropertyKey.SSL_CIPHER_SUITE,
433+
response.sslSession().get().getCipherSuite());
434+
});
435+
// TODO: add compression algorithm if any (https://github.com/mizosoft/methanol/issues/182)
436+
return props;
437+
}
438+
420439
private static Function<String, String> headerGetter(HttpResponse<?> response) {
421440
return s -> response.headers().firstValue(s).orElse(null);
422441
}
@@ -438,7 +457,8 @@ protected void implPut(PutTask task) throws Exception {
438457
}
439458
headers.forEach(request::setHeader);
440459
try (PathProcessor.TempFile tempFile = pathProcessor.newTempFile()) {
441-
utilPut(task, Files.newOutputStream(tempFile.getPath()), true);
460+
// TODO: add properties
461+
utilPut(task, Files.newOutputStream(tempFile.getPath()), true, Collections.emptyMap());
442462
request.PUT(HttpRequest.BodyPublishers.ofFile(tempFile.getPath()));
443463

444464
prepare(request);

0 commit comments

Comments
 (0)