- computeFingerprint 使用 rune 索引替代字节索引,修复多字节字符指纹不匹配 - utls Chrome TLS 指纹仅对 Anthropic 官方域名生效,自定义 base_url 走标准 transport - IPv6 地址使用 net.JoinHostPort 正确拼接端口
Unified · Split
| @@ -1200,15 +1200,16 @@ const fingerprintSalt = "59cf53e54c78" · 1199 unmodified lines | ||
| 1200 | 1200 | // Algorithm: SHA256(salt + messageText[4] + messageText[7] + messageText[20] + version)[:3] |
| 1201 | 1201 | func computeFingerprint(messageText, version string) string { |
| 1202 | 1202 | indices := [3]int{4, 7, 20} |
| 1203 | − var chars [3]byte | |
| 1204 | − for i, idx := range indices { | |
| 1205 | − if idx < len(messageText) { | |
| 1206 | − chars[i] = messageText[idx] | |
| 1203 | + runes := []rune(messageText) | |
| 1204 | + var sb strings.Builder | |
| 1205 | + for _, idx := range indices { | |
| 1206 | + if idx < len(runes) { | |
| 1207 | + sb.WriteRune(runes[idx]) | |
| 1207 | 1208 | } else { |
| 1208 | − chars[i] = '0' | |
| 1209 | + sb.WriteRune('0') | |
| 1209 | 1210 | } |
| 1210 | 1211 | } |
| 1211 | − input := fingerprintSalt + string(chars[:]) + version | |
| 1212 | + input := fingerprintSalt + sb.String() + version | |
| 1212 | 1213 | h := sha256.Sum256([]byte(input)) |
| 1213 | 1214 | return hex.EncodeToString(h[:])[:3] |
| 1214 | 1215 | } |
| @@ -103,13 +103,12 @@ func (t *utlsRoundTripper) createConnection(host, addr string) (*http2.ClientCon · 102 unmodified lines | ||
| 103 | 103 | } |
| 104 | 104 | |
| 105 | 105 | func (t *utlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { |
| 106 | − host := req.URL.Host | |
| 107 | − addr := host | |
| 108 | − if !strings.Contains(addr, ":") { | |
| 109 | − addr += ":443" | |
| 110 | − } | |
| 111 | − | |
| 112 | 106 | hostname := req.URL.Hostname() |
| 107 | + port := req.URL.Port() | |
| 108 | + if port == "" { | |
| 109 | + port = "443" | |
| 110 | + } | |
| 111 | + addr := net.JoinHostPort(hostname, port) | |
| 113 | 112 | |
| 114 | 113 | h2Conn, err := t.getOrCreateConnection(hostname, addr) |
| 115 | 114 | if err != nil { |
| @@ -129,8 +128,13 @@ func (t *utlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) · 13 unmodified lines | ||
| 129 | 128 | return resp, nil |
| 130 | 129 | } |
| 131 | 130 | |
| 132 | −// fallbackRoundTripper tries utls first; if the target is plain HTTP or a | |
| 133 | −// non-HTTPS scheme it falls back to a standard transport. | |
| 131 | +// anthropicHosts contains the hosts that should use utls Chrome TLS fingerprint. | |
| 132 | +var anthropicHosts = map[string]struct{}{ | |
| 133 | + "api.anthropic.com": {}, | |
| 134 | +} | |
| 135 | + | |
| 136 | +// fallbackRoundTripper uses utls for Anthropic HTTPS hosts and falls back to | |
| 137 | +// standard transport for all other requests (non-HTTPS or non-Anthropic hosts). | |
| 134 | 138 | type fallbackRoundTripper struct { |
| 135 | 139 | utls *utlsRoundTripper |
| 136 | 140 | fallback http.RoundTripper |
| @@ -138,7 +142,9 @@ type fallbackRoundTripper struct { · 1 unmodified line | ||
| 138 | 142 | |
| 139 | 143 | func (f *fallbackRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { |
| 140 | 144 | if req.URL.Scheme == "https" { |
| 141 | − return f.utls.RoundTrip(req) | |
| 145 | + if _, ok := anthropicHosts[strings.ToLower(req.URL.Hostname())]; ok { | |
| 146 | + return f.utls.RoundTrip(req) | |
| 147 | + } | |
| 142 | 148 | } |
| 143 | 149 | return f.fallback.RoundTrip(req) |
| 144 | 150 | } |