Skip to content

Commit

Permalink
Support ordered form data (#391)
Browse files Browse the repository at this point in the history
  • Loading branch information
imroc committed Oct 10, 2024
1 parent 8d3a134 commit cfff250
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 19 deletions.
51 changes: 46 additions & 5 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package req

import (
"bytes"
"errors"
"io"
"mime/multipart"
"net/http"
Expand Down Expand Up @@ -124,17 +125,30 @@ func writeMultipartFormFile(w *multipart.Writer, file *FileUpload, r *Request) e

func writeMultiPart(r *Request, w *multipart.Writer) {
defer w.Close() // close multipart to write tailer boundary
for k, vs := range r.FormData {
for _, v := range vs {
w.WriteField(k, v)
if len(r.FormData) > 0 {
for k, vs := range r.FormData {
for _, v := range vs {
w.WriteField(k, v)
}
}
} else if len(r.OrderedFormData) > 0 {
if len(r.OrderedFormData)%2 != 0 {
r.error = errBadOrderedFormData
return
}
maxIndex := len(r.OrderedFormData) - 2
for i := 0; i <= maxIndex; i += 2 {
key := r.OrderedFormData[i]
value := r.OrderedFormData[i+1]
w.WriteField(key, value)
}
}
for _, file := range r.uploadFiles {
writeMultipartFormFile(w, file, r)
}
}

func handleMultiPart(c *Client, r *Request) (err error) {
func handleMultiPart(r *Request) (err error) {
if r.forceChunkedEncoding {
pr, pw := io.Pipe()
r.GetBody = func() (io.ReadCloser, error) {
Expand Down Expand Up @@ -164,6 +178,29 @@ func handleFormData(r *Request) {
r.SetBodyBytes([]byte(r.FormData.Encode()))
}

var errBadOrderedFormData = errors.New("bad ordered form data, the number of key-value pairs should be an even number")

func handleOrderedFormData(r *Request) {
r.SetContentType(header.FormContentType)
if len(r.OrderedFormData)%2 != 0 {
r.error = errBadOrderedFormData
return
}
maxIndex := len(r.OrderedFormData) - 2
var buf strings.Builder
for i := 0; i <= maxIndex; i += 2 {
key := r.OrderedFormData[i]
value := r.OrderedFormData[i+1]
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(url.QueryEscape(key))
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(value))
}
r.SetBodyString(buf.String())
}

func handleMarshalBody(c *Client, r *Request) error {
ct := ""
if r.Headers != nil {
Expand Down Expand Up @@ -205,16 +242,20 @@ func parseRequestBody(c *Client, r *Request) (err error) {
}
// handle multipart
if r.isMultiPart {
return handleMultiPart(c, r)
return handleMultiPart(r)
}

// handle form data
if len(c.FormData) > 0 {
r.SetFormDataFromValues(c.FormData)
}

if len(r.FormData) > 0 {
handleFormData(r)
return
} else if len(r.OrderedFormData) > 0 {
handleOrderedFormData(r)
return
}

// handle marshal body
Expand Down
35 changes: 21 additions & 14 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,21 @@ import (
// req client. Request provides lots of chainable settings which can
// override client level settings.
type Request struct {
PathParams map[string]string
QueryParams urlpkg.Values
FormData urlpkg.Values
Headers http.Header
Cookies []*http.Cookie
Result interface{}
Error interface{}
RawRequest *http.Request
StartTime time.Time
RetryAttempt int
RawURL string // read only
Method string
Body []byte
GetBody GetContentFunc
PathParams map[string]string
QueryParams urlpkg.Values
FormData urlpkg.Values
OrderedFormData []string
Headers http.Header
Cookies []*http.Cookie
Result interface{}
Error interface{}
RawRequest *http.Request
StartTime time.Time
RetryAttempt int
RawURL string // read only
Method string
Body []byte
GetBody GetContentFunc
// URL is an auto-generated field, and is nil in request middleware (OnBeforeRequest),
// consider using RawURL if you want, it's not nil in client middleware (WrapRoundTripFunc)
URL *urlpkg.URL
Expand Down Expand Up @@ -187,6 +188,12 @@ func (r *Request) SetFormData(data map[string]string) *Request {
return r
}

// SetOrderedFormData set the ordered form data from key-values pairs.
func (r *Request) SetOrderedFormData(kvs ...string) *Request {
r.OrderedFormData = append(r.OrderedFormData, kvs...)
return r
}

// SetFormDataAnyType set the form data from a map, which value could be any type,
// will convert to string automatically.
// It will not been used if request method does not allow payload.
Expand Down

0 comments on commit cfff250

Please sign in to comment.