diff --git a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java index 9d74e067..1047fb02 100644 --- a/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java +++ b/common/src/main/java/com/discordsrv/common/AbstractDiscordSRV.java @@ -24,16 +24,17 @@ import com.discordsrv.api.events.lifecycle.DiscordSRVReloadedEvent; import com.discordsrv.api.events.lifecycle.DiscordSRVShuttingDownEvent; import com.discordsrv.api.module.Module; import com.discordsrv.api.reload.ReloadFlag; +import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.abstraction.bootstrap.IBootstrap; import com.discordsrv.common.command.discord.DiscordCommandModule; import com.discordsrv.common.command.game.GameCommandModule; -import com.discordsrv.api.reload.ReloadResult; import com.discordsrv.common.config.configurate.manager.ConnectionConfigManager; import com.discordsrv.common.config.configurate.manager.MainConfigManager; import com.discordsrv.common.config.configurate.manager.MessagesConfigManager; import com.discordsrv.common.config.configurate.manager.MessagesConfigSingleManager; import com.discordsrv.common.config.connection.BotConfig; import com.discordsrv.common.config.connection.ConnectionConfig; +import com.discordsrv.common.config.connection.HttpProxyConfig; import com.discordsrv.common.config.connection.UpdateConfig; import com.discordsrv.common.config.main.MainConfig; import com.discordsrv.common.config.main.linking.LinkedAccountConfig; @@ -102,10 +103,7 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; -import java.net.InetAddress; -import java.net.URL; -import java.net.URLClassLoader; -import java.net.UnknownHostException; +import java.net.*; import java.nio.file.Path; import java.time.Duration; import java.time.ZonedDateTime; @@ -210,15 +208,19 @@ public abstract class AbstractDiscordSRV< logger.warning(""); /////////////////////////////////////////////////////////////// - Dispatcher dispatcher = new Dispatcher(); - dispatcher.setMaxRequests(20); // Set maximum amount of requests at a time (to something more reasonable than 64) - dispatcher.setMaxRequestsPerHost(16); // Most requests are to discord.com + createHttpClient(); + } - ConnectionPool connectionPool = new ConnectionPool(5, 10, TimeUnit.SECONDS); + private HttpProxyConfig usedProxyConfig = null; + private void createHttpClient() { + HttpProxyConfig proxyConfig = connectionConfig() != null ? connectionConfig().httpProxy : null; + if (httpClient != null && Objects.equals(usedProxyConfig, proxyConfig)) { + // Skip recreating client, if proxy is the same + return; + } + usedProxyConfig = proxyConfig; - this.httpClient = new OkHttpClient.Builder() - .dispatcher(dispatcher) - .connectionPool(connectionPool) + OkHttpClient.Builder builder = new OkHttpClient.Builder() .addInterceptor(chain -> { Request original = chain.request(); String host = original.url().host(); @@ -238,8 +240,41 @@ public abstract class AbstractDiscordSRV< }) .connectTimeout(20, TimeUnit.SECONDS) .readTimeout(20, TimeUnit.SECONDS) - .writeTimeout(20, TimeUnit.SECONDS) - .build(); + .writeTimeout(20, TimeUnit.SECONDS); + + Dispatcher dispatcher = new Dispatcher(); + dispatcher.setMaxRequests(20); // Set maximum amount of requests at a time (to something more reasonable than 64) + dispatcher.setMaxRequestsPerHost(16); // Most requests are to discord.com + builder.dispatcher(dispatcher); + + builder.connectionPool(new ConnectionPool(5, 10, TimeUnit.SECONDS)); + + if (proxyConfig != null && proxyConfig.enabled) { + builder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyConfig.host, proxyConfig.port))); + + HttpProxyConfig.BasicAuthConfig basicAuthConfig = proxyConfig.basicAuth; + if (basicAuthConfig != null && basicAuthConfig.enabled) { + // https://stackoverflow.com/questions/41806422/java-web-start-unable-to-tunnel-through-proxy-since-java-8-update-111 + String disabledSchemes = "jdk.http.auth.tunneling.disabledSchemes"; + + String oldDisabledSchemes = System.getProperty(disabledSchemes); + if (oldDisabledSchemes != null) { + String newDisabledSchemes = Arrays.stream(oldDisabledSchemes.split(",")) + .filter(disabledScheme -> !disabledScheme.equalsIgnoreCase("Basic")) + .collect(Collectors.joining(",")); + if (!oldDisabledSchemes.equals(newDisabledSchemes)) { + System.setProperty(disabledSchemes, newDisabledSchemes); + } + } + + String credential = Credentials.basic(basicAuthConfig.username, basicAuthConfig.password); + builder.proxyAuthenticator((route, response) -> response.request().newBuilder() + .header("Proxy-Authorization", credential) + .build()); + } + } + + this.httpClient = builder.build(); } protected URL getManifest() { @@ -711,6 +746,7 @@ public abstract class AbstractDiscordSRV< messagesConfigManager().load(); channelConfig().reload(); + createHttpClient(); } catch (Throwable t) { if (initial) { setStatus(Status.FAILED_TO_LOAD_CONFIG); @@ -847,7 +883,7 @@ public abstract class AbstractDiscordSRV< // Modules are reloaded upon DiscordSRV being ready, thus not needed at initial if (!initial && flags.contains(ReloadFlag.CONFIG)) { - results.addAll(moduleManager.reload()); + results.addAll(moduleManager().reload()); } if (flags.contains(ReloadFlag.DISCORD_COMMANDS)) { @@ -893,14 +929,16 @@ public abstract class AbstractDiscordSRV< eventBus().shutdown(); // Shutdown OkHttp - httpClient.dispatcher().executorService().shutdownNow(); - httpClient.connectionPool().evictAll(); - try { - Cache cache = httpClient.cache(); - if (cache != null) { - cache.close(); - } - } catch (IOException ignored) {} + if (httpClient != null) { + httpClient.dispatcher().executorService().shutdownNow(); + httpClient.connectionPool().evictAll(); + try { + Cache cache = httpClient.cache(); + if (cache != null) { + cache.close(); + } + } catch (IOException ignored) {} + } try { if (storage != null) { diff --git a/common/src/main/java/com/discordsrv/common/config/connection/ConnectionConfig.java b/common/src/main/java/com/discordsrv/common/config/connection/ConnectionConfig.java index e3cfff2d..e2b40337 100644 --- a/common/src/main/java/com/discordsrv/common/config/connection/ConnectionConfig.java +++ b/common/src/main/java/com/discordsrv/common/config/connection/ConnectionConfig.java @@ -49,4 +49,6 @@ public class ConnectionConfig implements Config { public MinecraftAuthConfig minecraftAuth = new MinecraftAuthConfig(); public UpdateConfig update = new UpdateConfig(); + + public HttpProxyConfig httpProxy = new HttpProxyConfig(); } diff --git a/common/src/main/java/com/discordsrv/common/config/connection/HttpProxyConfig.java b/common/src/main/java/com/discordsrv/common/config/connection/HttpProxyConfig.java new file mode 100644 index 00000000..85cc4bc9 --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/config/connection/HttpProxyConfig.java @@ -0,0 +1,69 @@ +/* + * This file is part of DiscordSRV, licensed under the GPLv3 License + * Copyright (c) 2016-2024 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.discordsrv.common.config.connection; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +import java.util.Objects; + +@ConfigSerializable +public class HttpProxyConfig { + + public boolean enabled = false; + public String host = "example.com"; + public int port = 8080; + public BasicAuthConfig basicAuth = new BasicAuthConfig(); + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + HttpProxyConfig that = (HttpProxyConfig) o; + return enabled == that.enabled + && port == that.port + && Objects.equals(host, that.host) + && Objects.equals(basicAuth, that.basicAuth); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, host, port, basicAuth); + } + + @ConfigSerializable + public static class BasicAuthConfig { + + public boolean enabled = true; + public String username = "discordsrv"; + public String password = ""; + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + BasicAuthConfig that = (BasicAuthConfig) o; + return enabled == that.enabled + && Objects.equals(username, that.username) + && Objects.equals(password, that.password); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, username, password); + } + } +} diff --git a/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java b/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java index 51d2d5ac..9aa0f08e 100644 --- a/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java +++ b/common/src/main/java/com/discordsrv/common/discord/connection/jda/JDAConnectionManager.java @@ -33,6 +33,7 @@ import com.discordsrv.api.placeholder.PlaceholderLookupResult; import com.discordsrv.common.DiscordSRV; import com.discordsrv.common.config.connection.BotConfig; import com.discordsrv.common.config.connection.ConnectionConfig; +import com.discordsrv.common.config.connection.HttpProxyConfig; import com.discordsrv.common.config.documentation.DocumentationURLs; import com.discordsrv.common.config.main.MemberCachingConfig; import com.discordsrv.common.core.logging.Logger; @@ -46,6 +47,7 @@ import com.discordsrv.common.discord.connection.details.DiscordConnectionDetails import com.discordsrv.common.feature.debug.DebugGenerateEvent; import com.discordsrv.common.feature.debug.file.TextDebugFile; import com.discordsrv.common.helper.Timeout; +import com.neovisionaries.ws.client.ProxySettings; import com.neovisionaries.ws.client.WebSocketFactory; import com.neovisionaries.ws.client.WebSocketFrame; import net.dv8tion.jda.api.JDA; @@ -407,6 +409,19 @@ public class JDAConnectionManager implements DiscordConnectionManager { jdaBuilder.setHttpClient(discordSRV.httpClient()); WebSocketFactory webSocketFactory = new WebSocketFactory(); + + HttpProxyConfig proxyConfig = discordSRV.connectionConfig().httpProxy; + if (proxyConfig != null && proxyConfig.enabled) { + ProxySettings proxySettings = webSocketFactory.getProxySettings(); + proxySettings.setHost(proxyConfig.host); + proxySettings.setPort(proxyConfig.port); + + HttpProxyConfig.BasicAuthConfig basicAuthConfig = proxyConfig.basicAuth; + if (basicAuthConfig != null && basicAuthConfig.enabled) { + proxySettings.setCredentials(basicAuthConfig.username, basicAuthConfig.password); + } + } + jdaBuilder.setWebsocketFactory(webSocketFactory); try {