New Upstream Release - golang-github-go-resty-resty
Ready changes
Summary
Merged new upstream version: 2.7.0 (was: 2.6.0).
Resulting package
Built on 2022-11-11T01:28 (took 6m22s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases golang-github-go-resty-resty-dev
Lintian Result
Diff
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..13b025b
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,41 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - master
+ paths-ignore:
+ - '**.md'
+ pull_request:
+ branches:
+ - master
+ paths-ignore:
+ - '**.md'
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+jobs:
+ build:
+ name: Build
+ strategy:
+ matrix:
+ go: [ '1.17.x', '1.16.x' ]
+ os: [ ubuntu-latest ]
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Setup Go
+ uses: actions/setup-go@v2
+ with:
+ go-version: ${{ matrix.go }}
+
+ - name: Test
+ run: go test ./... -race -coverprofile=coverage.txt -covermode=atomic
+
+ - name: Coverage
+ run: bash <(curl -s https://codecov.io/bash)
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 583a039..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-language: go
-
-os: linux
-
-go: # use travis ci resource effectively, keep always latest 2 versions and tip :)
- - 1.15.x
- - 1.14.x
- - tip
-
-install:
- - go get -v -t ./...
-
-script:
- - go test ./... -race -coverprofile=coverage.txt -covermode=atomic
-
-after_success:
- - bash <(curl -s https://codecov.io/bash)
-
-jobs:
- allow_failures:
- - go: tip
diff --git a/BUILD.bazel b/BUILD.bazel
index 6c47cbb..03bb44c 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -1,36 +1,48 @@
-package(default_visibility = ["//visibility:private"])
-
-load("@bazel_gazelle//:def.bzl", "gazelle")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("@bazel_gazelle//:def.bzl", "gazelle")
-gazelle(
- name = "gazelle",
- command = "fix",
- prefix = "github.com/go-resty/resty/v2",
-)
+# gazelle:prefix github.com/go-resty/resty/v2
+# gazelle:go_naming_convention import_alias
+gazelle(name = "gazelle")
go_library(
- name = "go_default_library",
- srcs = glob(
- ["*.go"],
- exclude = ["*_test.go"],
- ),
+ name = "resty",
+ srcs = [
+ "client.go",
+ "middleware.go",
+ "redirect.go",
+ "request.go",
+ "response.go",
+ "resty.go",
+ "retry.go",
+ "trace.go",
+ "transport.go",
+ "transport112.go",
+ "util.go",
+ ],
importpath = "github.com/go-resty/resty/v2",
visibility = ["//visibility:public"],
deps = ["@org_golang_x_net//publicsuffix:go_default_library"],
)
go_test(
- name = "go_default_test",
- srcs =
- glob(
- ["*_test.go"],
- exclude = ["example_test.go"],
- ),
- data = glob([".testdata/*"]),
- embed = [":go_default_library"],
- importpath = "github.com/go-resty/resty/v2",
- deps = [
- "@org_golang_x_net//proxy:go_default_library",
+ name = "resty_test",
+ srcs = [
+ "client_test.go",
+ "context_test.go",
+ "example_test.go",
+ "request_test.go",
+ "resty_test.go",
+ "retry_test.go",
+ "util_test.go",
],
+ data = glob([".testdata/*"]),
+ embed = [":resty"],
+ deps = ["@org_golang_x_net//proxy:go_default_library"],
+)
+
+alias(
+ name = "go_default_library",
+ actual = ":resty",
+ visibility = ["//visibility:public"],
)
diff --git a/README.md b/README.md
index 819fbf3..8ec6518 100644
--- a/README.md
+++ b/README.md
@@ -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://travis-ci.org/go-resty/resty"><img src="https://travis-ci.org/go-resty/resty.svg?branch=master" 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.6.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 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.7.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>
@@ -13,7 +13,7 @@
## News
- * v2.6.0 [released](https://github.com/go-resty/resty/releases/tag/v2.6.0) and tagged on Apr 09, 2021.
+ * v2.7.0 [released](https://github.com/go-resty/resty/releases/tag/v2.7.0) and tagged on Nov 03, 2021.
* 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).
@@ -36,6 +36,7 @@
- Success scenario [Request.SetResult()](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetResult) and [Response.Result()](https://pkg.go.dev/github.com/go-resty/resty/v2#Response.Result).
- Error scenario [Request.SetError()](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetError) and [Response.Error()](https://pkg.go.dev/github.com/go-resty/resty/v2#Response.Error).
- Supports [RFC7807](https://tools.ietf.org/html/rfc7807) - `application/problem+json` & `application/problem+xml`
+ * Resty provides an option to override [JSON Marshal/Unmarshal and XML Marshal/Unmarshal](#override-json--xml-marshalunmarshal)
* Easy to upload one or more file(s) via `multipart/form-data`
* Auto detects file content type
* Request URL [Path Params (aka URI Params)](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetPathParams)
@@ -107,7 +108,7 @@ Resty author also published following projects for Go Community.
```bash
# Go Modules
-require github.com/go-resty/resty/v2 v2.4.0
+require github.com/go-resty/resty/v2 v2.7.0
```
## Usage
@@ -359,6 +360,24 @@ resp, err := client.R().
Options("https://myapp.com/servers/nyc-dc-01")
```
+#### Override JSON & XML Marshal/Unmarshal
+
+User could register choice of JSON/XML library into resty or write your own. By default resty registers standard `encoding/json` and `encoding/xml` respectively.
+```go
+// Example of registering json-iterator
+import jsoniter "github.com/json-iterator/go"
+
+json := jsoniter.ConfigCompatibleWithStandardLibrary
+
+client := resty.New()
+client.JSONMarshal = json.Marshal
+client.JSONUnmarshal = json.Unmarshal
+
+// similarly user could do for XML too with -
+client.XMLMarshal
+client.XMLUnmarshal
+```
+
### Multipart File(s) upload
#### Using io.Reader
@@ -829,13 +848,13 @@ client.SetTransport(&transport).SetScheme("http").SetHostURL(unixSocket)
client.R().Get("/index.html")
```
-#### Bazel support
+#### Bazel Support
Resty can be built, tested and depended upon via [Bazel](https://bazel.build).
For example, to run all tests:
```shell
-bazel test :go_default_test
+bazel test :resty_test
```
#### Mocking http requests using [httpmock](https://github.com/jarcoal/httpmock) library
@@ -876,7 +895,7 @@ BTW, I'd like to know what you think about `Resty`. Kindly open an issue or send
## Core Team
-Have a look on [Members](https://github.com/orgs/go-resty/teams/core/members) page.
+Have a look on [Members](https://github.com/orgs/go-resty/people) page.
## Contributors
diff --git a/WORKSPACE b/WORKSPACE
index 5459d63..9ef03e9 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,26 +1,30 @@
workspace(name = "resty")
-git_repository(
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+http_archive(
name = "io_bazel_rules_go",
- remote = "https://github.com/bazelbuild/rules_go.git",
- tag = "0.13.0",
+ sha256 = "69de5c704a05ff37862f7e0f5534d4f479418afc21806c887db544a316f3cb6b",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.27.0/rules_go-v0.27.0.tar.gz",
+ "https://github.com/bazelbuild/rules_go/releases/download/v0.27.0/rules_go-v0.27.0.tar.gz",
+ ],
)
-git_repository(
+http_archive(
name = "bazel_gazelle",
- remote = "https://github.com/bazelbuild/bazel-gazelle.git",
- tag = "0.13.0",
+ sha256 = "62ca106be173579c0a167deb23358fdfe71ffa1e4cfdddf5582af26520f1c66f",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.23.0/bazel-gazelle-v0.23.0.tar.gz",
+ "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.23.0/bazel-gazelle-v0.23.0.tar.gz",
+ ],
)
-load(
- "@io_bazel_rules_go//go:def.bzl",
- "go_rules_dependencies",
- "go_register_toolchains",
-)
+load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
go_rules_dependencies()
-go_register_toolchains()
+go_register_toolchains(version = "1.16")
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
diff --git a/client.go b/client.go
index 36b9f9c..1a03efa 100644
--- a/client.go
+++ b/client.go
@@ -10,6 +10,7 @@ import (
"crypto/tls"
"crypto/x509"
"encoding/json"
+ "encoding/xml"
"errors"
"fmt"
"io"
@@ -92,9 +93,11 @@ type (
// Resty also provides an options to override most of the client settings
// at request level.
type Client struct {
- HostURL string
+ BaseURL string
+ HostURL string // Deprecated: use BaseURL instead. To be removed in v3.0.0 release.
QueryParam url.Values
FormData url.Values
+ PathParams map[string]string
Header http.Header
UserInfo *User
Token string
@@ -112,6 +115,8 @@ type Client struct {
RetryAfter RetryAfterFunc
JSONMarshal func(v interface{}) ([]byte, error)
JSONUnmarshal func(data []byte, v interface{}) error
+ XMLMarshal func(v interface{}) ([]byte, error)
+ XMLUnmarshal func(data []byte, v interface{}) error
// HeaderAuthorizationKey is used to set/access Request Authorization header
// value when `SetAuthToken` option is used.
@@ -125,7 +130,6 @@ type Client struct {
debugBodySizeLimit int64
outputDirectory string
scheme string
- pathParams map[string]string
log Logger
httpClient *http.Client
proxyURL *url.URL
@@ -154,8 +158,25 @@ type User struct {
//
// // Setting HTTPS address
// client.SetHostURL("https://myjeeva.com")
+//
+// Deprecated: use SetBaseURL instead. To be removed in v3.0.0 release.
func (c *Client) SetHostURL(url string) *Client {
- c.HostURL = strings.TrimRight(url, "/")
+ c.SetBaseURL(url)
+ return c
+}
+
+// SetBaseURL method is to set Base URL in the client instance. It will be used with request
+// raised from this client with relative URL
+// // Setting HTTP address
+// client.SetBaseURL("http://myjeeva.com")
+//
+// // Setting HTTPS address
+// client.SetBaseURL("https://myjeeva.com")
+//
+// Since v2.7.0
+func (c *Client) SetBaseURL(url string) *Client {
+ c.BaseURL = strings.TrimRight(url, "/")
+ c.HostURL = c.BaseURL
return c
}
@@ -367,7 +388,7 @@ func (c *Client) R() *Request {
client: c,
multipartFiles: []*File{},
multipartFields: []*MultipartField{},
- pathParams: map[string]string{},
+ PathParams: map[string]string{},
jsonEscapeHTML: true,
}
return r
@@ -589,6 +610,9 @@ func (c *Client) SetRetryAfter(callback RetryAfterFunc) *Client {
// AddRetryCondition method adds a retry condition function to array of functions
// that are checked to determine if the request is retried. The request will
// retry if any of the functions return true and error is nil.
+//
+// Note: These retry conditions are applied on all Request made using this Client.
+// For Request specific retry conditions check *Request.AddRetryCondition
func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client {
c.RetryConditions = append(c.RetryConditions, condition)
return c
@@ -758,7 +782,7 @@ func (c *Client) SetTransport(transport http.RoundTripper) *Client {
// client.SetScheme("http")
func (c *Client) SetScheme(scheme string) *Client {
if !IsStringEmpty(scheme) {
- c.scheme = scheme
+ c.scheme = strings.TrimSpace(scheme)
}
return c
}
@@ -793,7 +817,7 @@ func (c *Client) SetDoNotParseResponse(parse bool) *Client {
// Also it can be overridden at request level Path Params options,
// see `Request.SetPathParam` or `Request.SetPathParams`.
func (c *Client) SetPathParam(param, value string) *Client {
- c.pathParams[param] = value
+ c.PathParams[param] = value
return c
}
@@ -869,7 +893,6 @@ func (c *Client) GetClient() *http.Client {
// Executes method executes the given `Request` object and returns response
// error.
func (c *Client) execute(req *Request) (*Response, error) {
- defer releaseBuffer(req.bodyBuf)
// Apply Request middleware
var err error
@@ -903,6 +926,8 @@ func (c *Client) execute(req *Request) (*Response, error) {
return nil, wrapNoRetryErr(err)
}
+ req.RawRequest.Body = newRequestBodyReleaser(req.RawRequest.Body, req.bodyBuf)
+
req.Time = time.Now()
resp, err := c.httpClient.Do(req.RawRequest)
@@ -1052,13 +1077,16 @@ func createClient(hc *http.Client) *Client {
Cookies: make([]*http.Cookie, 0),
RetryWaitTime: defaultWaitTime,
RetryMaxWaitTime: defaultMaxWaitTime,
+ PathParams: make(map[string]string),
JSONMarshal: json.Marshal,
JSONUnmarshal: json.Unmarshal,
+ XMLMarshal: xml.Marshal,
+ XMLUnmarshal: xml.Unmarshal,
HeaderAuthorizationKey: http.CanonicalHeaderKey("Authorization"),
- jsonEscapeHTML: true,
- httpClient: hc,
- debugBodySizeLimit: math.MaxInt32,
- pathParams: make(map[string]string),
+
+ jsonEscapeHTML: true,
+ httpClient: hc,
+ debugBodySizeLimit: math.MaxInt32,
}
// Logger
diff --git a/client_test.go b/client_test.go
index 790fcb4..84ae715 100644
--- a/client_test.go
+++ b/client_test.go
@@ -744,3 +744,46 @@ func TestResponseError(t *testing.T) {
assertNotNil(t, re.Unwrap())
assertEqual(t, err.Error(), re.Error())
}
+
+func TestHostURLForGH318AndGH407(t *testing.T) {
+ ts := createPostServer(t)
+ defer ts.Close()
+
+ targetURL, _ := url.Parse(ts.URL)
+ t.Log("ts.URL:", ts.URL)
+ t.Log("targetURL.Host:", targetURL.Host)
+ // Sample output
+ // ts.URL: http://127.0.0.1:55967
+ // targetURL.Host: 127.0.0.1:55967
+
+ // Unable use the local http test server for this
+ // use case testing
+ //
+ // using `targetURL.Host` value or test case yield to ERROR
+ // "parse "127.0.0.1:55967": first path segment in URL cannot contain colon"
+
+ // test the functionality with httpbin.org locally
+ // will figure out later
+
+ c := dc()
+ // c.SetScheme("http")
+ // c.SetHostURL(targetURL.Host + "/")
+
+ // t.Log("with leading `/`")
+ // resp, err := c.R().Post("/login")
+ // assertNil(t, err)
+ // assertNotNil(t, resp)
+
+ // t.Log("\nwithout leading `/`")
+ // resp, err = c.R().Post("login")
+ // assertNil(t, err)
+ // assertNotNil(t, resp)
+
+ t.Log("with leading `/` on request & with trailing `/` on host url")
+ c.SetHostURL(ts.URL + "/")
+ resp, err := c.R().
+ SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}).
+ Post("/login")
+ assertNil(t, err)
+ assertNotNil(t, resp)
+}
diff --git a/debian/changelog b/debian/changelog
index ee6af1c..d01bd7b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-github-go-resty-resty (2.7.0-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Fri, 11 Nov 2022 01:23:04 -0000
+
golang-github-go-resty-resty (2.6.0-1) unstable; urgency=medium
* New upstream version.
diff --git a/go.mod b/go.mod
index 0383ef8..5e78bdc 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,5 @@
module github.com/go-resty/resty/v2
-require golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
+require golang.org/x/net v0.0.0-20211029224645-99673261e6eb
go 1.11
diff --git a/go.sum b/go.sum
index bba7211..07a4515 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
+golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/middleware.go b/middleware.go
index 06ed48b..0e8ac2b 100644
--- a/middleware.go
+++ b/middleware.go
@@ -6,7 +6,6 @@ package resty
import (
"bytes"
- "encoding/xml"
"errors"
"fmt"
"io"
@@ -29,13 +28,13 @@ const debugRequestLogKey = "__restyDebugRequestLog"
func parseRequestURL(c *Client, r *Request) error {
// GitHub #103 Path Params
- if len(r.pathParams) > 0 {
- for p, v := range r.pathParams {
+ if len(r.PathParams) > 0 {
+ for p, v := range r.PathParams {
r.URL = strings.Replace(r.URL, "{"+p+"}", url.PathEscape(v), -1)
}
}
- if len(c.pathParams) > 0 {
- for p, v := range c.pathParams {
+ if len(c.PathParams) > 0 {
+ for p, v := range c.PathParams {
r.URL = strings.Replace(r.URL, "{"+p+"}", url.PathEscape(v), -1)
}
}
@@ -60,6 +59,11 @@ func parseRequestURL(c *Client, r *Request) error {
}
}
+ // GH #407 && #318
+ if reqURL.Scheme == "" && len(c.scheme) > 0 {
+ reqURL.Scheme = c.scheme
+ }
+
// Adding Query Param
query := make(url.Values)
for k, v := range c.QueryParam {
@@ -191,12 +195,6 @@ func createHTTPRequest(c *Client, r *Request) (err error) {
r.RawRequest.AddCookie(cookie)
}
- // it's for non-http scheme option
- if r.RawRequest.URL != nil && r.RawRequest.URL.Scheme == "" {
- r.RawRequest.URL.Scheme = c.scheme
- r.RawRequest.URL.Host = r.URL
- }
-
// Enable trace
if c.trace || r.trace {
r.clientTrace = &clientTrace{}
@@ -458,12 +456,12 @@ func handleRequestBody(c *Client, r *Request) (err error) {
bodyBytes = []byte(s)
} else if IsJSONType(contentType) &&
(kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) {
- bodyBytes, err = jsonMarshal(c, r, r.Body)
+ r.bodyBuf, err = jsonMarshal(c, r, r.Body)
if err != nil {
return
}
} else if IsXMLType(contentType) && (kind == reflect.Struct) {
- bodyBytes, err = xml.Marshal(r.Body)
+ bodyBytes, err = c.XMLMarshal(r.Body)
if err != nil {
return
}
diff --git a/request.go b/request.go
index 41709cf..672df88 100644
--- a/request.go
+++ b/request.go
@@ -33,6 +33,7 @@ type Request struct {
AuthScheme string
QueryParam url.Values
FormData url.Values
+ PathParams map[string]string
Header http.Header
Time time.Time
Body interface{}
@@ -60,13 +61,13 @@ type Request struct {
fallbackContentType string
forceContentType string
ctx context.Context
- pathParams map[string]string
values map[string]interface{}
client *Client
bodyBuf *bytes.Buffer
clientTrace *clientTrace
multipartFiles []*File
multipartFields []*MultipartField
+ retryConditions []RetryConditionFunc
}
// Context method returns the Context if its already set in request
@@ -117,6 +118,22 @@ func (r *Request) SetHeaders(headers map[string]string) *Request {
return r
}
+// SetHeaderMultiValues sets multiple headers fields and its values is list of strings at one go in the current request.
+//
+// For Example: To set `Accept` as `text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8`
+//
+// client.R().
+// SetHeaderMultiValues(map[string][]string{
+// "Accept": []string{"text/html", "application/xhtml+xml", "application/xml;q=0.9", "image/webp", "*/*;q=0.8"},
+// })
+// Also you can override header value, which was set at client instance level.
+func (r *Request) SetHeaderMultiValues(headers map[string][]string) *Request {
+ for key, values := range headers {
+ r.SetHeader(key, strings.Join(values, ", "))
+ }
+ return r
+}
+
// SetHeaderVerbatim method is to set a single header field and its value verbatim in the current request.
//
// For Example: To set `all_lowercase` and `UPPERCASE` as `available`.
@@ -494,7 +511,7 @@ func (r *Request) SetDoNotParseResponse(parse bool) *Request {
// It replaces the value of the key while composing the request URL. Also you can
// override Path Params value, which was set at client instance level.
func (r *Request) SetPathParam(param, value string) *Request {
- r.pathParams[param] = value
+ r.PathParams[param] = value
return r
}
@@ -577,6 +594,18 @@ func (r *Request) SetCookies(rs []*http.Cookie) *Request {
return r
}
+// AddRetryCondition method adds a retry condition function to the request's
+// array of functions that are checked to determine if the request is retried.
+// The request will retry if any of the functions return true and error is nil.
+//
+// Note: These retry conditions are checked before all retry conditions of the client.
+//
+// Since v2.7.0
+func (r *Request) AddRetryCondition(condition RetryConditionFunc) *Request {
+ r.retryConditions = append(r.retryConditions, condition)
+ return r
+}
+
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
// HTTP request tracing
//_______________________________________________________________________
@@ -747,7 +776,7 @@ func (r *Request) Execute(method, url string) (*Response, error) {
Retries(r.client.RetryCount),
WaitTime(r.client.RetryWaitTime),
MaxWaitTime(r.client.RetryMaxWaitTime),
- RetryConditions(r.client.RetryConditions),
+ RetryConditions(append(r.retryConditions, r.client.RetryConditions...)),
RetryHooks(r.client.RetryHooks),
)
@@ -854,11 +883,14 @@ func (r *Request) initValuesMap() {
}
}
-var noescapeJSONMarshal = func(v interface{}) ([]byte, error) {
+var noescapeJSONMarshal = func(v interface{}) (*bytes.Buffer, error) {
buf := acquireBuffer()
- defer releaseBuffer(buf)
encoder := json.NewEncoder(buf)
encoder.SetEscapeHTML(false)
- err := encoder.Encode(v)
- return buf.Bytes(), err
+ if err := encoder.Encode(v); err != nil {
+ releaseBuffer(buf)
+ return nil, err
+ }
+
+ return buf, nil
}
diff --git a/request_test.go b/request_test.go
index 368463a..3d0dc26 100644
--- a/request_test.go
+++ b/request_test.go
@@ -1371,6 +1371,19 @@ func TestSetHeaderVerbatim(t *testing.T) {
assertEqual(t, "value_standard", r.Header.Get("Header-Lowercase"))
}
+func TestSetHeaderMultipleValue(t *testing.T) {
+ ts := createPostServer(t)
+ defer ts.Close()
+
+ r := dclr().
+ SetHeaderMultiValues(map[string][]string{
+ "Content": {"text/*", "text/html", "*"},
+ "Authorization": {"Bearer xyz"},
+ })
+ assertEqual(t, "text/*, text/html, *", r.Header.Get("content"))
+ assertEqual(t, "Bearer xyz", r.Header.Get("authorization"))
+}
+
func TestOutputFileWithBaseDirAndRelativePath(t *testing.T) {
ts := createGetServer(t)
defer ts.Close()
diff --git a/resty.go b/resty.go
index 2a53e06..6f9c8b4 100644
--- a/resty.go
+++ b/resty.go
@@ -14,7 +14,7 @@ import (
)
// Version # of resty
-const Version = "2.6.0"
+const Version = "2.7.0"
// New method creates a new Resty client.
func New() *Client {
diff --git a/retry.go b/retry.go
index a841c46..00b8514 100644
--- a/retry.go
+++ b/retry.go
@@ -129,6 +129,12 @@ func Backoff(operation func() (*Response, error), options ...Option) error {
hook(resp, err)
}
+ // Don't need to wait when no retries left.
+ // Still run retry hooks even on last retry to keep compatibility.
+ if attempt == opts.maxRetries {
+ return err
+ }
+
waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt)
if err2 != nil {
if err == nil {
diff --git a/retry_test.go b/retry_test.go
index e302216..9f8fb38 100644
--- a/retry_test.go
+++ b/retry_test.go
@@ -32,6 +32,39 @@ func TestBackoffSuccess(t *testing.T) {
assertEqual(t, externalCounter, attempts)
}
+func TestBackoffNoWaitForLastRetry(t *testing.T) {
+ attempts := 1
+ externalCounter := 0
+ numRetries := 1
+
+ canceledCtx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ resp := &Response{
+ Request: &Request{
+ ctx: canceledCtx,
+ client: &Client{
+ RetryAfter: func(*Client, *Response) (time.Duration, error) {
+ return 6, nil
+ },
+ },
+ },
+ }
+
+ retryErr := Backoff(func() (*Response, error) {
+ externalCounter++
+ return resp, nil
+ }, RetryConditions([]RetryConditionFunc{func(response *Response, err error) bool {
+ if externalCounter == attempts + numRetries {
+ // Backoff returns context canceled if goes to sleep after last retry.
+ cancel()
+ }
+ return true
+ }}), Retries(numRetries))
+
+ assertNil(t, retryErr)
+}
+
func TestBackoffTenAttemptsSuccess(t *testing.T) {
attempts := 10
externalCounter := 0
@@ -169,8 +202,8 @@ func TestClientRetryGet(t *testing.T) {
assertNotNil(t, resp.Body())
assertEqual(t, 0, len(resp.Header()))
- assertEqual(t, true, (strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
- strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\"")))
+ assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
+ strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\""))
}
func TestClientRetryWait(t *testing.T) {
@@ -639,8 +672,8 @@ func TestClientRetryCount(t *testing.T) {
// 2 attempts were made
assertEqual(t, attempt, 2)
- assertEqual(t, true, (strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
- strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\"")))
+ assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
+ strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\""))
}
func TestClientErrorRetry(t *testing.T) {
@@ -693,8 +726,8 @@ func TestClientRetryHook(t *testing.T) {
assertEqual(t, 3, attempt)
- assertEqual(t, true, (strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
- strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\"")))
+ assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
+ strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\""))
}
func filler(*Response, error) bool {
diff --git a/util.go b/util.go
index b017256..1d563be 100644
--- a/util.go
+++ b/util.go
@@ -6,7 +6,6 @@ package resty
import (
"bytes"
- "encoding/xml"
"fmt"
"io"
"log"
@@ -19,6 +18,7 @@ import (
"runtime"
"sort"
"strings"
+ "sync"
)
//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
@@ -108,7 +108,7 @@ func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) {
if IsJSONType(ct) {
err = c.JSONUnmarshal(b, d)
} else if IsXMLType(ct) {
- err = xml.Unmarshal(b, d)
+ err = c.XMLUnmarshal(b, d)
}
return
@@ -139,13 +139,19 @@ type ResponseLog struct {
//_______________________________________________________________________
// way to disable the HTML escape as opt-in
-func jsonMarshal(c *Client, r *Request, d interface{}) ([]byte, error) {
- if !r.jsonEscapeHTML {
- return noescapeJSONMarshal(d)
- } else if !c.jsonEscapeHTML {
+func jsonMarshal(c *Client, r *Request, d interface{}) (*bytes.Buffer, error) {
+ if !r.jsonEscapeHTML || !c.jsonEscapeHTML {
return noescapeJSONMarshal(d)
}
- return c.JSONMarshal(d)
+
+ data, err := c.JSONMarshal(d)
+ if err != nil {
+ return nil, err
+ }
+
+ buf := acquireBuffer()
+ _, _ = buf.Write(data)
+ return buf, nil
}
func firstNonEmpty(v ...string) string {
@@ -195,7 +201,7 @@ func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r i
// Auto detect actual multipart content type
cbuf := make([]byte, 512)
size, err := r.Read(cbuf)
- if err != nil {
+ if err != nil && err != io.EOF {
return err
}
@@ -283,6 +289,34 @@ func releaseBuffer(buf *bytes.Buffer) {
}
}
+// requestBodyReleaser wraps requests's body and implements custom Close for it.
+// The Close method closes original body and releases request body back to sync.Pool.
+type requestBodyReleaser struct {
+ releaseOnce sync.Once
+ reqBuf *bytes.Buffer
+ io.ReadCloser
+}
+
+func newRequestBodyReleaser(respBody io.ReadCloser, reqBuf *bytes.Buffer) io.ReadCloser {
+ if reqBuf == nil {
+ return respBody
+ }
+
+ return &requestBodyReleaser{
+ reqBuf: reqBuf,
+ ReadCloser: respBody,
+ }
+}
+
+func (rr *requestBodyReleaser) Close() error {
+ err := rr.ReadCloser.Close()
+ rr.releaseOnce.Do(func() {
+ releaseBuffer(rr.reqBuf)
+ })
+
+ return err
+}
+
func closeq(v interface{}) {
if c, ok := v.(io.Closer); ok {
silently(c.Close())
diff --git a/util_test.go b/util_test.go
index e5690d4..ef2bb91 100644
--- a/util_test.go
+++ b/util_test.go
@@ -5,6 +5,8 @@
package resty
import (
+ "bytes"
+ "mime/multipart"
"testing"
)
@@ -79,3 +81,17 @@ func TestIsXMLType(t *testing.T) {
}
}
}
+
+func TestWriteMultipartFormFileReaderEmpty(t *testing.T) {
+ w := multipart.NewWriter(bytes.NewBuffer(nil))
+ defer func() { _ = w.Close() }()
+ if err := writeMultipartFormFile(w, "foo", "bar", bytes.NewReader(nil)); err != nil {
+ t.Errorf("Got unexpected error: %v", err)
+ }
+}
+
+func TestWriteMultipartFormFileReaderError(t *testing.T) {
+ err := writeMultipartFormFile(nil, "", "", &brokenReadCloser{})
+ assertNotNil(t, err)
+ assertEqual(t, "read error", err.Error())
+}
Debdiff
File lists identical (after any substitutions)
No differences were encountered in the control files