Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync v2 branch into branch main #780

Merged
merged 14 commits into from Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -3,12 +3,13 @@ name: CI
on:
push:
branches:
- master
- v2
paths-ignore:
- '**.md'
pull_request:
branches:
- master
- main
- v2
paths-ignore:
- '**.md'

Expand Down
10 changes: 5 additions & 5 deletions README.md
Expand Up @@ -4,7 +4,7 @@
<p align="center"><a href="#features">Features</a> section describes in detail about Resty capabilities</p>
</p>
<p align="center">
<p align="center"><a href="https://github.com/go-resty/resty/actions/workflows/ci.yml?query=branch%3Amaster"><img src="https://github.com/go-resty/resty/actions/workflows/ci.yml/badge.svg" alt="Build Status"></a> <a href="https://codecov.io/gh/go-resty/resty/branch/master"><img src="https://codecov.io/gh/go-resty/resty/branch/master/graph/badge.svg" alt="Code Coverage"></a> <a href="https://goreportcard.com/report/go-resty/resty"><img src="https://goreportcard.com/badge/go-resty/resty" alt="Go Report Card"></a> <a href="https://github.com/go-resty/resty/releases/latest"><img src="https://img.shields.io/badge/version-2.9.1-blue.svg" alt="Release Version"></a> <a href="https://pkg.go.dev/github.com/go-resty/resty/v2"><img src="https://pkg.go.dev/badge/github.com/go-resty/resty" alt="GoDoc"></a> <a href="LICENSE"><img src="https://img.shields.io/github/license/go-resty/resty.svg" alt="License"></a> <a href="https://github.com/avelino/awesome-go"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Go"></a></p>
<p align="center"><a href="https://github.com/go-resty/resty/actions/workflows/ci.yml?query=branch%3Amaster"><img src="https://github.com/go-resty/resty/actions/workflows/ci.yml/badge.svg" alt="Build Status"></a> <a href="https://codecov.io/gh/go-resty/resty/branch/master"><img src="https://codecov.io/gh/go-resty/resty/branch/master/graph/badge.svg" alt="Code Coverage"></a> <a href="https://goreportcard.com/report/go-resty/resty"><img src="https://goreportcard.com/badge/go-resty/resty" alt="Go Report Card"></a> <a href="https://github.com/go-resty/resty/releases/latest"><img src="https://img.shields.io/badge/version-2.11.0-blue.svg" alt="Release Version"></a> <a href="https://pkg.go.dev/github.com/go-resty/resty/v2"><img src="https://pkg.go.dev/badge/github.com/go-resty/resty" alt="GoDoc"></a> <a href="LICENSE"><img src="https://img.shields.io/github/license/go-resty/resty.svg" alt="License"></a> <a href="https://github.com/avelino/awesome-go"><img src="https://awesome.re/mentioned-badge.svg" alt="Mentioned in Awesome Go"></a></p>
</p>
<p align="center">
<h4 align="center">Resty Communication Channels</h4>
Expand All @@ -13,7 +13,7 @@

## News

