This is an automated email from the ASF dual-hosted git repository.
kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
The following commit(s) were added to refs/heads/master by this push:
new bca61862a Update to Jetty 12.1 (#1748)
bca61862a is described below
commit bca61862a72d50429cf7c1d5bc9ea4c45f4b624a
Author: Konrad Windszus <[email protected]>
AuthorDate: Fri Jan 16 20:21:46 2026 +0100
Update to Jetty 12.1 (#1748)
This affects both Server used for Tests and Jetty Transport (for HTTP).
It raises Java requirements to 17 for Jetty Transport.
Support rewind on PutTasks based on InputStream.
---
maven-resolver-test-http/pom.xml | 14 +-
.../aether/internal/test/util/http/HttpServer.java | 252 ++++++++---------
maven-resolver-transport-jetty/pom.xml | 8 +-
.../transport/jetty/JettyRFC9457Reporter.java | 4 +-
.../aether/transport/jetty/JettyTransporter.java | 18 +-
.../transport/jetty/PutTaskRequestContent.java | 310 ++++++++++++++++-----
pom.xml | 4 +-
7 files changed, 391 insertions(+), 219 deletions(-)
diff --git a/maven-resolver-test-http/pom.xml b/maven-resolver-test-http/pom.xml
index af6d7ca63..9617490fa 100644
--- a/maven-resolver-test-http/pom.xml
+++ b/maven-resolver-test-http/pom.xml
@@ -32,7 +32,7 @@
<description>A collection of utility classes to ease testing of HTTP
transports.</description>
<properties>
- <javaVersion>11</javaVersion>
+ <javaVersion>17</javaVersion>
</properties>
<dependencies>
@@ -75,7 +75,16 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
- <artifactId>http2-server</artifactId>
+ <artifactId>jetty-http2-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.compression</groupId>
+ <artifactId>jetty-compression-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <version>6.1.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -84,7 +93,6 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
- <version>${slf4jVersion}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
diff --git
a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java
b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java
index b08270b43..0ca20510c 100644
---
a/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java
+++
b/maven-resolver-test-http/src/main/java/org/eclipse/aether/internal/test/util/http/HttpServer.java
@@ -18,36 +18,38 @@
*/
package org.eclipse.aether.internal.test.util.http;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collections;
-import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import com.google.gson.Gson;
+import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.aether.internal.impl.checksum.Sha1ChecksumAlgorithmFactory;
import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmHelper;
import org.eclipse.aether.spi.connector.transport.http.RFC9457.RFC9457Payload;
import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory;
+import org.eclipse.jetty.http.DateGenerator;
+import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.io.Content;
+import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Request;
@@ -56,10 +58,8 @@ import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
-import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -287,20 +287,18 @@ public class HttpServer {
return this;
}
- HandlerList handlers = new HandlerList();
- handlers.addHandler(new ConnectionClosingHandler());
- handlers.addHandler(new ServerErrorHandler());
- handlers.addHandler(new LogHandler());
- handlers.addHandler(new ProxyAuthHandler());
- handlers.addHandler(new AuthHandler());
- handlers.addHandler(new RedirectHandler());
- handlers.addHandler(new RepoHandler());
- handlers.addHandler(new RFC9457Handler());
-
server = new Server();
httpConnector = new ServerConnector(server);
server.addConnector(httpConnector);
- server.setHandler(handlers);
+ server.setHandler(new Handler.Sequence(
+ new ConnectionClosingHandler(),
+ new ServerErrorHandler(),
+ new LogHandler(),
+ new ProxyAuthHandler(),
+ new AuthHandler(),
+ new RedirectHandler(),
+ new RepoHandler(),
+ new RFC9457Handler()));
server.start();
return this;
@@ -315,152 +313,142 @@ public class HttpServer {
}
}
- private class ConnectionClosingHandler extends AbstractHandler {
+ private class ConnectionClosingHandler extends Handler.Abstract {
+
@Override
- public void handle(String target, Request req, HttpServletRequest
request, HttpServletResponse response) {
+ public boolean handle(Request request, Response response, Callback
callback) throws Exception {
if (connectionsToClose.getAndDecrement() > 0) {
- Response jettyResponse = (Response) response;
- jettyResponse.getHttpChannel().getConnection().close();
+ request.getConnectionMetaData().getConnection().close();
}
+ return false;
}
}
- private class ServerErrorHandler extends AbstractHandler {
+ private class ServerErrorHandler extends Handler.Abstract {
@Override
- public void handle(String target, Request req, HttpServletRequest
request, HttpServletResponse response)
- throws IOException {
+ public boolean handle(Request request, Response response, Callback
callback) throws IOException {
if (serverErrorsBeforeWorks.getAndDecrement() > 0) {
response.setStatus(serverErrorStatusCode);
- writeResponseBodyMessage(response, "Oops, come back later!");
+ writeResponseBodyMessage(request, response, "Oops, come back
later!");
+ callback.succeeded();
+ return true;
}
+ return false;
}
}
- private class LogHandler extends AbstractHandler {
+ private class LogHandler extends Handler.Abstract {
@Override
- public void handle(String target, Request req, HttpServletRequest
request, HttpServletResponse response) {
+ public boolean handle(Request req, Response response, Callback
callback) {
LOGGER.info(
"{} {}{}",
req.getMethod(),
- req.getRequestURL(),
- req.getQueryString() != null ? "?" + req.getQueryString()
: "");
+ req.getHttpURI().getDecodedPath(),
+ req.getHttpURI().getQuery() != null ? "?" +
req.getHttpURI().getQuery() : "");
Map<String, String> headers = new
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- for (Enumeration<String> en = req.getHeaderNames();
en.hasMoreElements(); ) {
- String name = en.nextElement();
- StringBuilder buffer = new StringBuilder(128);
- for (Enumeration<String> ien = req.getHeaders(name);
ien.hasMoreElements(); ) {
- if (buffer.length() > 0) {
- buffer.append(", ");
- }
- buffer.append(ien.nextElement());
- }
- headers.put(name, buffer.toString());
+ for (HttpField header : req.getHeaders()) {
+ headers.put(header.getName(),
header.getValueList().stream().collect(Collectors.joining(", ")));
}
- logEntries.add(new LogEntry(req.getMethod(), req.getOriginalURI(),
Collections.unmodifiableMap(headers)));
+ logEntries.add(new LogEntry(
+ req.getMethod(), req.getHttpURI().getPathQuery(),
Collections.unmodifiableMap(headers)));
+ return false;
}
}
private static final Pattern SIMPLE_RANGE =
Pattern.compile("bytes=([0-9])+-");
- private class RepoHandler extends AbstractHandler {
+ private class RepoHandler extends Handler.Abstract {
@Override
- public void handle(String target, Request req, HttpServletRequest
request, HttpServletResponse response)
- throws IOException {
- String path = req.getPathInfo().substring(1);
+ public boolean handle(Request req, Response response, Callback
callback) throws Exception {
+ String path = req.getHttpURI().getDecodedPath().substring(1);
if (!path.startsWith("repo/")) {
- return;
+ return false;
}
- req.setHandled(true);
- if (ExpectContinue.FAIL.equals(expectContinue) &&
request.getHeader(HttpHeader.EXPECT.asString()) != null) {
+ if (ExpectContinue.FAIL.equals(expectContinue) &&
req.getHeaders().get(HttpHeader.EXPECT) != null) {
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
- writeResponseBodyMessage(response, "Expectation was set to
fail");
- return;
+ writeResponseBodyMessage(req, response, "Expectation was set
to fail");
+ callback.succeeded();
+ return true;
}
File file = new File(repoDir, path.substring(5));
if (HttpMethod.GET.is(req.getMethod()) ||
HttpMethod.HEAD.is(req.getMethod())) {
- if (!file.isFile() || path.endsWith(URIUtil.SLASH)) {
+ if (!file.isFile() || path.endsWith("/")) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
- writeResponseBodyMessage(response, "Not found");
- return;
+ writeResponseBodyMessage(req, response, "Not found");
+ callback.succeeded();
+ return true;
}
- long ifUnmodifiedSince =
request.getDateHeader(HttpHeader.IF_UNMODIFIED_SINCE.asString());
+ long ifUnmodifiedSince =
req.getHeaders().getDateField(HttpHeader.IF_UNMODIFIED_SINCE);
if (ifUnmodifiedSince != -1L && file.lastModified() >
ifUnmodifiedSince) {
response.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
- writeResponseBodyMessage(response, "Precondition failed");
- return;
+ writeResponseBodyMessage(req, response, "Precondition
failed");
+ callback.succeeded();
+ return true;
}
long offset = 0L;
- String range = request.getHeader(HttpHeader.RANGE.asString());
+ String range = req.getHeaders().get(HttpHeader.RANGE);
if (range != null && rangeSupport) {
Matcher m = SIMPLE_RANGE.matcher(range);
if (m.matches()) {
offset = Long.parseLong(m.group(1));
if (offset >= file.length()) {
response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
- writeResponseBodyMessage(response, "Range not
satisfiable");
- return;
+ writeResponseBodyMessage(req, response, "Range not
satisfiable");
+ callback.succeeded();
+ return true;
}
}
- String encoding =
request.getHeader(HttpHeader.ACCEPT_ENCODING.asString());
+ String encoding =
req.getHeaders().get(HttpHeader.ACCEPT_ENCODING);
if ((encoding != null && !"identity".equals(encoding)) ||
ifUnmodifiedSince == -1L) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
- return;
+ callback.succeeded();
+ return true;
}
}
response.setStatus((offset > 0L) ?
HttpServletResponse.SC_PARTIAL_CONTENT : HttpServletResponse.SC_OK);
- response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),
file.lastModified());
- response.setHeader(HttpHeader.CONTENT_LENGTH.asString(),
Long.toString(file.length() - offset));
+ response.getHeaders().add(HttpHeader.LAST_MODIFIED,
DateGenerator.formatDate(file.lastModified()));
+ response.getHeaders().add(HttpHeader.CONTENT_LENGTH,
Long.toString(file.length() - offset));
if (offset > 0L) {
- response.setHeader(
- HttpHeader.CONTENT_RANGE.asString(),
- "bytes " + offset + "-" + (file.length() - 1L) +
"/" + file.length());
+ response.getHeaders()
+ .add(
+ HttpHeader.CONTENT_RANGE,
+ "bytes " + offset + "-" + (file.length() -
1L) + "/" + file.length());
}
if (checksumHeader != null) {
Map<String, String> checksums =
ChecksumAlgorithmHelper.calculate(
file, Collections.singletonList(new
Sha1ChecksumAlgorithmFactory()));
if (checksumHeader == ChecksumHeader.NEXUS) {
- response.setHeader(HttpHeader.ETAG.asString(),
"{SHA1{" + checksums.get("SHA-1") + "}}");
+ response.getHeaders().add(HttpHeader.ETAG.asString(),
"{SHA1{" + checksums.get("SHA-1") + "}}");
} else if (checksumHeader == ChecksumHeader.XCHECKSUM) {
- response.setHeader("x-checksum-sha1",
checksums.get(Sha1ChecksumAlgorithmFactory.NAME));
+ response.getHeaders().add("x-checksum-sha1",
checksums.get(Sha1ChecksumAlgorithmFactory.NAME));
}
}
if (HttpMethod.HEAD.is(req.getMethod())) {
- return;
+ callback.succeeded();
+ return true;
}
- try (FileInputStream is = new FileInputStream(file)) {
+ try (FileInputStream is = new FileInputStream(file);
+ OutputStream os = Response.asBufferedOutputStream(req,
response)) {
if (offset > 0L) {
long skipped = is.skip(offset);
while (skipped < offset && is.read() >= 0) {
skipped++;
}
}
- IO.copy(is, response.getOutputStream());
+ IO.copy(is, os);
}
} else if (HttpMethod.PUT.is(req.getMethod())) {
if (!webDav) {
file.getParentFile().mkdirs();
}
if (file.getParentFile().exists()) {
- try {
- FileOutputStream os = null;
- try {
- os = new FileOutputStream(file);
- IO.copy(request.getInputStream(), os);
- os.close();
- os = null;
- } finally {
- try {
- if (os != null) {
- os.close();
- }
- } catch (final IOException e) {
- // Suppressed due to an exception already
thrown in the try block.
- }
- }
+ try (InputStream is = Content.Source.asInputStream(req);
+ FileOutputStream os = new FileOutputStream(file)) {
+ IO.copy(is, os);
} catch (IOException e) {
file.delete();
throw e;
@@ -471,9 +459,9 @@ public class HttpServer {
}
} else if (HttpMethod.OPTIONS.is(req.getMethod())) {
if (webDav) {
- response.setHeader("DAV", "1,2");
+ response.getHeaders().add("DAV", "1,2");
}
- response.setHeader(HttpHeader.ALLOW.asString(), "GET, PUT,
HEAD, OPTIONS");
+ response.getHeaders().add(HttpHeader.ALLOW, "GET, PUT, HEAD,
OPTIONS");
response.setStatus(HttpServletResponse.SC_OK);
} else if (webDav && "MKCOL".equals(req.getMethod())) {
if (file.exists()) {
@@ -486,33 +474,29 @@ public class HttpServer {
} else {
response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
+ callback.succeeded();
+ return true;
}
}
- private void writeResponseBodyMessage(HttpServletResponse response, String
message) throws IOException {
- try (OutputStream outputStream = response.getOutputStream()) {
+ private void writeResponseBodyMessage(Request request, Response response,
String message) throws IOException {
+ try (OutputStream outputStream =
Response.asBufferedOutputStream(request, response)) {
outputStream.write(message.getBytes(StandardCharsets.UTF_8));
}
}
- private class RFC9457Handler extends AbstractHandler {
+ private class RFC9457Handler extends Handler.Abstract {
@Override
- public void handle(
- final String target,
- final Request req,
- final HttpServletRequest request,
- final HttpServletResponse response)
- throws IOException, ServletException {
- String path = req.getPathInfo().substring(1);
+ public boolean handle(Request req, Response response, Callback
callback) throws Exception {
+ String path = req.getHttpURI().getPath().substring(1);
if (!path.startsWith("rfc9457/")) {
- return;
+ return false;
}
- req.setHandled(true);
if (HttpMethod.GET.is(req.getMethod())) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
- response.setHeader(HttpHeader.CONTENT_TYPE.asString(),
"application/problem+json");
+ response.getHeaders().add(HttpHeader.CONTENT_TYPE.asString(),
"application/problem+json");
RFC9457Payload rfc9457Payload;
if (path.endsWith("missing_fields.txt")) {
rfc9457Payload = new RFC9457Payload(null, null, null,
null, null);
@@ -524,8 +508,10 @@ public class HttpServer {
"Your current balance is 30, but that costs 50.",
URI.create("/account/12345/msgs/abc"));
}
- writeResponseBodyMessage(response,
buildRFC9457Message(rfc9457Payload));
+ writeResponseBodyMessage(req, response,
buildRFC9457Message(rfc9457Payload));
}
+ callback.succeeded();
+ return true;
}
}
@@ -533,64 +519,70 @@ public class HttpServer {
return new Gson().toJson(payload, RFC9457Payload.class);
}
- private class RedirectHandler extends AbstractHandler {
+ private class RedirectHandler extends Handler.Abstract {
@Override
- public void handle(String target, Request req, HttpServletRequest
request, HttpServletResponse response) {
- String path = req.getPathInfo();
+ public boolean handle(Request req, Response response, Callback
callback) throws Exception {
+ String path = req.getHttpURI().getPath();
if (!path.startsWith("/redirect/")) {
- return;
+ return false;
}
- req.setHandled(true);
StringBuilder location = new StringBuilder(128);
- String scheme = req.getParameter("scheme");
- location.append(scheme != null ? scheme : req.getScheme());
+ String scheme = Request.getParameters(req).getValue("scheme");
+ location.append(scheme != null ? scheme :
req.getHttpURI().getScheme());
location.append("://");
- location.append(req.getServerName());
+ location.append(Request.getServerName(req));
location.append(":");
if ("http".equalsIgnoreCase(scheme)) {
location.append(getHttpPort());
} else if ("https".equalsIgnoreCase(scheme)) {
location.append(getHttpsPort());
} else {
- location.append(req.getServerPort());
+ location.append(Request.getServerPort(req));
}
location.append("/repo").append(path.substring(9));
- response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
- response.setHeader(HttpHeader.LOCATION.asString(),
location.toString());
+ Response.sendRedirect(
+ req, response, callback,
HttpServletResponse.SC_MOVED_PERMANENTLY, location.toString(), false);
+ callback.succeeded();
+ return true;
}
}
- private class AuthHandler extends AbstractHandler {
+ private class AuthHandler extends Handler.Abstract {
@Override
- public void handle(String target, Request req, HttpServletRequest
request, HttpServletResponse response)
- throws IOException {
+ public boolean handle(Request request, Response response, Callback
callback) throws Exception {
if (ExpectContinue.BROKEN.equals(expectContinue)
- &&
"100-continue".equalsIgnoreCase(request.getHeader(HttpHeader.EXPECT.asString())))
{
- request.getInputStream();
+ &&
"100-continue".equalsIgnoreCase(request.getHeaders().get(HttpHeader.EXPECT))) {
+ // TODO: what is this for?
+ Request.asInputStream(request);
}
if (username != null && password != null) {
- if
(checkBasicAuth(request.getHeader(HttpHeader.AUTHORIZATION.asString()),
username, password)) {
- return;
+ if
(checkBasicAuth(request.getHeaders().get(HttpHeader.AUTHORIZATION), username,
password)) {
+ return false;
}
- req.setHandled(true);
- response.setHeader(HttpHeader.WWW_AUTHENTICATE.asString(),
"basic realm=\"Test-Realm\"");
+ response.getHeaders().add(HttpHeader.WWW_AUTHENTICATE, "Basic
realm=\"Test-Realm\"");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ callback.succeeded();
+ return true;
}
+ return false;
}
}
- private class ProxyAuthHandler extends AbstractHandler {
+ private class ProxyAuthHandler extends Handler.Abstract {
@Override
- public void handle(String target, Request req, HttpServletRequest
request, HttpServletResponse response) {
+ public boolean handle(Request req, Response response, Callback
callback) throws Exception {
if (proxyUsername != null && proxyPassword != null) {
if (checkBasicAuth(
-
request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString()), proxyUsername,
proxyPassword)) {
- return;
+ req.getHeaders().get(HttpHeader.PROXY_AUTHORIZATION),
proxyUsername, proxyPassword)) {
+ return false;
}
- req.setHandled(true);
- response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(),
"basic realm=\"Test-Realm\"");
+ response.getHeaders().add(HttpHeader.PROXY_AUTHENTICATE,
"basic realm=\"Test-Realm\"");
response.setStatus(HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED);
+ callback.succeeded();
+ return true;
+ } else {
+ return false;
}
}
}
diff --git a/maven-resolver-transport-jetty/pom.xml
b/maven-resolver-transport-jetty/pom.xml
index 8925783f7..c398e2901 100644
--- a/maven-resolver-transport-jetty/pom.xml
+++ b/maven-resolver-transport-jetty/pom.xml
@@ -30,10 +30,10 @@
<packaging>jar</packaging>
<name>Maven Artifact Resolver Transport Jetty</name>
- <description>Maven Artifact Transport Jetty.</description>
+ <description>Maven Artifact Transport based on Jetty 12.1.</description>
<properties>
- <javaVersion>11</javaVersion>
+ <javaVersion>17</javaVersion>
</properties>
<dependencies>
@@ -66,11 +66,11 @@
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
- <artifactId>http2-client</artifactId>
+ <artifactId>jetty-http2-client</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.http2</groupId>
- <artifactId>http2-http-client-transport</artifactId>
+ <artifactId>jetty-http2-client-transport</artifactId>
</dependency>
<dependency>
diff --git
a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyRFC9457Reporter.java
b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyRFC9457Reporter.java
index 1defcf471..2c5e5ca39 100644
---
a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyRFC9457Reporter.java
+++
b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyRFC9457Reporter.java
@@ -27,8 +27,8 @@ import java.util.concurrent.TimeoutException;
import
org.eclipse.aether.spi.connector.transport.http.HttpTransporterException;
import org.eclipse.aether.spi.connector.transport.http.RFC9457.RFC9457Reporter;
-import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.util.InputStreamResponseListener;
+import org.eclipse.jetty.client.InputStreamResponseListener;
+import org.eclipse.jetty.client.Response;
import org.eclipse.jetty.http.HttpHeader;
public class JettyRFC9457Reporter extends
RFC9457Reporter<InputStreamResponseListener, HttpTransporterException> {
diff --git
a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
index a4fc66885..3a90132a7 100644
---
a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
+++
b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/JettyTransporter.java
@@ -55,18 +55,18 @@ import org.eclipse.aether.spi.io.PathProcessor;
import org.eclipse.aether.transfer.NoTransporterException;
import org.eclipse.aether.transfer.TransferCancelledException;
import org.eclipse.aether.util.ConfigUtils;
+import org.eclipse.jetty.client.Authentication;
+import org.eclipse.jetty.client.BasicAuthentication;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
-import org.eclipse.jetty.client.api.Authentication;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.api.Response;
-import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
-import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
-import org.eclipse.jetty.client.util.BasicAuthentication;
-import org.eclipse.jetty.client.util.InputStreamResponseListener;
+import org.eclipse.jetty.client.InputStreamResponseListener;
+import org.eclipse.jetty.client.Request;
+import org.eclipse.jetty.client.Response;
+import org.eclipse.jetty.client.transport.HttpClientConnectionFactory;
+import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http2.client.HTTP2Client;
-import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
+import
org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -353,7 +353,7 @@ final class JettyTransporter extends AbstractTransporter
implements HttpTranspor
if (preemptiveAuth || preemptivePutAuth) {
mayApplyPreemptiveAuth(request);
}
- request.body(new PutTaskRequestContent(task));
+ request.body(PutTaskRequestContent.from(task));
AtomicBoolean started = new AtomicBoolean(false);
Response response;
try {
diff --git
a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/PutTaskRequestContent.java
b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/PutTaskRequestContent.java
index b14d29c17..a50c64331 100644
---
a/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/PutTaskRequestContent.java
+++
b/maven-resolver-transport-jetty/src/main/java/org/eclipse/aether/transport/jetty/PutTaskRequestContent.java
@@ -18,118 +18,290 @@
*/
package org.eclipse.aether.transport.jetty;
-import java.io.EOFException;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
import java.nio.channels.Channels;
+import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
+import java.util.Objects;
+import java.util.function.Supplier;
import org.eclipse.aether.spi.connector.transport.PutTask;
-import org.eclipse.jetty.client.util.AbstractRequestContent;
+import org.eclipse.jetty.client.ByteBufferRequestContent;
+import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.Content;
+import org.eclipse.jetty.io.RetainableByteBuffer;
+import org.eclipse.jetty.io.internal.ByteChannelContentSource;
import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.thread.AutoLock;
+import org.eclipse.jetty.util.thread.SerializedInvoker;
-class PutTaskRequestContent extends AbstractRequestContent {
- private final PutTask putTask;
- private final int bufferSize;
- private ByteBufferPool bufferPool;
- private boolean useDirectByteBuffers = true;
+/**
+ * Heavily inspired by Jetty's {@code
org.eclipse.jetty.io.internal.ByteChannelContentSource} but adjusted to deal
with
+ * {@link ReadableByteChannel}s and to support rewind (to be able to retry the
requests).
+ * Also Jetty's {@code ByteChannelContentSource} is an internal package so
should not be used directly.
+ * @see <a
href="https://javadoc.jetty.org/jetty-12/org/eclipse/jetty/io/internal/ByteChannelContentSource.html">ByteChannelContentSource</a>
+ * @see <a href="https://github.com/jetty/jetty.project/issues/14324">Jetty
Issue #14324</a>
+ */
+public class PutTaskRequestContent extends ByteBufferRequestContent implements
Request.Content {
- PutTaskRequestContent(PutTask putTask) {
- this(putTask, 4096);
+ public static Request.Content from(PutTask putTask) {
+ Supplier<ReadableByteChannel> newChannelSupplier;
+ if (putTask.getDataPath() != null) {
+ newChannelSupplier = () -> {
+ try {
+ return Files.newByteChannel(putTask.getDataPath(),
StandardOpenOption.READ);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ };
+ } else {
+ newChannelSupplier = () -> {
+ try {
+ return Channels.newChannel(putTask.newInputStream());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ };
+ }
+ return new PutTaskRequestContent(null, newChannelSupplier, 0L,
putTask.getDataLength());
}
- PutTaskRequestContent(PutTask putTask, int bufferSize) {
- super("application/octet-stream");
- this.putTask = putTask;
- this.bufferSize = bufferSize;
+ private final AutoLock lock = new AutoLock();
+ private final SerializedInvoker invoker = new
SerializedInvoker(ByteChannelContentSource.class);
+ private final ByteBufferPool.Sized byteBufferPool;
+ private ReadableByteChannel byteChannel;
+ private final long offset;
+ private final long length;
+ private RetainableByteBuffer buffer;
+ private long offsetRemaining;
+ private long totalRead;
+ private Runnable demandCallback;
+ private Content.Chunk terminal;
+ /** Only necessary for rewind support when leveraging the input stream. */
+ private Supplier<ReadableByteChannel> newByteChannelSupplier;
+
+ /**
+ * Create a {@link ByteChannelContentSource} which reads from a {@link
ByteChannel}.
+ * @param byteBufferPool The {@link
org.eclipse.jetty.io.ByteBufferPool.Sized} to use for any internal buffers.
+ * @param byteChannel The {@link ByteChannel}s to use as the source.
+ */
+ protected PutTaskRequestContent(
+ ByteBufferPool.Sized byteBufferPool, Supplier<ReadableByteChannel>
newByteChannelSupplier) {
+ this(byteBufferPool, newByteChannelSupplier, 0L, -1L);
}
- @Override
- public long getLength() {
- return putTask.getDataLength();
+ /**
+ * Create a {@link ByteChannelContentSource} which reads from a {@link
ByteChannel}.
+ * If the {@link ByteChannel} is an instance of {@link
SeekableByteChannel} the implementation will use
+ * {@link SeekableByteChannel#position(long)} to navigate to the starting
offset.
+ * @param byteBufferPool The {@link
org.eclipse.jetty.io.ByteBufferPool.Sized} to use for any internal buffers.
+ * @param byteChannel The {@link ByteChannel}s to use as the source.
+ * @param offset the offset byte of the content to start from.
+ * Must be greater than or equal to 0 and less than the
content length (if known).
+ * @param length the length of the content to make available, -1 for the
full length.
+ * If the size of the content is known, the length may be
truncated to the content size minus the offset.
+ * @throws IndexOutOfBoundsException if the offset or length are out of
range.
+ * @see TypeUtil#checkOffsetLengthSize(long, long, long)
+ */
+ protected PutTaskRequestContent(
+ ByteBufferPool.Sized byteBufferPool,
+ Supplier<ReadableByteChannel> newByteChannelSupplier,
+ long offset,
+ long length) {
+ this.byteBufferPool = Objects.requireNonNullElse(byteBufferPool,
ByteBufferPool.SIZED_NON_POOLING);
+ this.byteChannel = newByteChannelSupplier.get();
+ this.offset = offset;
+ this.length = TypeUtil.checkOffsetLengthSize(offset, length, -1L);
+ offsetRemaining = offset;
+ this.newByteChannelSupplier = newByteChannelSupplier;
}
- @Override
- public boolean isReproducible() {
- return true;
+ protected ReadableByteChannel open() throws IOException {
+ return byteChannel;
}
- public ByteBufferPool getByteBufferPool() {
- return bufferPool;
+ @Override
+ public void demand(Runnable demandCallback) {
+ try (AutoLock ignored = lock.lock()) {
+ if (this.demandCallback != null) {
+ throw new IllegalStateException("demand pending");
+ }
+ this.demandCallback = demandCallback;
+ }
+ invoker.run(this::invokeDemandCallback);
}
- public void setByteBufferPool(ByteBufferPool byteBufferPool) {
- this.bufferPool = byteBufferPool;
+ private void invokeDemandCallback() {
+ Runnable demandCallback;
+ try (AutoLock ignored = lock.lock()) {
+ demandCallback = this.demandCallback;
+ this.demandCallback = null;
+ }
+ if (demandCallback != null) {
+ ExceptionUtil.run(demandCallback, this::fail);
+ }
}
- public boolean isUseDirectByteBuffers() {
- return useDirectByteBuffers;
+ protected void lockedSetTerminal(Content.Chunk terminal) {
+ assert lock.isHeldByCurrentThread();
+ if (terminal == null) {
+ terminal = Objects.requireNonNull(terminal);
+ } else {
+ ExceptionUtil.addSuppressedIfNotAssociated(terminal.getFailure(),
terminal.getFailure());
+ }
+ IO.close(byteChannel);
+ if (buffer != null) {
+ buffer.release();
+ }
+ buffer = null;
}
- public void setUseDirectByteBuffers(boolean useDirectByteBuffers) {
- this.useDirectByteBuffers = useDirectByteBuffers;
+ private void lockedEnsureOpenOrTerminal() {
+ assert lock.isHeldByCurrentThread();
+ if (terminal == null && (byteChannel == null ||
!byteChannel.isOpen())) {
+ try {
+ byteChannel = open();
+ if (byteChannel == null || !byteChannel.isOpen()) {
+ lockedSetTerminal(Content.Chunk.from(new
ClosedChannelException(), true));
+ } else if (byteChannel instanceof SeekableByteChannel) {
+ ((SeekableByteChannel) byteChannel).position(offset);
+ offsetRemaining = 0;
+ }
+ } catch (IOException e) {
+ lockedSetTerminal(Content.Chunk.from(e, true));
+ }
+ }
}
@Override
- protected Subscription newSubscription(Consumer consumer, boolean
emitInitialContent) {
- return new SubscriptionImpl(consumer, emitInitialContent);
- }
+ public Content.Chunk read() {
+ try (AutoLock ignored = lock.lock()) {
+ lockedEnsureOpenOrTerminal();
- private class SubscriptionImpl extends AbstractSubscription {
- private ReadableByteChannel channel;
- private long readTotal;
+ if (terminal != null) {
+ return terminal;
+ }
- private SubscriptionImpl(Consumer consumer, boolean
emitInitialContent) {
- super(consumer, emitInitialContent);
- }
+ if (length == 0) {
+ lockedSetTerminal(Content.Chunk.EOF);
+ return Content.Chunk.EOF;
+ }
- @Override
- protected boolean produceContent(Producer producer) throws IOException
{
- ByteBuffer buffer;
- boolean last;
- if (channel == null) {
- if (putTask.getDataPath() != null) {
- channel = Files.newByteChannel(putTask.getDataPath(),
StandardOpenOption.READ);
- } else {
- channel = Channels.newChannel(putTask.newInputStream());
+ if (buffer == null) {
+ buffer = byteBufferPool.acquire();
+ } else if (buffer.isRetained()) {
+ buffer.release();
+ buffer = byteBufferPool.acquire();
+ }
+
+ try {
+ ByteBuffer byteBuffer = buffer.getByteBuffer();
+ if (offsetRemaining > 0) {
+ // Discard all bytes read until we reach the staring
offset.
+ while (offsetRemaining > 0) {
+ BufferUtil.clearToFill(byteBuffer);
+ byteBuffer.limit((int) Math.min(buffer.capacity(),
offsetRemaining));
+ int read = byteChannel.read(byteBuffer);
+ if (read < 0) {
+ lockedSetTerminal(Content.Chunk.EOF);
+ return terminal;
+ }
+ if (read == 0) {
+ return null;
+ }
+
+ offsetRemaining -= read;
+ }
+ }
+
+ BufferUtil.clearToFill(byteBuffer);
+ if (length > 0) {
+ byteBuffer.limit((int) Math.min(buffer.capacity(), length
- totalRead));
+ }
+ int read = byteChannel.read(byteBuffer);
+ BufferUtil.flipToFlush(byteBuffer, 0);
+ if (read == 0) {
+ return null;
}
+ if (read > 0) {
+ totalRead += read;
+ buffer.retain();
+ if (length < 0 || totalRead < length) {
+ return Content.Chunk.asChunk(byteBuffer, false,
buffer);
+ }
+
+ Content.Chunk last = Content.Chunk.asChunk(byteBuffer,
true, buffer);
+ lockedSetTerminal(Content.Chunk.EOF);
+ return last;
+ }
+ lockedSetTerminal(Content.Chunk.EOF);
+ } catch (Throwable t) {
+ lockedSetTerminal(Content.Chunk.from(t, true));
}
+ }
+ return terminal;
+ }
+
+ @Override
+ public void fail(Throwable failure) {
+ try (AutoLock ignored = lock.lock()) {
+ lockedSetTerminal(Content.Chunk.from(failure, true));
+ }
+ }
- buffer = bufferPool == null
- ? BufferUtil.allocate(bufferSize, isUseDirectByteBuffers())
- : bufferPool.acquire(bufferSize, isUseDirectByteBuffers());
+ @Override
+ public long getLength() {
+ return length;
+ }
- BufferUtil.clearToFill(buffer);
- int read = channel.read(buffer);
- BufferUtil.flipToFlush(buffer, 0);
- if (!channel.isOpen() && read < 0) {
- throw new EOFException("EOF reached for " + putTask);
+ @Override
+ public boolean rewind() {
+ try (AutoLock ignored = lock.lock()) {
+ // open a new ByteChannel if we don't have a SeekableByteChannel.
+ if (!(byteChannel instanceof SeekableByteChannel)) {
+ try {
+ byteChannel.close();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ byteChannel = newByteChannelSupplier.get();
+ offsetRemaining = 0;
+ totalRead = 0;
+ return true;
}
- if (read > 0) {
- readTotal += read;
+
+ // We can remove terminal condition for a rewind that is likely to
occur
+ if (terminal != null
+ && !Content.Chunk.isFailure(terminal)
+ && (byteChannel == null || byteChannel instanceof
SeekableByteChannel)) {
+ terminal = null;
}
- last = readTotal == getLength();
- if (last) {
- IO.close(channel);
+
+ lockedEnsureOpenOrTerminal();
+ if (terminal != null || byteChannel == null ||
!byteChannel.isOpen()) {
+ return false;
}
- return producer.produce(buffer, last, Callback.from(() ->
release(buffer)));
- }
- private void release(ByteBuffer buffer) {
- if (bufferPool != null) {
- bufferPool.release(buffer);
+ try {
+ ((SeekableByteChannel) byteChannel).position(offset);
+ offsetRemaining = 0;
+ totalRead = 0;
+ return true;
+ } catch (Throwable t) {
+ lockedSetTerminal(Content.Chunk.from(t, true));
}
- }
- @Override
- public void fail(Throwable failure) {
- super.fail(failure);
- IO.close(channel);
+ return true;
}
}
}
diff --git a/pom.xml b/pom.xml
index 84e1d0269..3b4f840d2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -109,8 +109,8 @@
<testcontainersVersion>2.0.3</testcontainersVersion>
<bouncycastleVersion>1.83</bouncycastleVersion>
- <!-- Used by Jetty Transport (client) and Test HTTP (server) -->
- <jettyVersion>10.0.26</jettyVersion>
+ <!-- Used by Jetty Transport (client) and Test HTTP (server), 12.1
introduced support for different compressions
(https://github.com/jetty/jetty.project/issues/8769) -->
+ <jettyVersion>12.1.5</jettyVersion>
<!-- used by supplier and demo only -->
<maven3Version>3.9.12</maven3Version>
<maven4Version>4.0.0-rc-5</maven4Version>