mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-25 05:46:18 +00:00
refactor(xhttp): split fields by direction, expand outbound coverage
Audit panel xhttp config against xray-core's runtime paths and split fields per direction so each side carries only what it actually uses: - Bidirectional (must match): host, path, mode, all xPadding*, session*/seq*, uplinkData*/Key, scMaxEachPostBytes - Server-only (inbound): noSSEHeader, scMaxBufferedPosts, scStreamUpServerSecs, serverMaxHeaderBytes - Client-only (outbound): uplinkHTTPMethod, uplinkChunkSize, noGRPCHeader, scMinPostsIntervalMs, xmux The inbound previously held client-only fields and the outbound was missing every must-match field beyond host/path/mode — meaning a panel-built outbound couldn't connect to an inbound with a custom xPaddingKey/sessionKey/etc. Headers stay on the inbound for URL-share purposes only; xray's listener ignores them at runtime, but they travel through the share link's `extra` blob so the client picks them up. Renames the URL helpers (applyXhttpPadding* -> applyXhttpExtra*) since the blob now carries more than padding, and folds path/host/mode into the helper so each link generator's xhttp branch is one line. Adds two enforcement points for xray's "uplinkHTTPMethod=GET only in packet-up" rule: the GET option is disabled when mode != packet-up, and a watcher on the outbound modal auto-clears GET when the user switches modes. Hides the XMUX block behind an `enableXmux` switch on the outbound form (mirrors the QUIC Params toggle) so the section doesn't clutter the form by default; fromJson auto-flips it on for outbounds with saved xmux config. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -402,11 +402,35 @@ class HttpUpgradeStreamSettings extends CommonClass {
|
||||
}
|
||||
}
|
||||
|
||||
// Mirrors the outbound (client-side) view of Xray-core's SplitHTTPConfig
|
||||
// (infra/conf/transport_internet.go). Only fields the client actually
|
||||
// reads at runtime, plus the bidirectional fields the client must match
|
||||
// against the server, live here. Server-only fields (noSSEHeader,
|
||||
// scMaxBufferedPosts, scStreamUpServerSecs, serverMaxHeaderBytes) belong
|
||||
// on the inbound class instead.
|
||||
class xHTTPStreamSettings extends CommonClass {
|
||||
constructor(
|
||||
// Bidirectional — must match the inbound side
|
||||
path = '/',
|
||||
host = '',
|
||||
mode = '',
|
||||
xPaddingBytes = "100-1000",
|
||||
xPaddingObfsMode = false,
|
||||
xPaddingKey = '',
|
||||
xPaddingHeader = '',
|
||||
xPaddingPlacement = '',
|
||||
xPaddingMethod = '',
|
||||
sessionPlacement = '',
|
||||
sessionKey = '',
|
||||
seqPlacement = '',
|
||||
seqKey = '',
|
||||
uplinkDataPlacement = '',
|
||||
uplinkDataKey = '',
|
||||
scMaxEachPostBytes = "1000000",
|
||||
// Client-side only
|
||||
headers = [],
|
||||
uplinkHTTPMethod = '',
|
||||
uplinkChunkSize = 0,
|
||||
noGRPCHeader = false,
|
||||
scMinPostsIntervalMs = "30",
|
||||
xmux = {
|
||||
@@ -417,32 +441,112 @@ class xHTTPStreamSettings extends CommonClass {
|
||||
hMaxReusableSecs: "1800-3000",
|
||||
hKeepAlivePeriod: 0,
|
||||
},
|
||||
// UI-only toggle — controls whether the XMUX block is expanded in
|
||||
// the form (mirrors the QUIC Params switch in stream_finalmask).
|
||||
// Never serialized; toJson() only emits the xmux block itself.
|
||||
enableXmux = false,
|
||||
) {
|
||||
super();
|
||||
this.path = path;
|
||||
this.host = host;
|
||||
this.mode = mode;
|
||||
this.xPaddingBytes = xPaddingBytes;
|
||||
this.xPaddingObfsMode = xPaddingObfsMode;
|
||||
this.xPaddingKey = xPaddingKey;
|
||||
this.xPaddingHeader = xPaddingHeader;
|
||||
this.xPaddingPlacement = xPaddingPlacement;
|
||||
this.xPaddingMethod = xPaddingMethod;
|
||||
this.sessionPlacement = sessionPlacement;
|
||||
this.sessionKey = sessionKey;
|
||||
this.seqPlacement = seqPlacement;
|
||||
this.seqKey = seqKey;
|
||||
this.uplinkDataPlacement = uplinkDataPlacement;
|
||||
this.uplinkDataKey = uplinkDataKey;
|
||||
this.scMaxEachPostBytes = scMaxEachPostBytes;
|
||||
this.headers = headers;
|
||||
this.uplinkHTTPMethod = uplinkHTTPMethod;
|
||||
this.uplinkChunkSize = uplinkChunkSize;
|
||||
this.noGRPCHeader = noGRPCHeader;
|
||||
this.scMinPostsIntervalMs = scMinPostsIntervalMs;
|
||||
this.xmux = xmux;
|
||||
this.enableXmux = enableXmux;
|
||||
}
|
||||
|
||||
addHeader(name, value) {
|
||||
this.headers.push({ name: name, value: value });
|
||||
}
|
||||
|
||||
removeHeader(index) {
|
||||
this.headers.splice(index, 1);
|
||||
}
|
||||
|
||||
static fromJson(json = {}) {
|
||||
const headersInput = json.headers;
|
||||
let headers = [];
|
||||
if (Array.isArray(headersInput)) {
|
||||
headers = headersInput;
|
||||
} else if (headersInput && typeof headersInput === 'object') {
|
||||
// Upstream uses a {name: value} map; convert to the panel's [{name, value}] form.
|
||||
headers = Object.entries(headersInput).map(([name, value]) => ({ name, value }));
|
||||
}
|
||||
return new xHTTPStreamSettings(
|
||||
json.path,
|
||||
json.host,
|
||||
json.mode,
|
||||
json.xPaddingBytes,
|
||||
json.xPaddingObfsMode,
|
||||
json.xPaddingKey,
|
||||
json.xPaddingHeader,
|
||||
json.xPaddingPlacement,
|
||||
json.xPaddingMethod,
|
||||
json.sessionPlacement,
|
||||
json.sessionKey,
|
||||
json.seqPlacement,
|
||||
json.seqKey,
|
||||
json.uplinkDataPlacement,
|
||||
json.uplinkDataKey,
|
||||
json.scMaxEachPostBytes,
|
||||
headers,
|
||||
json.uplinkHTTPMethod,
|
||||
json.uplinkChunkSize,
|
||||
json.noGRPCHeader,
|
||||
json.scMinPostsIntervalMs,
|
||||
json.xmux
|
||||
json.xmux,
|
||||
// Auto-toggle the XMUX switch on when an existing outbound has
|
||||
// the xmux key saved, so users editing such configs see their
|
||||
// values immediately.
|
||||
json.xmux !== undefined,
|
||||
);
|
||||
}
|
||||
|
||||
toJson() {
|
||||
// Upstream expects headers as a {name: value} map, not a list of entries.
|
||||
const headersMap = {};
|
||||
if (Array.isArray(this.headers)) {
|
||||
for (const h of this.headers) {
|
||||
if (h && h.name) headersMap[h.name] = h.value || '';
|
||||
}
|
||||
}
|
||||
return {
|
||||
path: this.path,
|
||||
host: this.host,
|
||||
mode: this.mode,
|
||||
xPaddingBytes: this.xPaddingBytes,
|
||||
xPaddingObfsMode: this.xPaddingObfsMode,
|
||||
xPaddingKey: this.xPaddingKey,
|
||||
xPaddingHeader: this.xPaddingHeader,
|
||||
xPaddingPlacement: this.xPaddingPlacement,
|
||||
xPaddingMethod: this.xPaddingMethod,
|
||||
sessionPlacement: this.sessionPlacement,
|
||||
sessionKey: this.sessionKey,
|
||||
seqPlacement: this.seqPlacement,
|
||||
seqKey: this.seqKey,
|
||||
uplinkDataPlacement: this.uplinkDataPlacement,
|
||||
uplinkDataKey: this.uplinkDataKey,
|
||||
scMaxEachPostBytes: this.scMaxEachPostBytes,
|
||||
headers: headersMap,
|
||||
uplinkHTTPMethod: this.uplinkHTTPMethod,
|
||||
uplinkChunkSize: this.uplinkChunkSize,
|
||||
noGRPCHeader: this.noGRPCHeader,
|
||||
scMinPostsIntervalMs: this.scMinPostsIntervalMs,
|
||||
xmux: {
|
||||
|
||||
Reference in New Issue
Block a user