feat: add http1.1 mode support for xhttp client

This commit is contained in:
wwqgtxx
2026-04-09 09:46:16 +08:00
parent cacd01a3b3
commit 61dc7b1038
3 changed files with 71 additions and 3 deletions
+2 -3
View File
@@ -807,7 +807,7 @@ proxies: # socks5
udp: true
tls: true
network: xhttp
alpn: [h2] # 默认h2,如果开启h3模式需要设置alpn: [h3]
alpn: [h2] # 默认仅支持h2,如果开启h3模式需要设置alpn: [h3],如果开启http1.1模式需要设置alpn: [http/1.1]
# ech-opts: ...
# reality-opts: ...
# skip-cert-verify: false
@@ -851,8 +851,7 @@ proxies: # socks5
# server: server
# port: 443
# tls: true
# alpn:
# - h2
# alpn: ...
# ech-opts: ...
# reality-opts: ...
# skip-cert-verify: false
+36
View File
@@ -488,6 +488,42 @@ func TestInboundVless_XHTTP_Reality(t *testing.T) {
}
}
func TestInboundVless_XHTTP_PacketUp_H1(t *testing.T) {
getConfig := func() (inbound.VlessOption, outbound.VlessOption) {
inboundOptions := inbound.VlessOption{
Certificate: tlsCertificate,
PrivateKey: tlsPrivateKey,
XHTTPConfig: inbound.XHTTPConfig{
Path: "/vless-xhttp",
Host: "example.com",
Mode: "packet-up",
},
}
outboundOptions := outbound.VlessOption{
TLS: true,
Fingerprint: tlsFingerprint,
Network: "xhttp",
ALPN: []string{"http/1.1"},
XHTTPOpts: outbound.XHTTPOptions{
Path: "/vless-xhttp",
Host: "example.com",
Mode: "packet-up",
},
}
return inboundOptions, outboundOptions
}
t.Run("default", func(t *testing.T) {
inboundOptions, outboundOptions := getConfig()
testInboundVlessTLS(t, inboundOptions, outboundOptions, false)
})
t.Run("reuse", func(t *testing.T) {
inboundOptions, outboundOptions := getConfig()
testInboundVlessTLS(t, inboundOptions, withXHTTPReuse(outboundOptions), false)
})
}
func withXHTTPReuse(out outbound.VlessOption) outbound.VlessOption {
out.XHTTPOpts.ReuseSettings = &outbound.XHTTPReuseSettings{
MaxConnections: "0",
+33
View File
@@ -19,6 +19,7 @@ import (
"github.com/metacubex/quic-go"
"github.com/metacubex/quic-go/http3"
"github.com/metacubex/tls"
"golang.org/x/sync/semaphore"
)
type DialRawFunc func(ctx context.Context) (net.Conn, error)
@@ -100,6 +101,16 @@ func (c *PacketUpWriter) Close() error {
return nil
}
// h1Transport is a wrapper that forces the underlying transport to use HTTP/1.1.
type h1Transport struct {
http.RoundTripper
}
func (c h1Transport) RoundTrip(req *http.Request) (*http.Response, error) {
req.URL.Scheme = "http" // change the scheme to http allow we can make TLS in ourselves Transport.DialContext
return c.RoundTripper.RoundTrip(req)
}
func NewTransport(dialRaw DialRawFunc, wrapTLS WrapTLSFunc, dialQUIC DialQUICFunc, alpn []string) http.RoundTripper {
if len(alpn) == 1 && alpn[0] == "h3" { // `alpn: [h3]` means using h3 mode
return &http3.Transport{
@@ -112,6 +123,28 @@ func NewTransport(dialRaw DialRawFunc, wrapTLS WrapTLSFunc, dialQUIC DialQUICFun
},
}
}
if len(alpn) == 1 && alpn[0] == "http/1.1" { // `alpn: [http/1.1]` means using http/1.1 mode
w := semaphore.NewWeighted(20) // limit concurrent dialing to avoid WSAECONNREFUSED on Windows
return h1Transport{&http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
if err := w.Acquire(ctx, 1); err != nil {
return nil, err
}
defer w.Release(1)
raw, err := dialRaw(ctx)
if err != nil {
return nil, err
}
wrapped, err := wrapTLS(ctx, raw, false)
if err != nil {
_ = raw.Close()
return nil, err
}
return wrapped, nil
},
ForceAttemptHTTP2: false, // only http/1.1
}}
}
return &http.Http2Transport{
DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
raw, err := dialRaw(ctx)