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