mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-15 00:27:29 +08:00
123 lines
2.8 KiB
Go
123 lines
2.8 KiB
Go
package providers
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
tls "github.com/refraction-networking/utls"
|
|
"golang.org/x/net/http2"
|
|
)
|
|
|
|
type anthropicOAuthRoundTripper struct {
|
|
mu sync.Mutex
|
|
connections map[string]*http2.ClientConn
|
|
pending map[string]*sync.Cond
|
|
dialer net.Dialer
|
|
dialContext func(context.Context, string, string) (net.Conn, error)
|
|
}
|
|
|
|
func newAnthropicOAuthHTTPClient(timeout time.Duration, proxyURL string) (*http.Client, error) {
|
|
rt, err := newAnthropicOAuthRoundTripper(proxyURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &http.Client{
|
|
Timeout: timeout,
|
|
Transport: rt,
|
|
}, nil
|
|
}
|
|
|
|
func newAnthropicOAuthRoundTripper(proxyURL string) (*anthropicOAuthRoundTripper, error) {
|
|
dialContext, err := proxyDialContext(proxyURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &anthropicOAuthRoundTripper{
|
|
connections: map[string]*http2.ClientConn{},
|
|
pending: map[string]*sync.Cond{},
|
|
dialer: net.Dialer{
|
|
Timeout: 15 * time.Second,
|
|
KeepAlive: 30 * time.Second,
|
|
},
|
|
dialContext: dialContext,
|
|
}, nil
|
|
}
|
|
|
|
func (t *anthropicOAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
host := req.URL.Hostname()
|
|
addr := req.URL.Host
|
|
if !strings.Contains(addr, ":") {
|
|
addr += ":443"
|
|
}
|
|
conn, err := t.getOrCreateConnection(host, addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := conn.RoundTrip(req)
|
|
if err != nil {
|
|
t.mu.Lock()
|
|
if cached, ok := t.connections[host]; ok && cached == conn {
|
|
delete(t.connections, host)
|
|
}
|
|
t.mu.Unlock()
|
|
return nil, err
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func (t *anthropicOAuthRoundTripper) getOrCreateConnection(host, addr string) (*http2.ClientConn, error) {
|
|
t.mu.Lock()
|
|
if conn, ok := t.connections[host]; ok && conn.CanTakeNewRequest() {
|
|
t.mu.Unlock()
|
|
return conn, nil
|
|
}
|
|
if wait, ok := t.pending[host]; ok {
|
|
wait.Wait()
|
|
if conn, ok := t.connections[host]; ok && conn.CanTakeNewRequest() {
|
|
t.mu.Unlock()
|
|
return conn, nil
|
|
}
|
|
}
|
|
wait := sync.NewCond(&t.mu)
|
|
t.pending[host] = wait
|
|
t.mu.Unlock()
|
|
|
|
conn, err := t.createConnection(host, addr)
|
|
|
|
t.mu.Lock()
|
|
defer t.mu.Unlock()
|
|
delete(t.pending, host)
|
|
wait.Broadcast()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
t.connections[host] = conn
|
|
return conn, nil
|
|
}
|
|
|
|
func (t *anthropicOAuthRoundTripper) createConnection(host, addr string) (*http2.ClientConn, error) {
|
|
dialContext := t.dialContext
|
|
if dialContext == nil {
|
|
dialContext = t.dialer.DialContext
|
|
}
|
|
rawConn, err := dialContext(context.Background(), "tcp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tlsConn := tls.UClient(rawConn, &tls.Config{ServerName: host}, tls.HelloChrome_Auto)
|
|
if err := tlsConn.Handshake(); err != nil {
|
|
_ = rawConn.Close()
|
|
return nil, err
|
|
}
|
|
h2Conn, err := (&http2.Transport{}).NewClientConn(tlsConn)
|
|
if err != nil {
|
|
_ = tlsConn.Close()
|
|
return nil, err
|
|
}
|
|
return h2Conn, nil
|
|
}
|