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`).
- `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).
- `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.
+2 -2
View File
@@ -56,7 +56,7 @@
"DeviceName": "Device Name",
"DeviceDescription": "The Description of Device",
"webUI": true,
"webPort": 4665,
"webListen": "http://0.0.0.0:4665",
"NetworkInterfaceExcepts": [],
"Type": "KLALBController"
},
@@ -110,4 +110,4 @@
},
"Type": "SocketBridge"
}
]
]
+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)
webapisettings=Web API settings
enablewebapi=Enable Web API
webport=Web port
invaildwebport=Invalid web port number
weblistenaddr=Web API listen address
invaildweblistenaddr=Invalid Web API listen address
+7 -7
View File
@@ -4,9 +4,9 @@ language=语言
basicsettings=基本设置
ipv6addr=IPv6地址
devicename=设备名称
devicedescription=设备描述
devicedescription=设备描述
dnsserver=DNS服务器
extraroutes=额外路由
extraroutes=额外路由
asnumber=AS号码
tcplistening=TCP监听端口
udplistening=UDP监听端口
@@ -111,8 +111,8 @@ performancesettings=性能设置
performancestrategy=性能策略
singlecore=单核-缓存命中率优先(高能耗比,适合低功耗设备、云机)
multifill=多核-负载按顺序填充(适合大多数物理服务器、电脑)
multiscatter=多核-负载均匀打散分配(适合特殊的多路NUMA服务器)
webapisettings=Web API \u8bbe\u7f6e
enablewebapi=\u542f\u7528 Web API
webport=Web \u7aef\u53e3
invaildwebport=\u65e0\u6548\u7684 Web \u7aef\u53e3\u53f7
multiscatter=多核-负载均匀打散分配(适合特殊的多路NUMA服务器)
webapisettings=Web API 设置
enablewebapi=启用 Web API
weblistenaddr=Web API 监听地址:端口
invaildweblistenaddr=无效的 Web API 监听地址:端口
@@ -7,8 +7,9 @@ import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.reflect.TypeToken;
@@ -78,11 +79,16 @@ public class KLALBConfigItem {
* denyConnectionQuery/denyLineTableQuery → denyExternalEndpointQuery
* denyConnectionBroadcast/denyLineTableBroadcast → denyExternalEndpointBroadcast。
*/
private void normalizeLegacyControllerKeys(JsonObject jobj) {
renameLegacyKey(jobj,"externalEndpoints","openConnections","OpenConnections","lineTable","LineTable");
renameLegacyKey(jobj,"denyExternalEndpointQuery","denyConnectionQuery","denyLineTableQuery");
renameLegacyKey(jobj,"denyExternalEndpointBroadcast","denyConnectionBroadcast","denyLineTableBroadcast");
}
private void normalizeLegacyControllerKeys(JsonObject jobj) {
renameLegacyKey(jobj,"externalEndpoints","openConnections","OpenConnections","lineTable","LineTable");
renameLegacyKey(jobj,"denyExternalEndpointQuery","denyConnectionQuery","denyLineTableQuery");
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) {
if (jobj.has(newKey)) {
@@ -35,7 +35,7 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
private String DeviceName;
private String DeviceDescription;
private boolean webUI = false;
private int webPort = 4665;
private MultiProtocolSocketAddress webListen = new MultiProtocolSocketAddress("http", "0.0.0.0", 4665);
public boolean isEnableTUN() {
return enableTUN;
@@ -53,12 +53,12 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
this.webUI = webUI;
}
public int getWebPort() {
return webPort;
}
public void setWebPort(int webPort) {
this.webPort = webPort;
public MultiProtocolSocketAddress getWebListen() {
return webListen;
}
public void setWebListen(MultiProtocolSocketAddress webListen) {
this.webListen = webListen;
}
public String getPerformanceStrategy() {
@@ -397,10 +397,12 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
", enableTUN=" + enableTUN +
", TUNName='" + TUNName + '\'' +
", performanceStrategy='" + performanceStrategy + '\'' +
", DeviceName='" + DeviceName + '\'' +
", DeviceDescription='" + DeviceDescription + '\'' +
", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts +
'}';
", DeviceName='" + DeviceName + '\'' +
", DeviceDescription='" + DeviceDescription + '\'' +
", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts +
", webUI=" + webUI +
", webListen=" + webListen +
'}';
}
@Override
@@ -408,11 +410,11 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
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
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);
}
}
@@ -108,12 +108,12 @@ public class KLALBMain {
case "web":
if(sc.length >= 2) {
String action = sc[1].toLowerCase();
if("start".equals(action)) {
int p = 4665;
if(sc.length >= 3) {
try { p = Integer.parseInt(sc[2]); } catch (NumberFormatException ignored) {}
} else if(kpcje.getControllerConfig() != null && kpcje.getControllerConfig().getWebPort() > 0) {
p = kpcje.getControllerConfig().getWebPort();
if("start".equals(action)) {
int p = 4665;
if(sc.length >= 3) {
try { p = Integer.parseInt(sc[2]); } catch (NumberFormatException ignored) {}
} else if(kpcje.getControllerConfig() != null && kpcje.getControllerConfig().getWebListen() != null) {
p = kpcje.getControllerConfig().getWebListen().getPort();
}
try {
kpcje.enableWebServer(p);
@@ -84,18 +84,20 @@ public class KLALBProxySystem {
public KLALBProxySystem() {
}
public void enableWebServer() throws IOException {
int port = 4665;
KLALBControllerConfigItem cci = getControllerConfig();
if (cci != null && cci.getWebPort() > 0) {
port = cci.getWebPort();
}
enableWebServer(port);
}
public void enableWebServer(int port) throws IOException {
if (webServer == null) {
webServer = new KLALBWebServer(this, port);
public void enableWebServer() throws IOException {
KLALBControllerConfigItem cci = getControllerConfig();
MultiProtocolSocketAddress listen = cci == null || cci.getWebListen() == null
? new MultiProtocolSocketAddress("http", "0.0.0.0", 4665) : cci.getWebListen();
enableWebServer(listen);
}
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) {
webServer = new KLALBWebServer(this, listen);
webServer.start();
} else {
throw new IllegalStateException("Web server already enabled!");
@@ -76,7 +76,7 @@ public class KLALBStateGUI3 extends XFrame {
private JTextField addressFieldSet; // 地址设置框
private JTextField asnFieldSet; // ASN设置框
private JTextField tunDeviceName; // 虚拟网卡名称设置框
private JTextField webPortSet; // Web API 端口设置框
private JTextField webListenSet; // Web API 监听地址设置框
private NetworkGraphPanel graph; // 网络图面板
private JTextField textFieldLocate; // 定位地址输入框
private JTextField asnField2; // ASN显示框
@@ -917,10 +917,10 @@ public class KLALBStateGUI3 extends XFrame {
webApiEnabled=webApic.getCheckBox();
settings.getView().add(webApic);
TextSettingItem webPort = new TextSettingItem(UIEnv.getRsb().getString("webport"),
CONST.itemwidth, CONST.settingheight);
webPortSet = webPort.getTextField();
settings.getView().add(webPort);
TextSettingItem webListen = new TextSettingItem(UIEnv.getRsb().getString("weblistenaddr"),
CONST.itemwidth, CONST.settingheight);
webListenSet = webListen.getTextField();
settings.getView().add(webListen);
//性能设置标题
@@ -1158,23 +1158,19 @@ public class KLALBStateGUI3 extends XFrame {
kck.setTUNName(tunNameText);
}
// 保存Web API设置
kck.setWebUI(webApiEnabled.isSelected());
String webPortText = webPortSet.getText().trim();
if (webPortText.equals("")) {
kck.setWebPort(4665);
} else {
try {
int wp = Integer.parseInt(webPortText);
if (wp < 0 || wp > 65535) {
throw new NumberFormatException("out of range");
}
kck.setWebPort(wp);
} catch (NumberFormatException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildwebport"),
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
return;
// 保存Web API设置
kck.setWebUI(webApiEnabled.isSelected());
String webListenText = webListenSet.getText().trim();
if (webListenText.equals("")) {
kck.setWebListen(new MultiProtocolSocketAddress("http", "0.0.0.0", 4665));
} else {
try {
kck.setWebListen(new MultiProtocolSocketAddress(webListenText));
} catch (RuntimeException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildweblistenaddr"),
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
return;
}
}
@@ -1578,9 +1574,10 @@ public class KLALBStateGUI3 extends XFrame {
String tunname=kck.getTUNName();
tunDeviceName.setText(tunname!=null?tunname:"");
// 加载Web API设置
webApiEnabled.setSelected(kck.isWebUI());
webPortSet.setText(Integer.toString(kck.getWebPort()));
// 加载Web API设置
webApiEnabled.setSelected(kck.isWebUI());
webListenSet.setText(kck.getWebListen() != null ? kck.getWebListen().toString()
: "http://0.0.0.0:4665");
// 加载TCP监听
MultiProtocolSocketAddress mpat = kck.getTCPListen();
@@ -1768,4 +1765,4 @@ public class KLALBStateGUI3 extends XFrame {
KLALBStateGUI3 kcg = new KLALBStateGUI3(new KLALBController());
kcg.setVisible(true);
}
}
}
@@ -31,7 +31,7 @@ import org.kne.cloud.network.srv6.SRv6Router;
*/
public class KLALBWebServer {
private final KLALBProxySystem proxySystem;
private final int port;
private final MultiProtocolSocketAddress listen;
private HttpServer server;
private final Gson gson;
private final ScheduledExecutorService sseExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
@@ -43,15 +43,21 @@ public class KLALBWebServer {
private final AtomicBoolean running = new AtomicBoolean(false);
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.port = port;
this.listen = listen;
this.gson = proxySystem.getGson();
}
public synchronized void start() throws IOException {
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());
// API Contexts
@@ -72,7 +78,7 @@ public class KLALBWebServer {
running.set(true);
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() {
@@ -97,7 +103,7 @@ public class KLALBWebServer {
}
public int getPort() {
return port;
return listen.getPort();
}
private void setCorsHeaders(HttpExchange exchange) {
@@ -605,8 +611,10 @@ public class KLALBWebServer {
current.setWebUI(json.get("webUI").getAsBoolean());
}
if (json.has("webPort")) {
current.setWebPort(json.get("webPort").getAsInt());
if (json.has("webListen") && !json.get("webListen").isJsonNull()) {
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")) {