github.com/v2fly/v2ray-core/v5@v5.16.2-0.20240507031116-8191faa6e095/transport/internet/http/dialer.go (about) 1 package http 2 3 import ( 4 "context" 5 gotls "crypto/tls" 6 gonet "net" 7 "net/http" 8 "net/url" 9 "sync" 10 11 "golang.org/x/net/http2" 12 13 core "github.com/v2fly/v2ray-core/v5" 14 "github.com/v2fly/v2ray-core/v5/common" 15 "github.com/v2fly/v2ray-core/v5/common/buf" 16 "github.com/v2fly/v2ray-core/v5/common/net" 17 "github.com/v2fly/v2ray-core/v5/transport/internet" 18 "github.com/v2fly/v2ray-core/v5/transport/internet/security" 19 "github.com/v2fly/v2ray-core/v5/transport/pipe" 20 ) 21 22 var ( 23 globalDialerMap map[net.Destination]*http.Client 24 globalDialerAccess sync.Mutex 25 ) 26 27 type dialerCanceller func() 28 29 func getHTTPClient(ctx context.Context, dest net.Destination, securityEngine *security.Engine, streamSettings *internet.MemoryStreamConfig) (*http.Client, dialerCanceller) { 30 globalDialerAccess.Lock() 31 defer globalDialerAccess.Unlock() 32 33 canceller := func() { 34 globalDialerAccess.Lock() 35 defer globalDialerAccess.Unlock() 36 delete(globalDialerMap, dest) 37 } 38 39 if globalDialerMap == nil { 40 globalDialerMap = make(map[net.Destination]*http.Client) 41 } 42 43 if client, found := globalDialerMap[dest]; found { 44 return client, canceller 45 } 46 47 transport := &http2.Transport{ 48 DialTLSContext: func(_ context.Context, network, addr string, tlsConfig *gotls.Config) (gonet.Conn, error) { 49 rawHost, rawPort, err := net.SplitHostPort(addr) 50 if err != nil { 51 return nil, err 52 } 53 if len(rawPort) == 0 { 54 rawPort = "443" 55 } 56 port, err := net.PortFromString(rawPort) 57 if err != nil { 58 return nil, err 59 } 60 address := net.ParseAddress(rawHost) 61 62 detachedContext := core.ToBackgroundDetachedContext(ctx) 63 pconn, err := internet.DialSystem(detachedContext, net.TCPDestination(address, port), streamSettings.SocketSettings) 64 if err != nil { 65 return nil, err 66 } 67 68 cn, err := (*securityEngine).Client(pconn, 69 security.OptionWithDestination{Dest: dest}) 70 if err != nil { 71 return nil, err 72 } 73 74 protocol := "" 75 if connAPLNGetter, ok := cn.(security.ConnectionApplicationProtocol); ok { 76 connectionALPN, err := connAPLNGetter.GetConnectionApplicationProtocol() 77 if err != nil { 78 return nil, newError("failed to get connection ALPN").Base(err).AtWarning() 79 } 80 protocol = connectionALPN 81 } 82 83 if protocol != http2.NextProtoTLS { 84 return nil, newError("http2: unexpected ALPN protocol " + protocol + "; want q" + http2.NextProtoTLS).AtError() 85 } 86 return cn, nil 87 }, 88 } 89 90 client := &http.Client{ 91 Transport: transport, 92 } 93 94 globalDialerMap[dest] = client 95 return client, canceller 96 } 97 98 // Dial dials a new TCP connection to the given destination. 99 func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) { 100 httpSettings := streamSettings.ProtocolSettings.(*Config) 101 securityEngine, err := security.CreateSecurityEngineFromSettings(ctx, streamSettings) 102 if err != nil { 103 return nil, newError("unable to create security engine").Base(err) 104 } 105 if securityEngine == nil { 106 return nil, newError("TLS must be enabled for http transport.").AtWarning() 107 } 108 client, canceller := getHTTPClient(ctx, dest, &securityEngine, streamSettings) 109 110 opts := pipe.OptionsFromContext(ctx) 111 preader, pwriter := pipe.New(opts...) 112 breader := &buf.BufferedReader{Reader: preader} 113 114 httpMethod := "PUT" 115 if httpSettings.Method != "" { 116 httpMethod = httpSettings.Method 117 } 118 119 httpHeaders := make(http.Header) 120 121 for _, httpHeader := range httpSettings.Header { 122 for _, httpHeaderValue := range httpHeader.Value { 123 httpHeaders.Set(httpHeader.Name, httpHeaderValue) 124 } 125 } 126 127 request := &http.Request{ 128 Method: httpMethod, 129 Host: httpSettings.getRandomHost(), 130 Body: breader, 131 URL: &url.URL{ 132 Scheme: "https", 133 Host: dest.NetAddr(), 134 Path: httpSettings.getNormalizedPath(), 135 }, 136 Proto: "HTTP/2", 137 ProtoMajor: 2, 138 ProtoMinor: 0, 139 Header: httpHeaders, 140 } 141 // Disable any compression method from server. 142 request.Header.Set("Accept-Encoding", "identity") 143 144 response, err := client.Do(request) // nolint: bodyclose 145 if err != nil { 146 canceller() 147 return nil, newError("failed to dial to ", dest).Base(err).AtWarning() 148 } 149 if response.StatusCode != 200 { 150 return nil, newError("unexpected status", response.StatusCode).AtWarning() 151 } 152 153 bwriter := buf.NewBufferedWriter(pwriter) 154 common.Must(bwriter.SetBuffered(false)) 155 return net.NewConnection( 156 net.ConnectionOutput(response.Body), 157 net.ConnectionInput(bwriter), 158 net.ConnectionOnClose(common.ChainedClosable{breader, bwriter, response.Body}), 159 ), nil 160 } 161 162 func init() { 163 common.Must(internet.RegisterTransportDialer(protocolName, Dial)) 164 }