Improve HTTP client function

This commit is contained in:
2025-11-03 22:15:45 +08:00
parent 5688304817
commit 300514531d
13 changed files with 1918 additions and 113 deletions

View File

@@ -23,7 +23,7 @@ type HttpRequest struct {
Method string `json:"method"`
URL string `json:"url"`
Headers map[string]string `json:"headers"`
BodyType string `json:"bodyType,omitempty"` // json, formdata, urlencoded, text
BodyType string `json:"bodyType,omitempty"` // json, formdata, urlencoded, text, params, xml, html, javascript, binary
Body interface{} `json:"body,omitempty"`
}
@@ -89,6 +89,7 @@ func (hcs *HttpClientService) setRequestBody(req *resty.Request, request *HttpRe
case "json":
req.SetHeader("Content-Type", "application/json")
req.SetBody(request.Body)
case "formdata":
if formData, ok := request.Body.(map[string]interface{}); ok {
for key, value := range formData {
@@ -104,6 +105,7 @@ func (hcs *HttpClientService) setRequestBody(req *resty.Request, request *HttpRe
}
}
}
case "urlencoded":
req.SetHeader("Content-Type", "application/x-www-form-urlencoded")
if formData, ok := request.Body.(map[string]interface{}); ok {
@@ -113,9 +115,79 @@ func (hcs *HttpClientService) setRequestBody(req *resty.Request, request *HttpRe
}
req.SetBody(values.Encode())
}
case "text":
req.SetHeader("Content-Type", "text/plain")
req.SetBody(fmt.Sprintf("%v", request.Body))
case "params":
// URL 参数:添加到查询字符串中
if params, ok := request.Body.(map[string]interface{}); ok {
for key, value := range params {
req.SetQueryParam(key, fmt.Sprintf("%v", value))
}
}
case "xml":
// XML 格式:从 Body 中提取 xml 字段
req.SetHeader("Content-Type", "application/xml")
if bodyMap, ok := request.Body.(map[string]interface{}); ok {
if xmlContent, exists := bodyMap["xml"]; exists {
req.SetBody(fmt.Sprintf("%v", xmlContent))
} else {
return fmt.Errorf("xml body type requires 'xml' field")
}
} else {
return fmt.Errorf("xml body must be an object with 'xml' field")
}
case "html":
// HTML 格式:从 Body 中提取 html 字段
req.SetHeader("Content-Type", "text/html")
if bodyMap, ok := request.Body.(map[string]interface{}); ok {
if htmlContent, exists := bodyMap["html"]; exists {
req.SetBody(fmt.Sprintf("%v", htmlContent))
} else {
return fmt.Errorf("html body type requires 'html' field")
}
} else {
return fmt.Errorf("html body must be an object with 'html' field")
}
case "javascript":
// JavaScript 格式:从 Body 中提取 javascript 字段
req.SetHeader("Content-Type", "application/javascript")
if bodyMap, ok := request.Body.(map[string]interface{}); ok {
if jsContent, exists := bodyMap["javascript"]; exists {
req.SetBody(fmt.Sprintf("%v", jsContent))
} else {
return fmt.Errorf("javascript body type requires 'javascript' field")
}
} else {
return fmt.Errorf("javascript body must be an object with 'javascript' field")
}
case "binary":
// Binary 格式:从 Body 中提取 binary 字段(文件路径)
req.SetHeader("Content-Type", "application/octet-stream")
if bodyMap, ok := request.Body.(map[string]interface{}); ok {
if binaryContent, exists := bodyMap["binary"]; exists {
binaryStr := fmt.Sprintf("%v", binaryContent)
// 检查是否是文件类型,使用 @file 关键词
if strings.HasPrefix(binaryStr, "@file ") {
// 提取文件路径(去掉 @file 前缀)
filePath := strings.TrimSpace(strings.TrimPrefix(binaryStr, "@file "))
req.SetFile("file", filePath)
} else {
return fmt.Errorf("binary body requires '@file path/to/file' format")
}
} else {
return fmt.Errorf("binary body type requires 'binary' field")
}
} else {
return fmt.Errorf("binary body must be an object with 'binary' field")
}
default:
return fmt.Errorf("unsupported body type: %s", request.BodyType)
}

