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  }