github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/transport/v2rayhttp/client.go (about)

     1  package v2rayhttp
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"math/rand"
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"time"
    11  
    12  	"github.com/inazumav/sing-box/adapter"
    13  	"github.com/inazumav/sing-box/common/tls"
    14  	"github.com/inazumav/sing-box/option"
    15  	E "github.com/sagernet/sing/common/exceptions"
    16  	M "github.com/sagernet/sing/common/metadata"
    17  	N "github.com/sagernet/sing/common/network"
    18  	sHTTP "github.com/sagernet/sing/protocol/http"
    19  
    20  	"golang.org/x/net/http2"
    21  )
    22  
    23  var _ adapter.V2RayClientTransport = (*Client)(nil)
    24  
    25  type Client struct {
    26  	ctx        context.Context
    27  	dialer     N.Dialer
    28  	serverAddr M.Socksaddr
    29  	transport  http.RoundTripper
    30  	http2      bool
    31  	url        *url.URL
    32  	host       []string
    33  	method     string
    34  	headers    http.Header
    35  }
    36  
    37  func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
    38  	var transport http.RoundTripper
    39  	if tlsConfig == nil {
    40  		transport = &http.Transport{
    41  			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
    42  				return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
    43  			},
    44  		}
    45  	} else {
    46  		if len(tlsConfig.NextProtos()) == 0 {
    47  			tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
    48  		}
    49  		transport = &http2.Transport{
    50  			ReadIdleTimeout: time.Duration(options.IdleTimeout),
    51  			PingTimeout:     time.Duration(options.PingTimeout),
    52  			DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
    53  				conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
    54  				if err != nil {
    55  					return nil, err
    56  				}
    57  				return tls.ClientHandshake(ctx, conn, tlsConfig)
    58  			},
    59  		}
    60  	}
    61  	client := &Client{
    62  		ctx:        ctx,
    63  		dialer:     dialer,
    64  		serverAddr: serverAddr,
    65  		host:       options.Host,
    66  		method:     options.Method,
    67  		headers:    make(http.Header),
    68  		transport:  transport,
    69  		http2:      tlsConfig != nil,
    70  	}
    71  	if client.method == "" {
    72  		client.method = "PUT"
    73  	}
    74  	var uri url.URL
    75  	if tlsConfig == nil {
    76  		uri.Scheme = "http"
    77  	} else {
    78  		uri.Scheme = "https"
    79  	}
    80  	uri.Host = serverAddr.String()
    81  	uri.Path = options.Path
    82  	err := sHTTP.URLSetPath(&uri, options.Path)
    83  	if err != nil {
    84  		return nil, E.New("failed to set path: " + err.Error())
    85  	}
    86  	for key, valueList := range options.Headers {
    87  		client.headers[key] = valueList
    88  	}
    89  	client.url = &uri
    90  	return client, nil
    91  }
    92  
    93  func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
    94  	if !c.http2 {
    95  		return c.dialHTTP(ctx)
    96  	} else {
    97  		return c.dialHTTP2(ctx)
    98  	}
    99  }
   100  
   101  func (c *Client) dialHTTP(ctx context.Context) (net.Conn, error) {
   102  	conn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	request := &http.Request{
   108  		Method: c.method,
   109  		URL:    c.url,
   110  		Header: c.headers.Clone(),
   111  	}
   112  	switch hostLen := len(c.host); hostLen {
   113  	case 0:
   114  		request.Host = c.serverAddr.AddrString()
   115  	case 1:
   116  		request.Host = c.host[0]
   117  	default:
   118  		request.Host = c.host[rand.Intn(hostLen)]
   119  	}
   120  
   121  	return NewHTTP1Conn(conn, request), nil
   122  }
   123  
   124  func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
   125  	pipeInReader, pipeInWriter := io.Pipe()
   126  	request := &http.Request{
   127  		Method: c.method,
   128  		Body:   pipeInReader,
   129  		URL:    c.url,
   130  		Header: c.headers.Clone(),
   131  	}
   132  	request = request.WithContext(ctx)
   133  	switch hostLen := len(c.host); hostLen {
   134  	case 0:
   135  		// https://github.com/v2fly/v2ray-core/blob/master/transport/internet/http/config.go#L13
   136  		request.Host = "www.example.com"
   137  	case 1:
   138  		request.Host = c.host[0]
   139  	default:
   140  		request.Host = c.host[rand.Intn(hostLen)]
   141  	}
   142  	conn := NewLateHTTPConn(pipeInWriter)
   143  	go func() {
   144  		response, err := c.transport.RoundTrip(request)
   145  		if err != nil {
   146  			conn.Setup(nil, err)
   147  		} else if response.StatusCode != 200 {
   148  			response.Body.Close()
   149  			conn.Setup(nil, E.New("unexpected status: ", response.StatusCode, " ", response.Status))
   150  		} else {
   151  			conn.Setup(response.Body, nil)
   152  		}
   153  	}()
   154  	return conn, nil
   155  }
   156  
   157  func (c *Client) Close() error {
   158  	CloseIdleConnections(c.transport)
   159  	return nil
   160  }