Files
proxy_server/utils/httphelper/http_helper.go

235 lines
7.6 KiB
Go
Raw Normal View History

2025-07-12 15:25:26 +08:00
package httphelper
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// HTTPClient 定义一个通用的 HTTP 客户端结构
type HTTPClient struct {
Client *http.Client // 底层的 http.Client 实例
BaseURL string // 基础 URL所有请求将基于此 URL
Headers map[string]string // 默认请求头
}
// NewHTTPClient 创建一个新的 HTTPClient 实例
// timeout: 请求超时时间,例如 10 * time.Second
// baseURL: 基础 URL例如 "https://api.example.com"
// defaultHeaders: 默认请求头,例如 {"Content-Type": "application/json"}
func NewHTTPClient(timeout time.Duration, baseURL string, defaultHeaders map[string]string) *HTTPClient {
return &HTTPClient{
Client: &http.Client{
Timeout: timeout,
},
BaseURL: baseURL,
Headers: defaultHeaders,
}
}
// applyHeaders 为请求应用默认和自定义请求头
func (c *HTTPClient) applyHeaders(req *http.Request, customHeaders map[string]string) {
// 应用默认请求头
for key, value := range c.Headers {
req.Header.Set(key, value)
}
// 应用自定义请求头(覆盖默认请求头)
for key, value := range customHeaders {
req.Header.Set(key, value)
}
}
// doRequest 执行实际的 HTTP 请求
// method: HTTP 方法 (GET, POST, PUT, DELETE等)
// path: 请求路径,将与 BaseURL 拼接
// requestBody: 请求体数据,如果为 GET/DELETE 请求则为 nil
// customHeaders: 自定义请求头,将覆盖默认请求头
// responseData: 用于存储响应数据的目标结构体(指针类型)
func (c *HTTPClient) doRequest(
method, path string,
requestBody interface{},
customHeaders map[string]string,
responseData interface{},
) error {
// 拼接完整的 URL
url := c.BaseURL + path
var reqBodyReader io.Reader
if requestBody != nil {
// 将请求体编码为 JSON
jsonBody, err := json.Marshal(requestBody)
if err != nil {
return fmt.Errorf("json marshal request body failed: %w", err)
}
reqBodyReader = bytes.NewBuffer(jsonBody)
}
// 创建新的 HTTP 请求
req, err := http.NewRequest(method, url, reqBodyReader)
if err != nil {
return fmt.Errorf("create http request failed: %w", err)
}
// 应用请求头
c.applyHeaders(req, customHeaders)
// 默认添加 gzip 接收头
req.Header.Set("Accept-Encoding", "gzip")
// 发送请求
resp, err := c.Client.Do(req)
if err != nil {
return fmt.Errorf("send http request failed: %w", err)
}
defer resp.Body.Close()
// 检查 HTTP 状态码
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest {
bodyBytes, _ := io.ReadAll(resp.Body) // 读取错误响应体
return fmt.Errorf("http request failed with status: %d, body: %s", resp.StatusCode, string(bodyBytes))
}
// 解码 JSON 响应(支持 gzip
var reader io.Reader = resp.Body
if resp.Header.Get("Content-Encoding") == "gzip" {
gzipReader, err := gzip.NewReader(resp.Body)
if err != nil {
return fmt.Errorf("create gzip reader failed: %w", err)
}
defer gzipReader.Close()
reader = gzipReader
}
if responseData != nil {
err = json.NewDecoder(reader).Decode(responseData)
if err != nil {
return fmt.Errorf("json decode response body failed: %w", err)
}
}
return nil
}
// Get 发送 GET 请求
// path: 请求路径
// customHeaders: 自定义请求头
// responseData: 用于存储响应数据的目标结构体(指针类型)
func (c *HTTPClient) Get(path string, customHeaders map[string]string, responseData interface{}) error {
return c.doRequest(http.MethodGet, path, nil, customHeaders, responseData)
}
// Post 发送 POST 请求
// path: 请求路径
// requestBody: 请求体数据
// customHeaders: 自定义请求头
// responseData: 用于存储响应数据的目标结构体(指针类型)
func (c *HTTPClient) Post(path string, requestBody interface{}, customHeaders map[string]string, responseData interface{}) error {
return c.doRequest(http.MethodPost, path, requestBody, customHeaders, responseData)
}
// PostWithContentType 发送 POST 请求,支持自定义 Content-Type如 application/json 或 multipart/form-data
// contentType: 请求体类型(如 "application/json", "multipart/form-data"
// requestBody: 请求体可以是结构体、map、或 multipart/form 格式)
// 注意:如果是 multipart/form-data请确保 requestBody 是 io.Reader 和 contentType 是完整的包含 boundary 的值。
func (c *HTTPClient) PostWithContentType(path string, requestBody interface{}, contentType string, customHeaders map[string]string, responseData interface{}) error {
// 拼接 URL
url := c.BaseURL + path
var reqBodyReader io.Reader
switch body := requestBody.(type) {
case io.Reader:
reqBodyReader = body
default:
// 默认处理为 JSON
jsonBody, err := json.Marshal(body)
if err != nil {
return fmt.Errorf("json marshal request body failed: %w", err)
}
reqBodyReader = bytes.NewBuffer(jsonBody)
if contentType == "" {
contentType = "application/json"
}
}
// 创建请求
req, err := http.NewRequest(http.MethodPost, url, reqBodyReader)
if err != nil {
return fmt.Errorf("create http request failed: %w", err)
}
// 设置 Content-Type
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
// 应用其他头部
c.applyHeaders(req, customHeaders)
// gzip 支持
req.Header.Set("Accept-Encoding", "gzip")
// 发出请求
resp, err := c.Client.Do(req)
if err != nil {
return fmt.Errorf("send http request failed: %w", err)
}
defer resp.Body.Close()
// 检查状态码
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf("http request failed with status: %d, body: %s", resp.StatusCode, string(bodyBytes))
}
// 读取响应
var reader io.Reader = resp.Body
if resp.Header.Get("Content-Encoding") == "gzip" {
gzipReader, err := gzip.NewReader(resp.Body)
if err != nil {
return fmt.Errorf("create gzip reader failed: %w", err)
}
defer gzipReader.Close()
reader = gzipReader
}
if responseData != nil {
err = json.NewDecoder(reader).Decode(responseData)
if err != nil {
return fmt.Errorf("json decode response body failed: %w", err)
}
}
return nil
}
// Put 发送 PUT 请求
// path: 请求路径
// requestBody: 请求体数据
// customHeaders: 自定义请求头
// responseData: 用于存储响应数据的目标结构体(指针类型)
func (c *HTTPClient) Put(path string, requestBody interface{}, customHeaders map[string]string, responseData interface{}) error {
return c.doRequest(http.MethodPut, path, requestBody, customHeaders, responseData)
}
// Delete 发送 DELETE 请求
// path: 请求路径
// customHeaders: 自定义请求头
// responseData: 用于存储响应数据的目标结构体(指针类型)
func (c *HTTPClient) Delete(path string, customHeaders map[string]string, responseData interface{}) error {
// DELETE 请求通常没有请求体,但某些 RESTful API 可能支持
return c.doRequest(http.MethodDelete, path, nil, customHeaders, responseData)
}
// Patch 发送 PATCH 请求
// path: 请求路径
// requestBody: 请求体数据
// customHeaders: 自定义请求头
// responseData: 用于存储响应数据的目标结构体(指针类型)
func (c *HTTPClient) Patch(path string, requestBody interface{}, customHeaders map[string]string, responseData interface{}) error {
return c.doRequest(http.MethodPatch, path, requestBody, customHeaders, responseData)
}