فهرست منبع

new - splithttp transport

splithttp inbound
splithttp outbound
change priority host for ws - httpupgrade (host>>headers)
mhsanaei 8 ماه پیش
والد
کامیت
7f2c11220f

+ 2 - 1
sub/subJsonService.go

@@ -223,8 +223,9 @@ func (s *SubJsonService) streamData(stream string) map[string]interface{} {
 		streamSettings["wsSettings"] = s.removeAcceptProxy(streamSettings["wsSettings"])
 	case "httpupgrade":
 		streamSettings["httpupgradeSettings"] = s.removeAcceptProxy(streamSettings["httpupgradeSettings"])
+	case "splithttp":
+		streamSettings["splithttpSettings"] = s.removeAcceptProxy(streamSettings["splithttpSettings"])
 	}
-
 	return streamSettings
 }
 

+ 76 - 54
sub/subService.go

@@ -202,12 +202,11 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
 	case "ws":
 		ws, _ := stream["wsSettings"].(map[string]interface{})
 		obj["path"] = ws["path"].(string)
-		obj["host"] = ws["host"].(string)
-		if headers, ok := ws["headers"].(map[string]interface{}); ok {
-			hostFromHeaders := searchHost(headers)
-			if hostFromHeaders != "" {
-				obj["host"] = hostFromHeaders
-			}
+		if host, ok := ws["host"].(string); ok && len(host) > 0 {
+			obj["host"] = host
+		} else {
+			headers, _ := ws["headers"].(map[string]interface{})
+			obj["host"] = searchHost(headers)
 		}
 	case "http":
 		obj["net"] = "h2"
@@ -230,12 +229,20 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string {
 	case "httpupgrade":
 		httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
 		obj["path"] = httpupgrade["path"].(string)
-		obj["host"] = httpupgrade["host"].(string)
-		if headers, ok := httpupgrade["headers"].(map[string]interface{}); ok {
-			hostFromHeaders := searchHost(headers)
-			if hostFromHeaders != "" {
-				obj["host"] = hostFromHeaders
-			}
+		if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
+			obj["host"] = host
+		} else {
+			headers, _ := httpupgrade["headers"].(map[string]interface{})
+			obj["host"] = searchHost(headers)
+		}
+	case "splithttp":
+		splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
+		obj["path"] = splithttp["path"].(string)
+		if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
+			obj["host"] = host
+		} else {
+			headers, _ := splithttp["headers"].(map[string]interface{})
+			obj["host"] = searchHost(headers)
 		}
 	}
 	security, _ := stream["security"].(string)
@@ -352,13 +359,11 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
 	case "ws":
 		ws, _ := stream["wsSettings"].(map[string]interface{})
 		params["path"] = ws["path"].(string)
-		params["host"] = ws["host"].(string)
-		headers, _ := ws["headers"].(map[string]interface{})
-		if headers != nil {
-			hostFromHeaders := searchHost(headers)
-			if hostFromHeaders != "" {
-				params["host"] = hostFromHeaders
-			}
+		if host, ok := ws["host"].(string); ok && len(host) > 0 {
+			params["host"] = host
+		} else {
+			headers, _ := ws["headers"].(map[string]interface{})
+			params["host"] = searchHost(headers)
 		}
 	case "http":
 		http, _ := stream["httpSettings"].(map[string]interface{})
@@ -380,13 +385,20 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string {
 	case "httpupgrade":
 		httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
 		params["path"] = httpupgrade["path"].(string)
-		params["host"] = httpupgrade["host"].(string)
-		headers, _ := httpupgrade["headers"].(map[string]interface{})
-		if headers != nil {
-			hostFromHeaders := searchHost(headers)
-			if hostFromHeaders != "" {
-				params["host"] = hostFromHeaders
-			}
+		if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
+			params["host"] = host
+		} else {
+			headers, _ := httpupgrade["headers"].(map[string]interface{})
+			params["host"] = searchHost(headers)
+		}
+	case "splithttp":
+		splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
+		params["path"] = splithttp["path"].(string)
+		if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
+			params["host"] = host
+		} else {
+			headers, _ := splithttp["headers"].(map[string]interface{})
+			params["host"] = searchHost(headers)
 		}
 	}
 	security, _ := stream["security"].(string)
