Files
verify/backend/internal/utils/httputil/client.go
T
admin e019f6a55c fix: 移除云端函数HTTP客户端硬编码代理
- 将硬编码的代理地址改为从环境变量读取
- 使用 http.ProxyFromEnvironment 让代理配置更灵活
- 服务器环境不设置代理环境变量即可直连
2026-05-07 12:39:33 +08:00

205 lines
4.3 KiB
Go

package httputil
import (
"bytes"
"encoding/json"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"time"
)
const DefaultTimeout = 10 * time.Second
type HTTPResponse struct {
StatusCode int `json:"statusCode"`
Status string `json:"status"`
Headers map[string]string `json:"headers"`
Body string `json:"body"`
JSON map[string]interface{} `json:"json,omitempty"`
Error string `json:"error,omitempty"`
}
type HTTPClient struct {
client *http.Client
timeout time.Duration
}
func NewHTTPClient(timeout time.Duration) *HTTPClient {
if timeout <= 0 {
timeout = DefaultTimeout
}
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
}
return &HTTPClient{
client: &http.Client{
Timeout: timeout,
Transport: transport,
},
timeout: timeout,
}
}
func (c *HTTPClient) Get(urlStr string, headers map[string]string) *HTTPResponse {
return c.doRequest("GET", urlStr, headers, nil)
}
func (c *HTTPClient) Post(urlStr string, headers map[string]string, body interface{}) *HTTPResponse {
return c.doRequest("POST", urlStr, headers, body)
}
func (c *HTTPClient) doRequest(method, urlStr string, headers map[string]string, body interface{}) *HTTPResponse {
resp := &HTTPResponse{
Headers: make(map[string]string),
}
if err := validateURL(urlStr); err != nil {
resp.Error = err.Error()
return resp
}
var reqBody []byte
var err error
contentType := "application/json"
if body != nil {
switch v := body.(type) {
case string:
reqBody = []byte(v)
contentType = "text/plain"
case map[string]interface{}:
reqBody, err = json.Marshal(v)
if err != nil {
resp.Error = fmt.Sprintf("failed to marshal body: %v", err)
return resp
}
default:
reqBody, err = json.Marshal(v)
if err != nil {
resp.Error = fmt.Sprintf("failed to marshal body: %v", err)
return resp
}
}
}
var req *http.Request
if reqBody != nil {
req, err = http.NewRequest(method, urlStr, bytes.NewReader(reqBody))
} else {
req, err = http.NewRequest(method, urlStr, nil)
}
if err != nil {
resp.Error = fmt.Sprintf("failed to create request: %v", err)
return resp
}
if headers != nil {
for k, v := range headers {
req.Header.Set(k, v)
}
}
if body != nil && req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", contentType)
}
httpResp, err := c.client.Do(req)
if err != nil {
resp.Error = fmt.Sprintf("request failed: %v", err)
return resp
}
defer httpResp.Body.Close()
resp.StatusCode = httpResp.StatusCode
resp.Status = httpResp.Status
for k, v := range httpResp.Header {
if len(v) > 0 {
resp.Headers[k] = v[0]
}
}
buf := new(bytes.Buffer)
if _, err := buf.ReadFrom(httpResp.Body); err != nil {
resp.Error = fmt.Sprintf("failed to read response body: %v", err)
return resp
}
resp.Body = buf.String()
if strings.Contains(httpResp.Header.Get("Content-Type"), "application/json") {
var jsonData map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &jsonData); err == nil {
resp.JSON = jsonData
}
}
return resp
}
func validateURL(urlStr string) error {
parsedURL, err := url.Parse(urlStr)
if err != nil {
return fmt.Errorf("invalid URL: %v", err)
}
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return fmt.Errorf("only http and https protocols are allowed")
}
host := parsedURL.Hostname()
if host == "" {
return fmt.Errorf("empty host")
}
if isPrivateIP(host) {
return fmt.Errorf("access to private IP addresses is not allowed")
}
if isLocalhost(host) {
return fmt.Errorf("access to localhost is not allowed")
}
return nil
}
func isLocalhost(host string) bool {
lowerHost := strings.ToLower(host)
return lowerHost == "localhost" || lowerHost == "127.0.0.1" || lowerHost == "::1"
}
func isPrivateIP(host string) bool {
ip := net.ParseIP(host)
if ip == nil {
return false
}
if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
return true
}
privateBlocks := []string{
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"169.254.0.0/16",
"127.0.0.0/8",
"::1/128",
"fc00::/7",
"fe80::/10",
}
for _, block := range privateBlocks {
_, cidr, _ := net.ParseCIDR(block)
if cidr != nil && cidr.Contains(ip) {
return true
}
}
return false
}