diff --git a/core/src/main/java/com/linecorp/armeria/client/proxy/ConnectProxyConfig.java b/core/src/main/java/com/linecorp/armeria/client/proxy/ConnectProxyConfig.java index 085609d2d41..565355bb9e8 100644 --- a/core/src/main/java/com/linecorp/armeria/client/proxy/ConnectProxyConfig.java +++ b/core/src/main/java/com/linecorp/armeria/client/proxy/ConnectProxyConfig.java @@ -18,6 +18,9 @@ import java.net.InetSocketAddress; import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.BiConsumer; import com.google.common.base.MoreObjects; @@ -29,7 +32,11 @@ */ public final class ConnectProxyConfig extends ProxyConfig { - private final InetSocketAddress proxyAddress; + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + private long lastUpdateTime = System.currentTimeMillis(); + + private InetSocketAddress proxyAddress; @Nullable private final String username; @@ -43,11 +50,30 @@ public final class ConnectProxyConfig extends ProxyConfig { ConnectProxyConfig(InetSocketAddress proxyAddress, @Nullable String username, @Nullable String password, HttpHeaders headers, boolean useTls) { + this(proxyAddress, username, password, headers, useTls, -1); + } + + ConnectProxyConfig(InetSocketAddress proxyAddress, @Nullable String username, + @Nullable String password, HttpHeaders headers, boolean useTls, + long refreshInterval) { this.proxyAddress = proxyAddress; this.username = username; this.password = password; this.headers = headers; this.useTls = useTls; + + if (refreshInterval > 0) { + final BiConsumer callback = (newProxyAddress, updateTime) -> { + this.proxyAddress = newProxyAddress; + this.lastUpdateTime = updateTime; + }; + + ProxyConfig.reserveDNSUpdate(callback, + proxyAddress.getHostName(), + proxyAddress.getPort(), + refreshInterval, + scheduler); + } } @Override diff --git a/core/src/main/java/com/linecorp/armeria/client/proxy/HAProxyConfig.java b/core/src/main/java/com/linecorp/armeria/client/proxy/HAProxyConfig.java index f300e524f80..c62c0e60148 100644 --- a/core/src/main/java/com/linecorp/armeria/client/proxy/HAProxyConfig.java +++ b/core/src/main/java/com/linecorp/armeria/client/proxy/HAProxyConfig.java @@ -20,6 +20,9 @@ import java.net.InetSocketAddress; import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.BiConsumer; import com.google.common.base.MoreObjects; @@ -31,21 +34,72 @@ */ public final class HAProxyConfig extends ProxyConfig { - private final InetSocketAddress proxyAddress; + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + private long lastUpdateTime = System.currentTimeMillis(); + + private InetSocketAddress proxyAddress; @Nullable - private final InetSocketAddress sourceAddress; + private InetSocketAddress sourceAddress; HAProxyConfig(InetSocketAddress proxyAddress) { + this(proxyAddress, -1); + } + + HAProxyConfig(InetSocketAddress proxyAddress, long refreshInterval) { this.proxyAddress = proxyAddress; sourceAddress = null; + + if (refreshInterval > 0) { + final BiConsumer callback = (newProxyAddress, updateTime) -> { + this.proxyAddress = newProxyAddress; + this.lastUpdateTime = updateTime; + }; + + ProxyConfig.reserveDNSUpdate(callback, + proxyAddress.getHostName(), + proxyAddress.getPort(), + refreshInterval, + scheduler); + } } HAProxyConfig(InetSocketAddress proxyAddress, InetSocketAddress sourceAddress) { + this(proxyAddress, sourceAddress, -1); + } + + HAProxyConfig(InetSocketAddress proxyAddress, InetSocketAddress sourceAddress, long refreshInterval) { checkArgument(sourceAddress.getAddress().getClass() == proxyAddress.getAddress().getClass(), "sourceAddress and proxyAddress should be the same type"); this.proxyAddress = proxyAddress; this.sourceAddress = sourceAddress; + + if (refreshInterval > 0) { + final BiConsumer callback = (newProxyAddress, updateTime) -> { + this.proxyAddress = newProxyAddress; + this.lastUpdateTime = updateTime; + }; + + ProxyConfig.reserveDNSUpdate(callback, + proxyAddress.getHostName(), + proxyAddress.getPort(), + refreshInterval, + scheduler); + } + + if (refreshInterval > 0) { + final BiConsumer callback = (newSourceAddress, updateTime) -> { + this.sourceAddress = newSourceAddress; + this.lastUpdateTime = updateTime; + }; + + ProxyConfig.reserveDNSUpdate(callback, + sourceAddress.getHostName(), + sourceAddress.getPort(), + refreshInterval, + scheduler); + } } @Override diff --git a/core/src/main/java/com/linecorp/armeria/client/proxy/ProxyConfig.java b/core/src/main/java/com/linecorp/armeria/client/proxy/ProxyConfig.java index 736124b8c91..7eb02eba18d 100644 --- a/core/src/main/java/com/linecorp/armeria/client/proxy/ProxyConfig.java +++ b/core/src/main/java/com/linecorp/armeria/client/proxy/ProxyConfig.java @@ -20,7 +20,15 @@ import static com.linecorp.armeria.client.proxy.DirectProxyConfig.DIRECT_PROXY_CONFIG; import static java.util.Objects.requireNonNull; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.linecorp.armeria.client.ClientFactory; import com.linecorp.armeria.common.HttpHeaders; @@ -33,6 +41,8 @@ */ public abstract class ProxyConfig { + private static final Logger logger = LoggerFactory.getLogger(ProxyConfig.class); + /** * Creates a {@code ProxyConfig} configuration for SOCKS4 protocol. * @@ -44,6 +54,18 @@ public static Socks4ProxyConfig socks4(InetSocketAddress proxyAddress) { return new Socks4ProxyConfig(proxyAddress, null); } + /** + * Creates a {@code ProxyConfig} configuration for SOCKS4 protocol. + * + * @param proxyAddress the proxy address + * @param refreshInterval the DNS refresh time + */ + public static Socks4ProxyConfig socks4(InetSocketAddress proxyAddress, long refreshInterval) { + requireNonNull(proxyAddress, "proxyAddress"); + checkArgument(!proxyAddress.isUnresolved(), "proxyAddress must be resolved"); + return new Socks4ProxyConfig(proxyAddress, null, refreshInterval); + } + /** * Creates a {@code ProxyConfig} configuration for SOCKS4 protocol. * @@ -56,6 +78,20 @@ public static Socks4ProxyConfig socks4(InetSocketAddress proxyAddress, String us return new Socks4ProxyConfig(proxyAddress, requireNonNull(username, "username")); } + /** + * Creates a {@code ProxyConfig} configuration for SOCKS4 protocol. + * + * @param proxyAddress the proxy address + * @param username the username + * @param refreshInterval the DNS refresh time + */ + public static Socks4ProxyConfig socks4(InetSocketAddress proxyAddress, String username, + long refreshInterval) { + requireNonNull(proxyAddress, "proxyAddress"); + checkArgument(!proxyAddress.isUnresolved(), "proxyAddress must be resolved"); + return new Socks4ProxyConfig(proxyAddress, requireNonNull(username, "username"), refreshInterval); + } + /** * Creates a {@code ProxyConfig} configuration for SOCKS5 protocol. * @@ -67,6 +103,18 @@ public static Socks5ProxyConfig socks5(InetSocketAddress proxyAddress) { return new Socks5ProxyConfig(proxyAddress, null, null); } + /** + * Creates a {@code ProxyConfig} configuration for SOCKS5 protocol. + * + * @param proxyAddress the proxy address + * @param refreshInterval the DNS refresh time + */ + public static Socks5ProxyConfig socks5(InetSocketAddress proxyAddress, long refreshInterval) { + requireNonNull(proxyAddress, "proxyAddress"); + checkArgument(!proxyAddress.isUnresolved(), "proxyAddress must be resolved"); + return new Socks5ProxyConfig(proxyAddress, null, null, refreshInterval); + } + /** * Creates a {@code ProxyConfig} configuration for SOCKS5 protocol. * @@ -82,6 +130,23 @@ public static Socks5ProxyConfig socks5( requireNonNull(password, "password")); } + /** + * Creates a {@code ProxyConfig} configuration for SOCKS5 protocol. + * + * @param proxyAddress the proxy address + * @param username the username + * @param password the password + * @param refreshInterval the DNS refresh time + */ + public static Socks5ProxyConfig socks5( + InetSocketAddress proxyAddress, String username, String password, long refreshInterval) { + requireNonNull(proxyAddress, "proxyAddress"); + checkArgument(!proxyAddress.isUnresolved(), "proxyAddress must be resolved"); + return new Socks5ProxyConfig(proxyAddress, requireNonNull(username, "username"), + requireNonNull(password, "password"), + refreshInterval); + } + /** * Creates a {@code ProxyConfig} configuration for CONNECT protocol. * @@ -93,6 +158,18 @@ public static ConnectProxyConfig connect(InetSocketAddress proxyAddress) { return new ConnectProxyConfig(proxyAddress, null, null, HttpHeaders.of(), false); } + /** + * Creates a {@code ProxyConfig} configuration for CONNECT protocol. + * + * @param proxyAddress the proxy address + * @param refreshInterval the DNS refresh time + */ + public static ConnectProxyConfig connect(InetSocketAddress proxyAddress, long refreshInterval) { + requireNonNull(proxyAddress, "proxyAddress"); + checkArgument(!proxyAddress.isUnresolved(), "proxyAddress must be resolved"); + return new ConnectProxyConfig(proxyAddress, null, null, HttpHeaders.of(), false, refreshInterval); + } + /** * Creates a {@code ProxyConfig} configuration for CONNECT protocol. * @@ -105,6 +182,20 @@ public static ConnectProxyConfig connect(InetSocketAddress proxyAddress, boolean return new ConnectProxyConfig(proxyAddress, null, null, HttpHeaders.of(), useTls); } + /** + * Creates a {@code ProxyConfig} configuration for CONNECT protocol. + * + * @param proxyAddress the proxy address + * @param useTls whether to use TLS to connect to the proxy + * @param refreshInterval the DNS refresh time + */ + public static ConnectProxyConfig connect(InetSocketAddress proxyAddress, boolean useTls, + long refreshInterval) { + requireNonNull(proxyAddress, "proxyAddress"); + checkArgument(!proxyAddress.isUnresolved(), "proxyAddress must be resolved"); + return new ConnectProxyConfig(proxyAddress, null, null, HttpHeaders.of(), useTls, refreshInterval); + } + /** * Creates a {@code ProxyConfig} configuration for CONNECT protocol. * @@ -118,6 +209,21 @@ public static ConnectProxyConfig connect( return connect(proxyAddress, username, password, HttpHeaders.of(), useTls); } + /** + * Creates a {@code ProxyConfig} configuration for CONNECT protocol. + * + * @param proxyAddress the proxy address + * @param username the username + * @param password the password + * @param useTls whether to use TLS to connect to the proxy + * @param refreshInterval the DNS refresh time + */ + public static ConnectProxyConfig connect( + InetSocketAddress proxyAddress, String username, String password, boolean useTls, + long refreshInterval) { + return connect(proxyAddress, username, password, HttpHeaders.of(), useTls, refreshInterval); + } + /** * Creates a {@code ProxyConfig} configuration for CONNECT protocol. * @@ -133,6 +239,22 @@ public static ConnectProxyConfig connect( return new ConnectProxyConfig(proxyAddress, null, null, headers, useTls); } + /** + * Creates a {@code ProxyConfig} configuration for CONNECT protocol. + * + * @param proxyAddress the proxy address + * @param headers the {@link HttpHeaders} to send to the proxy + * @param useTls whether to use TLS to connect to the proxy + * @param refreshInterval the DNS refresh time + */ + @UnstableApi + public static ConnectProxyConfig connect( + InetSocketAddress proxyAddress, HttpHeaders headers, boolean useTls, long refreshInterval) { + requireNonNull(proxyAddress, "proxyAddress"); + checkArgument(!proxyAddress.isUnresolved(), "proxyAddress must be resolved"); + return new ConnectProxyConfig(proxyAddress, null, null, headers, useTls, refreshInterval); + } + /** * Creates a {@code ProxyConfig} configuration for CONNECT protocol. * @@ -153,6 +275,27 @@ public static ConnectProxyConfig connect(InetSocketAddress proxyAddress, String return new ConnectProxyConfig(proxyAddress, username, password, headers, useTls); } + /** + * Creates a {@code ProxyConfig} configuration for CONNECT protocol. + * + * @param proxyAddress the proxy address + * @param username the username + * @param password the password + * @param headers the {@link HttpHeaders} to send to the proxy + * @param useTls whether to use TLS to connect to the proxy + * @param refreshInterval the DNS refresh time + */ + @UnstableApi + public static ConnectProxyConfig connect(InetSocketAddress proxyAddress, String username, String password, + HttpHeaders headers, boolean useTls, long refreshInterval) { + requireNonNull(proxyAddress, "proxyAddress"); + checkArgument(!proxyAddress.isUnresolved(), "proxyAddress must be resolved"); + requireNonNull(username, "username"); + requireNonNull(password, "password"); + requireNonNull(headers, "headers"); + return new ConnectProxyConfig(proxyAddress, username, password, headers, useTls, refreshInterval); + } + /** * Creates a {@link ProxyConfig} configuration for HAProxy protocol. * @@ -168,6 +311,22 @@ public static HAProxyConfig haproxy( return new HAProxyConfig(proxyAddress, sourceAddress); } + /** + * Creates a {@link ProxyConfig} configuration for HAProxy protocol. + * + * @param proxyAddress the proxy address + * @param sourceAddress the source address + * @param refreshInterval the DNS refresh time + */ + public static HAProxyConfig haproxy( + InetSocketAddress proxyAddress, InetSocketAddress sourceAddress, long refreshInterval) { + requireNonNull(proxyAddress, "proxyAddress"); + checkArgument(!proxyAddress.isUnresolved(), "proxyAddress must be resolved"); + requireNonNull(sourceAddress, "sourceAddress"); + checkArgument(!sourceAddress.isUnresolved(), "sourceAddress must be resolved"); + return new HAProxyConfig(proxyAddress, sourceAddress, refreshInterval); + } + /** * Creates a {@link ProxyConfig} configuration for HAProxy protocol. The {@code sourceAddress} will * be inferred from either the {@link ServiceRequestContext} or the local connection address. @@ -180,6 +339,19 @@ public static ProxyConfig haproxy(InetSocketAddress proxyAddress) { return new HAProxyConfig(proxyAddress); } + /** + * Creates a {@link ProxyConfig} configuration for HAProxy protocol. The {@code sourceAddress} will + * be inferred from either the {@link ServiceRequestContext} or the local connection address. + * + * @param proxyAddress the proxy address + * @param refreshInterval the DNS refresh time + */ + public static ProxyConfig haproxy(InetSocketAddress proxyAddress, long refreshInterval) { + requireNonNull(proxyAddress, "proxyAddress"); + checkArgument(!proxyAddress.isUnresolved(), "proxyAddress must be resolved"); + return new HAProxyConfig(proxyAddress, refreshInterval); + } + /** * Returns a {@code ProxyConfig} which signifies that a proxy is absent. */ @@ -205,4 +377,34 @@ public static ProxyConfig direct() { static String maskPassword(@Nullable String username, @Nullable String password) { return username != null ? "****" : null; } + + /** + * Reserves a task to periodically update DNS with a given scheduler. + * + * @param updateCallback The callback to update InetSocketAddress + * @param hostname The hostname + * @param port The port number + * @param refreshInterval The refresh Interval + * @param scheduler The scheduler + */ + protected static void reserveDNSUpdate(BiConsumer updateCallback, + String hostname, + int port, + long refreshInterval, + ScheduledExecutorService scheduler) { + scheduler.scheduleAtFixedRate(() -> { + try { + final String ipAddress = InetAddress.getByName(hostname) + .getHostAddress(); + final InetSocketAddress newInetSocketAddress = new InetSocketAddress(ipAddress, port); + final long lastUpdateTime = System.currentTimeMillis(); + updateCallback.accept(newInetSocketAddress, + lastUpdateTime); + } catch (UnknownHostException e) { + logger.warn("Failed to refresh {}'s ip address. " + + "Use the previous inet address instead.", + hostname); + } + }, 0, refreshInterval, TimeUnit.MILLISECONDS); + } } diff --git a/core/src/main/java/com/linecorp/armeria/client/proxy/Socks4ProxyConfig.java b/core/src/main/java/com/linecorp/armeria/client/proxy/Socks4ProxyConfig.java index 49adc940602..72afa76c197 100644 --- a/core/src/main/java/com/linecorp/armeria/client/proxy/Socks4ProxyConfig.java +++ b/core/src/main/java/com/linecorp/armeria/client/proxy/Socks4ProxyConfig.java @@ -18,6 +18,9 @@ import java.net.InetSocketAddress; import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.BiConsumer; import com.google.common.base.MoreObjects; @@ -28,14 +31,35 @@ */ public final class Socks4ProxyConfig extends ProxyConfig { - private final InetSocketAddress proxyAddress; + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + private InetSocketAddress proxyAddress; + + private long lastUpdateTime = System.currentTimeMillis(); @Nullable private final String username; Socks4ProxyConfig(InetSocketAddress proxyAddress, @Nullable String username) { + this(proxyAddress, username, -1); + } + + Socks4ProxyConfig(InetSocketAddress proxyAddress, @Nullable String username, long refreshInterval) { this.proxyAddress = proxyAddress; this.username = username; + + if (refreshInterval > 0) { + final BiConsumer callback = (newProxyAddress, updateTime) -> { + this.proxyAddress = newProxyAddress; + this.lastUpdateTime = updateTime; + }; + + ProxyConfig.reserveDNSUpdate(callback, + proxyAddress.getHostName(), + proxyAddress.getPort(), + refreshInterval, + scheduler); + } } @Override diff --git a/core/src/main/java/com/linecorp/armeria/client/proxy/Socks5ProxyConfig.java b/core/src/main/java/com/linecorp/armeria/client/proxy/Socks5ProxyConfig.java index 8e541b8c041..5fa7f870e52 100644 --- a/core/src/main/java/com/linecorp/armeria/client/proxy/Socks5ProxyConfig.java +++ b/core/src/main/java/com/linecorp/armeria/client/proxy/Socks5ProxyConfig.java @@ -18,6 +18,9 @@ import java.net.InetSocketAddress; import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.BiConsumer; import com.google.common.base.MoreObjects; @@ -28,7 +31,11 @@ */ public final class Socks5ProxyConfig extends ProxyConfig { - private final InetSocketAddress proxyAddress; + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + private InetSocketAddress proxyAddress; + + private long lastUpdateTime = System.currentTimeMillis(); @Nullable private final String username; @@ -38,9 +45,27 @@ public final class Socks5ProxyConfig extends ProxyConfig { Socks5ProxyConfig(InetSocketAddress proxyAddress, @Nullable String username, @Nullable String password) { + this(proxyAddress, username, password, -1); + } + + Socks5ProxyConfig(InetSocketAddress proxyAddress, @Nullable String username, + @Nullable String password, long refreshInterval) { this.proxyAddress = proxyAddress; this.username = username; this.password = password; + + if (refreshInterval > 0) { + final BiConsumer callback = (newProxyAddress, updateTime) -> { + this.proxyAddress = newProxyAddress; + this.lastUpdateTime = updateTime; + }; + + ProxyConfig.reserveDNSUpdate(callback, + proxyAddress.getHostName(), + proxyAddress.getPort(), + refreshInterval, + scheduler); + } } @Override