@@ -581,13 +593,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
 	case "ws":
 		ws, _ := stream["wsSettings"].(map[string]interface{})
 		params["path"] = ws["path"].(string)
-		params["host"] = ws["host"].(string)
-		headers, _ := ws["headers"].(map[string]interface{})
-		if headers != nil {
-			hostFromHeaders := searchHost(headers)
-			if hostFromHeaders != "" {
-				params["host"] = hostFromHeaders
-			}
+		if host, ok := ws["host"].(string); ok && len(host) > 0 {
+			params["host"] = host
+		} else {
+			headers, _ := ws["headers"].(map[string]interface{})
+			params["host"] = searchHost(headers)
 		}
 	case "http":
 		http, _ := stream["httpSettings"].(map[string]interface{})
@@ -609,13 +619,20 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string
 	case "httpupgrade":
 		httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
 		params["path"] = httpupgrade["path"].(string)
-		params["host"] = httpupgrade["host"].(string)
-		headers, _ := httpupgrade["headers"].(map[string]interface{})
-		if headers != nil {
-			hostFromHeaders := searchHost(headers)
-			if hostFromHeaders != "" {
-				params["host"] = hostFromHeaders
-			}
+		if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
+			params["host"] = host
+		} else {
+			headers, _ := httpupgrade["headers"].(map[string]interface{})
+			params["host"] = searchHost(headers)
+		}
+	case "splithttp":
+		splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
+		params["path"] = splithttp["path"].(string)
+		if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
+			params["host"] = host
+		} else {
+			headers, _ := splithttp["headers"].(map[string]interface{})
+			params["host"] = searchHost(headers)
 		}
 	}
 	security, _ := stream["security"].(string)
@@ -811,13 +828,11 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
 	case "ws":
 		ws, _ := stream["wsSettings"].(map[string]interface{})
 		params["path"] = ws["path"].(string)
-		params["host"] = ws["host"].(string)
-		headers, _ := ws["headers"].(map[string]interface{})
-		if headers != nil {
-			hostFromHeaders := searchHost(headers)
-			if hostFromHeaders != "" {
-				params["host"] = hostFromHeaders
-			}
+		if host, ok := ws["host"].(string); ok && len(host) > 0 {
+			params["host"] = host
+		} else {
+			headers, _ := ws["headers"].(map[string]interface{})
+			params["host"] = searchHost(headers)
 		}
 	case "http":
 		http, _ := stream["httpSettings"].(map[string]interface{})
@@ -839,13 +854,20 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st
 	case "httpupgrade":
 		httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{})
 		params["path"] = httpupgrade["path"].(string)
-		params["host"] = httpupgrade["host"].(string)
-		headers, _ := httpupgrade["headers"].(map[string]interface{})
-		if headers != nil {
-			hostFromHeaders := searchHost(headers)
-			if hostFromHeaders != "" {
-				params["host"] = hostFromHeaders
-			}
+		if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 {
+			params["host"] = host
+		} else {
+			headers, _ := httpupgrade["headers"].(map[string]interface{})
+			params["host"] = searchHost(headers)
+		}
+	case "splithttp":
+		splithttp, _ := stream["splithttpSettings"].(map[string]interface{})
+		params["path"] = splithttp["path"].(string)
+		if host, ok := splithttp["host"].(string); ok && len(host) > 0 {
+			params["host"] = host
+		} else {
+			headers, _ := splithttp["headers"].(map[string]interface{})
+			params["host"] = searchHost(headers)
 		}
 	}
 

+ 33 - 5
web/assets/js/model/outbound.js

