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