xhttp.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import { useTranslation } from 'react-i18next';
  2. import { Form, Input, InputNumber, Select, Switch, type FormInstance } from 'antd';
  3. import { HeaderMapEditor } from '@/components/form';
  4. import type { InboundFormValues } from '@/schemas/forms/inbound-form';
  5. import { XHttpXmuxSchema } from '@/schemas/protocols/stream/xhttp';
  6. const XMUX_DEFAULTS = XHttpXmuxSchema.parse({});
  7. export default function XhttpForm({ form }: { form: FormInstance<InboundFormValues> }) {
  8. const { t } = useTranslation();
  9. const xhttpMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'mode'], form);
  10. const xhttpObfsMode = Form.useWatch(['streamSettings', 'xhttpSettings', 'xPaddingObfsMode'], form) ?? false;
  11. const xhttpSessionPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'sessionPlacement'], form);
  12. const xhttpSeqPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'seqPlacement'], form);
  13. const xhttpUplinkPlacement = Form.useWatch(['streamSettings', 'xhttpSettings', 'uplinkDataPlacement'], form);
  14. function onXmuxToggle(checked: boolean) {
  15. if (!checked) return;
  16. const existing = form.getFieldValue(['streamSettings', 'xhttpSettings', 'xmux']);
  17. const hasValues = existing && typeof existing === 'object' && Object.keys(existing).length > 0;
  18. if (hasValues) return;
  19. form.setFieldValue(['streamSettings', 'xhttpSettings', 'xmux'], { ...XMUX_DEFAULTS });
  20. }
  21. return (
  22. <>
  23. <Form.Item name={['streamSettings', 'xhttpSettings', 'host']} label={t('host')}>
  24. <Input />
  25. </Form.Item>
  26. <Form.Item name={['streamSettings', 'xhttpSettings', 'path']} label={t('path')}>
  27. <Input />
  28. </Form.Item>
  29. <Form.Item name={['streamSettings', 'xhttpSettings', 'mode']} label={t('pages.inbounds.info.mode')}>
  30. <Select
  31. style={{ width: '50%' }}
  32. options={(['auto', 'packet-up', 'stream-up', 'stream-one'] as const).map((m) => ({
  33. value: m,
  34. label: m,
  35. }))}
  36. />
  37. </Form.Item>
  38. {(xhttpMode === 'packet-up' || xhttpMode === 'auto') && (
  39. <>
  40. <Form.Item
  41. name={['streamSettings', 'xhttpSettings', 'scMaxEachPostBytes']}
  42. label={t('pages.inbounds.form.maxUploadSize')}
  43. >
  44. <Input />
  45. </Form.Item>
  46. <Form.Item
  47. name={['streamSettings', 'xhttpSettings', 'scMaxBufferedPosts']}
  48. label={t('pages.inbounds.form.maxBufferedUpload')}
  49. >
  50. <InputNumber />
  51. </Form.Item>
  52. <Form.Item
  53. name={['streamSettings', 'xhttpSettings', 'scMinPostsIntervalMs']}
  54. label={t('pages.xray.outboundForm.minUploadInterval')}
  55. >
  56. <Input placeholder="e.g. 50-150" />
  57. </Form.Item>
  58. </>
  59. )}
  60. {xhttpMode === 'stream-up' && (
  61. <>
  62. <Form.Item
  63. name={['streamSettings', 'xhttpSettings', 'scMaxBufferedPosts']}
  64. label={t('pages.inbounds.form.maxBufferedUpload')}
  65. >
  66. <InputNumber />
  67. </Form.Item>
  68. <Form.Item
  69. name={['streamSettings', 'xhttpSettings', 'scStreamUpServerSecs']}
  70. label={t('pages.inbounds.form.streamUpServer')}
  71. >
  72. <Input />
  73. </Form.Item>
  74. </>
  75. )}
  76. <Form.Item
  77. name={['streamSettings', 'xhttpSettings', 'serverMaxHeaderBytes']}
  78. label={t('pages.inbounds.form.serverMaxHeaderBytes')}
  79. >
  80. <InputNumber min={0} placeholder="0 (default)" />
  81. </Form.Item>
  82. <Form.Item
  83. name={['streamSettings', 'xhttpSettings', 'xPaddingBytes']}
  84. label={t('pages.inbounds.form.paddingBytes')}
  85. >
  86. <Input />
  87. </Form.Item>
  88. <Form.Item
  89. name={['streamSettings', 'xhttpSettings', 'headers']}
  90. label={t('pages.inbounds.form.headers')}
  91. >
  92. <HeaderMapEditor mode="v1" />
  93. </Form.Item>
  94. <Form.Item
  95. name={['streamSettings', 'xhttpSettings', 'uplinkHTTPMethod']}
  96. label={t('pages.inbounds.form.uplinkHttpMethod')}
  97. >
  98. <Select
  99. options={[
  100. { value: '', label: 'Default (POST)' },
  101. { value: 'POST', label: 'POST' },
  102. { value: 'PUT', label: 'PUT' },
  103. {
  104. value: 'GET',
  105. label: 'GET (packet-up only)',
  106. disabled: xhttpMode !== 'packet-up',
  107. },
  108. ]}
  109. />
  110. </Form.Item>
  111. <Form.Item
  112. name={['streamSettings', 'xhttpSettings', 'xPaddingObfsMode']}
  113. label={t('pages.inbounds.form.paddingObfsMode')}
  114. valuePropName="checked"
  115. >
  116. <Switch />
  117. </Form.Item>
  118. {xhttpObfsMode && (
  119. <>
  120. <Form.Item
  121. name={['streamSettings', 'xhttpSettings', 'xPaddingKey']}
  122. label={t('pages.inbounds.form.paddingKey')}
  123. >
  124. <Input placeholder="x_padding" />
  125. </Form.Item>
  126. <Form.Item
  127. name={['streamSettings', 'xhttpSettings', 'xPaddingHeader']}
  128. label={t('pages.inbounds.form.paddingHeader')}
  129. >
  130. <Input placeholder="X-Padding" />
  131. </Form.Item>
  132. <Form.Item
  133. name={['streamSettings', 'xhttpSettings', 'xPaddingPlacement']}
  134. label={t('pages.inbounds.form.paddingPlacement')}
  135. >
  136. <Select
  137. options={[
  138. { value: '', label: 'Default (queryInHeader)' },
  139. { value: 'queryInHeader', label: 'queryInHeader' },
  140. { value: 'header', label: 'header' },
  141. { value: 'cookie', label: 'cookie' },
  142. { value: 'query', label: 'query' },
  143. ]}
  144. />
  145. </Form.Item>
  146. <Form.Item
  147. name={['streamSettings', 'xhttpSettings', 'xPaddingMethod']}
  148. label={t('pages.inbounds.form.paddingMethod')}
  149. >
  150. <Select
  151. options={[
  152. { value: '', label: 'Default (repeat-x)' },
  153. { value: 'repeat-x', label: 'repeat-x' },
  154. { value: 'tokenish', label: 'tokenish' },
  155. ]}
  156. />
  157. </Form.Item>
  158. </>
  159. )}
  160. <Form.Item
  161. name={['streamSettings', 'xhttpSettings', 'sessionPlacement']}
  162. label={t('pages.inbounds.form.sessionPlacement')}
  163. >
  164. <Select
  165. options={[
  166. { value: '', label: 'Default (path)' },
  167. { value: 'path', label: 'path' },
  168. { value: 'header', label: 'header' },
  169. { value: 'cookie', label: 'cookie' },
  170. { value: 'query', label: 'query' },
  171. ]}
  172. />
  173. </Form.Item>
  174. {xhttpSessionPlacement && xhttpSessionPlacement !== 'path' && (
  175. <Form.Item
  176. name={['streamSettings', 'xhttpSettings', 'sessionKey']}
  177. label={t('pages.inbounds.form.sessionKey')}
  178. >
  179. <Input placeholder="x_session" />
  180. </Form.Item>
  181. )}
  182. <Form.Item
  183. name={['streamSettings', 'xhttpSettings', 'seqPlacement']}
  184. label={t('pages.inbounds.form.sequencePlacement')}
  185. >
  186. <Select
  187. options={[
  188. { value: '', label: 'Default (path)' },
  189. { value: 'path', label: 'path' },
  190. { value: 'header', label: 'header' },
  191. { value: 'cookie', label: 'cookie' },
  192. { value: 'query', label: 'query' },
  193. ]}
  194. />
  195. </Form.Item>
  196. {xhttpSeqPlacement && xhttpSeqPlacement !== 'path' && (
  197. <Form.Item
  198. name={['streamSettings', 'xhttpSettings', 'seqKey']}
  199. label={t('pages.inbounds.form.sequenceKey')}
  200. >
  201. <Input placeholder="x_seq" />
  202. </Form.Item>
  203. )}
  204. {xhttpMode === 'packet-up' && (
  205. <>
  206. <Form.Item
  207. name={['streamSettings', 'xhttpSettings', 'uplinkDataPlacement']}
  208. label={t('pages.inbounds.form.uplinkDataPlacement')}
  209. >
  210. <Select
  211. options={[
  212. { value: '', label: 'Default (body)' },
  213. { value: 'body', label: 'body' },
  214. { value: 'header', label: 'header' },
  215. { value: 'cookie', label: 'cookie' },
  216. { value: 'query', label: 'query' },
  217. ]}
  218. />
  219. </Form.Item>
  220. {xhttpUplinkPlacement && xhttpUplinkPlacement !== 'body' && (
  221. <Form.Item
  222. name={['streamSettings', 'xhttpSettings', 'uplinkDataKey']}
  223. label={t('pages.inbounds.form.uplinkDataKey')}
  224. >
  225. <Input placeholder="x_data" />
  226. </Form.Item>
  227. )}
  228. </>
  229. )}
  230. <Form.Item
  231. name={['streamSettings', 'xhttpSettings', 'noSSEHeader']}
  232. label={t('pages.inbounds.form.noSseHeader')}
  233. valuePropName="checked"
  234. >
  235. <Switch />
  236. </Form.Item>
  237. {/* XMUX is the connection-multiplexing layer
  238. xHTTP uses to fan out parallel requests over
  239. a small pool of upstream connections. UI-only
  240. toggle (enableXmux) hides the 6 nested knobs
  241. when off. */}
  242. <Form.Item
  243. label="XMUX"
  244. name={['streamSettings', 'xhttpSettings', 'enableXmux']}
  245. valuePropName="checked"
  246. >
  247. <Switch onChange={onXmuxToggle} />
  248. </Form.Item>
  249. <Form.Item shouldUpdate noStyle>
  250. {() => {
  251. if (!form.getFieldValue([
  252. 'streamSettings', 'xhttpSettings', 'enableXmux',
  253. ])) return null;
  254. return (
  255. <>
  256. <Form.Item
  257. label={t('pages.xray.outboundForm.maxConcurrency')}
  258. name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConcurrency']}
  259. >
  260. <Input placeholder="16-32" />
  261. </Form.Item>
  262. <Form.Item
  263. label={t('pages.xray.outboundForm.maxConnections')}
  264. name={['streamSettings', 'xhttpSettings', 'xmux', 'maxConnections']}
  265. >
  266. <Input placeholder="0" />
  267. </Form.Item>
  268. <Form.Item
  269. label={t('pages.xray.outboundForm.maxReuseTimes')}
  270. name={['streamSettings', 'xhttpSettings', 'xmux', 'cMaxReuseTimes']}
  271. >
  272. <Input />
  273. </Form.Item>
  274. <Form.Item
  275. label={t('pages.xray.outboundForm.maxRequestTimes')}
  276. name={['streamSettings', 'xhttpSettings', 'xmux', 'hMaxRequestTimes']}
  277. >
  278. <Input placeholder="600-900" />
  279. </Form.Item>
  280. <Form.Item
  281. label={t('pages.xray.outboundForm.maxReusableSecs')}
  282. name={['streamSettings', 'xhttpSettings', 'xmux', 'hMaxReusableSecs']}
  283. >
  284. <Input placeholder="1800-3000" />
  285. </Form.Item>
  286. <Form.Item
  287. label={t('pages.xray.outboundForm.keepAlivePeriod')}
  288. name={['streamSettings', 'xhttpSettings', 'xmux', 'hKeepAlivePeriod']}
  289. >
  290. <InputNumber min={0} style={{ width: '100%' }} />
  291. </Form.Item>
  292. </>
  293. );
  294. }}
  295. </Form.Item>
  296. </>
  297. );
  298. }