package common import ( "bytes" "compress/zlib" "crypto/tls" "encoding/json" "fmt" "io" "net/http" "os" "strings" "time" ) // HTTPClient HTTP客户端 type HTTPClient struct { client *http.Client logger *Logger } // NewHTTPClient 创建新的HTTP客户端 func NewHTTPClient() *HTTPClient { return &HTTPClient{ client: &http.Client{ Timeout: 30 * time.Second, }, logger: NewLogger("http_utils", "INFO", ""), } } // ClientResponse 客户端响应 type ClientResponse struct { Status int Content []byte Headers http.Header Cookies []*http.Cookie Text string } // GetHeader 获取响应头(兼容方法) func (r *ClientResponse) GetHeader(key string) string { return r.Headers.Get(key) } // JSON 解析JSON响应 func (r *ClientResponse) JSON() (map[string]interface{}, error) { var result map[string]interface{} err := json.Unmarshal(r.Content, &result) return result, err } // Request HTTP请求 func (h *HTTPClient) Request(url string, options map[string]interface{}) (*ClientResponse, error) { // 获取请求方法 method := "GET" if m, ok := options["method"].(string); ok { method = strings.ToUpper(m) } // 获取请求头 headers := make(map[string]string) if h, ok := options["headers"].(map[string]string); ok { headers = h } // 设置默认User-Agent if _, ok := headers["User-Agent"]; !ok { headers["User-Agent"] = "okhttp/4.12.0" } // 获取请求体 var body io.Reader if method == "POST" || method == "PUT" { if b, ok := options["body"].(string); ok { body = strings.NewReader(b) } else if b, ok := options["body"].([]byte); ok { body = bytes.NewReader(b) } else if data, ok := options["data"].(string); ok { body = strings.NewReader(data) } else if data, ok := options["data"].([]byte); ok { body = bytes.NewReader(data) } } // 创建请求 req, err := http.NewRequest(method, url, body) if err != nil { h.logger.Error(fmt.Sprintf("Failed to create request: %v", err)) return nil, err } // 设置请求头 for k, v := range headers { req.Header.Set(k, v) } // 打印请求信息 // h.logger.Info(fmt.Sprintf("=== HTTP 请求 ===")) // h.logger.Info(fmt.Sprintf("方法: %s", method)) // h.logger.Info(fmt.Sprintf("URL: %s", url)) // h.logger.Info(fmt.Sprintf("请求头:")) // for k, v := range req.Header { // h.logger.Info(fmt.Sprintf(" %s: %s", k, strings.Join(v, ", "))) // } // 检查是否跳过SSL验证 skipSSL := os.Getenv("FQWEB_SKIP_SSL_VERIFY") if skipSSL != "" && h.client.Transport == nil { // 配置跳过SSL验证 transport := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } h.client.Transport = transport h.logger.Warning("已启用SSL验证跳过模式") } // 发送请求 resp, err := h.client.Do(req) if err != nil { h.logger.Error(fmt.Sprintf("HTTP Request error: %v", err)) return nil, err } defer resp.Body.Close() // 读取响应体 content, err := io.ReadAll(resp.Body) if err != nil { h.logger.Error(fmt.Sprintf("Failed to read response: %v", err)) return nil, err } // 处理zlib压缩 if len(content) > 2 && (content[0] == 0x78 && (content[1] == 0x9C || content[1] == 0x01)) { reader, err := zlib.NewReader(bytes.NewReader(content)) if err == nil { decompressed, err := io.ReadAll(reader) reader.Close() if err == nil { content = decompressed } } } response := &ClientResponse{ Status: resp.StatusCode, Content: content, Headers: resp.Header, Cookies: resp.Cookies(), Text: string(content), } return response, nil } // Get GET请求 func (h *HTTPClient) Get(url string, headers map[string]string) (*ClientResponse, error) { options := map[string]interface{}{ "method": "GET", "headers": headers, } return h.Request(url, options) } // Post POST请求 func (h *HTTPClient) Post(url string, data interface{}, headers map[string]string) (*ClientResponse, error) { options := map[string]interface{}{ "method": "POST", "headers": headers, "body": data, } return h.Request(url, options) } // ConvertDictToFormString 将字典转换为表单字符串 func ConvertDictToFormString(dict map[string]string) string { parts := make([]string, 0, len(dict)) for k, v := range dict { parts = append(parts, fmt.Sprintf("%s=%s", k, v)) } return strings.Join(parts, "&") } // DefaultHTTPClient 默认HTTP客户端 var DefaultHTTPClient = NewHTTPClient() // Request 全局请求函数 func Request(url string, options map[string]interface{}) (*ClientResponse, error) { return DefaultHTTPClient.Request(url, options) }