mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
feat(clash): emit xhttp and httpupgrade transports in subscription (#4531)
applyTransport's switch only covered tcp/ws/grpc; xhttp and
httpupgrade inbounds fell through to the default branch and returned
false. buildProxy then returned a nil map and the inbound was dropped
from the Clash subscription. When the subscription only contained
xhttp/httpupgrade inbounds, the proxies list ended up empty and the
client saw a 404 (or an "Error!" body on older builds), then refused
to parse.
Add a case for each, mapping the inbound's stream settings onto the
Mihomo-format opts blocks:
xhttp -> xhttp-opts: { path, host, mode }
httpupgrade -> http-upgrade-opts: { path, headers: { Host } }
Host falls back to the headers map when the dedicated `host` field is
empty, matching the existing ws behavior.
Closes #4531
This commit is contained in:
parent
64122ad80f
commit
3df0ed2143
2 changed files with 128 additions and 0 deletions
|
|
@ -363,6 +363,53 @@ func (s *SubClashService) applyTransport(proxy map[string]any, network string, s
|
|||
proxy["grpc-opts"] = grpcOpts
|
||||
}
|
||||
return true
|
||||
case "httpupgrade":
|
||||
proxy["network"] = "httpupgrade"
|
||||
hu, _ := stream["httpupgradeSettings"].(map[string]any)
|
||||
opts := map[string]any{}
|
||||
if hu != nil {
|
||||
if path, ok := hu["path"].(string); ok && path != "" {
|
||||
opts["path"] = path
|
||||
}
|
||||
host := ""
|
||||
if v, ok := hu["host"].(string); ok && v != "" {
|
||||
host = v
|
||||
} else if headers, ok := hu["headers"].(map[string]any); ok {
|
||||
host = searchHost(headers)
|
||||
}
|
||||
if host != "" {
|
||||
opts["headers"] = map[string]any{"Host": host}
|
||||
}
|
||||
}
|
||||
if len(opts) > 0 {
|
||||
proxy["http-upgrade-opts"] = opts
|
||||
}
|
||||
return true
|
||||
case "xhttp":
|
||||
proxy["network"] = "xhttp"
|
||||
xhttp, _ := stream["xhttpSettings"].(map[string]any)
|
||||
opts := map[string]any{}
|
||||
if xhttp != nil {
|
||||
if path, ok := xhttp["path"].(string); ok && path != "" {
|
||||
opts["path"] = path
|
||||
}
|
||||
host := ""
|
||||
if v, ok := xhttp["host"].(string); ok && v != "" {
|
||||
host = v
|
||||
} else if headers, ok := xhttp["headers"].(map[string]any); ok {
|
||||
host = searchHost(headers)
|
||||
}
|
||||
if host != "" {
|
||||
opts["host"] = host
|
||||
}
|
||||
if mode, ok := xhttp["mode"].(string); ok && mode != "" {
|
||||
opts["mode"] = mode
|
||||
}
|
||||
}
|
||||
if len(opts) > 0 {
|
||||
proxy["xhttp-opts"] = opts
|
||||
}
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
81
sub/subClashService_test.go
Normal file
81
sub/subClashService_test.go
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package sub
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestApplyTransport_XHTTP(t *testing.T) {
|
||||
svc := &SubClashService{}
|
||||
proxy := map[string]any{}
|
||||
stream := map[string]any{
|
||||
"xhttpSettings": map[string]any{
|
||||
"path": "/xh",
|
||||
"host": "example.com",
|
||||
"mode": "auto",
|
||||
},
|
||||
}
|
||||
|
||||
if !svc.applyTransport(proxy, "xhttp", stream) {
|
||||
t.Fatalf("applyTransport returned false for xhttp (#4531: would drop the inbound and yield an empty Clash YAML)")
|
||||
}
|
||||
if proxy["network"] != "xhttp" {
|
||||
t.Fatalf("network = %v, want xhttp", proxy["network"])
|
||||
}
|
||||
opts, ok := proxy["xhttp-opts"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("xhttp-opts missing or wrong type: %#v", proxy["xhttp-opts"])
|
||||
}
|
||||
want := map[string]any{"path": "/xh", "host": "example.com", "mode": "auto"}
|
||||
if !reflect.DeepEqual(opts, want) {
|
||||
t.Fatalf("xhttp-opts = %#v, want %#v", opts, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyTransport_XHTTP_HostFromHeaders(t *testing.T) {
|
||||
svc := &SubClashService{}
|
||||
proxy := map[string]any{}
|
||||
stream := map[string]any{
|
||||
"xhttpSettings": map[string]any{
|
||||
"path": "/xh",
|
||||
"headers": map[string]any{"Host": "via-header.example.com"},
|
||||
},
|
||||
}
|
||||
|
||||
if !svc.applyTransport(proxy, "xhttp", stream) {
|
||||
t.Fatalf("applyTransport returned false for xhttp")
|
||||
}
|
||||
opts, _ := proxy["xhttp-opts"].(map[string]any)
|
||||
if opts["host"] != "via-header.example.com" {
|
||||
t.Fatalf("host should fall back to headers.Host, got %v", opts["host"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyTransport_HTTPUpgrade(t *testing.T) {
|
||||
svc := &SubClashService{}
|
||||
proxy := map[string]any{}
|
||||
stream := map[string]any{
|
||||
"httpupgradeSettings": map[string]any{
|
||||
"path": "/hu",
|
||||
"host": "example.com",
|
||||
},
|
||||
}
|
||||
|
||||
if !svc.applyTransport(proxy, "httpupgrade", stream) {
|
||||
t.Fatalf("applyTransport returned false for httpupgrade")
|
||||
}
|
||||
if proxy["network"] != "httpupgrade" {
|
||||
t.Fatalf("network = %v, want httpupgrade", proxy["network"])
|
||||
}
|
||||
opts, ok := proxy["http-upgrade-opts"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("http-upgrade-opts missing: %#v", proxy["http-upgrade-opts"])
|
||||
}
|
||||
if opts["path"] != "/hu" {
|
||||
t.Fatalf("path = %v, want /hu", opts["path"])
|
||||
}
|
||||
headers, _ := opts["headers"].(map[string]any)
|
||||
if headers["Host"] != "example.com" {
|
||||
t.Fatalf("headers.Host = %v, want example.com", headers["Host"])
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue