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
|
proxy["grpc-opts"] = grpcOpts
|
||||||
}
|
}
|
||||||
return true
|
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:
|
default:
|
||||||
return false
|
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