diff --git a/CHANGELOG.md b/CHANGELOG.md index 758b515cf1..935c47ce64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * Fix #4426: [java-generator] Encode an `AnyType` instead of an Object if `x-kubernetes-preserve-unknown-fields` is present and the type is null. #### Improvements +* Fix #4471: Adding KubernetesClientBuilder.withHttpClientBuilderConsumer to further customize the HttpClient for any implementation. * Fix #4348: Introduce specific annotations for the generators * Refactor #4441: refactoring `TokenRefreshInterceptor` * Fix #4365: The Watch retry logic will handle more cases, as well as perform an exceptional close for events that are not properly handled. Informers can directly provide those exceptional outcomes via the SharedIndexInformer.stopped CompletableFuture. diff --git a/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientBuilderImpl.java b/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientBuilderImpl.java index 088e659611..a89fc687ed 100644 --- a/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientBuilderImpl.java +++ b/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientBuilderImpl.java @@ -18,25 +18,17 @@ import io.fabric8.kubernetes.client.http.BasicBuilder; import io.fabric8.kubernetes.client.http.HttpClient; -import io.fabric8.kubernetes.client.http.HttpClient.Builder; import io.fabric8.kubernetes.client.http.HttpHeaders; import io.fabric8.kubernetes.client.http.Interceptor; +import io.fabric8.kubernetes.client.http.StandardHttpClientBuilder; import io.fabric8.kubernetes.client.http.TlsVersion; -import io.fabric8.kubernetes.client.internal.SSLUtils; -import java.net.InetSocketAddress; import java.net.ProxySelector; import java.net.http.HttpClient.Redirect; import java.net.http.HttpClient.Version; -import java.time.Duration; import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.concurrent.TimeUnit; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; -import javax.net.ssl.TrustManager; /** * TODO: if there is another implementation that does not support client builder copying, then this needs to be abstracted - @@ -48,28 +40,17 @@ * */ -class JdkHttpClientBuilderImpl implements Builder { +class JdkHttpClientBuilderImpl + extends StandardHttpClientBuilder { - LinkedHashMap interceptors = new LinkedHashMap<>(); - Duration connectTimeout; - Duration readTimeout; - private SSLContext sslContext; - JdkHttpClientFactory clientFactory; - private String proxyAuthorization; - private InetSocketAddress proxyAddress; - private boolean followRedirects; - private boolean preferHttp11; - private TlsVersion[] tlsVersions; - private java.net.http.HttpClient httpClient; - - JdkHttpClientBuilderImpl(JdkHttpClientFactory factory) { - this.clientFactory = factory; + public JdkHttpClientBuilderImpl(JdkHttpClientFactory factory) { + super(factory); } @Override public HttpClient build() { - if (httpClient != null) { - return new JdkHttpClientImpl(this, httpClient); + if (client != null) { + return new JdkHttpClientImpl(this, client.getHttpClient(), this.requestConfig); } java.net.http.HttpClient.Builder builder = clientFactory.createNewHttpClientBuilder(); if (connectTimeout != null) { @@ -104,101 +85,12 @@ public void before(BasicBuilder builder, HttpHeaders headers) { Arrays.asList(tlsVersions).stream().map(TlsVersion::javaName).toArray(String[]::new))); } clientFactory.additionalConfig(builder); - return new JdkHttpClientImpl(this, builder.build()); - } - - @Override - public JdkHttpClientBuilderImpl readTimeout(long readTimeout, TimeUnit unit) { - if (readTimeout == 0) { - this.readTimeout = null; - } else { - this.readTimeout = Duration.ofNanos(unit.toNanos(readTimeout)); - } - return this; - } - - @Override - public Builder connectTimeout(long connectTimeout, TimeUnit unit) { - this.connectTimeout = Duration.ofNanos(unit.toNanos(connectTimeout)); - return this; - } - - @Override - public Builder forStreaming() { - // nothing to do - return this; - } - - @Override - public Builder writeTimeout(long timeout, TimeUnit timeoutUnit) { - // nothing to do - return this; - } - - @Override - public Builder addOrReplaceInterceptor(String name, Interceptor interceptor) { - if (interceptor == null) { - interceptors.remove(name); - } else { - interceptors.put(name, interceptor); - } - return this; + return new JdkHttpClientImpl(this, builder.build(), null); } @Override - public Builder authenticatorNone() { - return this; - } - - @Override - public Builder sslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) { - this.sslContext = SSLUtils.sslContext(keyManagers, trustManagers); - return this; - } - - @Override - public Builder followAllRedirects() { - this.followRedirects = true; - return this; - } - - @Override - public Builder proxyAddress(InetSocketAddress proxyAddress) { - this.proxyAddress = proxyAddress; - return this; - } - - @Override - public Builder proxyAuthorization(String credentials) { - this.proxyAuthorization = credentials; - return this; - } - - @Override - public Builder preferHttp11() { - this.preferHttp11 = true; - return this; - } - - @Override - public Builder tlsVersions(TlsVersion... tlsVersions) { - this.tlsVersions = tlsVersions; - return this; - } - - public JdkHttpClientBuilderImpl copy(java.net.http.HttpClient httpClient) { - JdkHttpClientBuilderImpl copy = new JdkHttpClientBuilderImpl(this.clientFactory); - copy.connectTimeout = this.connectTimeout; - copy.readTimeout = this.readTimeout; - copy.sslContext = this.sslContext; - copy.interceptors = new LinkedHashMap<>(this.interceptors); - copy.proxyAddress = this.proxyAddress; - copy.proxyAuthorization = this.proxyAuthorization; - copy.tlsVersions = this.tlsVersions; - copy.preferHttp11 = this.preferHttp11; - copy.followRedirects = this.followRedirects; - copy.httpClient = httpClient; - return copy; + protected JdkHttpClientBuilderImpl newInstance(JdkHttpClientFactory clientFactory) { + return new JdkHttpClientBuilderImpl(clientFactory); } } diff --git a/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientFactory.java b/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientFactory.java index d4da7f16d7..685a28f6db 100644 --- a/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientFactory.java +++ b/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientFactory.java @@ -18,7 +18,6 @@ import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.http.HttpClient; -import io.fabric8.kubernetes.client.utils.HttpClientUtils; import io.fabric8.kubernetes.client.utils.Utils; import java.util.concurrent.Executor; @@ -46,15 +45,6 @@ public void shutdownNow() { } - @Override - public HttpClient createHttpClient(Config config) { - JdkHttpClientBuilderImpl builderWrapper = newBuilder(); - - HttpClientUtils.applyCommonConfiguration(config, builderWrapper, this); - - return builderWrapper.build(); - } - @Override public JdkHttpClientBuilderImpl newBuilder() { return new JdkHttpClientBuilderImpl(this); diff --git a/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientImpl.java b/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientImpl.java index e68d1110d6..a6c186293b 100644 --- a/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientImpl.java +++ b/httpclient-jdk/src/main/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientImpl.java @@ -16,6 +16,7 @@ package io.fabric8.kubernetes.client.jdkhttp; +import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.http.HttpClient; import io.fabric8.kubernetes.client.http.HttpRequest; import io.fabric8.kubernetes.client.http.HttpResponse; @@ -214,10 +215,12 @@ public HandlerAndAsyncBody(BodyHandler handler, AsyncBody asyncBody) { private JdkHttpClientBuilderImpl builder; private java.net.http.HttpClient httpClient; + private Config config; - public JdkHttpClientImpl(JdkHttpClientBuilderImpl builderImpl, java.net.http.HttpClient httpClient) { + public JdkHttpClientImpl(JdkHttpClientBuilderImpl builderImpl, java.net.http.HttpClient httpClient, Config config) { this.builder = builderImpl; this.httpClient = httpClient; + this.config = config; } @Override @@ -225,14 +228,14 @@ public void close() { if (this.httpClient == null) { return; } - builder.clientFactory.closeHttpClient(this); + builder.getClientFactory().closeHttpClient(this); // help with default cleanup, which is based upon garbarge collection this.httpClient = null; } @Override public DerivedClientBuilder newBuilder() { - return this.builder.copy(getHttpClient()); + return this.builder.copy(this); } @Override @@ -286,8 +289,8 @@ public CompletableFuture> sendAsync(HttpRequest request, Supplier> handlerAndAsyncBodySupplier) { JdkHttpRequestImpl jdkRequest = (JdkHttpRequestImpl) request; JdkHttpRequestImpl.BuilderImpl builderImpl = jdkRequest.newBuilder(); - for (Interceptor interceptor : builder.interceptors.values()) { - interceptor.before(builderImpl, jdkRequest); + for (Interceptor interceptor : builder.getInterceptors().values()) { + Interceptor.useConfig(interceptor, config).before(builderImpl, jdkRequest); jdkRequest = builderImpl.build(); } @@ -296,19 +299,20 @@ public CompletableFuture> sendAsync(HttpRequest request, CompletableFuture> cf = this.getHttpClient().sendAsync(builderImpl.build().request, handlerAndAsyncBody.handler).thenApply(r -> new AsyncResponse<>(r, handlerAndAsyncBody.asyncBody)); - for (Interceptor interceptor : builder.interceptors.values()) { + for (Interceptor interceptor : builder.getInterceptors().values()) { cf = cf.thenCompose(ar -> { java.net.http.HttpResponse response = ar.response; if (response != null && !HttpResponse.isSuccessful(response.statusCode())) { - return interceptor.afterFailure(builderImpl, new JdkHttpResponseImpl<>(response)).thenCompose(b -> { - if (b) { - HandlerAndAsyncBody interceptedHandlerAndAsyncBody = handlerAndAsyncBodySupplier.get(); - - return this.getHttpClient().sendAsync(builderImpl.build().request, interceptedHandlerAndAsyncBody.handler) - .thenApply(r -> new AsyncResponse<>(r, interceptedHandlerAndAsyncBody.asyncBody)); - } - return CompletableFuture.completedFuture(ar); - }); + return Interceptor.useConfig(interceptor, config).afterFailure(builderImpl, new JdkHttpResponseImpl<>(response)) + .thenCompose(b -> { + if (b) { + HandlerAndAsyncBody interceptedHandlerAndAsyncBody = handlerAndAsyncBodySupplier.get(); + + return this.getHttpClient().sendAsync(builderImpl.build().request, interceptedHandlerAndAsyncBody.handler) + .thenApply(r -> new AsyncResponse<>(r, interceptedHandlerAndAsyncBody.asyncBody)); + } + return CompletableFuture.completedFuture(ar); + }); } return CompletableFuture.completedFuture(ar); }); @@ -324,7 +328,7 @@ public io.fabric8.kubernetes.client.http.WebSocket.Builder newWebSocketBuilder() @Override public io.fabric8.kubernetes.client.http.HttpRequest.Builder newHttpRequestBuilder() { - return new JdkHttpRequestImpl.BuilderImpl().timeout(this.builder.readTimeout); + return new JdkHttpRequestImpl.BuilderImpl().timeout(this.builder.getReadTimeout()); } /* @@ -344,23 +348,24 @@ public WebSocketResponse(WebSocket w, java.net.http.WebSocketHandshakeException public CompletableFuture buildAsync(JdkWebSocketImpl.BuilderImpl webSocketBuilder, Listener listener) { JdkWebSocketImpl.BuilderImpl copy = webSocketBuilder.copy(); - for (Interceptor interceptor : builder.interceptors.values()) { - interceptor.before(copy, new JdkHttpRequestImpl(null, copy.asRequest())); + for (Interceptor interceptor : builder.getInterceptors().values()) { + Interceptor.useConfig(interceptor, config).before(copy, new JdkHttpRequestImpl(null, copy.asRequest())); } CompletableFuture result = new CompletableFuture<>(); CompletableFuture cf = internalBuildAsync(copy, listener); - for (Interceptor interceptor : builder.interceptors.values()) { + for (Interceptor interceptor : builder.getInterceptors().values()) { cf = cf.thenCompose(response -> { if (response.wshse != null && response.wshse.getResponse() != null) { - return interceptor.afterFailure(copy, new JdkHttpResponseImpl<>(response.wshse.getResponse())).thenCompose(b -> { - if (b) { - return this.internalBuildAsync(copy, listener); - } - return CompletableFuture.completedFuture(response); - }); + return Interceptor.useConfig(interceptor, config) + .afterFailure(copy, new JdkHttpResponseImpl<>(response.wshse.getResponse())).thenCompose(b -> { + if (b) { + return this.internalBuildAsync(copy, listener); + } + return CompletableFuture.completedFuture(response); + }); } return CompletableFuture.completedFuture(response); }); @@ -399,8 +404,8 @@ public CompletableFuture internalBuildAsync(JdkWebSocketImpl. } // the Watch logic sets a websocketTimeout as the readTimeout // TODO: this should probably be made clearer in the docs - if (this.builder.readTimeout != null) { - newBuilder.connectTimeout(this.builder.readTimeout); + if (this.builder.getReadTimeout() != null) { + newBuilder.connectTimeout(this.builder.getReadTimeout()); } AtomicLong queueSize = new AtomicLong(); @@ -436,9 +441,4 @@ java.net.http.HttpClient getHttpClient() { return httpClient; } - @Override - public Factory getFactory() { - return builder.clientFactory; - } - } diff --git a/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/DerivedJettyHttpClientBuilder.java b/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/DerivedJettyHttpClientBuilder.java deleted file mode 100644 index 62febd6275..0000000000 --- a/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/DerivedJettyHttpClientBuilder.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright (C) 2015 Red Hat, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.fabric8.kubernetes.client.jetty; - -import io.fabric8.kubernetes.client.http.HttpClient; -import io.fabric8.kubernetes.client.http.Interceptor; - -import java.time.Duration; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -@SuppressWarnings("unchecked") -public abstract class DerivedJettyHttpClientBuilder - implements HttpClient.DerivedClientBuilder { - - final JettyHttpClientFactory factory; - Duration readTimeout = Duration.ZERO; - Duration writeTimeout = Duration.ZERO; - final Map interceptors; - - DerivedJettyHttpClientBuilder(JettyHttpClientFactory factory) { - this.factory = factory; - interceptors = new LinkedHashMap<>(); - } - - @Override - public final T readTimeout(long readTimeout, TimeUnit unit) { - this.readTimeout = Duration.ofNanos(unit.toNanos(readTimeout)); - return (T) this; - } - - @Override - public T writeTimeout(long writeTimeout, TimeUnit unit) { - this.writeTimeout = Duration.ofNanos(unit.toNanos(writeTimeout)); - return (T) this; - } - - @Override - public T forStreaming() { - // NO OP - return (T) this; - } - - @Override - public T authenticatorNone() { - // NO OP - return (T) this; - } - - @Override - public T addOrReplaceInterceptor(String name, Interceptor interceptor) { - interceptors.put(name, interceptor); - return (T) this; - } -} diff --git a/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClient.java b/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClient.java index e53dfaf2b7..dcf215952a 100644 --- a/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClient.java +++ b/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClient.java @@ -15,6 +15,7 @@ */ package io.fabric8.kubernetes.client.jetty; +import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.http.HttpRequest; import io.fabric8.kubernetes.client.http.HttpResponse; @@ -49,14 +50,16 @@ public class JettyHttpClient implements io.fabric8.kubernetes.client.http.HttpCl private final Collection interceptors; private final JettyHttpClientBuilder builder; private final JettyHttpClientFactory factory; + private Config config; public JettyHttpClient(JettyHttpClientBuilder builder, HttpClient httpClient, WebSocketClient webSocketClient, - Collection interceptors, JettyHttpClientFactory jettyHttpClientFactory) { + Collection interceptors, JettyHttpClientFactory jettyHttpClientFactory, Config config) { this.builder = builder; this.jetty = httpClient; this.jettyWs = webSocketClient; this.interceptors = interceptors; this.factory = jettyHttpClientFactory; + this.config = config; } @Override @@ -71,7 +74,7 @@ public void close() { @Override public DerivedClientBuilder newBuilder() { - return builder.copy(); + return builder.copy(this); } @Override @@ -125,7 +128,7 @@ protected List process(Response response, ByteBuffer content) { @Override public WebSocket.Builder newWebSocketBuilder() { - return new JettyWebSocketBuilder(jettyWs, builder.readTimeout); + return new JettyWebSocketBuilder(jettyWs, builder.getReadTimeout()); } @Override @@ -133,7 +136,6 @@ public HttpRequest.Builder newHttpRequestBuilder() { return new StandardHttpRequest.Builder(); } - @Override public Factory getFactory() { return factory; } @@ -145,11 +147,11 @@ private Request newRequest(StandardHttpRequest originalRequest) { throw KubernetesClientException.launderThrowable(e); } final var requestBuilder = originalRequest.toBuilder(); - interceptors.forEach(i -> i.before(requestBuilder, originalRequest)); + interceptors.forEach(i -> Interceptor.useConfig(i, config).before(requestBuilder, originalRequest)); final var request = requestBuilder.build(); var jettyRequest = jetty.newRequest(request.uri()).method(request.method()); - jettyRequest.timeout(builder.readTimeout.toMillis() + builder.writeTimeout.toMillis(), TimeUnit.MILLISECONDS); + jettyRequest.timeout(builder.getReadTimeout().toMillis() + builder.getWriteTimeout().toMillis(), TimeUnit.MILLISECONDS); jettyRequest.headers(m -> request.headers().forEach((k, l) -> l.forEach(v -> m.add(k, v)))); final var contentType = request.headers("Content-Type").stream().findAny(); @@ -167,7 +169,7 @@ private CompletableFuture> interceptResponse( for (var interceptor : interceptors) { originalResponse = originalResponse.thenCompose(r -> { if (!r.isSuccessful()) { - return interceptor.afterFailure(builder, r) + return Interceptor.useConfig(interceptor, config).afterFailure(builder, r) .thenCompose(b -> { if (Boolean.TRUE.equals(b)) { return function.apply(builder.build()); @@ -195,4 +197,5 @@ HttpClient getJetty() { WebSocketClient getJettyWs() { return jettyWs; } + } diff --git a/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientBuilder.java b/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientBuilder.java index 92e9a062f2..6519266f69 100644 --- a/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientBuilder.java +++ b/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientBuilder.java @@ -15,9 +15,8 @@ */ package io.fabric8.kubernetes.client.jetty; -import io.fabric8.kubernetes.client.http.HttpClient.Builder; +import io.fabric8.kubernetes.client.http.StandardHttpClientBuilder; import io.fabric8.kubernetes.client.http.TlsVersion; -import io.fabric8.kubernetes.client.internal.SSLUtils; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClientTransport; import org.eclipse.jetty.client.HttpProxy; @@ -32,38 +31,25 @@ import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.websocket.client.WebSocketClient; -import java.net.InetSocketAddress; import java.time.Duration; -import java.util.concurrent.TimeUnit; +import java.util.Optional; import java.util.stream.Stream; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; +public class JettyHttpClientBuilder + extends StandardHttpClientBuilder { -public class JettyHttpClientBuilder extends DerivedJettyHttpClientBuilder - implements Builder { - - private Duration connectTimeout; - private SSLContext sslContext; - private boolean followAllRedirects; - private Origin.Address proxyAddress; - private String proxyAuthorization; - private TlsVersion[] tlsVersions; - // TODO: HTTP2 disabled, MockWebServer support is limited and requires changes - // Enable (preferHttp11->false) the feature after fixing MockWebServer - private boolean preferHttp11 = true; - private HttpClient sharedHttpClient; - private WebSocketClient sharedWebSocketClient; - - public JettyHttpClientBuilder(JettyHttpClientFactory factory) { - super(factory); + public JettyHttpClientBuilder(JettyHttpClientFactory clientFactory) { + super(clientFactory); + // TODO: HTTP2 disabled, MockWebServer support is limited and requires changes + // Enable (preferHttp11->false) the feature after fixing MockWebServer + this.preferHttp11 = true; } @Override public JettyHttpClient build() { - if (sharedHttpClient != null) { - return new JettyHttpClient(this, sharedHttpClient, sharedWebSocketClient, interceptors.values(), factory); + if (client != null) { + return new JettyHttpClient(this, client.getJetty(), client.getJettyWs(), interceptors.values(), clientFactory, + this.requestConfig); } final var sslContextFactory = new SslContextFactory.Client(); if (sslContext != null) { @@ -72,16 +58,17 @@ public JettyHttpClient build() { if (tlsVersions != null && tlsVersions.length > 0) { sslContextFactory.setIncludeProtocols(Stream.of(tlsVersions).map(TlsVersion::javaName).toArray(String[]::new)); } - sharedHttpClient = new HttpClient(newTransport(sslContextFactory, preferHttp11)); - sharedWebSocketClient = new WebSocketClient(new HttpClient(newTransport(sslContextFactory, preferHttp11))); + HttpClient sharedHttpClient = new HttpClient(newTransport(sslContextFactory, preferHttp11)); + WebSocketClient sharedWebSocketClient = new WebSocketClient(new HttpClient(newTransport(sslContextFactory, preferHttp11))); sharedWebSocketClient.setIdleTimeout(Duration.ZERO); if (connectTimeout != null) { sharedHttpClient.setConnectTimeout(connectTimeout.toMillis()); sharedWebSocketClient.setConnectTimeout(connectTimeout.toMillis()); } - sharedHttpClient.setFollowRedirects(followAllRedirects); + sharedHttpClient.setFollowRedirects(followRedirects); if (proxyAddress != null) { - sharedHttpClient.getProxyConfiguration().getProxies().add(new HttpProxy(proxyAddress, false)); + sharedHttpClient.getProxyConfiguration().getProxies() + .add(new HttpProxy(new Origin.Address(proxyAddress.getHostString(), proxyAddress.getPort()), false)); } if (proxyAddress != null && proxyAuthorization != null) { sharedHttpClient.getRequestListeners().add(new Request.Listener.Adapter() { @@ -91,82 +78,40 @@ public void onBegin(Request request) { } }); } - return new JettyHttpClient(this, sharedHttpClient, sharedWebSocketClient, interceptors.values(), factory); - } - - @Override - public JettyHttpClientBuilder connectTimeout(long connectTimeout, TimeUnit unit) { - this.connectTimeout = Duration.ofNanos(unit.toNanos(connectTimeout)); - return this; - } - - @Override - public JettyHttpClientBuilder sslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) { - this.sslContext = SSLUtils.sslContext(keyManagers, trustManagers); - return this; - } - - @Override - public JettyHttpClientBuilder followAllRedirects() { - followAllRedirects = true; - return this; + return new JettyHttpClient(this, sharedHttpClient, sharedWebSocketClient, interceptors.values(), clientFactory, + requestConfig); } - @Override - public JettyHttpClientBuilder proxyAddress(InetSocketAddress proxyAddress) { - if (proxyAddress == null) { - this.proxyAddress = null; + private static HttpClientTransport newTransport(SslContextFactory.Client sslContextFactory, boolean preferHttp11) { + final var clientConnector = new ClientConnector(); + clientConnector.setSslContextFactory(sslContextFactory); + final HttpClientTransport transport; + if (preferHttp11) { + transport = new HttpClientTransportOverHTTP(clientConnector); } else { - this.proxyAddress = new Origin.Address(proxyAddress.getHostString(), proxyAddress.getPort()); + var http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)); + transport = new HttpClientTransportDynamic(clientConnector, http2, HttpClientConnectionFactory.HTTP11); } - return this; + return transport; } @Override - public JettyHttpClientBuilder proxyAuthorization(String credentials) { - proxyAuthorization = credentials; - return this; + protected JettyHttpClientBuilder newInstance(JettyHttpClientFactory clientFactory) { + return new JettyHttpClientBuilder(clientFactory); } @Override - public JettyHttpClientBuilder tlsVersions(TlsVersion... tlsVersions) { - this.tlsVersions = tlsVersions; - return this; + public Duration getReadTimeout() { + return Optional.ofNullable(readTimeout).orElse(Duration.ZERO); } @Override - public JettyHttpClientBuilder preferHttp11() { - preferHttp11 = true; - return this; - } - - public Builder copy() { - final var ret = new JettyHttpClientBuilder(factory); - ret.sharedHttpClient = sharedHttpClient; - ret.sharedWebSocketClient = sharedWebSocketClient; - ret.readTimeout = readTimeout; - ret.writeTimeout = writeTimeout; - ret.interceptors.putAll(interceptors); - ret.connectTimeout = connectTimeout; - ret.sslContext = sslContext; - ret.followAllRedirects = followAllRedirects; - ret.proxyAddress = proxyAddress; - ret.proxyAuthorization = proxyAuthorization; - ret.tlsVersions = tlsVersions; - ret.preferHttp11 = preferHttp11; - return ret; + public Duration getWriteTimeout() { + return Optional.ofNullable(writeTimeout).orElse(Duration.ZERO); } - private static HttpClientTransport newTransport(SslContextFactory.Client sslContextFactory, boolean preferHttp11) { - final var clientConnector = new ClientConnector(); - clientConnector.setSslContextFactory(sslContextFactory); - final HttpClientTransport transport; - if (preferHttp11) { - transport = new HttpClientTransportOverHTTP(clientConnector); - } else { - var http2 = new ClientConnectionFactoryOverHTTP2.HTTP2(new HTTP2Client(clientConnector)); - transport = new HttpClientTransportDynamic(clientConnector, http2, HttpClientConnectionFactory.HTTP11); - } - return transport; + @Override + public Duration getConnectTimeout() { + return Optional.ofNullable(connectTimeout).orElse(Duration.ZERO); } } diff --git a/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientFactory.java b/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientFactory.java index aabece1290..68c78505f7 100644 --- a/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientFactory.java +++ b/httpclient-jetty/src/main/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientFactory.java @@ -15,20 +15,10 @@ */ package io.fabric8.kubernetes.client.jetty; -import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.http.HttpClient; -import static io.fabric8.kubernetes.client.utils.HttpClientUtils.applyCommonConfiguration; - public class JettyHttpClientFactory implements HttpClient.Factory { - @Override - public HttpClient createHttpClient(Config config) { - final var builder = newBuilder(); - applyCommonConfiguration(config, builder, this); - return builder.build(); - } - @Override public JettyHttpClientBuilder newBuilder() { return new JettyHttpClientBuilder(this); diff --git a/httpclient-jetty/src/test/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientFactoryTest.java b/httpclient-jetty/src/test/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientFactoryTest.java index dcb8403ab0..19f74c1d66 100644 --- a/httpclient-jetty/src/test/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientFactoryTest.java +++ b/httpclient-jetty/src/test/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientFactoryTest.java @@ -27,7 +27,7 @@ class JettyHttpClientFactoryTest { @DisplayName("createHttpClient instantiates a JettyHttpClient") void createHttpClientInstantiatesJettyHttpClient() { // When - try (var result = new JettyHttpClientFactory().createHttpClient(Config.empty())) { + try (var result = new JettyHttpClientFactory().newBuilder(Config.empty()).build()) { // Then assertThat(result).isNotNull().isInstanceOf(JettyHttpClient.class); } diff --git a/httpclient-jetty/src/test/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientTest.java b/httpclient-jetty/src/test/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientTest.java index c701fa23fd..fdf80f1b63 100644 --- a/httpclient-jetty/src/test/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientTest.java +++ b/httpclient-jetty/src/test/java/io/fabric8/kubernetes/client/jetty/JettyHttpClientTest.java @@ -15,6 +15,8 @@ */ package io.fabric8.kubernetes.client.jetty; +import io.fabric8.kubernetes.client.http.HttpClient.DerivedClientBuilder; +import io.fabric8.kubernetes.client.http.StandardHttpClientBuilder; import io.fabric8.kubernetes.client.http.TestHttpRequest; import io.fabric8.kubernetes.client.http.TlsVersion; import org.eclipse.jetty.client.HttpClient; @@ -53,7 +55,7 @@ void tearDown() throws Exception { @DisplayName("close, should close all underlying clients") void closeShouldCloseClients() { try (var jettyHttpClient = new JettyHttpClient( - null, httpClient, webSocketClient, Collections.emptyList(), null)) { + null, httpClient, webSocketClient, Collections.emptyList(), null, null)) { // When jettyHttpClient.close(); // Then @@ -73,27 +75,25 @@ void newBuilderInstantiatesJettyHttpClientBuilderWithSameSettings() throws Excep .tlsVersions(TlsVersion.SSL_3_0) .followAllRedirects(); try (var firstClient = new JettyHttpClient( - originalBuilder, httpClient, webSocketClient, Collections.emptyList(), null)) { + originalBuilder, httpClient, webSocketClient, Collections.emptyList(), null, null)) { // When final var result = firstClient.newBuilder() .readTimeout(313373, TimeUnit.SECONDS); // Then assertThat(result) .isNotNull() - .isInstanceOf(DerivedJettyHttpClientBuilder.class) + .isInstanceOf(DerivedClientBuilder.class) .isNotSameAs(originalBuilder); final var expected = Map.of( - "tlsVersions", new TlsVersion[] { TlsVersion.SSL_3_0 }, - "followAllRedirects", true); + "getTlsVersions", new TlsVersion[] { TlsVersion.SSL_3_0 }, + "isFollowRedirects", true); for (var entry : expected.entrySet()) { - final var field = JettyHttpClientBuilder.class.getDeclaredField(entry.getKey()); - field.setAccessible(true); - assertThat(field.get(result)) - .isEqualTo(field.get(originalBuilder)) + final var method = StandardHttpClientBuilder.class.getMethod(entry.getKey()); + assertThat(method.invoke(result)) + .isEqualTo(method.invoke(originalBuilder)) .isEqualTo(entry.getValue()); - field.setAccessible(false); } - var readTimeout = DerivedJettyHttpClientBuilder.class.getDeclaredField("readTimeout"); + var readTimeout = StandardHttpClientBuilder.class.getDeclaredField("readTimeout"); readTimeout.setAccessible(true); assertThat(readTimeout.get(result)).isEqualTo(Duration.ofSeconds(313373)); assertThat(readTimeout.get(originalBuilder)).isEqualTo(Duration.ofSeconds(1337)); @@ -105,7 +105,7 @@ void newBuilderInstantiatesJettyHttpClientBuilderWithSameSettings() throws Excep @DisplayName("sendAsync with unsupported type throws Exception") void sendAsyncUnsupportedType() { try (var jettyHttpClient = new JettyHttpClient( - null, httpClient, webSocketClient, Collections.emptyList(), null)) { + null, httpClient, webSocketClient, Collections.emptyList(), null, null)) { // When final var result = assertThrows(IllegalArgumentException.class, () -> jettyHttpClient.sendAsync(null, Integer.class)); @@ -118,7 +118,7 @@ void sendAsyncUnsupportedType() { @DisplayName("sendAsync with unsupported HttpRequest throws Exception") void sendAsyncUnsupportedHttpRequest() { try (var jettyHttpClient = new JettyHttpClient( - new JettyHttpClientBuilder(null), httpClient, webSocketClient, Collections.emptyList(), null)) { + new JettyHttpClientBuilder(null), httpClient, webSocketClient, Collections.emptyList(), null, null)) { // When final var request = new TestHttpRequest(); final var result = assertThrows(IllegalArgumentException.class, @@ -132,7 +132,7 @@ void sendAsyncUnsupportedHttpRequest() { @DisplayName("newWebSocketBuilder instantiates a JettyWebSocketBuilder") void newWebSocketBuilderInstantiatesJettyWebSocketBuilder() { try (var jettyHttpClient = new JettyHttpClient( - new JettyHttpClientBuilder(null), httpClient, webSocketClient, Collections.emptyList(), null)) { + new JettyHttpClientBuilder(null), httpClient, webSocketClient, Collections.emptyList(), null, null)) { // When final var result = jettyHttpClient.newWebSocketBuilder(); // Then @@ -146,7 +146,7 @@ void getFactoryReturnsOriginal() { // Given final var factory = new JettyHttpClientFactory(); try (var jettyHttpClient = new JettyHttpClient( - null, httpClient, webSocketClient, Collections.emptyList(), factory)) { + null, httpClient, webSocketClient, Collections.emptyList(), factory, null)) { // When final var f1 = jettyHttpClient.getFactory(); final var f2 = jettyHttpClient.getFactory(); diff --git a/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientBuilderImpl.java b/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientBuilderImpl.java index 36d37117f7..f52e061c4d 100644 --- a/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientBuilderImpl.java +++ b/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientBuilderImpl.java @@ -16,8 +16,10 @@ package io.fabric8.kubernetes.client.okhttp; +import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.http.HttpClient.Builder; +import io.fabric8.kubernetes.client.http.HttpClient.DerivedClientBuilder; import io.fabric8.kubernetes.client.http.TlsVersion; import io.fabric8.kubernetes.client.internal.SSLUtils; import io.fabric8.kubernetes.client.okhttp.OkHttpClientImpl.OkHttpResponseImpl; @@ -61,13 +63,16 @@ static final class InteceptorAdapter implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request.Builder requestBuilder = chain.request().newBuilder(); + Config config = chain.request().tag(Config.class); OkHttpRequestImpl.BuilderImpl builderImpl = new OkHttpRequestImpl.BuilderImpl(requestBuilder); - interceptor.before(new OkHttpRequestImpl.BuilderImpl(requestBuilder), new OkHttpRequestImpl(chain.request())); + io.fabric8.kubernetes.client.http.Interceptor.useConfig(interceptor, config) + .before(new OkHttpRequestImpl.BuilderImpl(requestBuilder), new OkHttpRequestImpl(chain.request())); Response response = chain.proceed(requestBuilder.build()); if (!response.isSuccessful()) { // for okhttp this token refresh will be blocking try { - boolean call = interceptor.afterFailure(builderImpl, new OkHttpResponseImpl<>(response, InputStream.class)).get(); + boolean call = io.fabric8.kubernetes.client.http.Interceptor.useConfig(interceptor, config) + .afterFailure(builderImpl, new OkHttpResponseImpl<>(response, InputStream.class)).get(); if (call) { response.close(); return chain.proceed(requestBuilder.build()); @@ -87,10 +92,12 @@ public String getName() { private boolean streaming; private OkHttpClient.Builder builder; private OkHttpClientFactory factory; + private Config config; - public OkHttpClientBuilderImpl(okhttp3.OkHttpClient.Builder newBuilder, OkHttpClientFactory factory) { + public OkHttpClientBuilderImpl(okhttp3.OkHttpClient.Builder newBuilder, OkHttpClientFactory factory, Config config) { this.builder = newBuilder; this.factory = factory; + this.config = config; } @Override @@ -108,7 +115,7 @@ public OkHttpClientImpl build() { } } - return new OkHttpClientImpl(client, factory); + return new OkHttpClientImpl(client, factory, config); } @Override @@ -217,4 +224,10 @@ public Builder preferHttp11() { return this; } + @Override + public DerivedClientBuilder requestConfig(Config config) { + this.config = config; + return this; + } + } diff --git a/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientFactory.java b/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientFactory.java index 63c7a8c19c..330fe5c5e4 100644 --- a/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientFactory.java +++ b/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientFactory.java @@ -46,7 +46,7 @@ protected OkHttpClient.Builder newOkHttpClientBuilder() { /** * Subclasses may use this to apply additional configuration after the Config has been applied * This method is only called for clients constructed using the Config. - * + * * @param builder */ protected void additionalConfig(OkHttpClient.Builder builder) { @@ -55,17 +55,17 @@ protected void additionalConfig(OkHttpClient.Builder builder) { @Override public Builder newBuilder() { - return new OkHttpClientBuilderImpl(newOkHttpClientBuilder(), this); + return new OkHttpClientBuilderImpl(newOkHttpClientBuilder(), this, null); } /** - * Creates an HTTP client configured to access the Kubernetes API. - * + * Creates an HTTP client builder configured to access the Kubernetes API. + * * @param config Kubernetes API client config - * @return returns an HTTP client + * @return returns an HTTP client builder */ @Override - public OkHttpClientImpl createHttpClient(Config config) { + public OkHttpClientBuilderImpl newBuilder(Config config) { try { OkHttpClient.Builder httpClientBuilder = newOkHttpClientBuilder(); @@ -91,7 +91,7 @@ public OkHttpClientImpl createHttpClient(Config config) { httpClientBuilder.dispatcher(dispatcher); } - OkHttpClientBuilderImpl builderWrapper = new OkHttpClientBuilderImpl(httpClientBuilder, this); + OkHttpClientBuilderImpl builderWrapper = new OkHttpClientBuilderImpl(httpClientBuilder, this, null); HttpClientUtils.applyCommonConfiguration(config, builderWrapper, this); @@ -101,7 +101,7 @@ public OkHttpClientImpl createHttpClient(Config config) { additionalConfig(httpClientBuilder); - return builderWrapper.build(); + return builderWrapper; } catch (Exception e) { throw KubernetesClientException.launderThrowable(e); } diff --git a/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientImpl.java b/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientImpl.java index 820fdf54c4..c988720e03 100644 --- a/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientImpl.java +++ b/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpClientImpl.java @@ -16,6 +16,7 @@ package io.fabric8.kubernetes.client.okhttp; +import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.http.HttpClient; import io.fabric8.kubernetes.client.http.HttpRequest; import io.fabric8.kubernetes.client.http.HttpResponse; @@ -26,6 +27,7 @@ import okhttp3.Dispatcher; import okhttp3.MediaType; import okhttp3.OkHttpClient; +import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; import okio.BufferedSource; @@ -175,10 +177,12 @@ public Map> headers() { private final okhttp3.OkHttpClient httpClient; private final OkHttpClientFactory factory; + private final Config config; - public OkHttpClientImpl(OkHttpClient httpClient, OkHttpClientFactory factory) { + public OkHttpClientImpl(OkHttpClient httpClient, OkHttpClientFactory factory, Config config) { this.httpClient = httpClient; this.factory = factory; + this.config = config; } @Override @@ -201,8 +205,8 @@ public void close() { } @Override - public Builder newBuilder() { - return new OkHttpClientBuilderImpl(httpClient.newBuilder(), this.factory); + public DerivedClientBuilder newBuilder() { + return new OkHttpClientBuilderImpl(httpClient.newBuilder(), this.factory, this.config); } @Override @@ -288,7 +292,7 @@ public void onFailure(Call call, IOException e) { @Override public io.fabric8.kubernetes.client.http.WebSocket.Builder newWebSocketBuilder() { - return new OkHttpWebSocketImpl.BuilderImpl(this.httpClient); + return new OkHttpWebSocketImpl.BuilderImpl(this.httpClient, newRequestBuilder()); } public okhttp3.OkHttpClient getOkHttpClient() { @@ -297,12 +301,11 @@ public okhttp3.OkHttpClient getOkHttpClient() { @Override public HttpRequest.Builder newHttpRequestBuilder() { - return new OkHttpRequestImpl.BuilderImpl(); + return new OkHttpRequestImpl.BuilderImpl(newRequestBuilder()); } - @Override - public Factory getFactory() { - return this.factory; + private okhttp3.Request.Builder newRequestBuilder() { + return new Request.Builder().tag(Config.class, config); } } diff --git a/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpRequestImpl.java b/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpRequestImpl.java index cb65a79c64..841dd5ec18 100644 --- a/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpRequestImpl.java +++ b/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpRequestImpl.java @@ -40,10 +40,6 @@ static class BuilderImpl implements Builder { private final Request.Builder builder; - public BuilderImpl() { - this(new Request.Builder()); - } - public BuilderImpl(Request.Builder builder) { this.builder = builder; } diff --git a/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpWebSocketImpl.java b/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpWebSocketImpl.java index 5e11cc2547..d47f545d78 100644 --- a/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpWebSocketImpl.java +++ b/httpclient-okhttp/src/main/java/io/fabric8/kubernetes/client/okhttp/OkHttpWebSocketImpl.java @@ -40,8 +40,9 @@ static class BuilderImpl implements WebSocket.Builder { private Request.Builder builder = new Request.Builder(); private OkHttpClient httpClient; - public BuilderImpl(OkHttpClient httpClient) { + public BuilderImpl(OkHttpClient httpClient, okhttp3.Request.Builder builder) { this.httpClient = httpClient; + this.builder = builder; } @Override diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/Config.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/Config.java index 314f4d32c7..7500c2c690 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/Config.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/Config.java @@ -357,7 +357,7 @@ public Config(String masterUrl, String apiVersion, String namespace, boolean tru this.requestConfig = new RequestConfig(username, password, oauthToken, watchReconnectLimit, watchReconnectInterval, connectionTimeout, rollingTimeout, requestTimeout, scaleTimeout, loggingInterval, websocketTimeout, - websocketPingInterval, maxConcurrentRequests, maxConcurrentRequestsPerHost, oauthTokenProvider, + websocketPingInterval, oauthTokenProvider, requestRetryBackoffLimit, requestRetryBackoffInterval, uploadConnectionTimeout, uploadRequestTimeout); this.requestConfig.setImpersonateUsername(impersonateUsername); this.requestConfig.setImpersonateGroups(impersonateGroups); @@ -383,6 +383,8 @@ public Config(String masterUrl, String apiVersion, String namespace, boolean tru //We set the masterUrl because it's needed by ensureHttps this.masterUrl = masterUrl; this.masterUrl = ensureEndsWithSlash(ensureHttps(masterUrl, this)); + this.maxConcurrentRequests = maxConcurrentRequests; + this.maxConcurrentRequestsPerHost = maxConcurrentRequestsPerHost; } public static void configFromSysPropsOrEnvVars(Config config) { @@ -1268,19 +1270,19 @@ public void setWebsocketPingInterval(long websocketPingInterval) { } public int getMaxConcurrentRequests() { - return getRequestConfig().getMaxConcurrentRequests(); + return maxConcurrentRequests; } public void setMaxConcurrentRequests(int maxConcurrentRequests) { - this.requestConfig.setMaxConcurrentRequests(maxConcurrentRequests); + this.maxConcurrentRequests = maxConcurrentRequests; } public int getMaxConcurrentRequestsPerHost() { - return getRequestConfig().getMaxConcurrentRequestsPerHost(); + return maxConcurrentRequestsPerHost; } public void setMaxConcurrentRequestsPerHost(int maxConcurrentRequestsPerHost) { - this.requestConfig.setMaxConcurrentRequestsPerHost(maxConcurrentRequestsPerHost); + this.maxConcurrentRequestsPerHost = maxConcurrentRequestsPerHost; } @JsonProperty("proxyUsername") @@ -1347,11 +1349,11 @@ public String getKeyStoreFile() { @JsonIgnore public OAuthTokenProvider getOauthTokenProvider() { - return oauthTokenProvider; + return this.getRequestConfig().getOauthTokenProvider(); } public void setOauthTokenProvider(OAuthTokenProvider oauthTokenProvider) { - this.oauthTokenProvider = oauthTokenProvider; + this.requestConfig.setOauthTokenProvider(oauthTokenProvider); } @JsonProperty("customHeaders") diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/DefaultKubernetesClient.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/DefaultKubernetesClient.java index 3239e46781..292aaf456d 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/DefaultKubernetesClient.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/DefaultKubernetesClient.java @@ -19,6 +19,7 @@ import io.fabric8.kubernetes.client.http.HttpClient; import io.fabric8.kubernetes.client.http.HttpClient.Builder; import io.fabric8.kubernetes.client.http.HttpClient.Factory; +import io.fabric8.kubernetes.client.http.StandardHttpClientBuilder; import io.fabric8.kubernetes.client.utils.HttpClientUtils; import io.fabric8.kubernetes.client.utils.Serialization; @@ -64,18 +65,34 @@ public DefaultKubernetesClient(HttpClient httpClient, Config config, ExecutorSup KubernetesClientBuilder builder = new KubernetesClientBuilder().withConfig(config) .withTaskExecutorSupplier(executorSupplier); if (httpClient != null) { + // this reads a little oddly, but it supplies the given HttpClient via the factory. + // no further configuration is performed + // an alternative would be to add back a factory method that returns the client, or to allow the HttpClient to be set directly on the KubernetesClientBuilder builder.withHttpClientFactory(new Factory() { @Override public Builder newBuilder() { - // should not be called throw new UnsupportedOperationException(); } @Override - public HttpClient createHttpClient(Config config) { - return httpClient; + public Builder newBuilder(Config config) { + return new StandardHttpClientBuilder>( + null) { + + @Override + public HttpClient build() { + return httpClient; + } + + @Override + protected StandardHttpClientBuilder> newInstance( + Factory clientFactory) { + return null; + } + }; } + }); } this.init(builder.build()); diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/KubernetesClientBuilder.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/KubernetesClientBuilder.java index 9e9b3a54a5..d0c952a993 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/KubernetesClientBuilder.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/KubernetesClientBuilder.java @@ -18,11 +18,13 @@ import io.fabric8.kubernetes.client.http.HttpClient; import io.fabric8.kubernetes.client.informers.ResourceEventHandler; +import io.fabric8.kubernetes.client.utils.HttpClientUtils; import io.fabric8.kubernetes.client.utils.Serialization; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -44,6 +46,7 @@ default void onClose(Executor executor) { private HttpClient.Factory factory; private Class clazz; private ExecutorSupplier executorSupplier; + private Consumer builderConsumer; public KubernetesClientBuilder() { // basically the same logic as in KubernetesResourceUtil for finding list types @@ -60,15 +63,19 @@ public KubernetesClientBuilder() { } } + KubernetesClientBuilder(Class clazz) { + this.clazz = clazz; + } + public KubernetesClient build() { if (config == null) { config = new ConfigBuilder().build(); } try { if (factory == null) { - return clazz.getConstructor(Config.class).newInstance(config); + this.factory = HttpClientUtils.getHttpClientFactory(); } - HttpClient client = factory.createHttpClient(config); + HttpClient client = getHttpClient(); return clazz.getConstructor(HttpClient.class, Config.class, ExecutorSupplier.class).newInstance(client, config, executorSupplier); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException @@ -77,6 +84,14 @@ public KubernetesClient build() { } } + HttpClient getHttpClient() { + HttpClient.Builder builder = factory.newBuilder(config); + if (this.builderConsumer != null) { + this.builderConsumer.accept(builder); + } + return builder.build(); + } + public KubernetesClientBuilder withConfig(Config config) { this.config = config; return this; @@ -125,4 +140,15 @@ public KubernetesClientBuilder withTaskExecutorSupplier(ExecutorSupplier executo return this; } + /** + * Provide additional configuration for the {@link HttpClient} that is created for this {@link KubernetesClient}. + * + * @param consumer to modify the {@link HttpClient.Builder} + * @return this builder + */ + public KubernetesClientBuilder withHttpClientBuilderConsumer(Consumer consumer) { + this.builderConsumer = consumer; + return this; + } + } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/RequestConfig.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/RequestConfig.java index ebe86e31da..628629a9f4 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/RequestConfig.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/RequestConfig.java @@ -24,8 +24,6 @@ import java.util.Map; import static io.fabric8.kubernetes.client.Config.DEFAULT_LOGGING_INTERVAL; -import static io.fabric8.kubernetes.client.Config.DEFAULT_MAX_CONCURRENT_REQUESTS; -import static io.fabric8.kubernetes.client.Config.DEFAULT_MAX_CONCURRENT_REQUESTS_PER_HOST; import static io.fabric8.kubernetes.client.Config.DEFAULT_REQUEST_RETRY_BACKOFFINTERVAL; import static io.fabric8.kubernetes.client.Config.DEFAULT_REQUEST_RETRY_BACKOFFLIMIT; import static io.fabric8.kubernetes.client.Config.DEFAULT_ROLLING_TIMEOUT; @@ -59,8 +57,6 @@ public class RequestConfig { private int loggingInterval = DEFAULT_LOGGING_INTERVAL; private long websocketTimeout = DEFAULT_WEBSOCKET_TIMEOUT; private long websocketPingInterval = DEFAULT_WEBSOCKET_PING_INTERVAL; - private int maxConcurrentRequests = DEFAULT_MAX_CONCURRENT_REQUESTS; - private int maxConcurrentRequestsPerHost = DEFAULT_MAX_CONCURRENT_REQUESTS_PER_HOST; RequestConfig() { } @@ -90,7 +86,7 @@ public RequestConfig(String username, String password, String oauthToken, int maxConcurrentRequests, int maxConcurrentRequestsPerHost) { this(username, password, oauthToken, watchReconnectLimit, watchReconnectInterval, connectionTimeout, rollingTimeout, requestTimeout, scaleTimeout, loggingInterval, - websocketTimeout, websocketPingInterval, maxConcurrentRequests, maxConcurrentRequestsPerHost, null, + websocketTimeout, websocketPingInterval, null, DEFAULT_REQUEST_RETRY_BACKOFFLIMIT, DEFAULT_REQUEST_RETRY_BACKOFFINTERVAL, DEFAULT_UPLOAD_CONNECTION_TIMEOUT, DEFAULT_UPLOAD_REQUEST_TIMEOUT); } @@ -100,7 +96,7 @@ public RequestConfig(String username, String password, String oauthToken, int watchReconnectLimit, int watchReconnectInterval, int connectionTimeout, long rollingTimeout, int requestTimeout, long scaleTimeout, int loggingInterval, long websocketTimeout, long websocketPingInterval, - int maxConcurrentRequests, int maxConcurrentRequestsPerHost, OAuthTokenProvider oauthTokenProvider, + OAuthTokenProvider oauthTokenProvider, int requestRetryBackoffLimit, int requestRetryBackoffInterval, int uploadConnectionTimeout, int uploadRequestTimeout) { this.username = username; this.oauthToken = oauthToken; @@ -114,8 +110,6 @@ public RequestConfig(String username, String password, String oauthToken, this.websocketTimeout = websocketTimeout; this.loggingInterval = loggingInterval; this.websocketPingInterval = websocketPingInterval; - this.maxConcurrentRequests = maxConcurrentRequests; - this.maxConcurrentRequestsPerHost = maxConcurrentRequestsPerHost; this.oauthTokenProvider = oauthTokenProvider; this.requestRetryBackoffLimit = requestRetryBackoffLimit; this.requestRetryBackoffInterval = requestRetryBackoffInterval; @@ -262,22 +256,6 @@ public void setWebsocketPingInterval(long websocketPingInterval) { this.websocketPingInterval = websocketPingInterval; } - public int getMaxConcurrentRequests() { - return maxConcurrentRequests; - } - - public void setMaxConcurrentRequests(int maxConcurrentRequests) { - this.maxConcurrentRequests = maxConcurrentRequests; - } - - public int getMaxConcurrentRequestsPerHost() { - return maxConcurrentRequestsPerHost; - } - - public void setMaxConcurrentRequestsPerHost(int maxConcurrentRequestsPerHost) { - this.maxConcurrentRequestsPerHost = maxConcurrentRequestsPerHost; - } - public void setImpersonateUsername(String impersonateUsername) { this.impersonateUsername = impersonateUsername; } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/WithRequestCallable.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/WithRequestCallable.java index 5c26b57717..1df0782468 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/WithRequestCallable.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/WithRequestCallable.java @@ -31,8 +31,7 @@ public WithRequestCallable(C client, RequestConfig requestConfig) { @Override public O call(Function function) { - try (C newClient = (C) this.client.newClient(requestConfig).adapt(this.client.getClass())) { - return function.apply(newClient); - } + C newClient = (C) this.client.newClient(requestConfig).adapt(this.client.getClass()); + return function.apply(newClient); } } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/HttpClient.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/HttpClient.java index 88bc7b9177..ff17fce708 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/HttpClient.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/HttpClient.java @@ -17,6 +17,8 @@ package io.fabric8.kubernetes.client.http; import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.RequestConfig; +import io.fabric8.kubernetes.client.utils.HttpClientUtils; import java.io.BufferedReader; import java.net.InetSocketAddress; @@ -32,7 +34,18 @@ public interface HttpClient extends AutoCloseable { interface Factory { - HttpClient createHttpClient(Config config); + /** + * Create a builder that is customized by the {@link Config}. By default it + * will apply the common configuration {@link HttpClientUtils#applyCommonConfiguration(Config, Builder, Factory)} + * + * @param config the configuration to apply + * @return the configured {@link Builder} + */ + default HttpClient.Builder newBuilder(Config config) { + Builder builder = newBuilder(); + HttpClientUtils.applyCommonConfiguration(config, builder, this); + return builder; + } HttpClient.Builder newBuilder(); @@ -74,6 +87,14 @@ interface DerivedClientBuilder { * @return this Builder instance. */ DerivedClientBuilder authenticatorNone(); + + /** + * Supply an {@link RequestConfig} via a {@link Config} to {@link Interceptor#withConfig(Config)} + * + * @param config + * @return this Builder instance. + */ + DerivedClientBuilder requestConfig(Config config); } interface Builder extends DerivedClientBuilder { @@ -208,6 +229,4 @@ interface BodyConsumer { HttpRequest.Builder newHttpRequestBuilder(); - HttpClient.Factory getFactory(); - } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/Interceptor.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/Interceptor.java index e06b5e7280..9d299ef862 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/Interceptor.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/Interceptor.java @@ -16,13 +16,34 @@ package io.fabric8.kubernetes.client.http; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.RequestConfig; + import java.util.concurrent.CompletableFuture; public interface Interceptor { + /** + * {@link Interceptor}s that rely upon the {@link Config}, in particular the {@link RequestConfig}, must implement + * this method to receive the modified configuration + * + * @param config + * @return + */ + default Interceptor withConfig(Config config) { + return this; + } + + static Interceptor useConfig(Interceptor interceptor, Config config) { + if (config == null) { + return interceptor; + } + return interceptor.withConfig(config); + } + /** * Called before a request to allow for the manipulation of the request - * + * * @param builder used to modify the request * @param headers the current headers */ @@ -31,7 +52,7 @@ default void before(BasicBuilder builder, HttpHeaders headers) { /** * Called after a websocket failure or by default from a normal request - * + * * @param builder used to modify the request * @param response the failed response * @return true if the builder should be used to execute a new request @@ -42,7 +63,7 @@ default CompletableFuture afterFailure(BasicBuilder builder, HttpRespon /** * Called after a non-websocket failure - * + * * @param builder used to modify the request * @param response the failed response * @return true if the builder should be used to execute a new request diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/StandardHttpClientBuilder.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/StandardHttpClientBuilder.java new file mode 100644 index 0000000000..9bb7832854 --- /dev/null +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/http/StandardHttpClientBuilder.java @@ -0,0 +1,164 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.kubernetes.client.http; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.internal.SSLUtils; +import lombok.Getter; + +import java.net.InetSocketAddress; +import java.time.Duration; +import java.util.LinkedHashMap; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +@SuppressWarnings("unchecked") +@Getter +public abstract class StandardHttpClientBuilder> + implements HttpClient.Builder { + + protected LinkedHashMap interceptors = new LinkedHashMap<>(); + protected Duration connectTimeout; + protected Duration readTimeout; + protected Duration writeTimeout; + protected SSLContext sslContext; + protected String proxyAuthorization; + protected InetSocketAddress proxyAddress; + protected boolean followRedirects; + protected boolean preferHttp11; + protected TlsVersion[] tlsVersions; + protected boolean forStreaming; + protected boolean authenticatorNone; + protected Config requestConfig; + protected C client; + protected F clientFactory; + + protected StandardHttpClientBuilder(F clientFactory) { + this.clientFactory = clientFactory; + } + + @Override + public T readTimeout(long readTimeout, TimeUnit unit) { + this.readTimeout = Duration.ofNanos(unit.toNanos(readTimeout)); + return (T) this; + } + + @Override + public T writeTimeout(long writeTimeout, TimeUnit unit) { + this.writeTimeout = Duration.ofNanos(unit.toNanos(writeTimeout)); + return (T) this; + } + + @Override + public T connectTimeout(long connectTimeout, TimeUnit unit) { + this.connectTimeout = Duration.ofNanos(unit.toNanos(connectTimeout)); + return (T) this; + } + + @Override + public T forStreaming() { + this.forStreaming = true; + return (T) this; + } + + @Override + public T addOrReplaceInterceptor(String name, Interceptor interceptor) { + if (interceptor == null) { + interceptors.remove(name); + } else { + interceptors.put(name, interceptor); + } + return (T) this; + } + + @Override + public T authenticatorNone() { + this.authenticatorNone = true; + return (T) this; + } + + @Override + public T sslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) { + this.sslContext = SSLUtils.sslContext(keyManagers, trustManagers); + return (T) this; + } + + @Override + public T followAllRedirects() { + this.followRedirects = true; + return (T) this; + } + + @Override + public T proxyAddress(InetSocketAddress proxyAddress) { + this.proxyAddress = proxyAddress; + return (T) this; + } + + @Override + public T proxyAuthorization(String credentials) { + this.proxyAuthorization = credentials; + return (T) this; + } + + @Override + public T tlsVersions(TlsVersion... tlsVersions) { + this.tlsVersions = tlsVersions; + return (T) this; + } + + @Override + public T requestConfig(Config requestConfig) { + this.requestConfig = requestConfig; + return (T) this; + } + + @Override + public T preferHttp11() { + this.preferHttp11 = true; + return (T) this; + } + + public T clientFactory(F clientFactory) { + this.clientFactory = clientFactory; + return (T) this; + } + + protected abstract T newInstance(F clientFactory); + + public T copy(C client) { + T copy = newInstance(clientFactory); + copy.connectTimeout = this.connectTimeout; + copy.readTimeout = this.readTimeout; + copy.sslContext = this.sslContext; + copy.interceptors = new LinkedHashMap<>(this.interceptors); + copy.proxyAddress = this.proxyAddress; + copy.proxyAuthorization = this.proxyAuthorization; + copy.tlsVersions = this.tlsVersions; + copy.preferHttp11 = this.preferHttp11; + copy.followRedirects = this.followRedirects; + copy.authenticatorNone = this.authenticatorNone; + copy.writeTimeout = this.writeTimeout; + copy.requestConfig = this.requestConfig; + copy.client = client; + return copy; + } + +} diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/HttpClientUtils.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/HttpClientUtils.java index 486894f9c7..23ddc59ada 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/HttpClientUtils.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/HttpClientUtils.java @@ -47,6 +47,36 @@ public class HttpClientUtils { + private static final class HeaderInterceptor implements Interceptor { + private final Config config; + + private HeaderInterceptor(Config config) { + this.config = config; + } + + @Override + public Interceptor withConfig(Config config) { + return new HeaderInterceptor(config); + } + + @Override + public void before(BasicBuilder builder, HttpHeaders headers) { + if (Utils.isNotNullOrEmpty(config.getUsername()) && Utils.isNotNullOrEmpty(config.getPassword())) { + builder.header("Authorization", basicCredentials(config.getUsername(), config.getPassword())); + } else if (Utils.isNotNullOrEmpty(config.getOauthToken())) { + builder.header("Authorization", "Bearer " + config.getOauthToken()); + } + if (config.getCustomHeaders() != null && !config.getCustomHeaders().isEmpty()) { + for (Map.Entry entry : config.getCustomHeaders().entrySet()) { + builder.header(entry.getKey(), entry.getValue()); + } + } + if (config.getUserAgent() != null && !config.getUserAgent().isEmpty()) { + builder.setHeader("User-Agent", config.getUserAgent()); + } + } + } + private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtils.class); private static final String HEADER_INTERCEPTOR = "HEADER"; private static final String KUBERNETES_BACKWARDS_COMPATIBILITY_INTERCEPTOR_DISABLE = "kubernetes.backwardsCompatibilityInterceptor.disable"; @@ -84,25 +114,7 @@ public static Map createA Map interceptors = new LinkedHashMap<>(); // Header Interceptor - interceptors.put(HEADER_INTERCEPTOR, new Interceptor() { - - @Override - public void before(BasicBuilder builder, HttpHeaders headers) { - if (Utils.isNotNullOrEmpty(config.getUsername()) && Utils.isNotNullOrEmpty(config.getPassword())) { - builder.header("Authorization", basicCredentials(config.getUsername(), config.getPassword())); - } else if (Utils.isNotNullOrEmpty(config.getOauthToken())) { - builder.header("Authorization", "Bearer " + config.getOauthToken()); - } - if (config.getCustomHeaders() != null && !config.getCustomHeaders().isEmpty()) { - for (Map.Entry entry : config.getCustomHeaders().entrySet()) { - builder.header(entry.getKey(), entry.getValue()); - } - } - if (config.getUserAgent() != null && !config.getUserAgent().isEmpty()) { - builder.setHeader("User-Agent", config.getUserAgent()); - } - } - }); + interceptors.put(HEADER_INTERCEPTOR, new HeaderInterceptor(config)); // Impersonator Interceptor interceptors.put(ImpersonatorInterceptor.NAME, new ImpersonatorInterceptor(config)); // Token Refresh Interceptor @@ -130,6 +142,11 @@ public static String basicCredentials(String username, String password) { */ @Deprecated public static HttpClient createHttpClient(Config config) { + HttpClient.Factory factory = getHttpClientFactory(); + return factory.newBuilder(config).build(); + } + + public static HttpClient.Factory getHttpClientFactory() { ServiceLoader loader = ServiceLoader.load(HttpClient.Factory.class); HttpClient.Factory factory = null; for (Iterator iter = loader.iterator(); iter.hasNext();) { @@ -147,7 +164,7 @@ public static HttpClient createHttpClient(Config config) { throw new KubernetesClientException( "No httpclient implementations found on the context classloader, please ensure your classpath includes an implementation jar"); } - return factory.createHttpClient(config); + return factory; } public static void applyCommonConfiguration(Config config, HttpClient.Builder builder, HttpClient.Factory factory) { diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/ImpersonatorInterceptor.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/ImpersonatorInterceptor.java index d51390d16a..0b53abd413 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/ImpersonatorInterceptor.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/ImpersonatorInterceptor.java @@ -28,14 +28,20 @@ import static io.fabric8.kubernetes.client.utils.Utils.isNotNullOrEmpty; public class ImpersonatorInterceptor implements Interceptor { - + public static final String NAME = "IMPERSONATOR"; - + private final Config config; + public ImpersonatorInterceptor(Config config) { this.config = config; } - + + @Override + public Interceptor withConfig(Config config) { + return new ImpersonatorInterceptor(config); + } + @Override public void before(BasicBuilder builder, HttpHeaders headers) { RequestConfig requestConfig = config.getRequestConfig(); @@ -55,7 +61,7 @@ public void before(BasicBuilder builder, HttpHeaders headers) { Collection keys = impersonateExtras.keySet(); for (Object key : keys) { List values = impersonateExtras.get(key); - if(values != null) { + if (values != null) { for (String value : values) { builder.header("Impersonate-Extra-" + key, value); } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/TokenRefreshInterceptor.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/TokenRefreshInterceptor.java index 2448ef8ceb..f4e4347059 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/TokenRefreshInterceptor.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/TokenRefreshInterceptor.java @@ -47,6 +47,11 @@ public TokenRefreshInterceptor(Config config, HttpClient.Factory factory, Instan this.factory = factory; } + @Override + public Interceptor withConfig(Config config) { + return new TokenRefreshInterceptor(config, factory, latestRefreshTimestamp); + } + @Override public void before(BasicBuilder headerBuilder, HttpHeaders headers) { if (isTimeToRefresh()) { diff --git a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/KubernetesClientBuilderTest.java b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/KubernetesClientBuilderTest.java new file mode 100644 index 0000000000..71ccbc3ea5 --- /dev/null +++ b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/KubernetesClientBuilderTest.java @@ -0,0 +1,37 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.fabric8.kubernetes.client; + +import io.fabric8.kubernetes.client.http.HttpClient; +import io.fabric8.kubernetes.client.http.HttpClient.Factory; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class KubernetesClientBuilderTest { + + @Test + void testHttpClientConfiguration() { + KubernetesClientBuilder builder = new KubernetesClientBuilder(null); + Factory mockFactory = Mockito.mock(HttpClient.Factory.class); + HttpClient.Builder mockBuilder = Mockito.mock(HttpClient.Builder.class); + Mockito.when(mockFactory.newBuilder(Mockito.any())).thenReturn(mockBuilder); + builder.withHttpClientFactory(mockFactory).withHttpClientBuilderConsumer(b -> b.proxyAuthorization("something")); + builder.getHttpClient(); + Mockito.verify(mockBuilder).proxyAuthorization("something"); + } + +} diff --git a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/http/AbstractInterceptorTest.java b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/http/AbstractInterceptorTest.java index 5ea4f843da..e380a93a9a 100644 --- a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/http/AbstractInterceptorTest.java +++ b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/http/AbstractInterceptorTest.java @@ -15,6 +15,7 @@ */ package io.fabric8.kubernetes.client.http; +import io.fabric8.kubernetes.client.Config; import io.fabric8.mockwebserver.DefaultMockServer; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -31,6 +32,28 @@ public abstract class AbstractInterceptorTest { + private static final class ConfigAwareInterceptor implements Interceptor { + + private Config config; + + @Override + public CompletableFuture afterFailure(BasicBuilder builder, HttpResponse response) { + String endpoint = "intercepted-url"; + if (config != null && config.getImpersonateUsername() != null) { + endpoint = config.getImpersonateUsername(); + } + builder.uri(URI.create(server.url("/" + endpoint))); + return CompletableFuture.completedFuture(true); + } + + @Override + public Interceptor withConfig(Config config) { + ConfigAwareInterceptor result = new ConfigAwareInterceptor(); + result.config = config; + return result; + } + } + private static DefaultMockServer server; @BeforeAll @@ -177,4 +200,28 @@ public void before(BasicBuilder builder, HttpHeaders headers) { assertThat(server.getLastRequest().getHeaders().toMultimap()) .containsEntry("test-header", Collections.singletonList("Test-Value-Override")); } + + @Test + @DisplayName("afterFailure sees the overriden RequestConfig") + public void afterHttpFailureSuppliedConfig() throws Exception { + // Given + server.expect().withPath("/intercepted-url").andReturn(200, "This works").once(); + server.expect().withPath("/other-url").andReturn(200, "Overriden").once(); + final HttpClient.Builder builder = getHttpClientFactory().newBuilder() + .addOrReplaceInterceptor("test", new ConfigAwareInterceptor()); + // When + try (HttpClient client = builder.build()) { + Config config = Config.empty(); + config.setImpersonateUsername("other-url"); + HttpClient derivedClient = client.newBuilder().requestConfig(config).build(); + + final HttpResponse result = derivedClient + .sendAsync(derivedClient.newHttpRequestBuilder().uri(server.url("/not-found")).build(), String.class) + .get(10L, TimeUnit.SECONDS); + // Then + assertThat(result) + .returns("Overriden", HttpResponse::body) + .returns(200, HttpResponse::code); + } + } } diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/BaseClient.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/BaseClient.java index 6237dd9367..8ff5dd2033 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/BaseClient.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/BaseClient.java @@ -86,7 +86,7 @@ public void onClose(Executor executor) { BaseClient(Config config, BaseClient baseClient) { this.config = config; - this.httpClient = baseClient.httpClient; + this.httpClient = baseClient.httpClient.newBuilder().requestConfig(config).build(); this.adapters = baseClient.adapters; this.handlers = baseClient.handlers; this.matchingGroupPredicate = baseClient.matchingGroupPredicate; diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/KubernetesClientImpl.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/KubernetesClientImpl.java index f475b9f3fb..35265ac631 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/KubernetesClientImpl.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/KubernetesClientImpl.java @@ -708,11 +708,7 @@ public NonNamespaceOperation>( + null) { + + @Override + public HttpClient build() { + return httpClient; + } + + @Override + protected StandardHttpClientBuilder> newInstance( + Factory clientFactory) { + return null; + } + }; } + }); } this.init(builder.build()); diff --git a/openshift-client/src/main/java/io/fabric8/openshift/client/internal/OpenShiftOAuthInterceptor.java b/openshift-client/src/main/java/io/fabric8/openshift/client/internal/OpenShiftOAuthInterceptor.java index 1fb0a39110..f7cb0f3122 100644 --- a/openshift-client/src/main/java/io/fabric8/openshift/client/internal/OpenShiftOAuthInterceptor.java +++ b/openshift-client/src/main/java/io/fabric8/openshift/client/internal/OpenShiftOAuthInterceptor.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.authorization.v1.SelfSubjectAccessReview; +import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.http.BasicBuilder; import io.fabric8.kubernetes.client.http.HttpClient; @@ -72,14 +73,19 @@ public class OpenShiftOAuthInterceptor implements Interceptor { HasMetadata.getPlural(SelfSubjectAccessReview.class)))); private final HttpClient client; - private final OpenShiftConfig config; + private final Config config; private final AtomicReference oauthToken = new AtomicReference<>(); - public OpenShiftOAuthInterceptor(HttpClient client, OpenShiftConfig config) { + public OpenShiftOAuthInterceptor(HttpClient client, Config config) { this.client = client; this.config = config; } + @Override + public Interceptor withConfig(Config config) { + return new OpenShiftOAuthInterceptor(client, config); + } + @Override public void before(BasicBuilder builder, HttpHeaders headers) { String token = oauthToken.get();