diff --git a/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx b/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx
index 24016691..eafaa059 100644
--- a/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx
+++ b/frontend/src/pages/xray/outbounds/OutboundFormModal.tsx
@@ -14,7 +14,6 @@ import {
message,
} from 'antd';
import { FinalMaskForm } from '@/lib/xray/forms/transport';
-import { HeaderMapEditor } from '@/components/form';
import { HysteriaMasqueradeForm } from '@/lib/xray/forms/protocols/shared';
import { JsonEditor } from '@/components/form';
import { Wireguard } from '@/utils';
@@ -50,7 +49,6 @@ import {
ALPN_OPTIONS,
FLOW_OPTIONS,
HYSTERIA_NETWORK_OPTION,
- MODE_OPTIONS,
NETWORK_OPTIONS,
PROTOCOL_OPTIONS,
SERVER_PROTOCOLS,
@@ -82,6 +80,7 @@ import {
KcpForm,
RawForm,
WsForm,
+ XhttpForm,
} from './transport';
import './OutboundFormModal.css';
@@ -524,345 +523,7 @@ export default function OutboundFormModal({
{network === 'httpupgrade' && }
- {network === 'xhttp' && (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Padding obfs sub-section: gated by a Switch.
- When on, four extra knobs (key/header/placement/
- method) tune how Xray injects random padding to
- disguise the post body shape. */}
-
-
-
-
- {() => {
- const obfs = !!form.getFieldValue([
- 'streamSettings', 'xhttpSettings', 'xPaddingObfsMode',
- ]);
- if (!obfs) return null;
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
- }}
-
-
-
- prev?.streamSettings?.xhttpSettings?.mode !==
- curr?.streamSettings?.xhttpSettings?.mode
- }
- >
- {() => {
- const mode = form.getFieldValue([
- 'streamSettings', 'xhttpSettings', 'mode',
- ]);
- return (
-
-
-
- );
- }}
-
-
- {/* Session + sequence + uplinkData placements:
- three orthogonal slots Xray uses to thread
- request metadata through the transport
- (path / header / cookie / query). Key field
- only matters when placement is not 'path'. */}
-
-
-
-
- {() => {
- const placement = form.getFieldValue([
- 'streamSettings', 'xhttpSettings', 'sessionPlacement',
- ]);
- if (!placement || placement === 'path') return null;
- return (
-
-
-
- );
- }}
-
-
-
-
-
- {() => {
- const placement = form.getFieldValue([
- 'streamSettings', 'xhttpSettings', 'seqPlacement',
- ]);
- if (!placement || placement === 'path') return null;
- return (
-
-
-
- );
- }}
-
-
- {/* Mode-conditional sub-sections. */}
-
- {() => {
- const mode = form.getFieldValue([
- 'streamSettings', 'xhttpSettings', 'mode',
- ]);
- if (mode !== 'packet-up') return null;
- return (
- <>
-
-
-
-
-
-
-
-
-
-
- {() => {
- const place = form.getFieldValue([
- 'streamSettings', 'xhttpSettings', 'uplinkDataPlacement',
- ]);
- if (!place || place === 'body') return null;
- return (
- <>
-
-
-
-
-
-
- >
- );
- }}
-
- >
- );
- }}
-
-
- {() => {
- const mode = form.getFieldValue([
- 'streamSettings', 'xhttpSettings', 'mode',
- ]);
- if (mode !== 'stream-up' && mode !== 'stream-one') return null;
- return (
-
-
-
- );
- }}
-
-
- {/* XMUX is the connection-multiplexing layer
- xHTTP uses to fan out parallel requests over
- a small pool of upstream connections. UI-only
- toggle (enableXmux) hides the 6 nested knobs
- when off. */}
-
-
-
-
- {() => {
- if (!form.getFieldValue([
- 'streamSettings', 'xhttpSettings', 'enableXmux',
- ])) return null;
- return (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
- }}
-
- >
- )}
+ {network === 'xhttp' && }
{network === 'hysteria' && (
<>
diff --git a/frontend/src/pages/xray/outbounds/transport/index.ts b/frontend/src/pages/xray/outbounds/transport/index.ts
index 9dd145b7..dfe6673f 100644
--- a/frontend/src/pages/xray/outbounds/transport/index.ts
+++ b/frontend/src/pages/xray/outbounds/transport/index.ts
@@ -3,3 +3,4 @@ export { default as KcpForm } from './kcp';
export { default as WsForm } from './ws';
export { default as GrpcForm } from './grpc';
export { default as HttpUpgradeForm } from './httpupgrade';
+export { default as XhttpForm } from './xhttp';
diff --git a/frontend/src/pages/xray/outbounds/transport/xhttp.tsx b/frontend/src/pages/xray/outbounds/transport/xhttp.tsx
new file mode 100644
index 00000000..2b1e7332
--- /dev/null
+++ b/frontend/src/pages/xray/outbounds/transport/xhttp.tsx
@@ -0,0 +1,355 @@
+import { useTranslation } from 'react-i18next';
+import { Form, Input, InputNumber, Select, Switch, type FormInstance } from 'antd';
+
+import { HeaderMapEditor } from '@/components/form';
+import type { OutboundFormValues } from '@/schemas/forms/outbound-form';
+
+import { MODE_OPTIONS } from '../outbound-form-constants';
+
+interface XhttpFormProps {
+ form: FormInstance;
+ onXmuxToggle: (checked: boolean) => void;
+}
+
+export default function XhttpForm({ form, onXmuxToggle }: XhttpFormProps) {
+ const { t } = useTranslation();
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Padding obfs sub-section: gated by a Switch.
+ When on, four extra knobs (key/header/placement/
+ method) tune how Xray injects random padding to
+ disguise the post body shape. */}
+
+
+
+
+ {() => {
+ const obfs = !!form.getFieldValue([
+ 'streamSettings', 'xhttpSettings', 'xPaddingObfsMode',
+ ]);
+ if (!obfs) return null;
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }}
+
+
+
+ prev?.streamSettings?.xhttpSettings?.mode !==
+ curr?.streamSettings?.xhttpSettings?.mode
+ }
+ >
+ {() => {
+ const mode = form.getFieldValue([
+ 'streamSettings', 'xhttpSettings', 'mode',
+ ]);
+ return (
+
+
+
+ );
+ }}
+
+
+ {/* Session + sequence + uplinkData placements:
+ three orthogonal slots Xray uses to thread
+ request metadata through the transport
+ (path / header / cookie / query). Key field
+ only matters when placement is not 'path'. */}
+
+
+
+
+ {() => {
+ const placement = form.getFieldValue([
+ 'streamSettings', 'xhttpSettings', 'sessionPlacement',
+ ]);
+ if (!placement || placement === 'path') return null;
+ return (
+
+
+
+ );
+ }}
+
+
+
+
+
+ {() => {
+ const placement = form.getFieldValue([
+ 'streamSettings', 'xhttpSettings', 'seqPlacement',
+ ]);
+ if (!placement || placement === 'path') return null;
+ return (
+
+
+
+ );
+ }}
+
+
+ {/* Mode-conditional sub-sections. */}
+
+ {() => {
+ const mode = form.getFieldValue([
+ 'streamSettings', 'xhttpSettings', 'mode',
+ ]);
+ if (mode !== 'packet-up') return null;
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {() => {
+ const place = form.getFieldValue([
+ 'streamSettings', 'xhttpSettings', 'uplinkDataPlacement',
+ ]);
+ if (!place || place === 'body') return null;
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+ }}
+
+ >
+ );
+ }}
+
+
+ {() => {
+ const mode = form.getFieldValue([
+ 'streamSettings', 'xhttpSettings', 'mode',
+ ]);
+ if (mode !== 'stream-up' && mode !== 'stream-one') return null;
+ return (
+
+
+
+ );
+ }}
+
+
+ {/* XMUX is the connection-multiplexing layer
+ xHTTP uses to fan out parallel requests over
+ a small pool of upstream connections. UI-only
+ toggle (enableXmux) hides the 6 nested knobs
+ when off. */}
+
+
+
+
+ {() => {
+ if (!form.getFieldValue([
+ 'streamSettings', 'xhttpSettings', 'enableXmux',
+ ])) return null;
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }}
+
+ >
+ );
+}