diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce87cba..1443754 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -85,6 +85,7 @@ jobs: bash publish.sh lib-lang bash publish.sh lib-lock bash publish.sh lib-lock-redis + bash publish.sh lib-util-net bash publish.sh lib-httpx bash publish.sh lib-mail bash publish.sh lib-node-id-redis @@ -94,7 +95,6 @@ jobs: bash publish.sh lib-util-string bash publish.sh lib-util-time bash publish.sh lib-util-tsid - bash publish.sh lib-util-net bash publish.sh lib-pool bash publish.sh lib-random bash publish.sh lib-retry diff --git a/lib-httpx/README.md b/lib-httpx/README.md index 48244c1..52f52a7 100644 --- a/lib-httpx/README.md +++ b/lib-httpx/README.md @@ -10,7 +10,7 @@ Add the dependency to your `build.gradle`: ```gradle dependencies { - implementation 'io.seqera:lib-httpx:2.6.0' + implementation 'io.seqera:lib-httpx:2.7.0' } ``` @@ -25,7 +25,7 @@ dependencies { - **Custom Token Storage**: Pluggable token store interface for distributed deployments (Redis, database, etc.) - **WWW-Authenticate Support**: Automatic handling of HTTP authentication challenges (Basic and Bearer schemes) - **Anonymous Authentication**: Fallback to anonymous authentication when credentials aren't provided -- **Proxy Support**: Authenticated forward-proxy support via `.proxy(...)`/`.authenticator(...)`, or an `HxProxyConfig` value applied with `.withProxyConfig(...)` +- **Proxy Support**: Authenticated forward-proxy support via `.proxy(...)`/`.authenticator(...)`, or an `io.seqera.util.net.ProxyConfig` (from `io.seqera:lib-util-net`) applied with `.withProxyConfig(...)` - **Configurable**: Customizable retry policies, timeouts, token refresh, authentication settings, and cookie policies - **Generic Integration**: Compatible with any `Retryable.Config` for flexible retry configuration - **Thread-safe**: Safe for concurrent use with atomic token refresh coordination @@ -403,16 +403,17 @@ HxClient client = HxClient.newBuilder() .build(); ``` -For callers that resolve proxy settings themselves (host, port, optional credentials per protocol and -`NO_PROXY` entries), `HxProxyConfig` bundles them into a single value and produces the matching selector -and a proxy-only authenticator (credentials are supplied only for proxy authentication challenges, never -to origin servers). Apply it in one call with `.withProxyConfig(...)`: +For callers that resolve proxy settings themselves, `io.seqera.util.net.ProxyConfig` (from +`io.seqera:lib-util-net`, an `api` dependency of this library) bundles a proxy URI or the +`HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` environment variables into a single value and produces the matching +selector and a proxy-only authenticator (credentials are supplied only for proxy authentication +challenges, never to origin servers). Apply it in one call with `.withProxyConfig(...)`: ```java -HxProxyConfig proxy = HxProxyConfig.newBuilder() - .httpsProxy("proxy.example.com", 8080, "user", "pass") - .noProxy(List.of("internal.example.com")) - .build(); +// prefer the explicit-credentials overload - it avoids having to percent-encode a password +// containing '@', ':' or '%' that would otherwise be embedded in the URI +ProxyConfig proxy = ProxyConfig.fromUri("http://proxy.example.com:8080", "user", "pass", + List.of("internal.example.com")); HxClient client = HxClient.newBuilder() .withProxyConfig(proxy) // no-op if proxy is null .build(); @@ -469,7 +470,7 @@ HxClient client = HxClient.newBuilder() - **`HxClient`**: Main HTTP client with retry, JWT, and WWW-Authenticate functionality - **`HxConfig`**: Configuration builder with all available options -- **`HxProxyConfig`**: Forward-proxy settings (selector + proxy-only authenticator) assembled from explicit values via its builder +- **`io.seqera.util.net.ProxyConfig`** (from `io.seqera:lib-util-net`): Forward-proxy settings (selector + proxy-only authenticator) resolved from a URI or the environment, applied via `HxClient.Builder.withProxyConfig(...)` - **`HxAuth`**: Interface for authentication credentials with stable identity across refreshes - **`HxTokenStore`**: Interface for pluggable token storage (default: in-memory ConcurrentHashMap) - **`HxTokenManager`**: Thread-safe JWT token lifecycle management with multi-session support diff --git a/lib-httpx/VERSION b/lib-httpx/VERSION index e70b452..9aa3464 100644 --- a/lib-httpx/VERSION +++ b/lib-httpx/VERSION @@ -1 +1 @@ -2.6.0 +2.7.0 \ No newline at end of file diff --git a/lib-httpx/build.gradle b/lib-httpx/build.gradle index 76c51b4..deb95b0 100644 --- a/lib-httpx/build.gradle +++ b/lib-httpx/build.gradle @@ -24,6 +24,7 @@ version = "${project.file('VERSION').text.trim()}" dependencies { api project(':lib-retry') + api project(':lib-util-net') implementation 'com.google.code.gson:gson:2.10.1' testImplementation 'com.github.tomakehurst:wiremock:3.0.1' diff --git a/lib-httpx/changelog.txt b/lib-httpx/changelog.txt index d8a86b0..739452a 100644 --- a/lib-httpx/changelog.txt +++ b/lib-httpx/changelog.txt @@ -1,5 +1,22 @@ # lib-httpx changelog +2.7.0 +- BREAKING (see note below): remove HxProxyConfig in favour of io.seqera.util.net.ProxyConfig + (io.seqera:lib-util-net), so the proxy selector / authenticator / no-proxy semantics live in a + single place shared with Wave and Nextflow +- HxClient.Builder.withProxyConfig now accepts an io.seqera.util.net.ProxyConfig; callers that built an + HxProxyConfig should resolve a ProxyConfig instead - its toProxySelector()/toAuthenticator() are + unchanged. For raw credentials prefer the explicit overload + ProxyConfig.fromUri(uri, user, pass, noProxy) over embedding them in the URI, so a password with + '@'/':'/'%' does not need percent-encoding; fromEnvironment(env) reads HTTP_PROXY/HTTPS_PROXY/NO_PROXY +- Note: HxProxyConfig could hold an http proxy without an https one (or vice versa); fromUri applies one + endpoint to both protocols, so asymmetric per-protocol config from explicit values is now only + expressible via fromEnvironment +- lib-httpx now depends on io.seqera:lib-util-net +- NOTE: this removes a public type, so it is source-incompatible for any caller of HxProxyConfig. + It is released as a minor (2.7.0) rather than a major because the only affected consumer is + Nextflow, which is migrated in lock-step; callers pinning <= 2.6.0 are unaffected. + 2.6.0 - 12 Aug 2026 - Add HxClient.shouldRetryOnException(HttpRequest, Throwable) - the hook the retry policy now consults - so a subclass can decide on the exception per request diff --git a/lib-httpx/src/main/java/io/seqera/http/HxClient.java b/lib-httpx/src/main/java/io/seqera/http/HxClient.java index 9b66919..9ae5622 100644 --- a/lib-httpx/src/main/java/io/seqera/http/HxClient.java +++ b/lib-httpx/src/main/java/io/seqera/http/HxClient.java @@ -40,6 +40,7 @@ import io.seqera.http.auth.AuthenticationChallenge; import io.seqera.http.auth.AuthenticationScheme; import io.seqera.http.auth.WwwAuthenticateParser; +import io.seqera.util.net.ProxyConfig; import io.seqera.util.retry.Retryable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1195,12 +1196,12 @@ public Builder version(HttpClient.Version version) { *
The selector is forwarded to the inner {@link HttpClient.Builder} and is also * inherited by the internal HTTP clients used for JWT token refresh and anonymous * Bearer token retrieval. Proxy settings are never resolved from the environment - * automatically; supply them explicitly here (or via {@link #withProxyConfig(HxProxyConfig)}). + * automatically; supply them explicitly here (or via {@link #withProxyConfig(ProxyConfig)}). * * @param proxySelector the proxy selector to use * @return this Builder instance * @see HttpClient.Builder#proxy(ProxySelector) - * @see HxProxyConfig + * @see ProxyConfig */ public Builder proxy(ProxySelector proxySelector) { this.proxySelector = proxySelector; @@ -1220,12 +1221,12 @@ public Builder proxy(ProxySelector proxySelector) { * {@link Authenticator#setDefault(Authenticator)} - proxy credentials only take effect * when supplied via this method. For Basic proxy authentication of HTTPS traffic the JDK's * {@code jdk.http.auth.tunneling.disabledSchemes} property must also be cleared - see - * {@link HxProxyConfig} for details. + * {@link ProxyConfig} for details. * * @param authenticator the authenticator providing credentials * @return this Builder instance * @see HttpClient.Builder#authenticator(Authenticator) - * @see HxProxyConfig#toAuthenticator() + * @see ProxyConfig#toAuthenticator() */ public Builder authenticator(Authenticator authenticator) { this.proxyAuthenticator = authenticator; @@ -1233,16 +1234,17 @@ public Builder authenticator(Authenticator authenticator) { } /** - * Applies the proxy selector and authenticator carried by the given {@link HxProxyConfig}, - * a convenience over calling {@link #proxy(ProxySelector)} and - * {@link #authenticator(Authenticator)} separately. A {@code null} config is a no-op, so - * callers can pass an optionally-resolved configuration directly. + * Applies the proxy selector and authenticator carried by the given + * {@link ProxyConfig} (from {@code io.seqera:lib-util-net}), a convenience over calling + * {@link #proxy(ProxySelector)} and {@link #authenticator(Authenticator)} separately. A + * {@code null} config is a no-op, so callers can pass an optionally-resolved configuration + * directly. * * @param config the proxy configuration to apply, or null for none * @return this Builder instance - * @see HxProxyConfig + * @see ProxyConfig */ - public Builder withProxyConfig(HxProxyConfig config) { + public Builder withProxyConfig(ProxyConfig config) { if( config == null ) return this; if( config.getHttpProxy() != null || config.getHttpsProxy() != null ) diff --git a/lib-httpx/src/main/java/io/seqera/http/HxConfig.java b/lib-httpx/src/main/java/io/seqera/http/HxConfig.java index 6ba1e41..cfeb87b 100644 --- a/lib-httpx/src/main/java/io/seqera/http/HxConfig.java +++ b/lib-httpx/src/main/java/io/seqera/http/HxConfig.java @@ -701,7 +701,7 @@ public Builder withRefreshCookiePolicy(CookiePolicy policy) { * * @param proxySelector the proxy selector, or null to use the JVM default behaviour * @return this builder instance for method chaining - * @see HxProxyConfig + * @see io.seqera.util.net.ProxyConfig */ public Builder proxySelector(ProxySelector proxySelector) { this.proxySelector = proxySelector; @@ -711,12 +711,12 @@ public Builder proxySelector(ProxySelector proxySelector) { /** * Sets the authenticator used to supply credentials to an authenticating forward proxy, * applied to the main HTTP client and the internal token refresh clients. See - * {@link HxProxyConfig} for the {@code Authenticator.setDefault} and Basic-over-HTTPS - * tunnelling caveats. + * {@link io.seqera.util.net.ProxyConfig} for the {@code Authenticator.setDefault} and + * Basic-over-HTTPS tunnelling caveats. * * @param proxyAuthenticator the authenticator providing proxy credentials, or null for none * @return this builder instance for method chaining - * @see HxProxyConfig#toAuthenticator() + * @see io.seqera.util.net.ProxyConfig#toAuthenticator() */ public Builder proxyAuthenticator(Authenticator proxyAuthenticator) { this.proxyAuthenticator = proxyAuthenticator; diff --git a/lib-httpx/src/main/java/io/seqera/http/HxProxyConfig.java b/lib-httpx/src/main/java/io/seqera/http/HxProxyConfig.java deleted file mode 100644 index 6e75fa7..0000000 --- a/lib-httpx/src/main/java/io/seqera/http/HxProxyConfig.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright 2026, Seqera Labs - * - * 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.seqera.http; - -import java.io.IOException; -import java.net.Authenticator; -import java.net.InetSocketAddress; -import java.net.PasswordAuthentication; -import java.net.Proxy; -import java.net.ProxySelector; -import java.net.SocketAddress; -import java.net.URI; -import java.util.List; -import java.util.Locale; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Holds HTTP/HTTPS forward-proxy settings supplied explicitly (host, port and optional - * credentials per protocol, plus {@code NO_PROXY} entries) and exposes them as a - * {@link ProxySelector} and a proxy-only {@link Authenticator} suitable for - * {@link java.net.http.HttpClient}. Build one with {@link #newBuilder()}. - * - *
The library never reads the environment or system properties on its own; the caller - * resolves proxy settings however it wishes and passes the values to {@link Builder}. - * - *
NO_PROXY entries are honoured as a list of host names or domain suffixes
- * (optionally prefixed with . or *.); the single entry *
- * disables proxying entirely. Loopback targets (localhost, 127.*,
- * [::1]) always bypass the proxy, mirroring the JDK default
- * http.nonProxyHosts behaviour. CIDR notation entries are not supported.
- *
- *
Why an explicit Authenticator?
- * {@link java.net.http.HttpClient} ignores {@link Authenticator#setDefault(Authenticator)};
- * proxy credentials only take effect when supplied via
- * {@link java.net.http.HttpClient.Builder#authenticator(Authenticator)}. The authenticator
- * returned by {@link #toAuthenticator()} releases credentials only for
- * {@link Authenticator.RequestorType#PROXY} requests that match the configured proxy
- * host and port, never for origin-server challenges.
- *
- *
Basic auth over HTTPS tunnels: Credentials are released only for {@link Authenticator.RequestorType#PROXY}
- * requests whose host and port match the configured proxy, never for origin-server
- * authentication challenges.
- *
- * @return a new Authenticator, or null when no proxy credentials are configured
- */
- public Authenticator toAuthenticator() {
- if (!hasCredentials())
- return null;
- return new Authenticator() {
- @Override
- protected PasswordAuthentication getPasswordAuthentication() {
- if (getRequestorType() != RequestorType.PROXY)
- return null;
- final ProxyEntry entry = credentialsFor(getRequestingHost(), getRequestingPort());
- if (entry == null)
- return null;
- final String password = entry.password != null ? entry.password : "";
- return new PasswordAuthentication(entry.username, password.toCharArray());
- }
- };
- }
-
- /**
- * @return true when at least one configured proxy carries credentials
- */
- public boolean hasCredentials() {
- return (httpProxy != null && httpProxy.hasCredentials())
- || (httpsProxy != null && httpsProxy.hasCredentials());
- }
-
- ProxyEntry getHttpProxy() {
- return httpProxy;
- }
-
- ProxyEntry getHttpsProxy() {
- return httpsProxy;
- }
-
- /**
- * Determines whether the given target host must bypass the proxy, either because it is
- * a loopback address or because it matches a {@code NO_PROXY} entry.
- */
- boolean isBypassed(String host) {
- if (host == null)
- return true;
- final String target = host.toLowerCase(Locale.ROOT);
- // always bypass loopback targets, consistent with the JDK default `http.nonProxyHosts`
- if (target.equals("localhost") || target.startsWith("127.") || target.equals("::1") || target.equals("[::1]"))
- return true;
- for (String entry : noProxyHosts) {
- if (entry.equals("*"))
- return true;
- // "*.example.com" and ".example.com" match sub-domains only;
- // "example.com" matches the host itself and any sub-domain
- final String suffix = entry.startsWith("*.") ? entry.substring(1) : entry;
- if (suffix.startsWith(".")) {
- if (target.endsWith(suffix))
- return true;
- }
- else if (target.equals(suffix) || target.endsWith("." + suffix)) {
- return true;
- }
- }
- return false;
- }
-
- private ProxyEntry credentialsFor(String host, int port) {
- if (httpsProxy != null && httpsProxy.hasCredentials() && httpsProxy.host.equalsIgnoreCase(host) && httpsProxy.port == port)
- return httpsProxy;
- if (httpProxy != null && httpProxy.hasCredentials() && httpProxy.host.equalsIgnoreCase(host) && httpProxy.port == port)
- return httpProxy;
- return null;
- }
-}
diff --git a/lib-httpx/src/test/groovy/io/seqera/http/HxClientProxyAuthIntegrationTest.groovy b/lib-httpx/src/test/groovy/io/seqera/http/HxClientProxyAuthIntegrationTest.groovy
index b7dbe93..dc95ffe 100644
--- a/lib-httpx/src/test/groovy/io/seqera/http/HxClientProxyAuthIntegrationTest.groovy
+++ b/lib-httpx/src/test/groovy/io/seqera/http/HxClientProxyAuthIntegrationTest.groovy
@@ -24,6 +24,7 @@ import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
+import io.seqera.util.net.ProxyConfig
import spock.lang.Specification
/**
@@ -202,11 +203,9 @@ class HxClientProxyAuthIntegrationTest extends Specification {
client.getConfig().getProxyAuthenticator().is(authenticator)
}
- def 'should route through the proxy via withProxyConfig and the HxProxyConfig builder'() {
- given: 'a proxy config assembled from explicit values'
- def proxyConfig = HxProxyConfig.newBuilder()
- .httpProxy('127.0.0.1', proxyPort, 'alice', 's3cret')
- .build()
+ def 'should route through the proxy via withProxyConfig and a lib-util-net ProxyConfig'() {
+ given: 'a proxy config resolved from an explicit uri'
+ def proxyConfig = ProxyConfig.fromUri("http://alice:s3cret@127.0.0.1:${proxyPort}".toString())
def client = HxClient.newBuilder()
.withProxyConfig(proxyConfig)
.build()
diff --git a/lib-httpx/src/test/groovy/io/seqera/http/HxProxyConfigTest.groovy b/lib-httpx/src/test/groovy/io/seqera/http/HxProxyConfigTest.groovy
deleted file mode 100644
index b5d40d9..0000000
--- a/lib-httpx/src/test/groovy/io/seqera/http/HxProxyConfigTest.groovy
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright 2026, Seqera Labs
- *
- * 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.seqera.http
-
-import java.net.Authenticator
-import java.net.PasswordAuthentication
-import java.net.Proxy
-
-import spock.lang.Specification
-
-/**
- * Test cases for {@link HxProxyConfig} proxy selection and proxy authentication behaviour.
- */
-class HxProxyConfigTest extends Specification {
-
- private static PasswordAuthentication invokeAuth(Authenticator authenticator, Authenticator.RequestorType type, String host, int port) {
- // drive the protected Authenticator API the same way the JDK HTTP client does
- return authenticator.requestPasswordAuthenticationInstance(
- host, null, port, 'http', 'proxy auth', 'basic', new URL('http://example.com'), type )
- }
-
- def 'builder should assemble a proxy config from explicit values'() {
- when:
- def config = HxProxyConfig.newBuilder()
- .httpsProxy('proxy.example.com', 8080, 'foo', 'bar')
- .noProxy(['internal.example.com'])
- .build()
-
- then:
- config.httpsProxy.host == 'proxy.example.com'
- config.httpsProxy.port == 8080
- config.httpsProxy.username == 'foo'
- config.httpsProxy.password == 'bar'
- config.httpProxy == null
- config.hasCredentials()
- and: 'the selector honours the explicit NO_PROXY entry'
- config.isBypassed('internal.example.com')
- !config.isBypassed('api.example.com')
- and:
- config.toAuthenticator() != null
- }
-
- def 'builder without credentials yields no authenticator'() {
- when:
- def config = HxProxyConfig.newBuilder()
- .httpProxy('proxy.local', 3128, null, null)
- .build()
-
- then:
- config.httpProxy.host == 'proxy.local'
- config.httpProxy.port == 3128
- !config.hasCredentials()
- config.toAuthenticator() == null
- }
-
- def 'should select the proxy matching the target scheme'() {
- given:
- def config = HxProxyConfig.newBuilder()
- .httpProxy('http-proxy', 3128, null, null)
- .httpsProxy('https-proxy', 3129, null, null)
- .build()
- def selector = config.toProxySelector()
-
- when:
- def httpResult = selector.select(URI.create('http://api.example.com/foo'))
- def httpsResult = selector.select(URI.create('https://api.example.com/foo'))
-
- then:
- (httpResult[0].address() as InetSocketAddress).hostString == 'http-proxy'
- (httpResult[0].address() as InetSocketAddress).port == 3128
- (httpsResult[0].address() as InetSocketAddress).hostString == 'https-proxy'
- (httpsResult[0].address() as InetSocketAddress).port == 3129
- }
-
- def 'should connect directly when no proxy matches the target scheme'() {
- given:
- def config = HxProxyConfig.newBuilder().httpsProxy('proxy', 3128, null, null).build()
-
- expect:
- config.toProxySelector().select(URI.create('http://api.example.com/foo')) == [Proxy.NO_PROXY]
- }
-
- def 'should bypass proxy for NO_PROXY entries'() {
- given:
- def config = HxProxyConfig.newBuilder()
- .httpProxy('proxy', 3128, null, null)
- .httpsProxy('proxy', 3128, null, null)
- .noProxy(['internal.example.com', '.corp.example.org', '*.svc.cluster.local'])
- .build()
-
- expect:
- config.isBypassed(host) == bypassed
-
- where:
- host | bypassed
- 'internal.example.com' | true
- 'sub.internal.example.com' | true
- 'other.example.com' | false
- 'internal.example.com.evil.io'| false
- 'foo.corp.example.org' | true
- 'corp.example.org' | false
- 'db.default.svc.cluster.local'| true
- }
-
- def 'should bypass everything when NO_PROXY is a wildcard'() {
- given:
- def config = HxProxyConfig.newBuilder().httpsProxy('proxy', 3128, null, null).noProxy(['*']).build()
-
- expect:
- config.toProxySelector().select(URI.create('https://api.example.com')) == [Proxy.NO_PROXY]
- }
-
- def 'should always bypass loopback targets'() {
- given:
- def config = HxProxyConfig.newBuilder().httpsProxy('proxy', 3128, null, null).build()
-
- expect:
- config.isBypassed('localhost')
- config.isBypassed('127.0.0.1')
- !config.isBypassed('api.example.com')
- }
-
- def 'should provide credentials only for matching proxy requests'() {
- given:
- def config = HxProxyConfig.newBuilder().httpsProxy('proxy.example.com', 8080, 'foo', 'bar').build()
- def authenticator = config.toAuthenticator()
-
- when: 'the proxy itself requests authentication'
- def auth = invokeAuth(authenticator, Authenticator.RequestorType.PROXY, 'proxy.example.com', 8080)
-
- then:
- auth.userName == 'foo'
- new String(auth.password) == 'bar'
-
- when: 'an origin server requests authentication'
- auth = invokeAuth(authenticator, Authenticator.RequestorType.SERVER, 'proxy.example.com', 8080)
-
- then:
- auth == null
-
- when: 'a different host requests proxy authentication'
- auth = invokeAuth(authenticator, Authenticator.RequestorType.PROXY, 'other-proxy.example.com', 8080)
-
- then:
- auth == null
-
- when: 'the right host but a different port requests proxy authentication'
- auth = invokeAuth(authenticator, Authenticator.RequestorType.PROXY, 'proxy.example.com', 9999)
-
- then:
- auth == null
- }
-}
- * The JDK disables the Basic scheme for HTTPS CONNECT tunnelling by default via
- * jdk.http.auth.tunneling.disabledSchemes=Basic (see
- * $JAVA_HOME/conf/net.properties). For proxy credentials to be sent on the
- * CONNECT request of HTTPS traffic, that property must be cleared, e.g. with
- * -Djdk.http.auth.tunneling.disabledSchemes=.
- *
- * @see HxClient.Builder#proxy(ProxySelector)
- * @see HxClient.Builder#authenticator(Authenticator)
- */
-public class HxProxyConfig {
-
- private static final Logger log = LoggerFactory.getLogger(HxProxyConfig.class);
-
- /**
- * Connection details for a single proxy server, with optional credentials.
- */
- static final class ProxyEntry {
- final String host;
- final int port;
- final String username;
- final String password;
-
- ProxyEntry(String host, int port, String username, String password) {
- this.host = host;
- this.port = port;
- this.username = username;
- this.password = password;
- }
-
- boolean hasCredentials() {
- return username != null && !username.isEmpty();
- }
-
- InetSocketAddress address() {
- return InetSocketAddress.createUnresolved(host, port);
- }
- }
-
- private final ProxyEntry httpProxy;
- private final ProxyEntry httpsProxy;
- private final List