优化代理设置 (#3876)

This commit is contained in:
Glavo 2025-05-13 16:39:43 +08:00 committed by GitHub
parent b69602b735
commit 9c0f823705
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 232 additions and 156 deletions

View File

@ -30,7 +30,6 @@ import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.ModpackProvider;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.ProxyManager;
import org.jackhuang.hmcl.setting.VersionIconType;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.ui.FXUtils;
@ -204,6 +203,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
/**
* Create new version setting if version id has no version setting.
*
* @param id the version id.
* @return new version setting, null if given version does not exist.
*/
@ -226,7 +226,6 @@ public class HMCLGameRepository extends DefaultGameRepository {
* Get the version setting for version id.
*
* @param id version id
*
* @return corresponding version setting, null if the version has no its own version setting.
*/
@Nullable
@ -350,6 +349,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
/**
* Make version use self version settings instead of the global one.
*
* @param id the version id.
* @return specialized version setting, null if given version does not exist.
*/
@ -416,8 +416,12 @@ public class HMCLGameRepository extends DefaultGameRepository {
.setDaemon(!makeLaunchScript && vs.getLauncherVisibility().isDaemon())
.setJavaAgents(javaAgents)
.setJavaArguments(javaArguments);
if (config().hasProxy()) {
builder.setProxy(ProxyManager.getProxy());
builder.setProxyType(config().getProxyType());
builder.setProxyHost(config().getProxyHost());
builder.setProxyPort(config().getProxyPort());
if (config().hasProxyAuth()) {
builder.setProxyUser(config().getProxyUser());
builder.setProxyPass(config().getProxyPass());

View File

@ -17,100 +17,118 @@
*/
package org.jackhuang.hmcl.setting;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ObservableObjectValue;
import javafx.beans.InvalidationListener;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.io.IOException;
import java.net.*;
import java.util.Collections;
import java.util.List;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class ProxyManager {
private ProxyManager() {
private static final ProxySelector NO_PROXY = new SimpleProxySelector(Proxy.NO_PROXY);
private static final ProxySelector SYSTEM_DEFAULT = Lang.requireNonNullElse(ProxySelector.getDefault(), NO_PROXY);
private static ProxySelector getProxySelector() {
if (config().hasProxy()) {
Proxy.Type proxyType = config().getProxyType();
String host = config().getProxyHost();
int port = config().getProxyPort();
if (proxyType == Proxy.Type.DIRECT || StringUtils.isBlank(host)) {
return NO_PROXY;
} else if (port < 0 || port > 0xFFFF) {
LOG.warning("Illegal proxy port: " + port);
return NO_PROXY;
} else {
return new SimpleProxySelector(new Proxy(proxyType, new InetSocketAddress(host, port)));
}
} else {
return ProxyManager.SYSTEM_DEFAULT;
}
}
private static ObjectBinding<Proxy> proxyProperty;
private static Authenticator getAuthenticator() {
if (config().hasProxy() && config().hasProxyAuth()) {
String username = config().getProxyUser();
String password = config().getProxyPass();
public static Proxy getProxy() {
return proxyProperty.get();
}
public static ObservableObjectValue<Proxy> proxyProperty() {
return proxyProperty;
if (username != null || password != null)
return new SimpleAuthenticator(username, password.toCharArray());
else
return null;
} else
return null;
}
static void init() {
proxyProperty = Bindings.createObjectBinding(
() -> {
String host = config().getProxyHost();
int port = config().getProxyPort();
if (!config().hasProxy() || StringUtils.isBlank(host) || config().getProxyType() == Proxy.Type.DIRECT) {
return Proxy.NO_PROXY;
} else {
if (port < 0 || port > 0xFFFF) {
LOG.warning("Illegal proxy port: " + port);
return Proxy.NO_PROXY;
}
return new Proxy(config().getProxyType(), new InetSocketAddress(host, port));
}
},
config().proxyTypeProperty(),
config().proxyHostProperty(),
config().proxyPortProperty(),
config().hasProxyProperty());
ProxySelector.setDefault(getProxySelector());
InvalidationListener updateProxySelector = observable -> ProxySelector.setDefault(getProxySelector());
config().proxyTypeProperty().addListener(updateProxySelector);
config().proxyHostProperty().addListener(updateProxySelector);
config().proxyPortProperty().addListener(updateProxySelector);
config().hasProxyProperty().addListener(updateProxySelector);
proxyProperty.addListener(any -> updateSystemProxy());
updateSystemProxy();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (config().hasProxyAuth()) {
String username = config().getProxyUser();
String password = config().getProxyPass();
if (username != null && password != null) {
return new PasswordAuthentication(username, password.toCharArray());
}
}
return null;
}
});
Authenticator.setDefault(getAuthenticator());
InvalidationListener updateAuthenticator = observable -> Authenticator.setDefault(getAuthenticator());
config().hasProxyProperty().addListener(updateAuthenticator);
config().hasProxyAuthProperty().addListener(updateAuthenticator);
config().proxyUserProperty().addListener(updateAuthenticator);
config().proxyPassProperty().addListener(updateAuthenticator);
}
private static void updateSystemProxy() {
Proxy proxy = proxyProperty.get();
if (proxy.type() == Proxy.Type.DIRECT) {
System.clearProperty("http.proxyHost");
System.clearProperty("http.proxyPort");
System.clearProperty("https.proxyHost");
System.clearProperty("https.proxyPort");
System.clearProperty("socksProxyHost");
System.clearProperty("socksProxyPort");
} else {
InetSocketAddress address = (InetSocketAddress) proxy.address();
String host = address.getHostString();
String port = String.valueOf(address.getPort());
if (proxy.type() == Type.HTTP) {
System.clearProperty("socksProxyHost");
System.clearProperty("socksProxyPort");
System.setProperty("http.proxyHost", host);
System.setProperty("http.proxyPort", port);
System.setProperty("https.proxyHost", host);
System.setProperty("https.proxyPort", port);
} else if (proxy.type() == Type.SOCKS) {
System.clearProperty("http.proxyHost");
System.clearProperty("http.proxyPort");
System.clearProperty("https.proxyHost");
System.clearProperty("https.proxyPort");
System.setProperty("socksProxyHost", host);
System.setProperty("socksProxyPort", port);
private static final class SimpleProxySelector extends ProxySelector {
private final List<Proxy> proxies;
SimpleProxySelector(Proxy proxy) {
this(Collections.singletonList(proxy));
}
SimpleProxySelector(List<Proxy> proxies) {
this.proxies = proxies;
}
@Override
public List<Proxy> select(URI uri) {
if (uri == null)
throw new IllegalArgumentException("URI can't be null.");
return proxies;
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
if (uri == null || sa == null || ioe == null) {
throw new IllegalArgumentException("Arguments can't be null.");
}
}
@Override
public String toString() {
return "SimpleProxySelector" + proxies;
}
}
private static final class SimpleAuthenticator extends Authenticator {
private final String username;
private final char[] password;
private SimpleAuthenticator(String username, char[] password) {
this.username = username;
this.password = password;
}
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return getRequestorType() == RequestorType.PROXY ? new PasswordAuthentication(username, password) : null;
}
}
private ProxyManager() {
}
}

View File

@ -18,6 +18,7 @@
package org.jackhuang.hmcl.ui.main;
import com.jfoenix.controls.*;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
@ -28,6 +29,7 @@ import javafx.scene.layout.*;
import org.jackhuang.hmcl.setting.DownloadProviders;
import org.jackhuang.hmcl.task.FetchTask;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.WeakListenerHolder;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
@ -37,11 +39,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.reversedSelectedPropertyFor;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
public class DownloadSettingsPage extends StackPane {
private final WeakListenerHolder holder = new WeakListenerHolder();
public DownloadSettingsPage() {
VBox content = new VBox(10);
content.setPadding(new Insets(10));
@ -164,38 +167,76 @@ public class DownloadSettingsPage extends StackPane {
VBox proxyList = new VBox(10);
proxyList.getStyleClass().add("card-non-transparent");
VBox proxyPane = new VBox();
HBox proxyTypePane = new HBox();
{
JFXCheckBox chkDisableProxy = new JFXCheckBox(i18n("settings.launcher.proxy.disable"));
VBox.setMargin(chkDisableProxy, new Insets(8, 0, 0, 0));
proxyList.getChildren().add(chkDisableProxy);
reversedSelectedPropertyFor(chkDisableProxy).bindBidirectional(config().hasProxyProperty());
proxyPane.disableProperty().bind(chkDisableProxy.selectedProperty());
proxyTypePane.setPadding(new Insets(10, 0, 0, 0));
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
JFXRadioButton chkProxyDefault = new JFXRadioButton(i18n("settings.launcher.proxy.default"));
chkProxyDefault.setUserData(null);
chkProxyDefault.setToggleGroup(proxyConfigurationGroup);
JFXRadioButton chkProxyNone = new JFXRadioButton(i18n("settings.launcher.proxy.none"));
chkProxyNone.setUserData(Proxy.Type.DIRECT);
chkProxyNone.setToggleGroup(proxyConfigurationGroup);
JFXRadioButton chkProxyHttp = new JFXRadioButton(i18n("settings.launcher.proxy.http"));
chkProxyHttp.setUserData(Proxy.Type.HTTP);
chkProxyHttp.setToggleGroup(proxyConfigurationGroup);
JFXRadioButton chkProxySocks = new JFXRadioButton(i18n("settings.launcher.proxy.socks"));
chkProxySocks.setUserData(Proxy.Type.SOCKS);
chkProxySocks.setToggleGroup(proxyConfigurationGroup);
if (config().hasProxy()) {
Proxy.Type proxyType = config().getProxyType();
if (proxyType == Proxy.Type.DIRECT) {
chkProxyNone.setSelected(true);
} else if (proxyType == Proxy.Type.HTTP) {
chkProxyHttp.setSelected(true);
} else if (proxyType == Proxy.Type.SOCKS) {
chkProxySocks.setSelected(true);
} else {
chkProxyNone.setSelected(true);
}
} else {
chkProxyDefault.setSelected(true);
}
holder.add(FXUtils.onWeakChange(proxyConfigurationGroup.selectedToggleProperty(), toggle -> {
Proxy.Type proxyType = toggle != null ? (Proxy.Type) toggle.getUserData() : null;
if (proxyType == null) {
config().setHasProxy(false);
config().setProxyType(null);
} else {
config().setHasProxy(true);
config().setProxyType(proxyType);
}
}));
proxyTypePane.getChildren().setAll(chkProxyDefault, chkProxyNone, chkProxyHttp, chkProxySocks);
proxyList.getChildren().add(proxyTypePane);
}
VBox proxyPane = new VBox();
{
proxyPane.setPadding(new Insets(0, 0, 0, 30));
proxyPane.disableProperty().bind(
Bindings.createBooleanBinding(() ->
!config().hasProxy() || config().getProxyType() == null || config().getProxyType() == Proxy.Type.DIRECT,
config().hasProxyProperty(),
config().proxyTypeProperty()));
ColumnConstraints colHgrow = new ColumnConstraints();
colHgrow.setHgrow(Priority.ALWAYS);
JFXRadioButton chkProxyNone;
JFXRadioButton chkProxyHttp;
JFXRadioButton chkProxySocks;
{
HBox hBox = new HBox();
chkProxyNone = new JFXRadioButton(i18n("settings.launcher.proxy.none"));
chkProxyHttp = new JFXRadioButton(i18n("settings.launcher.proxy.http"));
chkProxySocks = new JFXRadioButton(i18n("settings.launcher.proxy.socks"));
hBox.getChildren().setAll(chkProxyNone, chkProxyHttp, chkProxySocks);
proxyPane.getChildren().add(hBox);
}
{
GridPane gridPane = new GridPane();
gridPane.setPadding(new Insets(0, 0, 0, 30));
gridPane.setHgap(20);
gridPane.setVgap(10);
gridPane.setStyle("-fx-padding: 0 0 0 15;");
gridPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);
gridPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
@ -223,6 +264,8 @@ public class DownloadSettingsPage extends StackPane {
{
JFXTextField txtProxyPort = new JFXTextField();
GridPane.setFillWidth(txtProxyPort, false);
txtProxyPort.setMaxWidth(200);
GridPane.setRowIndex(txtProxyPort, 2);
GridPane.setColumnIndex(txtProxyPort, 1);
FXUtils.setValidateWhileTextChanged(txtProxyPort, true);
@ -236,25 +279,25 @@ public class DownloadSettingsPage extends StackPane {
proxyPane.getChildren().add(gridPane);
}
GridPane authPane = new GridPane();
VBox chkProxyAuthenticationPane = new VBox();
{
VBox vBox = new VBox();
vBox.setStyle("-fx-padding: 20 0 20 5;");
chkProxyAuthenticationPane.setPadding(new Insets(20, 0, 20, 5));
JFXCheckBox chkProxyAuthentication = new JFXCheckBox(i18n("settings.launcher.proxy.authentication"));
vBox.getChildren().setAll(chkProxyAuthentication);
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
chkProxyAuthenticationPane.getChildren().add(chkProxyAuthentication);
chkProxyAuthentication.selectedProperty().bindBidirectional(config().hasProxyAuthProperty());
proxyPane.getChildren().add(vBox);
proxyPane.getChildren().add(chkProxyAuthenticationPane);
}
GridPane authPane = new GridPane();
{
authPane.setPadding(new Insets(0, 0, 0, 30));
authPane.setHgap(20);
authPane.setVgap(10);
authPane.setStyle("-fx-padding: 0 0 0 15;");
authPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);
authPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
authPane.disableProperty().bind(config().hasProxyAuthProperty().not());
{
Label username = new Label(i18n("settings.launcher.proxy.username"));
@ -286,18 +329,9 @@ public class DownloadSettingsPage extends StackPane {
txtProxyPassword.textProperty().bindBidirectional(config().proxyPassProperty());
}
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
chkProxyNone.setUserData(Proxy.Type.DIRECT);
chkProxyNone.setToggleGroup(proxyConfigurationGroup);
chkProxyHttp.setUserData(Proxy.Type.HTTP);
chkProxyHttp.setToggleGroup(proxyConfigurationGroup);
chkProxySocks.setUserData(Proxy.Type.SOCKS);
chkProxySocks.setToggleGroup(proxyConfigurationGroup);
selectedItemPropertyFor(proxyConfigurationGroup, Proxy.Type.class).bindBidirectional(config().proxyTypeProperty());
proxyPane.getChildren().add(authPane);
proxyList.getChildren().add(proxyPane);
}
proxyList.getChildren().add(proxyPane);
}
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("settings.launcher.proxy")), proxyList);
}

View File

@ -1274,7 +1274,7 @@ settings.launcher.log=Logging
settings.launcher.log.font=Font
settings.launcher.proxy=Proxy
settings.launcher.proxy.authentication=Requires Authentication
settings.launcher.proxy.disable=Use System Proxy
settings.launcher.proxy.default=Use System Proxy
settings.launcher.proxy.host=Host
settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=No Proxy

View File

@ -1278,7 +1278,7 @@ settings.launcher.log=Registro
settings.launcher.log.font=Fuente
settings.launcher.proxy=Proxy
settings.launcher.proxy.authentication=Requiere autenticación
settings.launcher.proxy.disable=Usar proxy del sistema
settings.launcher.proxy.default=Usar proxy del sistema
settings.launcher.proxy.host=Host
settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=Sin proxy

View File

@ -853,7 +853,7 @@ settings.launcher.log=ログ
settings.launcher.log.font=ログフォント
settings.launcher.proxy=プロキシ
settings.launcher.proxy.authentication=プロキシ認証
settings.launcher.proxy.disable=システムプロキシを使用する
settings.launcher.proxy.default=システムプロキシを使用する
settings.launcher.proxy.host=Host
settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=プロキシなし

View File

@ -1278,7 +1278,7 @@ settings.launcher.log=Запись логов
settings.launcher.log.font=Шрифт
settings.launcher.proxy=Прокси
settings.launcher.proxy.authentication=Требуется авторизация
settings.launcher.proxy.disable=Использовать прокси системы
settings.launcher.proxy.default=Использовать прокси системы
settings.launcher.proxy.host=Хост
settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=Без прокси

View File

@ -1070,7 +1070,7 @@ settings.launcher.log=日誌
settings.launcher.log.font=日誌字體
settings.launcher.proxy=代理
settings.launcher.proxy.authentication=身份驗證
settings.launcher.proxy.disable=使用系統代理
settings.launcher.proxy.default=使用系統代理
settings.launcher.proxy.host=IP 位址
settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=不使用代理

View File

@ -1080,7 +1080,7 @@ settings.launcher.log=日志
settings.launcher.log.font=日志字体
settings.launcher.proxy=代理
settings.launcher.proxy.authentication=身份验证
settings.launcher.proxy.disable=使用系统代理
settings.launcher.proxy.default=使用系统代理
settings.launcher.proxy.host=主机
settings.launcher.proxy.http=HTTP
settings.launcher.proxy.none=不使用代理

View File

@ -49,7 +49,9 @@ public class LaunchOptions implements Serializable {
private boolean fullscreen;
private String serverIp;
private String wrapper;
private Proxy proxy;
private Proxy.Type proxyType;
private String proxyHost;
private int proxyPort;
private String proxyUser;
private String proxyPass;
private boolean noGeneratedJVMArgs;
@ -191,11 +193,16 @@ public class LaunchOptions implements Serializable {
return wrapper;
}
/**
* Proxy settings
*/
public Proxy getProxy() {
return proxy;
public Proxy.Type getProxyType() {
return proxyType;
}
public String getProxyHost() {
return proxyHost;
}
public int getProxyPort() {
return proxyPort;
}
/**
@ -402,8 +409,18 @@ public class LaunchOptions implements Serializable {
return this;
}
public Builder setProxy(Proxy proxy) {
options.proxy = proxy;
public Builder setProxyType(Proxy.Type proxyType) {
options.proxyType = proxyType;
return this;
}
public Builder setProxyHost(String proxyHost) {
options.proxyHost = proxyHost;
return this;
}
public Builder setProxyPort(int proxyPort) {
options.proxyPort = proxyPort;
return this;
}

View File

@ -31,7 +31,6 @@ import org.jackhuang.hmcl.util.platform.*;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@ -111,24 +110,6 @@ public class DefaultLauncher extends Launcher {
res.addAllWithoutParsing(options.getOverrideJavaArguments());
Proxy proxy = options.getProxy();
if (proxy != null && StringUtils.isBlank(options.getProxyUser()) && StringUtils.isBlank(options.getProxyPass())) {
InetSocketAddress address = (InetSocketAddress) options.getProxy().address();
if (address != null) {
String host = address.getHostString();
int port = address.getPort();
if (proxy.type() == Proxy.Type.HTTP) {
res.addDefault("-Dhttp.proxyHost=", host);
res.addDefault("-Dhttp.proxyPort=", String.valueOf(port));
res.addDefault("-Dhttps.proxyHost=", host);
res.addDefault("-Dhttps.proxyPort=", String.valueOf(port));
} else if (proxy.type() == Proxy.Type.SOCKS) {
res.addDefault("-DsocksProxyHost=", host);
res.addDefault("-DsocksProxyPort=", String.valueOf(port));
}
}
}
if (options.getMaxMemory() != null && options.getMaxMemory() > 0)
res.addDefault("-Xmx", options.getMaxMemory() + "m");
@ -189,6 +170,26 @@ public class DefaultLauncher extends Launcher {
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)
res.addDefault("-Duser.home=", options.getGameDir().getParent());
Proxy.Type proxyType = options.getProxyType();
if (proxyType == null) {
res.addDefault("-Djava.net.useSystemProxies", "true");
} else {
String proxyHost = options.getProxyHost();
int proxyPort = options.getProxyPort();
if (StringUtils.isNotBlank(proxyHost) && proxyPort >= 0 && proxyPort <= 0xFFFF) {
if (proxyType == Proxy.Type.HTTP) {
res.addDefault("-Dhttp.proxyHost=", proxyHost);
res.addDefault("-Dhttp.proxyPort=", String.valueOf(proxyPort));
res.addDefault("-Dhttps.proxyHost=", proxyHost);
res.addDefault("-Dhttps.proxyPort=", String.valueOf(proxyPort));
} else if (proxyType == Proxy.Type.SOCKS) {
res.addDefault("-DsocksProxyHost=", proxyHost);
res.addDefault("-DsocksProxyPort=", String.valueOf(proxyPort));
}
}
}
final int javaVersion = options.getJava().getParsedVersion();
final boolean is64bit = options.getJava().getBits() == Bits.BIT_64;
@ -309,13 +310,15 @@ public class DefaultLauncher extends Launcher {
if (options.isFullscreen())
res.add("--fullscreen");
if (options.getProxy() != null && options.getProxy().type() == Proxy.Type.SOCKS) {
InetSocketAddress address = (InetSocketAddress) options.getProxy().address();
if (address != null) {
if (options.getProxyType() == Proxy.Type.SOCKS) {
String proxyHost = options.getProxyHost();
int proxyPort = options.getProxyPort();
if (StringUtils.isNotBlank(proxyHost) && proxyPort >= 0 && proxyPort <= 0xFFFF) {
res.add("--proxyHost");
res.add(address.getHostString());
res.add(proxyHost);
res.add("--proxyPort");
res.add(String.valueOf(address.getPort()));
res.add(String.valueOf(proxyPort));
if (StringUtils.isNotBlank(options.getProxyUser()) && StringUtils.isNotBlank(options.getProxyPass())) {
res.add("--proxyUser");
res.add(options.getProxyUser());