feat(config): switch webPort to URI-style webListen address

- replace integer webPort with MultiProtocolSocketAddress webListen
  (default: http://0.0.0.0:4665) in controller config
- rename Swing UI item to "Web API 监听地址" and validate as full URI
- update WebServer binding to honor configured host and port
- normalize legacy webPort integers to http://0.0.0.0:<port> on load
  and keep JSON API backward compatibility
- update i18n keys in zh_CN and en_US resource bundles
- update root klalb-config.json and AGENTS.md documentation
This commit was merged in pull request #2.
This commit is contained in:
2026-08-26 19:42:26 +08:00
parent 9cea5ed493
commit 61868fe93e
11 changed files with 99 additions and 84 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ Key controller config fields:
- `ntpServers`: time server list (replaces `ntpServerTable`). - `ntpServers`: time server list (replaces `ntpServerTable`).
- `denyExternalEndpointQuery` / `denyExternalEndpointBroadcast`: safety flags — the query flag hides ONLY the external-endpoint list in full node-info responses (device name/description still answer; Tiny queries are never gated), the broadcast flag disables LAN multicast discovery (renamed from `denyConnectionQuery` / `denyConnectionBroadcast`, which replaced `denyLineTableQuery` / `denyLineTableBroadcast`). - `denyExternalEndpointQuery` / `denyExternalEndpointBroadcast`: safety flags — the query flag hides ONLY the external-endpoint list in full node-info responses (device name/description still answer; Tiny queries are never gated), the broadcast flag disables LAN multicast discovery (renamed from `denyConnectionQuery` / `denyConnectionBroadcast`, which replaced `denyLineTableQuery` / `denyLineTableBroadcast`).
- `enableTUN`: boolean flag for TUN interface creation (`"TUNName"` configures device name). - `enableTUN`: boolean flag for TUN interface creation (`"TUNName"` configures device name).
- `webPort`: default `4665`. - `webListen`: Web API listen address, normally `http://0.0.0.0:4665`; legacy `webPort` is accepted on load/API input.
Legacy JSON keys are still accepted on load: `KLALBConfigItem.getDefaultJsonDeserializer()` normalizes old key names (`openConnections`/`LineTable`, `denyConnectionQuery`, `denyLineTable*`, ...) before reflective deserialization (manual rewrite because gson-2.1 has no `@SerializedName(alternate=...)`), and `handleConfig` in the web server accepts them too. New saves always write canonical names. Legacy JSON keys are still accepted on load: `KLALBConfigItem.getDefaultJsonDeserializer()` normalizes old key names (`openConnections`/`LineTable`, `denyConnectionQuery`, `denyLineTable*`, ...) before reflective deserialization (manual rewrite because gson-2.1 has no `@SerializedName(alternate=...)`), and `handleConfig` in the web server accepts them too. New saves always write canonical names.
+1 -1
View File
@@ -56,7 +56,7 @@
"DeviceName": "Device Name", "DeviceName": "Device Name",
"DeviceDescription": "The Description of Device", "DeviceDescription": "The Description of Device",
"webUI": true, "webUI": true,
"webPort": 4665, "webListen": "http://0.0.0.0:4665",
"NetworkInterfaceExcepts": [], "NetworkInterfaceExcepts": [],
"Type": "KLALBController" "Type": "KLALBController"
}, },
+2 -2
View File
@@ -114,5 +114,5 @@ multifill=Multi-Core - Fill Cores Sequentially (Recommended for general-purpose
multiscatter=Multi-Core - Spread Load Evenly (Optimized for multi-socket NUMA architectures) multiscatter=Multi-Core - Spread Load Evenly (Optimized for multi-socket NUMA architectures)
webapisettings=Web API settings webapisettings=Web API settings
enablewebapi=Enable Web API enablewebapi=Enable Web API
webport=Web port weblistenaddr=Web API listen address
invaildwebport=Invalid web port number invaildweblistenaddr=Invalid Web API listen address
+4 -4
View File
@@ -112,7 +112,7 @@ performancestrategy=性能策略
singlecore=单核-缓存命中率优先(高能耗比,适合低功耗设备、云机) singlecore=单核-缓存命中率优先(高能耗比,适合低功耗设备、云机)
multifill=多核-负载按顺序填充(适合大多数物理服务器、电脑) multifill=多核-负载按顺序填充(适合大多数物理服务器、电脑)
multiscatter=多核-负载均匀打散分配(适合特殊的多路NUMA服务器) multiscatter=多核-负载均匀打散分配(适合特殊的多路NUMA服务器)
webapisettings=Web API \u8bbe\u7f6e webapisettings=Web API 设置
enablewebapi=\u542f\u7528 Web API enablewebapi=启用 Web API
webport=Web \u7aef\u53e3 weblistenaddr=Web API 监听地址:端口
invaildwebport=\u65e0\u6548\u7684 Web \u7aef\u53e3\u53f7 invaildweblistenaddr=无效的 Web API 监听地址:端口
@@ -9,6 +9,7 @@ import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
@@ -82,6 +83,11 @@ public class KLALBConfigItem {
renameLegacyKey(jobj,"externalEndpoints","openConnections","OpenConnections","lineTable","LineTable"); renameLegacyKey(jobj,"externalEndpoints","openConnections","OpenConnections","lineTable","LineTable");
renameLegacyKey(jobj,"denyExternalEndpointQuery","denyConnectionQuery","denyLineTableQuery"); renameLegacyKey(jobj,"denyExternalEndpointQuery","denyConnectionQuery","denyLineTableQuery");
renameLegacyKey(jobj,"denyExternalEndpointBroadcast","denyConnectionBroadcast","denyLineTableBroadcast"); renameLegacyKey(jobj,"denyExternalEndpointBroadcast","denyConnectionBroadcast","denyLineTableBroadcast");
if (!jobj.has("webListen") && jobj.has("webPort") && !jobj.get("webPort").isJsonNull()) {
int port = jobj.get("webPort").getAsInt();
jobj.add("webListen", new JsonPrimitive("http://0.0.0.0:" + port));
}
jobj.remove("webPort");
} }
private void renameLegacyKey(JsonObject jobj, String newKey, String... legacyKeys) { private void renameLegacyKey(JsonObject jobj, String newKey, String... legacyKeys) {
@@ -35,7 +35,7 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
private String DeviceName; private String DeviceName;
private String DeviceDescription; private String DeviceDescription;
private boolean webUI = false; private boolean webUI = false;
private int webPort = 4665; private MultiProtocolSocketAddress webListen = new MultiProtocolSocketAddress("http", "0.0.0.0", 4665);
public boolean isEnableTUN() { public boolean isEnableTUN() {
return enableTUN; return enableTUN;
@@ -53,12 +53,12 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
this.webUI = webUI; this.webUI = webUI;
} }
public int getWebPort() { public MultiProtocolSocketAddress getWebListen() {
return webPort; return webListen;
} }
public void setWebPort(int webPort) { public void setWebListen(MultiProtocolSocketAddress webListen) {
this.webPort = webPort; this.webListen = webListen;
} }
public String getPerformanceStrategy() { public String getPerformanceStrategy() {
@@ -400,6 +400,8 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
", DeviceName='" + DeviceName + '\'' + ", DeviceName='" + DeviceName + '\'' +
", DeviceDescription='" + DeviceDescription + '\'' + ", DeviceDescription='" + DeviceDescription + '\'' +
", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts + ", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts +
", webUI=" + webUI +
", webListen=" + webListen +
'}'; '}';
} }
@@ -408,11 +410,11 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false; if (!super.equals(o)) return false;
KLALBControllerConfigItem that = (KLALBControllerConfigItem) o; KLALBControllerConfigItem that = (KLALBControllerConfigItem) o;
return nogui == that.nogui && enableTUN == that.enableTUN && webUI == that.webUI && denyExternalEndpointQuery == that.denyExternalEndpointQuery && denyExternalEndpointBroadcast == that.denyExternalEndpointBroadcast && Double.compare(burstLimit, that.burstLimit) == 0 && Double.compare(delayUpperBound, that.delayUpperBound) == 0 && Double.compare(delayLowerBound, that.delayLowerBound) == 0 && nagleDelayTime == that.nagleDelayTime && linkNagleDelayTime == that.linkNagleDelayTime && linkConnectionsCount == that.linkConnectionsCount && webPort == that.webPort && Objects.equals(language, that.language) && Objects.equals(VirtualAddress, that.VirtualAddress) && Objects.equals(VirtualASN, that.VirtualASN) && Objects.equals(DNS, that.DNS) && Objects.equals(TCPListen, that.TCPListen) && Objects.equals(UDPListen, that.UDPListen) && Objects.equals(VirtualSocketName, that.VirtualSocketName) && Objects.equals(externalEndpoints, that.externalEndpoints) && Objects.equals(autoConnections, that.autoConnections) && Objects.equals(ntpServers, that.ntpServers) && Objects.equals(ExtraRoutes, that.ExtraRoutes) && Objects.equals(congestionAlgorithm, that.congestionAlgorithm) && Objects.equals(TUNName, that.TUNName) && Objects.equals(performanceStrategy, that.performanceStrategy) && Objects.equals(DeviceName, that.DeviceName) && Objects.equals(DeviceDescription, that.DeviceDescription) && Objects.equals(NetworkInterfaceExcepts, that.NetworkInterfaceExcepts); return nogui == that.nogui && enableTUN == that.enableTUN && webUI == that.webUI && denyExternalEndpointQuery == that.denyExternalEndpointQuery && denyExternalEndpointBroadcast == that.denyExternalEndpointBroadcast && Double.compare(burstLimit, that.burstLimit) == 0 && Double.compare(delayUpperBound, that.delayUpperBound) == 0 && Double.compare(delayLowerBound, that.delayLowerBound) == 0 && nagleDelayTime == that.nagleDelayTime && linkNagleDelayTime == that.linkNagleDelayTime && linkConnectionsCount == that.linkConnectionsCount && Objects.equals(webListen, that.webListen) && Objects.equals(language, that.language) && Objects.equals(VirtualAddress, that.VirtualAddress) && Objects.equals(VirtualASN, that.VirtualASN) && Objects.equals(DNS, that.DNS) && Objects.equals(TCPListen, that.TCPListen) && Objects.equals(UDPListen, that.UDPListen) && Objects.equals(VirtualSocketName, that.VirtualSocketName) && Objects.equals(externalEndpoints, that.externalEndpoints) && Objects.equals(autoConnections, that.autoConnections) && Objects.equals(ntpServers, that.ntpServers) && Objects.equals(ExtraRoutes, that.ExtraRoutes) && Objects.equals(congestionAlgorithm, that.congestionAlgorithm) && Objects.equals(TUNName, that.TUNName) && Objects.equals(performanceStrategy, that.performanceStrategy) && Objects.equals(DeviceName, that.DeviceName) && Objects.equals(DeviceDescription, that.DeviceDescription) && Objects.equals(NetworkInterfaceExcepts, that.NetworkInterfaceExcepts);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(super.hashCode(), language, nogui, VirtualAddress, VirtualASN, DNS, TCPListen, UDPListen, VirtualSocketName, externalEndpoints, autoConnections, ntpServers, ExtraRoutes, denyExternalEndpointQuery, denyExternalEndpointBroadcast, congestionAlgorithm, burstLimit, delayUpperBound, delayLowerBound, nagleDelayTime, linkNagleDelayTime, linkConnectionsCount, enableTUN, TUNName, performanceStrategy, DeviceName, DeviceDescription, NetworkInterfaceExcepts, webUI, webPort); return Objects.hash(super.hashCode(), language, nogui, VirtualAddress, VirtualASN, DNS, TCPListen, UDPListen, VirtualSocketName, externalEndpoints, autoConnections, ntpServers, ExtraRoutes, denyExternalEndpointQuery, denyExternalEndpointBroadcast, congestionAlgorithm, burstLimit, delayUpperBound, delayLowerBound, nagleDelayTime, linkNagleDelayTime, linkConnectionsCount, enableTUN, TUNName, performanceStrategy, DeviceName, DeviceDescription, NetworkInterfaceExcepts, webUI, webListen);
} }
} }
@@ -112,8 +112,8 @@ public class KLALBMain {
int p = 4665; int p = 4665;
if(sc.length >= 3) { if(sc.length >= 3) {
try { p = Integer.parseInt(sc[2]); } catch (NumberFormatException ignored) {} try { p = Integer.parseInt(sc[2]); } catch (NumberFormatException ignored) {}
} else if(kpcje.getControllerConfig() != null && kpcje.getControllerConfig().getWebPort() > 0) { } else if(kpcje.getControllerConfig() != null && kpcje.getControllerConfig().getWebListen() != null) {
p = kpcje.getControllerConfig().getWebPort(); p = kpcje.getControllerConfig().getWebListen().getPort();
} }
try { try {
kpcje.enableWebServer(p); kpcje.enableWebServer(p);
@@ -85,17 +85,19 @@ public class KLALBProxySystem {
} }
public void enableWebServer() throws IOException { public void enableWebServer() throws IOException {
int port = 4665;
KLALBControllerConfigItem cci = getControllerConfig(); KLALBControllerConfigItem cci = getControllerConfig();
if (cci != null && cci.getWebPort() > 0) { MultiProtocolSocketAddress listen = cci == null || cci.getWebListen() == null
port = cci.getWebPort(); ? new MultiProtocolSocketAddress("http", "0.0.0.0", 4665) : cci.getWebListen();
} enableWebServer(listen);
enableWebServer(port);
} }
public void enableWebServer(int port) throws IOException { public void enableWebServer(int port) throws IOException {
enableWebServer(new MultiProtocolSocketAddress("http", "0.0.0.0", port));
}
public void enableWebServer(MultiProtocolSocketAddress listen) throws IOException {
if (webServer == null) { if (webServer == null) {
webServer = new KLALBWebServer(this, port); webServer = new KLALBWebServer(this, listen);
webServer.start(); webServer.start();
} else { } else {
throw new IllegalStateException("Web server already enabled!"); throw new IllegalStateException("Web server already enabled!");
@@ -76,7 +76,7 @@ public class KLALBStateGUI3 extends XFrame {
private JTextField addressFieldSet; // 地址设置框 private JTextField addressFieldSet; // 地址设置框
private JTextField asnFieldSet; // ASN设置框 private JTextField asnFieldSet; // ASN设置框
private JTextField tunDeviceName; // 虚拟网卡名称设置框 private JTextField tunDeviceName; // 虚拟网卡名称设置框
private JTextField webPortSet; // Web API 端口设置框 private JTextField webListenSet; // Web API 监听地址设置框
private NetworkGraphPanel graph; // 网络图面板 private NetworkGraphPanel graph; // 网络图面板
private JTextField textFieldLocate; // 定位地址输入框 private JTextField textFieldLocate; // 定位地址输入框
private JTextField asnField2; // ASN显示框 private JTextField asnField2; // ASN显示框
@@ -917,10 +917,10 @@ public class KLALBStateGUI3 extends XFrame {
webApiEnabled=webApic.getCheckBox(); webApiEnabled=webApic.getCheckBox();
settings.getView().add(webApic); settings.getView().add(webApic);
TextSettingItem webPort = new TextSettingItem(UIEnv.getRsb().getString("webport"), TextSettingItem webListen = new TextSettingItem(UIEnv.getRsb().getString("weblistenaddr"),
CONST.itemwidth, CONST.settingheight); CONST.itemwidth, CONST.settingheight);
webPortSet = webPort.getTextField(); webListenSet = webListen.getTextField();
settings.getView().add(webPort); settings.getView().add(webListen);
//性能设置标题 //性能设置标题
@@ -1160,19 +1160,15 @@ public class KLALBStateGUI3 extends XFrame {
// 保存Web API设置 // 保存Web API设置
kck.setWebUI(webApiEnabled.isSelected()); kck.setWebUI(webApiEnabled.isSelected());
String webPortText = webPortSet.getText().trim(); String webListenText = webListenSet.getText().trim();
if (webPortText.equals("")) { if (webListenText.equals("")) {
kck.setWebPort(4665); kck.setWebListen(new MultiProtocolSocketAddress("http", "0.0.0.0", 4665));
} else { } else {
try { try {
int wp = Integer.parseInt(webPortText); kck.setWebListen(new MultiProtocolSocketAddress(webListenText));
if (wp < 0 || wp > 65535) { } catch (RuntimeException e) {
throw new NumberFormatException("out of range");
}
kck.setWebPort(wp);
} catch (NumberFormatException e) {
e.printStackTrace(); e.printStackTrace();
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildwebport"), JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildweblistenaddr"),
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE); UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
return; return;
} }
@@ -1580,7 +1576,8 @@ public class KLALBStateGUI3 extends XFrame {
// 加载Web API设置 // 加载Web API设置
webApiEnabled.setSelected(kck.isWebUI()); webApiEnabled.setSelected(kck.isWebUI());
webPortSet.setText(Integer.toString(kck.getWebPort())); webListenSet.setText(kck.getWebListen() != null ? kck.getWebListen().toString()
: "http://0.0.0.0:4665");
// 加载TCP监听 // 加载TCP监听
MultiProtocolSocketAddress mpat = kck.getTCPListen(); MultiProtocolSocketAddress mpat = kck.getTCPListen();
@@ -31,7 +31,7 @@ import org.kne.cloud.network.srv6.SRv6Router;
*/ */
public class KLALBWebServer { public class KLALBWebServer {
private final KLALBProxySystem proxySystem; private final KLALBProxySystem proxySystem;
private final int port; private final MultiProtocolSocketAddress listen;
private HttpServer server; private HttpServer server;
private final Gson gson; private final Gson gson;
private final ScheduledExecutorService sseExecutor = Executors.newSingleThreadScheduledExecutor(r -> { private final ScheduledExecutorService sseExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
@@ -43,15 +43,21 @@ public class KLALBWebServer {
private final AtomicBoolean running = new AtomicBoolean(false); private final AtomicBoolean running = new AtomicBoolean(false);
public KLALBWebServer(KLALBProxySystem proxySystem, int port) { public KLALBWebServer(KLALBProxySystem proxySystem, int port) {
this(proxySystem, new MultiProtocolSocketAddress("http", "0.0.0.0", port));
}
public KLALBWebServer(KLALBProxySystem proxySystem, MultiProtocolSocketAddress listen) {
this.proxySystem = proxySystem; this.proxySystem = proxySystem;
this.port = port; this.listen = listen;
this.gson = proxySystem.getGson(); this.gson = proxySystem.getGson();
} }
public synchronized void start() throws IOException { public synchronized void start() throws IOException {
if (running.get()) return; if (running.get()) return;
server = HttpServer.create(new InetSocketAddress(port), 0); InetSocketAddress socketAddress = "0.0.0.0".equals(listen.getHost())
? new InetSocketAddress(listen.getPort()) : listen.getSocketAddress();
server = HttpServer.create(socketAddress, 0);
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor()); server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
// API Contexts // API Contexts
@@ -72,7 +78,7 @@ public class KLALBWebServer {
running.set(true); running.set(true);
startSseBroadcaster(); startSseBroadcaster();
System.out.println("KLALB Web Dashboard started at http://localhost:" + port); System.out.println("KLALB Web Dashboard started at " + listen);
} }
public synchronized void stop() { public synchronized void stop() {
@@ -97,7 +103,7 @@ public class KLALBWebServer {
} }
public int getPort() { public int getPort() {
return port; return listen.getPort();
} }
private void setCorsHeaders(HttpExchange exchange) { private void setCorsHeaders(HttpExchange exchange) {
@@ -605,8 +611,10 @@ public class KLALBWebServer {
current.setWebUI(json.get("webUI").getAsBoolean()); current.setWebUI(json.get("webUI").getAsBoolean());
} }
if (json.has("webPort")) { if (json.has("webListen") && !json.get("webListen").isJsonNull()) {
current.setWebPort(json.get("webPort").getAsInt()); current.setWebListen(new MultiProtocolSocketAddress(json.get("webListen").getAsString()));
} else if (json.has("webPort")) {
current.setWebListen(new MultiProtocolSocketAddress("http", "0.0.0.0", json.get("webPort").getAsInt()));
} }
if (json.has("nogui")) { if (json.has("nogui")) {