mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-07 01:45:27 +08:00
Add admin CRUD and connectivity checks for SOCKS5/SOCKS5H proxy nodes. Allow accounts to bind to a proxy, route DeepSeek requests through the selected node, and expose proxy management in the admin UI.
114 lines
3.1 KiB
Go
114 lines
3.1 KiB
Go
package transport
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"time"
|
|
|
|
utls "github.com/refraction-networking/utls"
|
|
)
|
|
|
|
type Doer interface {
|
|
Do(req *http.Request) (*http.Response, error)
|
|
}
|
|
|
|
type DialContextFunc func(ctx context.Context, network, addr string) (net.Conn, error)
|
|
|
|
type Client struct {
|
|
http *http.Client
|
|
}
|
|
|
|
func New(timeout time.Duration) *Client {
|
|
return NewWithDialContext(timeout, nil)
|
|
}
|
|
|
|
func NewWithDialContext(timeout time.Duration, dialContext DialContextFunc) *Client {
|
|
useEnvProxy := dialContext == nil
|
|
if dialContext == nil {
|
|
dialContext = (&net.Dialer{Timeout: 15 * time.Second, KeepAlive: 30 * time.Second}).DialContext
|
|
}
|
|
base := &http.Transport{
|
|
ForceAttemptHTTP2: false,
|
|
MaxIdleConns: 200,
|
|
MaxIdleConnsPerHost: 100,
|
|
IdleConnTimeout: 90 * time.Second,
|
|
DialContext: dialContext,
|
|
DialTLSContext: safariTLSDialer(dialContext),
|
|
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
|
|
}
|
|
if useEnvProxy {
|
|
base.Proxy = http.ProxyFromEnvironment
|
|
}
|
|
return &Client{http: &http.Client{Timeout: timeout, Transport: base}}
|
|
}
|
|
|
|
func (c *Client) Do(req *http.Request) (*http.Response, error) {
|
|
return c.http.Do(req)
|
|
}
|
|
|
|
func NewFallbackClient(timeout time.Duration, dialContext DialContextFunc) *http.Client {
|
|
useEnvProxy := dialContext == nil
|
|
if dialContext == nil {
|
|
dialContext = (&net.Dialer{Timeout: 15 * time.Second, KeepAlive: 30 * time.Second}).DialContext
|
|
}
|
|
base := &http.Transport{
|
|
ForceAttemptHTTP2: false,
|
|
MaxIdleConns: 200,
|
|
MaxIdleConnsPerHost: 100,
|
|
IdleConnTimeout: 90 * time.Second,
|
|
DialContext: dialContext,
|
|
TLSClientConfig: &tls.Config{MinVersion: tls.VersionTLS12},
|
|
}
|
|
if useEnvProxy {
|
|
base.Proxy = http.ProxyFromEnvironment
|
|
}
|
|
return &http.Client{Timeout: timeout, Transport: base}
|
|
}
|
|
|
|
func safariTLSDialer(dialContext DialContextFunc) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
if dialContext == nil {
|
|
dialContext = (&net.Dialer{Timeout: 15 * time.Second, KeepAlive: 30 * time.Second}).DialContext
|
|
}
|
|
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
plainConn, err := dialContext(ctx, network, addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
host, _, _ := net.SplitHostPort(addr)
|
|
uCfg := &utls.Config{ServerName: host}
|
|
uConn := utls.UClient(plainConn, uCfg, utls.HelloSafari_Auto)
|
|
if err := forceHTTP11ALPN(uConn); err != nil {
|
|
_ = plainConn.Close()
|
|
return nil, err
|
|
}
|
|
err = uConn.HandshakeContext(ctx)
|
|
if err != nil {
|
|
_ = plainConn.Close()
|
|
return nil, err
|
|
}
|
|
if negotiated := uConn.ConnectionState().NegotiatedProtocol; negotiated != "" && negotiated != "http/1.1" {
|
|
_ = uConn.Close()
|
|
return nil, fmt.Errorf("unexpected ALPN protocol negotiated: %s", negotiated)
|
|
}
|
|
return uConn, nil
|
|
}
|
|
}
|
|
|
|
func forceHTTP11ALPN(uConn *utls.UConn) error {
|
|
if err := uConn.BuildHandshakeState(); err != nil {
|
|
return err
|
|
}
|
|
for _, ext := range uConn.Extensions {
|
|
alpnExt, ok := ext.(*utls.ALPNExtension)
|
|
if !ok {
|
|
continue
|
|
}
|
|
alpnExt.AlpnProtocols = []string{"http/1.1"}
|
|
return nil
|
|
}
|
|
return nil
|
|
}
|