Skip to content

Commit

Permalink
Add Benchmarks and Unit tests for parseRequestBody and improve it (#714)
Browse files Browse the repository at this point in the history
* Unit tests and Benchmarks for parseRequestBody function

```shell
% go test -benchmem -bench=. -run=^Benchmark
goos: darwin
goarch: amd64
pkg: github.com/go-resty/resty/v2
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Benchmark_parseRequestBody_string-16                                     2194669               550.2 ns/op           128 B/op          3 allocs/op
Benchmark_parseRequestBody_byte-16                                       2260675               531.5 ns/op           128 B/op          3 allocs/op
Benchmark_parseRequestBody_reader_with_SetContentLength-16               8393974               141.4 ns/op            16 B/op          1 allocs/op
Benchmark_parseRequestBody_reader_without_SetContentLength-16           14253069                84.30 ns/op            0 B/op          0 allocs/op
Benchmark_parseRequestBody_struct-16                                      880549              1307 ns/op             155 B/op          5 allocs/op
Benchmark_parseRequestBody_struct_xml-16                                  424707              2886 ns/op            4762 B/op         13 allocs/op
Benchmark_parseRequestBody_map-16                                         547629              2152 ns/op             569 B/op         15 allocs/op
Benchmark_parseRequestBody_slice-16                                       959576              1264 ns/op             146 B/op          4 allocs/op
Benchmark_parseRequestBody_FormData-16                                    973964              1243 ns/op             304 B/op         14 allocs/op
Benchmark_parseRequestBody_MultiPart-16                                    98246             12320 ns/op            8746 B/op        131 allocs/op
```

* improve handleFormData

Benchmarks:
```
Old: Benchmark_parseRequestBody_FormData-16            954213              1266 ns/op             304 B/op         14 allocs/op
New: Benchmark_parseRequestBody_FormData-16            968466              1248 ns/op             280 B/op         10 allocs/op
```

* improve handleRequestBody

Benchmarks:
```
Old:
Benchmark_parseRequestBody_string-16             2199196               550.3 ns/op           128 B/op          3 allocs/op
Benchmark_parseRequestBody_byte-16               2264421               532.9 ns/op           128 B/op          3 allocs/op
Benchmark_parseRequestBody_reader-16             8307141               141.8 ns/op            16 B/op          1 allocs/op
Benchmark_parseRequestBody_struct-16              931632              1317 ns/op             156 B/op          5 allocs/op
Benchmark_parseRequestBody_struct_xml-16          409074              2921 ns/op            4765 B/op         13 allocs/op
Benchmark_parseRequestBody_map-16                 566750              2158 ns/op             570 B/op         15 allocs/op
Benchmark_parseRequestBody_slice-16               957828              1279 ns/op             146 B/op          4 allocs/op

New:
Benchmark_parseRequestBody_string-16             5084247               237.0 ns/op            16 B/op          1 allocs/op
Benchmark_parseRequestBody_byte-16               5298362               218.0 ns/op            16 B/op          1 allocs/op
Benchmark_parseRequestBody_reader-16             8402954               141.3 ns/op            16 B/op          1 allocs/op
Benchmark_parseRequestBody_struct-16             1000000              1066 ns/op              42 B/op          3 allocs/op
Benchmark_parseRequestBody_struct_xml-16          452389              2575 ns/op            4648 B/op         11 allocs/op
Benchmark_parseRequestBody_map-16                 620391              1913 ns/op             457 B/op         13 allocs/op
Benchmark_parseRequestBody_slice-16              1207551              1203 ns/op              32 B/op          2 allocs/op
```

* improve parseRequesBody and add additional benchmarks

Final benchmarks:
```shell
 % go test -benchmem -bench=. -run=^Benchmark
goos: darwin
goarch: amd64
pkg: github.com/go-resty/resty/v2
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
Benchmark_parseRequestBody_string-16                                     7623501               155.3 ns/op            16 B/op          1 allocs/op
Benchmark_parseRequestBody_byte-16                                       8421992               141.6 ns/op            16 B/op          1 allocs/op
Benchmark_parseRequestBody_reader_with_SetContentLength-16              17632350                67.37 ns/op           16 B/op          1 allocs/op
Benchmark_parseRequestBody_reader_without_SetContentLength-16           26575016                45.34 ns/op            0 B/op          0 allocs/op
Benchmark_parseRequestBody_struct-16                                     1243986               953.8 ns/op            40 B/op          2 allocs/op
Benchmark_parseRequestBody_struct_xml-16                                  495250              2458 ns/op            4647 B/op         10 allocs/op
Benchmark_parseRequestBody_map-16                                         694786              1761 ns/op             454 B/op         12 allocs/op
Benchmark_parseRequestBody_slice-16                                      1304724               913.1 ns/op            32 B/op          2 allocs/op
Benchmark_parseRequestBody_FormData-16                                   1000000              1128 ns/op             272 B/op          9 allocs/op
Benchmark_parseRequestBody_MultiPart-16                                    93248             12583 ns/op            8738 B/op        130 allocs/op
```

* add error test cases

* use r.FormData inatead of creating new variable
  • Loading branch information
SVilgelm committed Oct 2, 2023
1 parent 6310546 commit 4604150
Show file tree
Hide file tree
Showing 2 changed files with 681 additions and 83 deletions.
136 changes: 53 additions & 83 deletions middleware.go
Expand Up @@ -15,6 +15,7 @@ import (
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"time"
)
Expand Down Expand Up @@ -134,45 +135,34 @@ func parseRequestHeader(c *Client, r *Request) error {
return nil
}

func parseRequestBody(c *Client, r *Request) (err error) {
func parseRequestBody(c *Client, r *Request) error {
if isPayloadSupported(r.Method, c.AllowGetMethodPayload) {
// Handling Multipart
if r.isMultiPart {
if err = handleMultipart(c, r); err != nil {
return
switch {
case r.isMultiPart: // Handling Multipart
if err := handleMultipart(c, r); err != nil {
return err
}

goto CL
}

// Handling Form Data
if len(c.FormData) > 0 || len(r.FormData) > 0 {
case len(c.FormData) > 0 || len(r.FormData) > 0: // Handling Form Data
handleFormData(c, r)

goto CL
}

// Handling Request body
if r.Body != nil {
case r.Body != nil: // Handling Request body
handleContentType(c, r)

if err = handleRequestBody(c, r); err != nil {
return
if err := handleRequestBody(c, r); err != nil {
return err
}
}
}

CL:
// by default resty won't set content length, you can if you want to :)
if c.setContentLength || r.setContentLength {
if r.bodyBuf == nil {
r.Header.Set(hdrContentLengthKey, "0")
} else {
r.Header.Set(hdrContentLengthKey, fmt.Sprintf("%d", r.bodyBuf.Len()))
r.Header.Set(hdrContentLengthKey, strconv.Itoa(r.bodyBuf.Len()))
}
}

return
return nil
}

func createHTTPRequest(c *Client, r *Request) (err error) {
Expand Down Expand Up @@ -370,13 +360,13 @@ func parseResponseBody(c *Client, res *Response) (err error) {
return
}

func handleMultipart(c *Client, r *Request) (err error) {
func handleMultipart(c *Client, r *Request) error {
r.bodyBuf = acquireBuffer()
w := multipart.NewWriter(r.bodyBuf)

for k, v := range c.FormData {
for _, iv := range v {
if err = w.WriteField(k, iv); err != nil {
if err := w.WriteField(k, iv); err != nil {
return err
}
}
Expand All @@ -385,63 +375,45 @@ func handleMultipart(c *Client, r *Request) (err error) {
for k, v := range r.FormData {
for _, iv := range v {
if strings.HasPrefix(k, "@") { // file
err = addFile(w, k[1:], iv)
if err != nil {
return
if err := addFile(w, k[1:], iv); err != nil {
return err
}
} else { // form value
if err = w.WriteField(k, iv); err != nil {
if err := w.WriteField(k, iv); err != nil {
return err
}
}
}
}

// #21 - adding io.Reader support
if len(r.multipartFiles) > 0 {
for _, f := range r.multipartFiles {
err = addFileReader(w, f)
if err != nil {
return
}
for _, f := range r.multipartFiles {
if err := addFileReader(w, f); err != nil {
return err
}
}

// GitHub #130 adding multipart field support with content type
if len(r.multipartFields) > 0 {
for _, mf := range r.multipartFields {
if err = addMultipartFormField(w, mf); err != nil {
return
}
for _, mf := range r.multipartFields {
if err := addMultipartFormField(w, mf); err != nil {
return err
}
}

r.Header.Set(hdrContentTypeKey, w.FormDataContentType())
err = w.Close()

return
return w.Close()
}

func handleFormData(c *Client, r *Request) {
formData := url.Values{}

for k, v := range c.FormData {
for _, iv := range v {
formData.Add(k, iv)
}
}

for k, v := range r.FormData {
// remove form data field from client level by key
// since overrides happens for that key in the request
formData.Del(k)

for _, iv := range v {
formData.Add(k, iv)
if _, ok := r.FormData[k]; ok {
continue
}
r.FormData[k] = v[:]
}

r.bodyBuf = bytes.NewBuffer([]byte(formData.Encode()))
r.bodyBuf = acquireBuffer()
r.bodyBuf.WriteString(r.FormData.Encode())
r.Header.Set(hdrContentTypeKey, formContentType)
r.isFormData = true
}
Expand All @@ -454,45 +426,43 @@ func handleContentType(c *Client, r *Request) {
}
}

func handleRequestBody(c *Client, r *Request) (err error) {
func handleRequestBody(c *Client, r *Request) error {
var bodyBytes []byte
contentType := r.Header.Get(hdrContentTypeKey)
kind := kindOf(r.Body)
releaseBuffer(r.bodyBuf)
r.bodyBuf = nil

if reader, ok := r.Body.(io.Reader); ok {
switch body := r.Body.(type) {
case io.Reader:
if c.setContentLength || r.setContentLength { // keep backward compatibility
r.bodyBuf = acquireBuffer()
_, err = r.bodyBuf.ReadFrom(reader)
if _, err := r.bodyBuf.ReadFrom(body); err != nil {
return err
}
r.Body = nil
} else {
// Otherwise buffer less processing for `io.Reader`, sounds good.
return
}
} else if b, ok := r.Body.([]byte); ok {
bodyBytes = b
} else if s, ok := r.Body.(string); ok {
bodyBytes = []byte(s)
} else if IsJSONType(contentType) &&
(kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
r.bodyBuf, err = jsonMarshal(c, r, r.Body)
if err != nil {
return
return nil
}
case []byte:
bodyBytes = body
case string:
bodyBytes = []byte(body)
default:
contentType := r.Header.Get(hdrContentTypeKey)
kind := kindOf(r.Body)
var err error
if IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
r.bodyBuf, err = jsonMarshal(c, r, r.Body)
} else if IsXMLType(contentType) && (kind == reflect.Struct) {
bodyBytes, err = c.XMLMarshal(r.Body)
}
} else if IsXMLType(contentType) && (kind == reflect.Struct) {
bodyBytes, err = c.XMLMarshal(r.Body)
if err != nil {
return
return err
}
}

if bodyBytes == nil && r.bodyBuf == nil {
err = errors.New("unsupported 'Body' type/value")
}

// if any errors during body bytes handling, return it
if err != nil {
return
return errors.New("unsupported 'Body' type/value")
}

// []byte into Buffer
Expand All @@ -501,7 +471,7 @@ func handleRequestBody(c *Client, r *Request) (err error) {
_, _ = r.bodyBuf.Write(bodyBytes)
}

return
return nil
}

func saveResponseIntoFile(c *Client, res *Response) error {
Expand Down

0 comments on commit 4604150

Please sign in to comment.