feat: Implement initial full-stack application structure including frontend pages, components, hooks, API integration, and backend services for account pooling and management.
This commit is contained in:
240
backend/internal/client/tls.go
Normal file
240
backend/internal/client/tls.go
Normal file
@@ -0,0 +1,240 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/andybalholm/brotli"
|
||||
http2 "github.com/bogdanfinn/fhttp"
|
||||
tls_client "github.com/bogdanfinn/tls-client"
|
||||
"github.com/bogdanfinn/tls-client/profiles"
|
||||
)
|
||||
|
||||
// TLSClient 使用 tls-client 模拟浏览器指纹的 HTTP 客户端
|
||||
type TLSClient struct {
|
||||
client tls_client.HttpClient
|
||||
userAgent string
|
||||
chromeVer string
|
||||
acceptLang string
|
||||
}
|
||||
|
||||
// 语言偏好池
|
||||
var languagePrefs = []string{
|
||||
"en-US,en;q=0.9",
|
||||
"en-GB,en;q=0.9,en-US;q=0.8",
|
||||
"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||
}
|
||||
|
||||
// New 创建一个新的 TLS 客户端
|
||||
func New(proxyStr string) (*TLSClient, error) {
|
||||
jar := tls_client.NewCookieJar()
|
||||
chromeVer := "133"
|
||||
|
||||
options := []tls_client.HttpClientOption{
|
||||
tls_client.WithTimeoutSeconds(60),
|
||||
tls_client.WithClientProfile(profiles.Chrome_133),
|
||||
tls_client.WithRandomTLSExtensionOrder(),
|
||||
tls_client.WithCookieJar(jar),
|
||||
tls_client.WithInsecureSkipVerify(),
|
||||
}
|
||||
|
||||
if proxyStr != "" {
|
||||
options = append(options, tls_client.WithProxyUrl(proxyStr))
|
||||
}
|
||||
|
||||
client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acceptLang := languagePrefs[rand.Intn(len(languagePrefs))]
|
||||
userAgent := generateUserAgent(chromeVer)
|
||||
|
||||
return &TLSClient{
|
||||
client: client,
|
||||
userAgent: userAgent,
|
||||
chromeVer: chromeVer,
|
||||
acceptLang: acceptLang,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generateUserAgent 生成随机化的 User-Agent
|
||||
func generateUserAgent(chromeVer string) string {
|
||||
winVersions := []string{
|
||||
"Windows NT 10.0; Win64; x64",
|
||||
"Windows NT 10.0; WOW64",
|
||||
}
|
||||
winVer := winVersions[rand.Intn(len(winVersions))]
|
||||
|
||||
return "Mozilla/5.0 (" + winVer + ") AppleWebKit/537.36 (KHTML, like Gecko) Chrome/" + chromeVer + ".0.0.0 Safari/537.36"
|
||||
}
|
||||
|
||||
// getDefaultHeaders 获取默认请求头
|
||||
func (c *TLSClient) getDefaultHeaders() map[string]string {
|
||||
secChUa := `"Chromium";v="` + c.chromeVer + `", "Not(A:Brand";v="99", "Google Chrome";v="` + c.chromeVer + `"`
|
||||
|
||||
return map[string]string{
|
||||
"User-Agent": c.userAgent,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
"Accept-Language": c.acceptLang,
|
||||
"Accept-Encoding": "gzip, deflate, br, zstd",
|
||||
"Cache-Control": "max-age=0",
|
||||
"Sec-Ch-Ua": secChUa,
|
||||
"Sec-Ch-Ua-Mobile": "?0",
|
||||
"Sec-Ch-Ua-Platform": `"Windows"`,
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-Site": "none",
|
||||
"Sec-Fetch-User": "?1",
|
||||
"Upgrade-Insecure-Requests": "1",
|
||||
}
|
||||
}
|
||||
|
||||
// Do 执行 HTTP 请求
|
||||
func (c *TLSClient) Do(req *http.Request) (*http.Response, error) {
|
||||
fhttpReq, err := http2.NewRequest(req.Method, req.URL.String(), req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, value := range c.getDefaultHeaders() {
|
||||
if req.Header.Get(key) == "" {
|
||||
fhttpReq.Header.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
for key, values := range req.Header {
|
||||
for _, value := range values {
|
||||
fhttpReq.Header.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(fhttpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
finalReq := req
|
||||
if resp.Request != nil && resp.Request.URL != nil {
|
||||
finalReq = &http.Request{
|
||||
Method: resp.Request.Method,
|
||||
URL: (*url.URL)(resp.Request.URL),
|
||||
Header: http.Header(resp.Request.Header),
|
||||
}
|
||||
}
|
||||
|
||||
stdResp := &http.Response{
|
||||
Status: resp.Status,
|
||||
StatusCode: resp.StatusCode,
|
||||
Proto: resp.Proto,
|
||||
ProtoMajor: resp.ProtoMajor,
|
||||
ProtoMinor: resp.ProtoMinor,
|
||||
Header: http.Header(resp.Header),
|
||||
Body: resp.Body,
|
||||
ContentLength: resp.ContentLength,
|
||||
TransferEncoding: resp.TransferEncoding,
|
||||
Close: resp.Close,
|
||||
Uncompressed: resp.Uncompressed,
|
||||
Request: finalReq,
|
||||
}
|
||||
|
||||
return stdResp, nil
|
||||
}
|
||||
|
||||
// Get 执行 GET 请求
|
||||
func (c *TLSClient) Get(urlStr string) (*http.Response, error) {
|
||||
req, err := http.NewRequest("GET", urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// Post 执行 POST 请求
|
||||
func (c *TLSClient) Post(urlStr string, contentType string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequest("POST", urlStr, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// PostForm 执行 POST 表单请求
|
||||
func (c *TLSClient) PostForm(urlStr string, data url.Values) (*http.Response, error) {
|
||||
return c.Post(urlStr, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||
}
|
||||
|
||||
// PostJSON 执行 POST JSON 请求
|
||||
func (c *TLSClient) PostJSON(urlStr string, body io.Reader) (*http.Response, error) {
|
||||
return c.Post(urlStr, "application/json", body)
|
||||
}
|
||||
|
||||
// GetCookie 获取指定 URL 的 Cookie
|
||||
func (c *TLSClient) GetCookie(urlStr string, name string) string {
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
cookies := c.client.GetCookies(u)
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == name {
|
||||
return cookie.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetCookie 设置 Cookie
|
||||
func (c *TLSClient) SetCookie(urlStr string, cookie *http.Cookie) {
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.client.SetCookies(u, []*http2.Cookie{
|
||||
{
|
||||
Name: cookie.Name,
|
||||
Value: cookie.Value,
|
||||
Path: cookie.Path,
|
||||
Domain: cookie.Domain,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ReadBody 读取响应体并自动处理压缩
|
||||
func ReadBody(resp *http.Response) ([]byte, error) {
|
||||
defer resp.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch resp.Header.Get("Content-Encoding") {
|
||||
case "gzip":
|
||||
gzReader, err := gzip.NewReader(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return data, nil
|
||||
}
|
||||
defer gzReader.Close()
|
||||
return io.ReadAll(gzReader)
|
||||
case "br":
|
||||
return io.ReadAll(brotli.NewReader(bytes.NewReader(data)))
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ReadBodyString 读取响应体为字符串
|
||||
func ReadBodyString(resp *http.Response) (string, error) {
|
||||
body, err := ReadBody(resp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
Reference in New Issue
Block a user