@@ -194,7 +194,7 @@ class WsStreamSettings extends CommonClass {
     static fromJson(json={}) {
         return new WsStreamSettings(
             json.path,
-            json.host
+            json.host,
         );
     }
 
@@ -202,7 +202,6 @@ class WsStreamSettings extends CommonClass {
         return {
             path: this.path,
             host: this.host,
-            headers: ObjectUtil.isEmpty(this.host) ? undefined : {Host: this.host},
         };
     }
 }
@@ -288,7 +287,29 @@ class HttpUpgradeStreamSettings extends CommonClass {
     static fromJson(json={}) {
         return new HttpUpgradeStreamSettings(
             json.path,
-            json.host
+            json.host,
+        );
+    }
+
+    toJson() {
+        return {
+            path: this.path,
+            host: this.host,
+        };
+    }
+}
+
+class SplitHTTPStreamSettings extends CommonClass {
+    constructor(path='/', host='') {
+        super();
+        this.path = path;
+        this.host = host;
+    }
+
+    static fromJson(json={}) {
+        return new SplitHTTPStreamSettings(
+            json.path,
+            json.host,
         );
     }
 
@@ -296,7 +317,6 @@ class HttpUpgradeStreamSettings extends CommonClass {
         return {
             path: this.path,
             host: this.host,
-            headers: ObjectUtil.isEmpty(this.host) ? undefined : {Host: this.host},
         };
     }
 }
@@ -404,6 +424,7 @@ class StreamSettings extends CommonClass {
                 quicSettings=new QuicStreamSettings(),
                 grpcSettings=new GrpcStreamSettings(),
                 httpupgradeSettings=new HttpUpgradeStreamSettings(),
+                splithttpSettings=new SplitHTTPStreamSettings(),
                 sockopt = undefined,
                 ) {
         super();
@@ -418,6 +439,7 @@ class StreamSettings extends CommonClass {
         this.quic = quicSettings;
         this.grpc = grpcSettings;
         this.httpupgrade = httpupgradeSettings;
+        this.splithttp = splithttpSettings;
         this.sockopt = sockopt;
     }
     
@@ -450,6 +472,7 @@ class StreamSettings extends CommonClass {
             QuicStreamSettings.fromJson(json.quicSettings),
             GrpcStreamSettings.fromJson(json.grpcSettings),
             HttpUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
+            SplitHTTPStreamSettings.fromJson(json.splithttpSettings),
             SockoptStreamSettings.fromJson(json.sockopt),
         );
     }
@@ -468,6 +491,7 @@ class StreamSettings extends CommonClass {
             quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
             grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
             httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
+            splithttpSettings: network === 'splithttp' ? this.splithttp.toJson() : undefined,
             sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
         };
     }