View File

@@ -0,0 +1,212 @@
package services
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
func TestHttpClientService_SetRequestBody(t *testing.T) {
logger := log.New()
service := NewHttpClientService(logger)
tests := []struct {
name string
request *HttpRequest
expectError bool
errorMsg string
}{
{
name: "JSON 请求",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/json",
BodyType: "json",
Body: map[string]interface{}{
"name": "张三",
"age": 25,
},
},
expectError: false,
},
{
name: "XML 请求 - 正确格式",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/xml",
BodyType: "xml",
Body: map[string]interface{}{
"xml": "<user><name>张三</name></user>",
},
},
expectError: false,
},
{
name: "XML 请求 - 缺少 xml 字段",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/xml",
BodyType: "xml",
Body: map[string]interface{}{
"data": "<user><name>张三</name></user>",
},
},
expectError: true,
errorMsg: "xml body type requires 'xml' field",
},
{
name: "HTML 请求 - 正确格式",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/html",
BodyType: "html",
Body: map[string]interface{}{
"html": "<div><h1>标题</h1></div>",
},
},
expectError: false,
},
{
name: "JavaScript 请求 - 正确格式",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/js",
BodyType: "javascript",
Body: map[string]interface{}{
"javascript": "function hello() { return 'world'; }",
},
},
expectError: false,
},
{
name: "Binary 请求 - 正确格式",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/upload",
BodyType: "binary",
Body: map[string]interface{}{
"binary": "@file C:/test.bin",
},
},
expectError: false,
},
{
name: "Binary 请求 - 错误格式(缺少 @file",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/upload",
BodyType: "binary",
Body: map[string]interface{}{
"binary": "C:/test.bin",
},
},
expectError: true,
errorMsg: "binary body requires '@file path/to/file' format",
},
{
name: "Params 请求",
request: &HttpRequest{
Method: "GET",
URL: "https://api.example.com/users",
BodyType: "params",
Body: map[string]interface{}{
"page": 1,
"size": 20,
"query": "test",
},
},
expectError: false,
},
{
name: "FormData 请求",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/form",
BodyType: "formdata",
Body: map[string]interface{}{
"username": "admin",
"password": "123456",
},
},
expectError: false,
},
{
name: "UrlEncoded 请求",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/login",
BodyType: "urlencoded",
Body: map[string]interface{}{
"username": "admin",
"password": "123456",
},
},
expectError: false,
},
{
name: "Text 请求",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/text",
BodyType: "text",
Body: "纯文本内容",
},
expectError: false,
},
{
name: "不支持的 Body 类型",
request: &HttpRequest{
Method: "POST",
URL: "https://api.example.com/unknown",
BodyType: "unknown",
Body: "test",
},
expectError: true,
errorMsg: "unsupported body type: unknown",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := service.client.R().SetContext(context.Background())
err := service.setRequestBody(req, tt.request)
if tt.expectError {
assert.Error(t, err)
if tt.errorMsg != "" {
assert.Contains(t, err.Error(), tt.errorMsg)
}
} else {
assert.NoError(t, err)
}
})
}
}
func TestHttpClientService_FormatBytes(t *testing.T) {
logger := log.New()
service := NewHttpClientService(logger)
tests := []struct {
name string
bytes int64
expected string
}{
{"负数", -1, "0 B"},
{"零字节", 0, "0 B"},
{"小于1KB", 500, "500 B"},
{"1KB", 1024, "1.0 KB"},
{"1MB", 1024 * 1024, "1.0 MB"},
{"1.5MB", 1024*1024 + 512*1024, "1.5 MB"},
{"1GB", 1024 * 1024 * 1024, "1.0 GB"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := service.formatBytes(tt.bytes)
assert.Equal(t, tt.expected, result)
})
}
}