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 }