@@ -532,7 +556,7 @@ class Outbound extends CommonClass {
 
     canEnableTls() {
         if (![Protocols.VMess, Protocols.VLESS, Protocols.Trojan, Protocols.Shadowsocks].includes(this.protocol)) return false;
-        return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade"].includes(this.stream.network);
+        return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade" , "splithttp"].includes(this.stream.network);
     }
 
     //this is used for xtls-rprx-vision
@@ -653,6 +677,8 @@ class Outbound extends CommonClass {
             stream.grpc = new GrpcStreamSettings(json.path, json.authority, json.type == 'multi');
         } else if (network === 'httpupgrade') {
             stream.httpupgrade = new HttpUpgradeStreamSettings(json.path,json.host);
+        } else if (network === 'splithttp') {
+            stream.splithttp = new SplitHTTPStreamSettings(json.path,json.host);
         }
 
         if(json.tls && json.tls == 'tls'){
@@ -700,6 +726,8 @@ class Outbound extends CommonClass {
                 url.searchParams.get('mode') == 'multi');
         } else if (type === 'httpupgrade') {
             stream.httpupgrade = new HttpUpgradeStreamSettings(path,host);
+        } else if (type === 'splithttp') {
+            stream.splithttp = new SplitHTTPStreamSettings(path,host);
         }
 
         if(security == 'tls'){

+ 110 - 108
web/assets/js/model/xray.js

@@ -241,15 +241,6 @@ TcpStreamSettings.TcpRequest = class extends XrayCommonClass {
         this.headers.push({ name: name, value: value });
     }
 
-    getHeader(name) {
-        for (const header of this.headers) {
-            if (header.name.toLowerCase() === name.toLowerCase()) {
-                return header.value;
-            }
-        }
-        return null;
-    }
-
     removeHeader(index) {
         this.headers.splice(index, 1);
     }
@@ -379,15 +370,6 @@ class WsStreamSettings extends XrayCommonClass {
         this.headers.push({ name: name, value: value });
     }
 
-    getHeader(name) {
-        for (const header of this.headers) {
-            if (header.name.toLowerCase() === name.toLowerCase()) {
-                return header.value;
-            }
-        }
-        return null;
-    }
-
     removeHeader(index) {
         this.headers.splice(index, 1);
     }
@@ -517,15 +499,6 @@ class HTTPUpgradeStreamSettings extends XrayCommonClass {
         this.headers.push({ name: name, value: value });
     }
 
-    getHeader(name) {
-        for (const header of this.headers) {
-            if (header.name.toLowerCase() === name.toLowerCase()) {
-                return header.value;
-            }
-        }
-        return null;
-    }
-
     removeHeader(index) {
         this.headers.splice(index, 1);
     }
@@ -549,6 +522,45 @@ class HTTPUpgradeStreamSettings extends XrayCommonClass {
     }
 }
 
+class SplitHTTPStreamSettings extends XrayCommonClass {
+    constructor(path='/', host='', headers=[] , maxUploadSize= 1, maxConcurrentUploads= 10) {
+        super();
+        this.path = path;
+        this.host = host;
+        this.headers = headers;
+        this.maxUploadSize = maxUploadSize;
+        this.maxConcurrentUploads = maxConcurrentUploads;
+    }
+
+    addHeader(name, value) {
+        this.headers.push({ name: name, value: value });
+    }
+
+    removeHeader(index) {
+        this.headers.splice(index, 1);
+    }
+
+    static fromJson(json={}) {
+        return new SplitHTTPStreamSettings(
+            json.path,
+            json.host,
+            XrayCommonClass.toHeaders(json.headers),
+            json.maxUploadSize,
+            json.maxConcurrentUploads,
+        );
+    }
+
+    toJson() {
+        return {
+            path: this.path,
+            host: this.host,
+            headers: XrayCommonClass.toV2Headers(this.headers, false),
+            maxUploadSize: this.maxUploadSize,
+            maxConcurrentUploads: this.maxConcurrentUploads,
+        };
+    }
+}
+
 class TlsStreamSettings extends XrayCommonClass {
     constructor(serverName='',
                 minVersion = TLS_VERSION_OPTION.TLS12,
@@ -1001,6 +1013,7 @@ class StreamSettings extends XrayCommonClass {
         quicSettings=new QuicStreamSettings(),
         grpcSettings=new GrpcStreamSettings(),
         httpupgradeSettings=new HTTPUpgradeStreamSettings(),
+        splithttpSettings=new SplitHTTPStreamSettings(),
         sockopt = undefined,
         ) {
         super();
@@ -1017,6 +1030,7 @@ class StreamSettings extends XrayCommonClass {
         this.quic = quicSettings;
         this.grpc = grpcSettings;
         this.httpupgrade = httpupgradeSettings;
+        this.splithttp = splithttpSettings;
         this.sockopt = sockopt;
     }
 
@@ -1080,6 +1094,7 @@ class StreamSettings extends XrayCommonClass {
             QuicStreamSettings.fromJson(json.quicSettings),
             GrpcStreamSettings.fromJson(json.grpcSettings),
             HTTPUpgradeStreamSettings.fromJson(json.httpupgradeSettings),
+            SplitHTTPStreamSettings.fromJson(json.splithttpSettings),
             SockoptStreamSettings.fromJson(json.sockopt),
         );
     }
@@ -1100,6 +1115,7 @@ class StreamSettings extends XrayCommonClass {
             quicSettings: network === 'quic' ? this.quic.toJson() : undefined,
             grpcSettings: network === 'grpc' ? this.grpc.toJson() : undefined,
             httpupgradeSettings: network === 'httpupgrade' ? this.httpupgrade.toJson() : undefined,
+            splithttpSettings: network === 'splithttp' ? this.splithttp.toJson() : undefined,
             sockopt: this.sockopt != undefined ? this.sockopt.toJson() : undefined,
         };
     }
@@ -1228,6 +1244,10 @@ class Inbound extends XrayCommonClass {
         return this.network === "httpupgrade";
     }
 
+    get isSplithttp() {
+        return this.network === "splithttp";
+    }
+
     // Shadowsocks
     get method() {
         switch (this.protocol) {
@@ -1251,25 +1271,26 @@ class Inbound extends XrayCommonClass {
         return "";
     }
 
+    getHeader(obj, name) {
+        for (const header of obj.headers) {
+            if (header.name.toLowerCase() === name.toLowerCase()) {
+                return header.value;
+            }
+        }
+        return "";
+    }
+
     get host() {
         if (this.isTcp) {
-            return this.stream.tcp.request.getHeader("Host");
+            return this.getHeader(this.stream.tcp.request, 'host');
         } else if (this.isWs) {
-            const hostHeader = this.stream.ws.getHeader("Host");
-            if (hostHeader !== null) {
-                return hostHeader;
-            } else {
-                return this.stream.ws.host;
-            }        
+            return this.stream.ws.host?.length>0 ? this.stream.ws.host : this.getHeader(this.stream.ws, 'host');
         } else if (this.isH2) {
             return this.stream.http.host[0];
         } else if (this.isHttpupgrade) {
-            const hostHeader = this.stream.httpupgrade.getHeader("Host");
-            if (hostHeader !== null) {
-                return hostHeader;
-            } else {
-                return this.stream.httpupgrade.host;
-            }        
+            return this.stream.httpupgrade.host?.length>0 ? this.stream.httpupgrade.host : this.getHeader(this.stream.httpupgrade, 'host');
+        } else if (this.isSplithttp) {
+            return this.stream.splithttp.host?.length>0 ? this.stream.splithttp.host : this.getHeader(this.stream.splithttp, 'host');
         }
         return null;
     }
@@ -1283,6 +1304,8 @@ class Inbound extends XrayCommonClass {
             return this.stream.http.path;
         } else if (this.isHttpupgrade) {
             return this.stream.httpupgrade.path;
+        } else if (this.isSplithttp) {
+            return this.stream.splithttp.path;
         }
         return null;
     }
@@ -1318,7 +1341,7 @@ class Inbound extends XrayCommonClass {
 
     canEnableTls() {
         if(![Protocols.VMESS, Protocols.VLESS, Protocols.TROJAN, Protocols.SHADOWSOCKS].includes(this.protocol)) return false;
-        return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade"].includes(this.network);
+        return ["tcp", "ws", "http", "quic", "grpc", "httpupgrade" , "splithttp"].includes(this.network);
     }
 
     //this is used for xtls-rprx-vision
@@ -1370,15 +1393,13 @@ class Inbound extends XrayCommonClass {
         };
         let network = this.stream.network;
         if (network === 'tcp') {
-            let tcp = this.stream.tcp;
+            const tcp = this.stream.tcp;
             obj.type = tcp.type;
             if (tcp.type === 'http') {
-                let request = tcp.request;
+                const request = tcp.request;
                 obj.path = request.path.join(',');
-                let index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
-                if (index >= 0) {
-                    obj.host = request.headers[index].value;
-                }
+                const host = this.getHeader(request,'host');
+                if (host) obj.host = host;
             }
         } else if (network === 'kcp') {
             let kcp = this.stream.kcp;
@@ -1387,11 +1408,7 @@ class Inbound extends XrayCommonClass {
         } else if (network === 'ws') {
             let ws = this.stream.ws;
             obj.path = ws.path;
-            obj.host = ws.host;
-            let index = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
-            if (index >= 0) {
-                obj.host = ws.headers[index].value;
-            }
+            obj.host = ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host');
         } else if (network === 'http') {
             obj.net = 'h2';
             obj.path = this.stream.http.path;
@@ -1409,11 +1426,11 @@ class Inbound extends XrayCommonClass {
         } else if (network === 'httpupgrade') {
             let httpupgrade = this.stream.httpupgrade;
             obj.path = httpupgrade.path;
-            obj.host = httpupgrade.host;
-            let index = httpupgrade.headers.findIndex(header => header.name.toLowerCase() === 'host');
-            if (index >= 0) {
-                obj.host = httpupgrade.headers[index].value;
-            }
+            obj.host = httpupgrade.host?.length>0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host');
+        } else if (network === 'splithttp') {
+            let splithttp = this.stream.splithttp;
+            obj.path = splithttp.path;
+            obj.host = splithttp.host?.length>0 ? splithttp.host : this.getHeader(splithttp, 'host');
         }
 
         if (security === 'tls') {
@@ -1446,9 +1463,9 @@ class Inbound extends XrayCommonClass {
                 if (tcp.type === 'http') {
                     const request = tcp.request;
                     params.set("path", request.path.join(','));
-                    const tcpIndex = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
-                    if (tcpIndex >= 0) {
-                        const host = request.headers[tcpIndex].value;
+                    const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
+                    if (index >= 0) {
+                        const host = request.headers[index].value;
                         params.set("host", host);
                     }
                     params.set("headerType", 'http');
@@ -1462,12 +1479,7 @@ class Inbound extends XrayCommonClass {
             case "ws":
                 const ws = this.stream.ws;
                 params.set("path", ws.path);
-                params.set("host", ws.host);
-                const wsIndex = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
-                if (wsIndex >= 0) {
-                    const host = ws.headers[wsIndex].value;
-                    params.set("host", host);
-                }
+                params.set("host", ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host'));
                 break;
             case "http":
                 const http = this.stream.http;
@@ -1489,14 +1501,14 @@ class Inbound extends XrayCommonClass {
                 }
                 break;
             case "httpupgrade":
-                const httpupgrade = this.stream.httpupgrade;
-                params.set("path", httpupgrade.path);
-                params.set("host", httpupgrade.host);
-                const httpupgradeIndex = httpupgrade.headers.findIndex(header => header.name.toLowerCase() === 'host');
-                if (httpupgradeIndex >= 0) {
-                    const host = httpupgrade.headers[httpupgradeIndex].value;
-                    params.set("host", host);
-                }
+                    const httpupgrade = this.stream.httpupgrade;
+                    params.set("path", httpupgrade.path);
+                    params.set("host", httpupgrade.host?.length>0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
+                break;
+            case "splithttp":
+                    const splithttp = this.stream.splithttp;
+                    params.set("path", splithttp.path);
+                    params.set("host", splithttp.host?.length>0 ? splithttp.host : this.getHeader(splithttp, 'host'));
                 break;
         }
 
@@ -1572,9 +1584,9 @@ class Inbound extends XrayCommonClass {
                 if (tcp.type === 'http') {
                     const request = tcp.request;
                     params.set("path", request.path.join(','));
-                    const tcpIndex = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
-                    if (tcpIndex >= 0) {
-                        const host = request.headers[tcpIndex].value;
+                    const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
+                    if (index >= 0) {
+                        const host = request.headers[index].value;
                         params.set("host", host);
                     }
                     params.set("headerType", 'http');
@@ -1588,12 +1600,7 @@ class Inbound extends XrayCommonClass {
             case "ws":
                 const ws = this.stream.ws;
                 params.set("path", ws.path);
-                params.set("host", ws.host);
-                const wsIndex = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
-                if (wsIndex >= 0) {
-                    const host = ws.headers[wsIndex].value;
-                    params.set("host", host);
-                }
+                params.set("host", ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host'));
                 break;
             case "http":
                 const http = this.stream.http;
@@ -1615,14 +1622,14 @@ class Inbound extends XrayCommonClass {
                 }
                 break;
             case "httpupgrade":
-                const httpupgrade = this.stream.httpupgrade;
-                params.set("path", httpupgrade.path);
-                params.set("host", httpupgrade.host);
-                const httpupgradeIndex = httpupgrade.headers.findIndex(header => header.name.toLowerCase() === 'host');
-                if (httpupgradeIndex >= 0) {
-                    const host = httpupgrade.headers[httpupgradeIndex].value;
-                    params.set("host", host);
-                }
+                    const httpupgrade = this.stream.httpupgrade;
+                    params.set("path", httpupgrade.path);
+                    params.set("host", httpupgrade.host?.length>0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
+                break;
+            case "splithttp":
+                    const splithttp = this.stream.splithttp;
+                    params.set("path", splithttp.path);
+                    params.set("host", splithttp.host?.length>0 ? splithttp.host : this.getHeader(splithttp, 'host'));
                 break;
         }
 
@@ -1665,9 +1672,9 @@ class Inbound extends XrayCommonClass {
                 if (tcp.type === 'http') {
                     const request = tcp.request;
                     params.set("path", request.path.join(','));
-                    const tcpIndex = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
-                    if (tcpIndex >= 0) {
-                        const host = request.headers[tcpIndex].value;
+                    const index = request.headers.findIndex(header => header.name.toLowerCase() === 'host');
+                    if (index >= 0) {
+                        const host = request.headers[index].value;
                         params.set("host", host);
                     }
                     params.set("headerType", 'http');
@@ -1681,12 +1688,7 @@ class Inbound extends XrayCommonClass {
             case "ws":
                 const ws = this.stream.ws;
                 params.set("path", ws.path);
-                params.set("host", ws.host);
-                const wsIndex = ws.headers.findIndex(header => header.name.toLowerCase() === 'host');
-                if (wsIndex >= 0) {
-                    const host = ws.headers[wsIndex].value;
-                    params.set("host", host);
-                }
+                params.set("host", ws.host?.length>0 ? ws.host : this.getHeader(ws, 'host'));
                 break;
             case "http":
                 const http = this.stream.http;
@@ -1708,14 +1710,14 @@ class Inbound extends XrayCommonClass {
                 }
                 break;
             case "httpupgrade":
-                const httpupgrade = this.stream.httpupgrade;
-                params.set("path", httpupgrade.path);
-                params.set("host", httpupgrade.host);
-                const httpUpgradeIndex = httpupgrade.headers.findIndex(header => header.name.toLowerCase() === 'host');
-                if (httpUpgradeIndex >= 0) {
-                    const host = httpupgrade.headers[httpUpgradeIndex].value;
-                    params.set("host", host);
-                }
+                    const httpupgrade = this.stream.httpupgrade;
+                    params.set("path", httpupgrade.path);
+                    params.set("host", httpupgrade.host?.length>0 ? httpupgrade.host : this.getHeader(httpupgrade, 'host'));
+                break;
+            case "splithttp":
+                    const splithttp = this.stream.splithttp;
+                    params.set("path", splithttp.path);
+                    params.set("host", splithttp.host?.length>0 ? splithttp.host : this.getHeader(splithttp, 'host'));
                 break;
         }
 

+ 11 - 0
web/html/xui/form/outbound.html

@@ -204,6 +204,7 @@
             <a-select-option value="quic">QUIC</a-select-option>
             <a-select-option value="grpc">gRPC</a-select-option>
             <a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
+            <a-select-option value="splithttp">SplitHTTP</a-select-option>
           </a-select>
         </a-form-item>
         <template v-if="outbound.stream.network === 'tcp'">
@@ -325,6 +326,16 @@
             <a-input v-model.trim="outbound.stream.httpupgrade.path"></a-input>
           </a-form-item>
         </template>
+
+        <!-- splithttp -->
+        <template v-if="outbound.stream.network === 'splithttp'">
+          <a-form-item label='{{ i18n "host" }}'>
+            <a-input v-model="outbound.stream.splithttp.host"></a-input>
+          </a-form-item>
+          <a-form-item label='{{ i18n "path" }}'>
+            <a-input v-model.trim="outbound.stream.splithttp.path"></a-input>
+          </a-form-item>
+        </template>
       </template>
 
       <!-- tls settings -->

+ 6 - 0
web/html/xui/form/stream/stream_settings.html

@@ -11,6 +11,7 @@
             <a-select-option value="quic">QUIC</a-select-option>
             <a-select-option value="grpc">gRPC</a-select-option>
             <a-select-option value="httpupgrade">HTTPUpgrade</a-select-option>
+            <a-select-option value="splithttp">SplitHTTP</a-select-option>
         </a-select>
     </a-form-item>
 </a-form>
@@ -50,6 +51,11 @@
     {{template "form/streamHTTPUpgrade"}}
 </template>
 
+<!-- splithttp -->
+<template v-if="inbound.stream.network === 'splithttp'">
+    {{template "form/streamSplitHTTP"}}
+</template>
+
 <!-- sockopt -->
 <template>
     {{template "form/streamSockopt"}}

+ 29 - 0
web/html/xui/form/stream/stream_splithttp.html

@@ -0,0 +1,29 @@
+{{define "form/streamSplitHTTP"}}
+<a-form :colon="false" :label-col="{ md: {span:8} }" :wrapper-col="{ md: {span:14} }">
+    <a-form-item label='{{ i18n "host" }}'>
+        <a-input v-model.trim="inbound.stream.splithttp.host"></a-input>
+    </a-form-item>
+    <a-form-item label='{{ i18n "path" }}'>
+      <a-input v-model.trim="inbound.stream.splithttp.path"></a-input>
+    </a-form-item>
+    <a-form-item label='{{ i18n "pages.inbounds.stream.tcp.requestHeader" }}'>
+      <a-button icon="plus" size="small" @click="inbound.stream.splithttp.addHeader('host', '')"></a-button>
+    </a-form-item>
+    <a-form-item :wrapper-col="{span:24}">
+        <a-input-group compact v-for="(header, index) in inbound.stream.splithttp.headers">
+            <a-input style="width: 50%" v-model.trim="header.name" placeholder='{{ i18n "pages.inbounds.stream.general.name"}}'>
+                <template slot="addonBefore" style="margin: 0;">[[ index+1 ]]</template>
+            </a-input>
+            <a-input style="width: 50%" v-model.trim="header.value" placeholder='{{ i18n "pages.inbounds.stream.general.value" }}'>
+                <a-button slot="addonAfter" size="small" @click="inbound.stream.splithttp.removeHeader(index)">-</a-button>
+            </a-input>
+        </a-input-group>
+    </a-form-item>
+    <a-form-item label="max Upload Size">
+        <a-input-number v-model="inbound.stream.splithttp.maxUploadSize" :min="0"></a-input-number>
+    </a-form-item>
+    <a-form-item label="max Concurrent Uploads">
+        <a-input-number v-model="inbound.stream.splithttp.maxConcurrentUploads" :min="0"></a-input-number>
+    </a-form-item>
+</a-form>
+{{end}}

+ 1 - 1
web/html/xui/inbound_info_modal.html

@@ -34,7 +34,7 @@
               <a-tag color="green">[[ inbound.network ]]</a-tag>
             </td>
           </tr>
-          <template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade ">
+          <template v-if="inbound.isTcp || inbound.isWs || inbound.isH2 || inbound.isHttpupgrade || inbound.isSplithttp">
             <tr>
               <td>{{ i18n "host" }}</td>
               <td v-if="inbound.host">