* v2.9.1 [released](https://github.com/go-resty/resty/releases/tag/v2.9.1) and tagged on Sep 30, 2023.
* v2.11.0 [released](https://github.com/go-resty/resty/releases/tag/v2.11.0) and tagged on Dec 27, 2023.
* v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on Jul 16, 2019.
* v1.12.0 [released](https://github.com/go-resty/resty/releases/tag/v1.12.0) and tagged on Feb 27, 2019.
* v1.0 released and tagged on Sep 25, 2017. - Resty's first version was released on Sep 15, 2015 then it grew gradually as a very handy and helpful library. Its been a two years since first release. I'm very thankful to Resty users and its [contributors](https://github.com/go-resty/resty/graphs/contributors).
Expand Down Expand Up @@ -110,7 +110,7 @@ Resty author also published following projects for Go Community.

```bash
# Go Modules
require github.com/go-resty/resty/v2 v2.7.0
require github.com/go-resty/resty/v2 v2.11.0
```

## Usage
Expand Down Expand Up @@ -797,7 +797,7 @@ client.SetTimeout(1 * time.Minute)
// You can override all below settings and options at request level if you want to
//--------------------------------------------------------------------------------
// Host URL for all request. So you can use relative URL in the request
client.SetHostURL("http://httpbin.org")
client.SetBaseURL("http://httpbin.org")

// Headers for all request
client.SetHeader("Accept", "application/json")
Expand Down Expand Up @@ -861,7 +861,7 @@ client := resty.New()

// Set the previous transport that we created, set the scheme of the communication to the
// socket and set the unixSocket as the HostURL.
client.SetTransport(&transport).SetScheme("http").SetHostURL(unixSocket)
client.SetTransport(&transport).SetScheme("http").SetBaseURL(unixSocket)

// No need to write the host's URL on the request, just the path.
client.R().Get("http://localhost/index.html")
Expand Down
23 changes: 23 additions & 0 deletions client_test.go
Expand Up @@ -766,6 +766,29 @@ func TestLogCallbacks(t *testing.T) {
assertNotNil(t, resp)
}

func TestDebugLogSimultaneously(t *testing.T) {
ts := createGetServer(t)

c := New().
SetDebug(true).
SetBaseURL(ts.URL).
outputLogTo(io.Discard)

t.Cleanup(ts.Close)
for i := 0; i < 50; i++ {
t.Run(fmt.Sprint(i), func(t *testing.T) {
t.Parallel()
resp, err := c.R().
SetBody([]int{1, 2, 3}).
SetHeader(hdrContentTypeKey, "application/json; charset=utf-8").
Post("/")

assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())
})
}
}

func TestNewWithLocalAddr(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
Expand Down
20 changes: 17 additions & 3 deletions digest.go
Expand Up @@ -55,6 +55,17 @@ func (dt *digestTransport) RoundTrip(req *http.Request) (*http.Response, error)
req2.Header[k] = s
}

// Fix http: ContentLength=xxx with Body length 0
if req2.Body == nil {
req2.ContentLength = 0
} else if req2.GetBody != nil {
var err error
req2.Body, err = req2.GetBody()
if err != nil {
return nil, err
}
}

// Make a request to get the 401 that contains the challenge.
resp, err := dt.transport.RoundTrip(req)
if err != nil || resp.StatusCode != http.StatusUnauthorized {
Expand Down Expand Up @@ -122,14 +133,17 @@ func parseChallenge(input string) (*challenge, error) {
return nil, ErrDigestBadChallenge
}
s = strings.Trim(s[7:], ws)
sl := strings.Split(s, ", ")
sl := strings.Split(s, ",")
c := &challenge{}
var r []string
for i := range sl {
sl[i] = strings.TrimSpace(sl[i])
r = strings.SplitN(sl[i], "=", 2)
if len(r) != 2 {
return nil, ErrDigestBadChallenge
}
r[0] = strings.TrimSpace(r[0])
r[1] = strings.TrimSpace(r[1])
switch r[0] {
case "realm":
c.realm = strings.Trim(r[1], qs)
Expand All @@ -140,9 +154,9 @@ func parseChallenge(input string) (*challenge, error) {
case "opaque":
c.opaque = strings.Trim(r[1], qs)
case "stale":
c.stale = r[1]
c.stale = strings.Trim(r[1], qs)
case "algorithm":
c.algorithm = r[1]
c.algorithm = strings.Trim(r[1], qs)
case "qop":
c.qop = strings.Trim(r[1], qs)
case "charset":
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Expand Up @@ -3,6 +3,6 @@ module github.com/go-resty/resty/v2
go 1.16

require (
golang.org/x/net v0.15.0
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
golang.org/x/net v0.17.0
golang.org/x/time v0.3.0
)
14 changes: 7 additions & 7 deletions go.sum
@@ -1,16 +1,16 @@
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -21,20 +21,20 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
Expand Down
17 changes: 13 additions & 4 deletions middleware.go
Expand Up @@ -58,7 +58,7 @@ func parseRequestURL(c *Client, r *Request) error {
defer releaseBuffer(buf)
// search for the next or first opened curly bracket
for curr := strings.Index(r.URL, "{"); curr > prev; curr = prev + strings.Index(r.URL[prev:], "{") {
// write everything form the previous position up to the current
// write everything from the previous position up to the current
if curr > prev {
buf.WriteString(r.URL[prev:curr])
}
Expand Down Expand Up @@ -310,13 +310,23 @@ func addCredentials(c *Client, r *Request) error {
func requestLogger(c *Client, r *Request) error {
if r.Debug {
rr := r.RawRequest
rl := &RequestLog{Header: copyHeaders(rr.Header), Body: r.fmtBodyString(c.debugBodySizeLimit)}
rh := copyHeaders(rr.Header)
if c.GetClient().Jar != nil {
for _, cookie := range c.GetClient().Jar.Cookies(r.RawRequest.URL) {
s := fmt.Sprintf("%s=%s", cookie.Name, cookie.Value)
if c := rh.Get("Cookie"); c != "" {
rh.Set("Cookie", c+"; "+s)
} else {
rh.Set("Cookie", s)
}
}
}
rl := &RequestLog{Header: rh, Body: r.fmtBodyString(c.debugBodySizeLimit)}
if c.requestLog != nil {
if err := c.requestLog(rl); err != nil {
return err
}
}
// fmt.Sprintf("COOKIES:\n%s\n", composeCookies(c.GetClient().Jar, *rr.URL)) +

reqLog := "\n==============================================================================\n" +
"~~~ REQUEST ~~~\n" +
Expand Down Expand Up @@ -471,7 +481,6 @@ func handleContentType(c *Client, r *Request) {

func handleRequestBody(c *Client, r *Request) error {
var bodyBytes []byte
releaseBuffer(r.bodyBuf)
r.bodyBuf = nil

switch body := r.Body.(type) {
Expand Down
6 changes: 3 additions & 3 deletions middleware_test.go
Expand Up @@ -756,7 +756,7 @@ func Test_parseRequestBody(t *testing.T) {
expectedContentLength: "744",
},
{
name: "mulipart fields",
name: "multipart fields",
init: func(c *Client, r *Request) {
r.SetMultipartFields(
&MultipartField{
Expand All @@ -776,15 +776,15 @@ func Test_parseRequestBody(t *testing.T) {
expectedContentLength: "344",
},
{
name: "mulipart files",
name: "multipart files",
init: func(c *Client, r *Request) {
r.SetFileReader("foo", "foo.txt", strings.NewReader("1")).
SetFileReader("bar", "bar.txt", strings.NewReader("2")).
SetContentLength(true)
},
expectedBodyBuf: []byte(`{"bar":"2","foo":"1"}`),
expectedContentType: "multipart/form-data; boundary=",
expectedContentLength: "412",
expectedContentLength: "414",
},
{
name: "body with errorReader",
Expand Down
14 changes: 9 additions & 5 deletions request.go
Expand Up @@ -1014,7 +1014,12 @@ func (r *Request) fmtBodyString(sl int64) (body string) {
contentType := r.Header.Get(hdrContentTypeKey)
kind := kindOf(r.Body)
if canJSONMarshal(contentType, kind) {
prtBodyBytes, err = noescapeJSONMarshalIndent(&r.Body)
var bodyBuf *bytes.Buffer
bodyBuf, err = noescapeJSONMarshalIndent(&r.Body)
if err == nil {
prtBodyBytes = bodyBuf.Bytes()
defer releaseBuffer(bodyBuf)
}
} else if IsXMLType(contentType) && (kind == reflect.Struct) {
prtBodyBytes, err = xml.MarshalIndent(&r.Body, "", " ")
} else if b, ok := r.Body.(string); ok {
Expand Down Expand Up @@ -1077,17 +1082,16 @@ var noescapeJSONMarshal = func(v interface{}) (*bytes.Buffer, error) {
return buf, nil
}

var noescapeJSONMarshalIndent = func(v interface{}) ([]byte, error) {
var noescapeJSONMarshalIndent = func(v interface{}) (*bytes.Buffer, error) {
buf := acquireBuffer()
defer releaseBuffer(buf)

encoder := json.NewEncoder(buf)
encoder.SetEscapeHTML(false)
encoder.SetIndent("", " ")

if err := encoder.Encode(v); err != nil {
releaseBuffer(buf)
return nil, err
}

return buf.Bytes(), nil
return buf, nil
}
19 changes: 19 additions & 0 deletions request_test.go
Expand Up @@ -722,6 +722,25 @@ func TestRequestDigestAuthFail(t *testing.T) {
logResponse(t, resp)
}

func TestRequestDigestAuthWithBody(t *testing.T) {
conf := defaultDigestServerConf()
ts := createDigestServer(t, nil)
defer ts.Close()

resp, err := dclr().
SetDigestAuth(conf.username, conf.password).
SetResult(&AuthSuccess{}).
SetHeader(hdrContentTypeKey, "application/json").
SetBody(map[string]interface{}{"zip_code": "00000", "city": "Los Angeles"}).
Post(ts.URL + conf.uri)

assertError(t, err)
assertEqual(t, http.StatusOK, resp.StatusCode())

t.Logf("Result Success: %q", resp.Result().(*AuthSuccess))
logResponse(t, resp)
}

func TestFormData(t *testing.T) {
ts := createFormPostServer(t)
defer ts.Close()
Expand Down
2 changes: 1 addition & 1 deletion resty.go
Expand Up @@ -14,7 +14,7 @@ import (
)

// Version # of resty
const Version = "2.10.0-dev"
const Version = "2.10.0"

// New method creates a new Resty client.
func New() *Client {
Expand Down
22 changes: 2 additions & 20 deletions util.go
Expand Up @@ -216,7 +216,7 @@ func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r i
return err
}

partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf)))
partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf[:size])))
if err != nil {
return err
}
Expand Down Expand Up @@ -339,25 +339,7 @@ func silently(_ ...interface{}) {}
func composeHeaders(c *Client, r *Request, hdrs http.Header) string {
str := make([]string, 0, len(hdrs))
for _, k := range sortHeaderKeys(hdrs) {
var v string
if k == "Cookie" {
cv := strings.TrimSpace(strings.Join(hdrs[k], ", "))
if c.GetClient().Jar != nil {
for _, c := range c.GetClient().Jar.Cookies(r.RawRequest.URL) {
if cv != "" {
cv = cv + "; " + c.String()
} else {
cv = c.String()
}
}
}
v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, cv))
} else {
v = strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", ")))
}
if v != "" {
str = append(str, "\t"+v)
}
str = append(str, "\t"+strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", "))))
}
return strings.Join(str, "\n")
}
Expand Down