sing-box/transport/v2rayhttp/client.go

161 lines
4 KiB
Go
Raw Permalink Normal View History

2022-08-22 14:19:25 +00:00
package v2rayhttp
import (
"context"
"io"
"math/rand"
"net"
"net/http"
"net/url"
"strings"
"time"
2022-08-22 14:19:25 +00:00
"github.com/sagernet/sing-box/adapter"
2022-09-09 10:45:10 +00:00
"github.com/sagernet/sing-box/common/tls"
2022-08-22 14:19:25 +00:00
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
2023-04-12 07:14:55 +00:00
sHTTP "github.com/sagernet/sing/protocol/http"
"golang.org/x/net/http2"
2022-08-22 14:19:25 +00:00
)
var _ adapter.V2RayClientTransport = (*Client)(nil)
type Client struct {
2022-08-22 14:51:08 +00:00
ctx context.Context
dialer N.Dialer
serverAddr M.Socksaddr
transport http.RoundTripper
2022-08-22 14:51:08 +00:00
http2 bool
requestURL url.URL
2022-08-22 14:51:08 +00:00
host []string
method string
headers http.Header
2022-08-22 14:19:25 +00:00
}
2023-04-12 07:14:55 +00:00
func NewClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayHTTPOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
var transport http.RoundTripper
if tlsConfig == nil {
transport = &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
},
}
} else {
2023-04-04 13:09:08 +00:00
if len(tlsConfig.NextProtos()) == 0 {
tlsConfig.SetNextProtos([]string{http2.NextProtoTLS})
}
transport = &http2.Transport{
ReadIdleTimeout: time.Duration(options.IdleTimeout),
PingTimeout: time.Duration(options.PingTimeout),
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.STDConfig) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
if err != nil {
return nil, err
}
return tls.ClientHandshake(ctx, conn, tlsConfig)
},
}
}
if options.Method == "" {
options.Method = http.MethodPut
}
var requestURL url.URL
if tlsConfig == nil {
requestURL.Scheme = "http"
} else {
requestURL.Scheme = "https"
}
requestURL.Host = serverAddr.String()
requestURL.Path = options.Path
err := sHTTP.URLSetPath(&requestURL, options.Path)
if err != nil {
return nil, E.Cause(err, "parse path")
}
if !strings.HasPrefix(requestURL.Path, "/") {
requestURL.Path = "/" + requestURL.Path
}
return &Client{
2022-08-22 14:51:08 +00:00
ctx: ctx,
dialer: dialer,
serverAddr: serverAddr,
requestURL: requestURL,
2022-08-22 14:51:08 +00:00
host: options.Host,
method: options.Method,
2023-10-21 04:00:00 +00:00
headers: options.Headers.Build(),
transport: transport,
2022-09-09 10:45:10 +00:00
http2: tlsConfig != nil,
}, nil
2022-08-22 14:19:25 +00:00
}
func (c *Client) DialContext(ctx context.Context) (net.Conn, error) {
2022-08-22 14:51:08 +00:00
if !c.http2 {
2022-08-26 00:41:45 +00:00
return c.dialHTTP(ctx)
2022-08-22 14:51:08 +00:00
} else {
2022-08-26 00:41:45 +00:00
return c.dialHTTP2(ctx)
2022-08-22 14:51:08 +00:00
}
}
2022-08-26 00:41:45 +00:00
func (c *Client) dialHTTP(ctx context.Context) (net.Conn, error) {
conn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr)
2022-08-22 14:51:08 +00:00
if err != nil {
return nil, err
}
2022-08-22 14:51:08 +00:00
request := &http.Request{
Method: c.method,
URL: &c.requestURL,
Header: c.headers.Clone(),
2022-08-22 14:51:08 +00:00
}
switch hostLen := len(c.host); hostLen {
case 0:
request.Host = c.serverAddr.AddrString()
2022-08-22 14:51:08 +00:00
case 1:
request.Host = c.host[0]
default:
request.Host = c.host[rand.Intn(hostLen)]
}
return NewHTTP1Conn(conn, request), nil
2022-08-22 14:51:08 +00:00
}
2022-08-26 00:41:45 +00:00
func (c *Client) dialHTTP2(ctx context.Context) (net.Conn, error) {
2022-08-22 14:19:25 +00:00
pipeInReader, pipeInWriter := io.Pipe()
request := &http.Request{
Method: c.method,
Body: pipeInReader,
URL: &c.requestURL,
Header: c.headers.Clone(),
2022-08-22 14:19:25 +00:00
}
2022-08-26 00:41:45 +00:00
request = request.WithContext(ctx)
2022-08-22 14:19:25 +00:00
switch hostLen := len(c.host); hostLen {
case 0:
// https://github.com/v2fly/v2ray-core/blob/master/transport/internet/http/config.go#L13
request.Host = "www.example.com"
2022-08-22 14:19:25 +00:00
case 1:
request.Host = c.host[0]
default:
request.Host = c.host[rand.Intn(hostLen)]
}
2023-04-21 09:49:43 +00:00
conn := NewLateHTTPConn(pipeInWriter)
go func() {
response, err := c.transport.RoundTrip(request)
if err != nil {
2023-04-21 09:49:43 +00:00
conn.Setup(nil, err)
} else if response.StatusCode != 200 {
response.Body.Close()
conn.Setup(nil, E.New("unexpected status: ", response.Status))
} else {
2023-04-21 09:49:43 +00:00
conn.Setup(response.Body, nil)
}
}()
return conn, nil
2022-08-22 14:19:25 +00:00
}
2023-03-30 11:06:57 +00:00
func (c *Client) Close() error {
CloseIdleConnections(c.transport)
return nil
}