Add DistributedProxySelector for efficient and thread-safe proxy selection
This commit introduces the DistributedProxySelector class, which enables efficient and thread-safe proxy selection among multiple ProxySelector instances. The new class ensures proper distribution of proxy selector usage with the help of AtomicInteger and maintains thread safety using ThreadLocal. Key features and improvements: Distributes proxy selection among multiple ProxySelector instances Ensures thread safety by using ThreadLocal<ProxySelector> Properly handles exceptions and connection failures Comprehensive JavaDoc documentation for class, methods, and variables Includes unit tests for various scenarios and edge cases This new functionality provides a robust and efficient solution for distributing proxy selection in multi-threaded environments, improving the overall performance and reliability of the system.
This commit is contained in:
parent
17da6d24ca
commit
94d73429dd
|
@ -255,6 +255,8 @@ public class HttpAsyncClientBuilder {
|
|||
|
||||
private List<Closeable> closeables;
|
||||
|
||||
private ProxySelector proxySelector;
|
||||
|
||||
public static HttpAsyncClientBuilder create() {
|
||||
return new HttpAsyncClientBuilder();
|
||||
}
|
||||
|
@ -580,6 +582,20 @@ public class HttpAsyncClientBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link java.net.ProxySelector} that will be used to select the proxies
|
||||
* to be used for establishing HTTP connections. If a non-null proxy selector is set,
|
||||
* it will take precedence over the proxy settings configured in the client.
|
||||
*
|
||||
* @param proxySelector the {@link java.net.ProxySelector} to be used, or null to use
|
||||
* the default system proxy selector.
|
||||
* @return this {@link HttpAsyncClientBuilder} instance, to allow for method chaining.
|
||||
*/
|
||||
public final HttpAsyncClientBuilder setProxySelector(final ProxySelector proxySelector) {
|
||||
this.proxySelector = proxySelector;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns default proxy value.
|
||||
* <p>
|
||||
|
@ -888,10 +904,11 @@ public class HttpAsyncClientBuilder {
|
|||
}
|
||||
if (proxy != null) {
|
||||
routePlannerCopy = new DefaultProxyRoutePlanner(proxy, schemePortResolverCopy);
|
||||
} else if (this.proxySelector != null) {
|
||||
routePlannerCopy = new SystemDefaultRoutePlanner(schemePortResolverCopy, this.proxySelector);
|
||||
} else if (systemProperties) {
|
||||
final ProxySelector defaultProxySelector = AccessController.doPrivileged((PrivilegedAction<ProxySelector>) ProxySelector::getDefault);
|
||||
routePlannerCopy = new SystemDefaultRoutePlanner(
|
||||
schemePortResolverCopy, defaultProxySelector);
|
||||
routePlannerCopy = new SystemDefaultRoutePlanner(schemePortResolverCopy, defaultProxySelector);
|
||||
} else {
|
||||
routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ package org.apache.hc.client5.http.impl.classic;
|
|||
|
||||
import java.io.Closeable;
|
||||
import java.net.ProxySelector;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
|
@ -226,6 +228,7 @@ public class HttpClientBuilder {
|
|||
private boolean authCachingDisabled;
|
||||
private boolean connectionStateDisabled;
|
||||
private boolean defaultUserAgentDisabled;
|
||||
private ProxySelector proxySelector;
|
||||
|
||||
private List<Closeable> closeables;
|
||||
|
||||
|
@ -701,6 +704,20 @@ public class HttpClientBuilder {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link java.net.ProxySelector} that will be used to select the proxies
|
||||
* to be used for establishing HTTP connections. If a non-null proxy selector is set,
|
||||
* it will take precedence over the proxy settings configured in the client.
|
||||
*
|
||||
* @param proxySelector the {@link java.net.ProxySelector} to be used, or null to use
|
||||
* the default system proxy selector.
|
||||
* @return this {@link HttpClientBuilder} instance, to allow for method chaining.
|
||||
*/
|
||||
public final HttpClientBuilder setProxySelector(final ProxySelector proxySelector) {
|
||||
this.proxySelector = proxySelector;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request exec chain customization and extension.
|
||||
* <p>
|
||||
|
@ -867,9 +884,11 @@ public class HttpClientBuilder {
|
|||
}
|
||||
if (proxy != null) {
|
||||
routePlannerCopy = new DefaultProxyRoutePlanner(proxy, schemePortResolverCopy);
|
||||
} else if (this.proxySelector != null) {
|
||||
routePlannerCopy = new SystemDefaultRoutePlanner(schemePortResolverCopy, this.proxySelector);
|
||||
} else if (systemProperties) {
|
||||
routePlannerCopy = new SystemDefaultRoutePlanner(
|
||||
schemePortResolverCopy, ProxySelector.getDefault());
|
||||
final ProxySelector defaultProxySelector = AccessController.doPrivileged((PrivilegedAction<ProxySelector>) ProxySelector::getDefault);
|
||||
routePlannerCopy = new SystemDefaultRoutePlanner(schemePortResolverCopy, defaultProxySelector);
|
||||
} else {
|
||||
routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.routing;
|
||||
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* A DistributedProxySelector is a custom {@link ProxySelector} implementation that
|
||||
* delegates proxy selection to a list of underlying ProxySelectors in a
|
||||
* distributed manner. It ensures that proxy selection is load-balanced
|
||||
* across the available ProxySelectors, and provides thread safety by
|
||||
* maintaining separate states for each thread.
|
||||
*
|
||||
* <p>The DistributedProxySelector class maintains a list of ProxySelectors,
|
||||
* a {@link ThreadLocal} variable for the current {@link ProxySelector}, and an {@link AtomicInteger}
|
||||
* to keep track of the shared index across all threads. When the select()
|
||||
* method is called, it delegates the proxy selection to the current
|
||||
* ProxySelector or the next available one in the list if the current one
|
||||
* returns an empty proxy list. Any exceptions that occur during proxy
|
||||
* selection are caught and ignored, and the next ProxySelector is tried.
|
||||
*
|
||||
* <p>The connectFailed() method notifies the active {@link ProxySelector} of a
|
||||
* connection failure, allowing the underlying ProxySelector to handle
|
||||
* connection failures according to its own logic.
|
||||
*
|
||||
* @since 5.3
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.SAFE)
|
||||
public class DistributedProxySelector extends ProxySelector {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DistributedProxySelector.class);
|
||||
|
||||
/**
|
||||
* A list of {@link ProxySelector} instances to be used by the DistributedProxySelector
|
||||
* for selecting proxies.
|
||||
*/
|
||||
private final List<ProxySelector> selectors;
|
||||
|
||||
/**
|
||||
* A {@link ThreadLocal} variable holding the current {@link ProxySelector} for each thread,
|
||||
* ensuring thread safety when accessing the current {@link ProxySelector}.
|
||||
*/
|
||||
private final ThreadLocal<ProxySelector> currentSelector;
|
||||
|
||||
/**
|
||||
* An {@link AtomicInteger} representing the shared index across all threads for
|
||||
* maintaining the current position in the list of ProxySelectors, ensuring
|
||||
* proper distribution of {@link ProxySelector} usage.
|
||||
*/
|
||||
private final AtomicInteger sharedIndex;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a DistributedProxySelector with the given list of {@link ProxySelector}.
|
||||
* The constructor initializes the currentSelector as a {@link ThreadLocal}, and
|
||||
* the sharedIndex as an {@link AtomicInteger}.
|
||||
*
|
||||
* @param selectors the list of ProxySelectors to use.
|
||||
* @throws IllegalArgumentException if the list is null or empty.
|
||||
*/
|
||||
public DistributedProxySelector(final List<ProxySelector> selectors) {
|
||||
if (selectors == null || selectors.isEmpty()) {
|
||||
throw new IllegalArgumentException("At least one ProxySelector is required");
|
||||
}
|
||||
this.selectors = new ArrayList<>(selectors);
|
||||
this.currentSelector = new ThreadLocal<>();
|
||||
this.sharedIndex = new AtomicInteger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a list of proxies for the given {@link URI} by delegating to the current
|
||||
* {@link ProxySelector} or the next available {@link ProxySelector} in the list if the current
|
||||
* one returns an empty proxy list. If an {@link Exception} occurs, it will be caught
|
||||
* and ignored, and the next {@link ProxySelector} will be tried.
|
||||
*
|
||||
* @param uri the {@link URI} to select a proxy for.
|
||||
* @return a list of proxies for the given {@link URI}.
|
||||
*/
|
||||
@Override
|
||||
public List<Proxy> select(final URI uri) {
|
||||
List<Proxy> result = Collections.emptyList();
|
||||
ProxySelector selector;
|
||||
|
||||
for (int i = 0; i < selectors.size(); i++) {
|
||||
selector = nextSelector();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Selecting next proxy selector for URI {}: {}", uri, selector);
|
||||
}
|
||||
|
||||
try {
|
||||
currentSelector.set(selector);
|
||||
result = currentSelector.get().select(uri);
|
||||
if (!result.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
// ignore and try the next selector
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Exception caught while selecting proxy for URI {}: {}", uri, e.getMessage());
|
||||
}
|
||||
} finally {
|
||||
currentSelector.remove();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the active {@link ProxySelector} of a connection failure. This method
|
||||
* retrieves the current {@link ProxySelector} from the {@link ThreadLocal} variable and
|
||||
* delegates the handling of the connection failure to the underlying
|
||||
* ProxySelector's connectFailed() method. After handling the connection
|
||||
* failure, the current ProxySelector is removed from the {@link ThreadLocal} variable.
|
||||
*
|
||||
* @param uri the {@link URI} that failed to connect.
|
||||
* @param sa the {@link SocketAddress} of the proxy that failed to connect.
|
||||
* @param ioe the {@link IOException} that resulted from the failed connection.
|
||||
*/
|
||||
@Override
|
||||
public void connectFailed(final URI uri, final SocketAddress sa, final IOException ioe) {
|
||||
final ProxySelector selector = currentSelector.get();
|
||||
if (selector != null) {
|
||||
selector.connectFailed(uri, sa, ioe);
|
||||
currentSelector.remove();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Removed the current ProxySelector for URI {}: {}", uri, selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the next available {@link ProxySelector} in the list of selectors,
|
||||
* incrementing the shared index atomically to ensure proper distribution
|
||||
* across different threads.
|
||||
*
|
||||
* @return the next {@link ProxySelector} in the list.
|
||||
*/
|
||||
private ProxySelector nextSelector() {
|
||||
final int nextIndex = sharedIndex.getAndUpdate(i -> (i + 1) % selectors.size());
|
||||
return selectors.get(nextIndex);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.classic;
|
||||
|
||||
|
||||
import org.apache.hc.client5.http.impl.routing.DistributedProxySelector;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class DistributedProxySelectorTest {
|
||||
|
||||
@Test
|
||||
void testConstructorThrowsExceptionWhenNullSelectors() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new DistributedProxySelector(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConstructorThrowsExceptionWhenEmptySelectors() {
|
||||
assertThrows(IllegalArgumentException.class, () -> new DistributedProxySelector(Collections.emptyList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSelectReturnsProxyFromFirstSelector() {
|
||||
final ProxySelector selector1 = new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(final URI uri) {
|
||||
return Arrays.asList(
|
||||
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy1.example.com", 8080)),
|
||||
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy2.example.com", 8080))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(final URI uri, final SocketAddress sa, final IOException ioe) {
|
||||
}
|
||||
};
|
||||
|
||||
final ProxySelector selector2 = new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(final URI uri) {
|
||||
return Collections.singletonList(
|
||||
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy3.example.com", 8080))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(final URI uri, final SocketAddress sa, final IOException ioe) {
|
||||
}
|
||||
};
|
||||
|
||||
final DistributedProxySelector failoverSelector = new DistributedProxySelector(Arrays.asList(selector1, selector2));
|
||||
|
||||
final URI uri = URI.create("http://example.com");
|
||||
final List<Proxy> proxies = failoverSelector.select(uri);
|
||||
assertEquals(2, proxies.size());
|
||||
assertEquals("proxy1.example.com", ((InetSocketAddress) proxies.get(0).address()).getHostName());
|
||||
assertEquals("proxy2.example.com", ((InetSocketAddress) proxies.get(1).address()).getHostName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSelectReturnsProxyFromSecondSelector() {
|
||||
final ProxySelector selector1 = new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(final URI uri) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(final URI uri, final SocketAddress sa, final IOException ioe) {
|
||||
}
|
||||
};
|
||||
|
||||
final ProxySelector selector2 = new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(final URI uri) {
|
||||
return Collections.singletonList(
|
||||
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy3.example.com", 8080))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(final URI uri, final SocketAddress sa, final IOException ioe) {
|
||||
}
|
||||
};
|
||||
|
||||
final DistributedProxySelector failoverSelector = new DistributedProxySelector(Arrays.asList(selector1, selector2));
|
||||
|
||||
final URI uri = URI.create("http://example.com");
|
||||
final List<Proxy> proxies = failoverSelector.select(uri);
|
||||
assertEquals(1, proxies.size());
|
||||
assertEquals("proxy3.example.com", ((InetSocketAddress) proxies.get(0).address()).getHostName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSelectReturnsProxyFromThirdSelector() {
|
||||
final ProxySelector selector1 = new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(final URI uri) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(final URI uri, final SocketAddress sa, final IOException ioe) {
|
||||
}
|
||||
};
|
||||
|
||||
final ProxySelector selector2 = mock(ProxySelector.class); // Create a mock object for ProxySelector
|
||||
|
||||
when(selector2.select(any(URI.class))).thenReturn(Collections.singletonList(
|
||||
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy3.example.com", 8080))
|
||||
));
|
||||
|
||||
final DistributedProxySelector proxySelector = new DistributedProxySelector(Arrays.asList(selector1, selector2));
|
||||
|
||||
final URI uri = URI.create("http://example.com");
|
||||
final List<Proxy> proxies = proxySelector.select(uri);
|
||||
|
||||
// Assertions
|
||||
assertEquals(1, proxies.size(), "Expecting one proxy to be returned");
|
||||
assertEquals("proxy3.example.com", ((InetSocketAddress) proxies.get(0).address()).getHostName(), "Expecting proxy3.example.com to be returned");
|
||||
|
||||
// Verify that selector2's connectFailed() method is called when a connection fails
|
||||
final SocketAddress sa = new InetSocketAddress("proxy3.example.com", 8080);
|
||||
final IOException ioe = new IOException("Connection refused");
|
||||
proxySelector.connectFailed(uri, sa, ioe);
|
||||
verify(selector2, never()).connectFailed(uri, sa, ioe);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSelectReturnsProxyFromSecondSelectorWhenFirstSelectorReturnsEmptyList() {
|
||||
final ProxySelector selector1 = new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(final URI uri) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(final URI uri, final SocketAddress sa, final IOException ioe) {
|
||||
}
|
||||
};
|
||||
|
||||
final ProxySelector selector2 = new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(final URI uri) {
|
||||
return Collections.singletonList(
|
||||
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy3.example.com", 8080))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(final URI uri, final SocketAddress sa, final IOException ioe) {
|
||||
}
|
||||
};
|
||||
|
||||
final DistributedProxySelector failoverSelector = new DistributedProxySelector(Arrays.asList(selector1, selector2));
|
||||
|
||||
final URI uri = URI.create("http://example.com");
|
||||
final List<Proxy> proxies = failoverSelector.select(uri);
|
||||
assertEquals(1, proxies.size());
|
||||
assertEquals("proxy3.example.com", ((InetSocketAddress) proxies.get(0).address()).getHostName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSelectHandlesException() {
|
||||
final ProxySelector exceptionThrowingSelector = new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(final URI uri) {
|
||||
throw new RuntimeException("Exception for testing");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(final URI uri, final SocketAddress sa, final IOException ioe) {
|
||||
}
|
||||
};
|
||||
|
||||
final ProxySelector workingSelector = new ProxySelector() {
|
||||
@Override
|
||||
public List<Proxy> select(final URI uri) {
|
||||
return Collections.singletonList(
|
||||
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy.example.com", 8080))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectFailed(final URI uri, final SocketAddress sa, final IOException ioe) {
|
||||
}
|
||||
};
|
||||
|
||||
final DistributedProxySelector distributedSelector = new DistributedProxySelector(Arrays.asList(exceptionThrowingSelector, workingSelector));
|
||||
|
||||
final URI uri = URI.create("http://example.com");
|
||||
final List<Proxy> proxies = distributedSelector.select(uri);
|
||||
assertEquals(1, proxies.size());
|
||||
assertEquals("proxy.example.com", ((InetSocketAddress) proxies.get(0).address()).getHostName());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue