github.com/sagernet/sing-box@v1.9.0-rc.20/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 "strings" 11 "time" 12 13 "github.com/sagernet/sing-box/adapter" 14 "github.com/sagernet/sing-box/common/tls" 15 "github.com/sagernet/sing-box/option" 16 E "github.com/sagernet/sing/common/exceptions" 17 M "github.com/sagernet/sing/common/metadata" 18 N "github.com/sagernet/sing/common/network" 19 sHTTP "github.com/sagernet/sing/protocol/http" 20 21 "golang.org/x/net/http2" 22 ) 23 24 var _ adapter.V2RayClientTransport = (*Client)(nil) 25 26 type Client struct { 27 ctx context.Context 28 dialer N.Dialer 29 serverAddr M.Socksaddr 30 transport http.RoundTripper 31 http2 bool 32 requestURL url.URL 33 host []string 34 method string 35 headers http.Header 36 } 37 38 func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { 39 var transport http.RoundTripper 40 if tlsConfig == nil { 41 transport = &http.Transport{ 42 DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 43 return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) 44 }, 45 } 46 } else { 47 if len(tlsConfig.NextProtos()) == 0 { 48 tlsConfig.SetNextProtos([]string{http2.NextProtoTLS}) 49 } 50 transport = &http2.Transport{ 51 ReadIdleTimeout: time.Duration(options.IdleTimeout), 52 PingTimeout: time.Duration(options.PingTimeout), 53 DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) { 54 conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) 55 if err != nil { 56 return nil, err 57 } 58 return tls.ClientHandshake(ctx, conn, tlsConfig) 59 }, 60 } 61 } 62 if options.Method == "" { 63 options.Method = http.MethodPut 64 } 65 var requestURL url.URL 66 if tlsConfig == nil { 67 requestURL.Scheme = "http" 68 } else { 69 requestURL.Scheme = "https" 70 } 71 requestURL.Host = serverAddr.String() 72 requestURL.Path = options.Path 73 err := sHTTP.URLSetPath(&requestURL, options.Path) 74 if err != nil { 75 return nil, E.Cause(err, "parse path") 76 } 77 if !strings.HasPrefix(requestURL.Path, "/") { 78 requestURL.Path = "/" + requestURL.Path 79 } 80 return &Client{ 81 ctx: ctx, 82 dialer: dialer, 83 serverAddr: serverAddr, 84 requestURL: requestURL, 85 host: options.Host, 86 method: options.Method, 87 headers: options.Headers.Build(), 88 transport: transport, 89 http2: tlsConfig != nil, 90 }, 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.requestURL, 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.requestURL, 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.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 }