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  }