Files
requests/body.go
gospider 835a54f638 sync
2025-08-25 17:18:57 +08:00

463 lines
11 KiB
Go

package requests
import (
"bytes"
"context"
"crypto/rand"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"sort"
"strings"
"github.com/gospider007/gson"
"github.com/gospider007/tools"
)
type OrderData struct {
data []struct {
key string
val any
}
}
func NewOrderData() *OrderData {
return &OrderData{
data: []struct {
key string
val any
}{},
}
}
func (obj *OrderData) Add(key string, val any) {
obj.data = append(obj.data, struct {
key string
val any
}{key: key, val: val})
}
func (obj *OrderData) Keys() []string {
keys := make([]string, len(obj.data))
for i, value := range obj.data {
keys[i] = value.key
}
return keys
}
func (obj *OrderData) ReorderWithKeys(key ...string) {
if len(key) == 0 {
return
}
for i, k := range key {
key[i] = textproto.CanonicalMIMEHeaderKey(k)
}
sort.SliceStable(obj.data, func(x, y int) bool {
xIndex := -1
yIndex := -1
for i, k := range key {
if k == obj.data[x].key {
xIndex = i
}
if k == obj.data[y].key {
yIndex = i
}
}
if xIndex == -1 {
return false
}
if yIndex == -1 {
return true
}
return xIndex < yIndex
})
}
type orderT struct {
key string
val any
}
func (obj orderT) Key() string {
return obj.key
}
func (obj orderT) Val() any {
return obj.val
}
func (obj *OrderData) Data() []interface {
Key() string
Val() any
} {
if obj == nil {
return nil
}
keys := make([]interface {
Key() string
Val() any
}, len(obj.data))
for i, value := range obj.data {
keys[i] = orderT{
key: value.key,
val: value.val,
}
}
return keys
}
func formWrite(writer *multipart.Writer, key string, val any) (err error) {
switch value := val.(type) {
case File:
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(key), escapeQuotes(value.FileName)))
if value.ContentType == "" {
switch content := value.Content.(type) {
case []byte:
h.Set("Content-Type", http.DetectContentType(content))
case string:
h.Set("Content-Type", http.DetectContentType(tools.StringToBytes(content)))
case io.Reader:
h.Set("Content-Type", "application/octet-stream")
default:
con, err := gson.Encode(content)
if err != nil {
return err
}
h.Set("Content-Type", http.DetectContentType(con))
}
} else {
h.Set("Content-Type", value.ContentType)
}
var wp io.Writer
if wp, err = writer.CreatePart(h); err != nil {
return
}
switch content := value.Content.(type) {
case []byte:
_, err = wp.Write(content)
case string:
_, err = wp.Write(tools.StringToBytes(content))
case io.Reader:
_, err = tools.Copy(wp, content)
default:
con, err := gson.Encode(content)
if err != nil {
return err
}
_, err = wp.Write(con)
if err != nil {
return err
}
}
case []byte:
err = writer.WriteField(key, tools.BytesToString(value))
case string:
err = writer.WriteField(key, value)
default:
con, err := gson.Encode(val)
if err != nil {
return err
}
err = writer.WriteField(key, tools.BytesToString(con))
if err != nil {
return err
}
}
return
}
func (obj *OrderData) isformPip() bool {
if len(obj.data) == 0 {
return false
}
for _, value := range obj.data {
if file, ok := value.val.(File); ok {
if _, ok := file.Content.(io.Reader); ok {
return true
}
}
}
return false
}
func (obj *OrderData) formWriteMain(writer *multipart.Writer) (err error) {
for _, value := range obj.data {
if err = formWrite(writer, value.key, value.val); err != nil {
return
}
}
return writer.Close()
}
func paramsWrite(content []byte, key string, val any) []byte {
if len(content) > 0 {
content = append(content, '&')
}
content = append(content, []byte(url.QueryEscape(key))...)
content = append(content, '=')
switch value := val.(type) {
case []byte:
content = append(content, value...)
case string:
content = append(content, []byte(value)...)
default:
v, _ := gson.Encode(val)
content = append(content, v...)
}
return content
}
func (obj *OrderData) MarshalJSON() ([]byte, error) {
content := []byte{}
content = append(content, '{')
for i, value := range obj.data {
if i > 0 {
content = append(content, ',')
}
content = append(content, []byte(`"`+value.key+`":`)...)
val, err := gson.Encode(value.val)
if err != nil {
return nil, err
}
content = append(content, val...)
}
content = append(content, '}')
return content, nil
}
func (obj *OrderData) parseParams() []byte {
content := []byte{}
for _, value := range obj.data {
content = paramsWrite(content, value.key, value.val)
}
return content
}
func (obj *OrderData) parseForm(ctx context.Context, boundary string) (io.Reader, bool, error) {
if len(obj.data) == 0 {
return nil, false, nil
}
if obj.isformPip() {
pr, pw := io.Pipe()
writer := multipart.NewWriter(pw)
go func() {
stop := context.AfterFunc(ctx, func() {
pw.CloseWithError(ctx.Err())
})
pw.CloseWithError(obj.formWriteMain(writer))
stop()
}()
return pr, true, nil
}
body := bytes.NewBuffer(nil)
writer := multipart.NewWriter(body)
writer.SetBoundary(boundary)
err := obj.formWriteMain(writer)
if err != nil {
return nil, false, err
}
return bytes.NewReader(body.Bytes()), false, err
}
// Upload files with form-data,
type File struct {
Content any
FileName string
ContentType string
}
func randomBoundary() (string, string) {
var buf [30]byte
io.ReadFull(rand.Reader, buf[:])
boundary := fmt.Sprintf("%x", buf[:])
if strings.ContainsAny(boundary, `()<>@,;:\"/[]?= `) {
boundary = `"` + boundary + `"`
}
return "multipart/form-data; boundary=" + boundary, boundary
}
func (obj *RequestOption) initBody(ctx context.Context) (io.Reader, error) {
if obj.Body != nil {
switch value := obj.Body.(type) {
case io.Reader:
obj.readOne = true
return value, nil
case string:
return bytes.NewReader(tools.StringToBytes(value)), nil
case []byte:
return bytes.NewReader(value), nil
default:
content, err := gson.Encode(value)
if err != nil {
return nil, errors.New("invalid body type")
}
return bytes.NewReader(content), nil
}
} else if obj.Form != nil {
var boundary string
if obj.ContentType == "" {
obj.ContentType, boundary = randomBoundary()
}
switch value := obj.Form.(type) {
case *OrderData:
body, once, err := value.parseForm(ctx, boundary)
if err != nil {
return nil, err
}
obj.readOne = once
return body, nil
case io.Reader:
obj.readOne = true
return value, nil
case string:
return bytes.NewReader(tools.StringToBytes(value)), nil
case []byte:
return bytes.NewReader(value), nil
case map[string]any:
orderMap := NewOrderData()
for key, val := range value {
orderMap.Add(key, val)
}
body, once, err := orderMap.parseForm(ctx, boundary)
if err != nil {
return nil, err
}
obj.readOne = once
return body, nil
default:
jsonData, err := gson.Decode(value)
if err != nil {
return nil, errors.New("invalid body type")
}
orderMap := NewOrderData()
for kk, vv := range jsonData.Map() {
orderMap.Add(kk, vv.Value())
}
body, once, err := orderMap.parseForm(ctx, boundary)
if err != nil {
return nil, err
}
obj.readOne = once
return body, nil
}
} else if obj.Data != nil {
if obj.ContentType == "" {
obj.ContentType = "application/x-www-form-urlencoded"
}
switch value := obj.Data.(type) {
case *OrderData:
return bytes.NewReader(value.parseParams()), nil
case io.Reader:
obj.readOne = true
return value, nil
case string:
return bytes.NewReader(tools.StringToBytes(value)), nil
case []byte:
return bytes.NewReader(value), nil
case map[string]any:
orderMap := NewOrderData()
for key, val := range value {
orderMap.Add(key, val)
}
return bytes.NewReader(orderMap.parseParams()), nil
default:
jsonData, err := gson.Decode(value)
if err != nil {
return nil, errors.New("invalid body type")
}
orderMap := NewOrderData()
for kk, vv := range jsonData.Map() {
orderMap.Add(kk, vv.Value())
}
return bytes.NewReader(orderMap.parseParams()), nil
}
} else if obj.Json != nil {
if obj.ContentType == "" {
obj.ContentType = "application/json"
}
switch value := obj.Json.(type) {
case io.Reader:
obj.readOne = true
return value, nil
case string:
return bytes.NewReader(tools.StringToBytes(value)), nil
case []byte:
return bytes.NewReader(value), nil
default:
content, err := gson.Encode(value)
if err != nil {
return nil, errors.New("invalid body type")
}
return bytes.NewReader(content), nil
}
} else if obj.Text != nil {
if obj.ContentType == "" {
obj.ContentType = "text/plain"
}
switch value := obj.Text.(type) {
case io.Reader:
obj.readOne = true
return value, nil
case string:
return bytes.NewReader(tools.StringToBytes(value)), nil
case []byte:
return bytes.NewReader(value), nil
default:
content, err := gson.Encode(value)
if err != nil {
return nil, errors.New("invalid body type")
}
return bytes.NewReader(content), nil
}
} else {
return nil, nil
}
}
func (obj *RequestOption) initParams() (*url.URL, error) {
baseUrl := cloneUrl(obj.Url)
if obj.Params == nil {
return baseUrl, nil
}
var query string
switch value := obj.Params.(type) {
case *OrderData:
query = tools.BytesToString(value.parseParams())
case io.Reader:
obj.readOne = true
con, err := io.ReadAll(value)
if err != nil {
return nil, err
}
query = tools.BytesToString(con)
case string:
query = value
case []byte:
query = tools.BytesToString(value)
case map[string]any:
orderMap := NewOrderData()
for key, val := range value {
orderMap.Add(key, val)
}
query = tools.BytesToString(orderMap.parseParams())
default:
jsonData, err := gson.Decode(value)
if err != nil {
return nil, errors.New("invalid body type")
}
orderMap := NewOrderData()
for kk, vv := range jsonData.Map() {
orderMap.Add(kk, vv.Value())
}
query = tools.BytesToString(orderMap.parseParams())
}
if query == "" {
return baseUrl, nil
}
pquery := baseUrl.Query().Encode()
if pquery == "" {
baseUrl.RawQuery = query
} else {
baseUrl.RawQuery = pquery + "&" + query
}
return baseUrl, nil
}