Skip to content

Commit

Permalink
[MRESOLVER-584] Jetty fix for GOAWAY (#533)
Browse files Browse the repository at this point in the history
Just shorten the client lifespan to connector transaction duration vs as before, for whole session duration. Also simplify the code.

---

https://issues.apache.org/jira/browse/MRESOLVER-584
  • Loading branch information
cstamas committed Aug 2, 2024
1 parent 78d8eb6 commit d013a32
Showing 1 changed file with 134 additions and 176 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@
import org.eclipse.jetty.http2.client.http.ClientConnectionFactoryOverHTTP2;
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.ACCEPT_ENCODING;
import static org.eclipse.aether.spi.connector.transport.http.HttpConstants.CONTENT_LENGTH;
Expand All @@ -93,6 +91,10 @@
final class JettyTransporter extends AbstractTransporter implements HttpTransporter {
private static final long MODIFICATION_THRESHOLD = 60L * 1000L;

private final RepositorySystemSession session;

private final RemoteRepository repository;

private final ChecksumExtractor checksumExtractor;

private final PathProcessor pathProcessor;
Expand All @@ -109,16 +111,20 @@ final class JettyTransporter extends AbstractTransporter implements HttpTranspor

private final boolean preemptivePutAuth;

private final BasicAuthentication.BasicResult basicServerAuthenticationResult;
private final boolean insecure;

private final AtomicReference<BasicAuthentication.BasicResult> basicServerAuthenticationResult;

private final BasicAuthentication.BasicResult basicProxyAuthenticationResult;
private final AtomicReference<BasicAuthentication.BasicResult> basicProxyAuthenticationResult;

JettyTransporter(
RepositorySystemSession session,
RemoteRepository repository,
ChecksumExtractor checksumExtractor,
PathProcessor pathProcessor)
throws NoTransporterException {
this.session = session;
this.repository = repository;
this.checksumExtractor = checksumExtractor;
this.pathProcessor = pathProcessor;
try {
Expand Down Expand Up @@ -177,14 +183,34 @@ final class JettyTransporter extends AbstractTransporter implements HttpTranspor
ConfigurationProperties.DEFAULT_HTTP_PREEMPTIVE_PUT_AUTH,
ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH + "." + repository.getId(),
ConfigurationProperties.HTTP_PREEMPTIVE_PUT_AUTH);
final String httpsSecurityMode = ConfigUtils.getString(
session,
ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT,
ConfigurationProperties.HTTPS_SECURITY_MODE + "." + repository.getId(),
ConfigurationProperties.HTTPS_SECURITY_MODE);

this.client = getOrCreateClient(session, repository);
if (!ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(httpsSecurityMode)
&& !ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(httpsSecurityMode)) {
throw new IllegalArgumentException("Unsupported '" + httpsSecurityMode + "' HTTPS security mode.");
}
this.insecure = ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(httpsSecurityMode);

this.basicServerAuthenticationResult = new AtomicReference<>(null);
this.basicProxyAuthenticationResult = new AtomicReference<>(null);
try {
this.client = createClient();
} catch (Exception e) {
throw new NoTransporterException(repository, e);
}
}

final String instanceKey = JETTY_INSTANCE_KEY_PREFIX + repository.getId();
this.basicServerAuthenticationResult =
(BasicAuthentication.BasicResult) session.getData().get(instanceKey + ".serverAuth");
this.basicProxyAuthenticationResult =
(BasicAuthentication.BasicResult) session.getData().get(instanceKey + ".proxyAuth");
private void mayApplyPreemptiveAuth(Request request) {
if (basicServerAuthenticationResult.get() != null) {
basicServerAuthenticationResult.get().apply(request);
}
if (basicProxyAuthenticationResult.get() != null) {
basicProxyAuthenticationResult.get().apply(request);
}
}

private URI resolve(TransportTask task) {
Expand All @@ -207,12 +233,7 @@ protected void implPeek(PeekTask task) throws Exception {
.method("HEAD");
request.headers(m -> headers.forEach(m::add));
if (preemptiveAuth) {
if (basicServerAuthenticationResult != null) {
basicServerAuthenticationResult.apply(request);
}
if (basicProxyAuthenticationResult != null) {
basicProxyAuthenticationResult.apply(request);
}
mayApplyPreemptiveAuth(request);
}
Response response = request.send();
if (response.getStatus() >= MULTIPLE_CHOICES) {
Expand All @@ -232,12 +253,7 @@ protected void implGet(GetTask task) throws Exception {
.method("GET");
request.headers(m -> headers.forEach(m::add));
if (preemptiveAuth) {
if (basicServerAuthenticationResult != null) {
basicServerAuthenticationResult.apply(request);
}
if (basicProxyAuthenticationResult != null) {
basicProxyAuthenticationResult.apply(request);
}
mayApplyPreemptiveAuth(request);
}

if (resume) {
Expand Down Expand Up @@ -335,12 +351,7 @@ protected void implPut(PutTask task) throws Exception {
Request request = client.newRequest(resolve(task)).method("PUT").timeout(requestTimeout, TimeUnit.MILLISECONDS);
request.headers(m -> headers.forEach(m::add));
if (preemptiveAuth || preemptivePutAuth) {
if (basicServerAuthenticationResult != null) {
basicServerAuthenticationResult.apply(request);
}
if (basicProxyAuthenticationResult != null) {
basicProxyAuthenticationResult.apply(request);
}
mayApplyPreemptiveAuth(request);
}
request.body(new PutTaskRequestContent(task));
AtomicBoolean started = new AtomicBoolean(false);
Expand Down Expand Up @@ -395,176 +406,123 @@ protected void implPut(PutTask task) throws Exception {

@Override
protected void implClose() {
// noop
try {
this.client.stop();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

/**
* Visible for testing.
*/
static final String JETTY_INSTANCE_KEY_PREFIX = JettyTransporterFactory.class.getName() + ".jetty.";
@SuppressWarnings("checkstyle:methodlength")
private HttpClient createClient() throws Exception {
BasicAuthentication.BasicResult serverAuth = null;
BasicAuthentication.BasicResult proxyAuth = null;
SSLContext sslContext = null;
BasicAuthentication basicAuthentication = null;
try (AuthenticationContext repoAuthContext = AuthenticationContext.forRepository(session, repository)) {
if (repoAuthContext != null) {
sslContext = repoAuthContext.get(AuthenticationContext.SSL_CONTEXT, SSLContext.class);

String username = repoAuthContext.get(AuthenticationContext.USERNAME);
String password = repoAuthContext.get(AuthenticationContext.PASSWORD);

URI uri = URI.create(repository.getUrl());
basicAuthentication = new BasicAuthentication(uri, Authentication.ANY_REALM, username, password);
if (preemptiveAuth || preemptivePutAuth) {
serverAuth = new BasicAuthentication.BasicResult(uri, HttpHeader.AUTHORIZATION, username, password);
}
}
}

static final Logger LOGGER = LoggerFactory.getLogger(JettyTransporter.class);
if (sslContext == null) {
if (insecure) {
sslContext = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}

@SuppressWarnings("checkstyle:methodlength")
private HttpClient getOrCreateClient(RepositorySystemSession session, RemoteRepository repository)
throws NoTransporterException {
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}

final String instanceKey = JETTY_INSTANCE_KEY_PREFIX + repository.getId();
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
sslContext.init(null, new X509TrustManager[] {tm}, null);
} else {
sslContext = SSLContext.getDefault();
}
}

final String httpsSecurityMode = ConfigUtils.getString(
int connectTimeout = ConfigUtils.getInteger(
session,
ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT,
ConfigurationProperties.HTTPS_SECURITY_MODE + "." + repository.getId(),
ConfigurationProperties.HTTPS_SECURITY_MODE);

if (!ConfigurationProperties.HTTPS_SECURITY_MODE_DEFAULT.equals(httpsSecurityMode)
&& !ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(httpsSecurityMode)) {
throw new IllegalArgumentException("Unsupported '" + httpsSecurityMode + "' HTTPS security mode.");
ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT,
ConfigurationProperties.CONNECT_TIMEOUT + "." + repository.getId(),
ConfigurationProperties.CONNECT_TIMEOUT);

SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
sslContextFactory.setSslContext(sslContext);
if (insecure) {
sslContextFactory.setEndpointIdentificationAlgorithm(null);
sslContextFactory.setHostnameVerifier((name, context) -> true);
}
final boolean insecure = ConfigurationProperties.HTTPS_SECURITY_MODE_INSECURE.equals(httpsSecurityMode);

try {
AtomicReference<BasicAuthentication.BasicResult> serverAuth = new AtomicReference<>(null);
AtomicReference<BasicAuthentication.BasicResult> proxyAuth = new AtomicReference<>(null);
HttpClient client = (HttpClient) session.getData().computeIfAbsent(instanceKey, () -> {
SSLContext sslContext = null;
BasicAuthentication basicAuthentication = null;
try {
try (AuthenticationContext repoAuthContext =
AuthenticationContext.forRepository(session, repository)) {
if (repoAuthContext != null) {
sslContext = repoAuthContext.get(AuthenticationContext.SSL_CONTEXT, SSLContext.class);

String username = repoAuthContext.get(AuthenticationContext.USERNAME);
String password = repoAuthContext.get(AuthenticationContext.PASSWORD);

URI uri = URI.create(repository.getUrl());
basicAuthentication =
new BasicAuthentication(uri, Authentication.ANY_REALM, username, password);
if (preemptiveAuth || preemptivePutAuth) {
serverAuth.set(new BasicAuthentication.BasicResult(
uri, HttpHeader.AUTHORIZATION, username, password));
}
}
}
ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(sslContextFactory);

if (sslContext == null) {
if (insecure) {
sslContext = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactoryOverHTTP2.HTTP2 http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
HttpClientTransportDynamic transport;
if ("https".equalsIgnoreCase(repository.getProtocol())) {
transport = new HttpClientTransportDynamic(
clientConnector, http2, HttpClientConnectionFactory.HTTP11); // HTTPS, prefer H2
} else {
transport = new HttpClientTransportDynamic(
clientConnector, HttpClientConnectionFactory.HTTP11, http2); // plaintext HTTP, H2 cannot be used
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};
sslContext.init(null, new X509TrustManager[] {tm}, null);
} else {
sslContext = SSLContext.getDefault();
}
}
HttpClient httpClient = new HttpClient(transport);
httpClient.setConnectTimeout(connectTimeout);
httpClient.setFollowRedirects(true);
httpClient.setMaxRedirects(2);

int connectTimeout = ConfigUtils.getInteger(
session,
ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT,
ConfigurationProperties.CONNECT_TIMEOUT + "." + repository.getId(),
ConfigurationProperties.CONNECT_TIMEOUT);

SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
sslContextFactory.setSslContext(sslContext);
if (insecure) {
sslContextFactory.setEndpointIdentificationAlgorithm(null);
sslContextFactory.setHostnameVerifier((name, context) -> true);
}
httpClient.setUserAgentField(null); // we manage it

ClientConnector clientConnector = new ClientConnector();
clientConnector.setSslContextFactory(sslContextFactory);

HTTP2Client http2Client = new HTTP2Client(clientConnector);
ClientConnectionFactoryOverHTTP2.HTTP2 http2 =
new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client);

HttpClientTransportDynamic transport;
if ("https".equalsIgnoreCase(repository.getProtocol())) {
transport = new HttpClientTransportDynamic(
clientConnector, http2, HttpClientConnectionFactory.HTTP11); // HTTPS, prefer H2
} else {
transport = new HttpClientTransportDynamic(
clientConnector,
HttpClientConnectionFactory.HTTP11,
http2); // plaintext HTTP, H2 cannot be used
}
if (basicAuthentication != null) {
httpClient.getAuthenticationStore().addAuthentication(basicAuthentication);
}

HttpClient httpClient = new HttpClient(transport);
httpClient.setConnectTimeout(connectTimeout);
httpClient.setFollowRedirects(true);
httpClient.setMaxRedirects(2);
if (repository.getProxy() != null) {
HttpProxy proxy = new HttpProxy(
repository.getProxy().getHost(), repository.getProxy().getPort());

httpClient.setUserAgentField(null); // we manage it
httpClient.getProxyConfiguration().addProxy(proxy);
try (AuthenticationContext proxyAuthContext = AuthenticationContext.forProxy(session, repository)) {
if (proxyAuthContext != null) {
String username = proxyAuthContext.get(AuthenticationContext.USERNAME);
String password = proxyAuthContext.get(AuthenticationContext.PASSWORD);

if (basicAuthentication != null) {
httpClient.getAuthenticationStore().addAuthentication(basicAuthentication);
}
BasicAuthentication proxyAuthentication =
new BasicAuthentication(proxy.getURI(), Authentication.ANY_REALM, username, password);

if (repository.getProxy() != null) {
HttpProxy proxy = new HttpProxy(
repository.getProxy().getHost(),
repository.getProxy().getPort());

httpClient.getProxyConfiguration().addProxy(proxy);
try (AuthenticationContext proxyAuthContext =
AuthenticationContext.forProxy(session, repository)) {
if (proxyAuthContext != null) {
String username = proxyAuthContext.get(AuthenticationContext.USERNAME);
String password = proxyAuthContext.get(AuthenticationContext.PASSWORD);

BasicAuthentication proxyAuthentication = new BasicAuthentication(
proxy.getURI(), Authentication.ANY_REALM, username, password);

httpClient.getAuthenticationStore().addAuthentication(proxyAuthentication);
if (preemptiveAuth || preemptivePutAuth) {
proxyAuth.set(new BasicAuthentication.BasicResult(
proxy.getURI(), HttpHeader.PROXY_AUTHORIZATION, username, password));
}
}
}
httpClient.getAuthenticationStore().addAuthentication(proxyAuthentication);
if (preemptiveAuth || preemptivePutAuth) {
proxyAuth = new BasicAuthentication.BasicResult(
proxy.getURI(), HttpHeader.PROXY_AUTHORIZATION, username, password);
}
if (!session.addOnSessionEndedHandler(() -> {
try {
httpClient.stop();
} catch (Exception e) {
throw new RuntimeException(e);
}
})) {
LOGGER.warn(
"Using Resolver 2 feature without Resolver 2 session handling, you may leak resources.");
}
httpClient.start();
return httpClient;
} catch (Exception e) {
throw new WrapperEx(e);
}
});
if (serverAuth.get() != null) {
session.getData().set(instanceKey + ".serverAuth", serverAuth.get());
}
if (proxyAuth.get() != null) {
session.getData().set(instanceKey + ".proxyAuth", proxyAuth.get());
}
return client;
} catch (WrapperEx e) {
throw new NoTransporterException(repository, e.getCause());
}
}

private static final class WrapperEx extends RuntimeException {
private WrapperEx(Throwable cause) {
super(cause);
if (serverAuth != null) {
this.basicServerAuthenticationResult.set(serverAuth);
}
if (proxyAuth != null) {
this.basicProxyAuthenticationResult.set(proxyAuth);
}

httpClient.start();
return httpClient;
}
}

0 comments on commit d013a32

Please sign in to comment.