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  }