New Upstream Release - golang-github-dghubble-sling
Ready changes
Summary
Merged new upstream version: 1.4.1 (was: 1.4.0).
Resulting package
Built on 2023-05-16T17:28 (took 4m19s)
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-dghubble-sling-dev
Lintian Result
Diff
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 0000000..6184161
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,9 @@
+version: 2
+updates:
+- package-ecosystem: gomod
+ directory: "/"
+ schedule:
+ interval: daily
+ pull-request-branch-name:
+ separator: "-"
+ open-pull-requests-limit: 3
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 0000000..aff546a
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,25 @@
+name: test
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+jobs:
+ build:
+ name: go
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ go: ['1.18', '1.19']
+ steps:
+ - name: setup
+ uses: actions/setup-go@v3
+ with:
+ go-version: ${{matrix.go}}
+
+ - name: checkout
+ uses: actions/checkout@v3
+
+ - name: test
+ run: make
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index a654faa..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-language: go
-go:
- - "1.10.x"
- - "1.11.x"
- - "1.12.x"
-install:
- - go get golang.org/x/lint/golint
- - go get -v -t .
-script:
- - make
-notifications:
- email: change
diff --git a/CHANGES.md b/CHANGES.md
index 0abb2e5..aa00a77 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -4,6 +4,15 @@ Notable changes between releases.
## Latest
+## v1.4.1
+
+* Update minimum Go version to v1.18 ([#76](https://github.com/dghubble/sling/pull/76))
+
+## v1.4.0
+
+* `Do` reads Body to reuse HTTP/1.x "keep-alive" TCP connections ([#59](https://github.com/dghubble/sling/pull/59))
+* `Receive` skips decoding if status is 204 (no content) ([#63](https://github.com/dghubble/sling/pull/63))
+
## v1.3.0
* Add Sling `ResponseDecoder` setter for receiving responses with a custom `ResponseDecoder` ([#49](https://github.com/dghubble/sling/pull/49))
diff --git a/Makefile b/Makefile
index ca4e069..fb990de 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
.PHONY: all
-all: test vet lint fmt
+all: test vet fmt
.PHONY: test
test:
@@ -9,10 +9,6 @@ test:
vet:
@go vet -all .
-.PHONY: lint
-lint:
- @golint -set_exit_status ./...
-
.PHONY: fmt
fmt:
@test -z $$(go fmt ./...)
diff --git a/README.md b/README.md
index ff793c6..8e9d7de 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-# Sling [![Build Status](https://travis-ci.org/dghubble/sling.svg?branch=master)](https://travis-ci.org/dghubble/sling) [![GoDoc](https://godoc.org/github.com/dghubble/sling?status.svg)](https://godoc.org/github.com/dghubble/sling)
+# Sling [![GoDoc](https://pkg.go.dev/badge/github.com/dghubble/sling.svg)](https://pkg.go.dev/github.com/dghubble/sling) [![Workflow](https://github.com/dghubble/sling/actions/workflows/test.yaml/badge.svg)](https://github.com/dghubble/sling/actions/workflows/test.yaml?query=branch%3Amain) [![Coverage](https://gocover.io/_badge/github.com/dghubble/sling)](https://gocover.io/github.com/dghubble/sling) [![Sponsors](https://img.shields.io/github/sponsors/dghubble?logo=github)](https://github.com/sponsors/dghubble) [![Twitter](https://img.shields.io/badge/twitter-follow-1da1f2?logo=twitter)](https://twitter.com/dghubble)
+
<img align="right" src="https://storage.googleapis.com/dghubble/small-gopher-with-sling.png">
Sling is a Go HTTP client library for creating and sending API requests.
@@ -272,10 +273,8 @@ func (s *IssueService) ListByRepo(owner, repo string, params *IssueListParams) (
* GoSquared [drinkin/go-gosquared](https://github.com/drinkin/go-gosquared)
* Kala [ajvb/kala](https://github.com/ajvb/kala)
* Parse [fergstar/go-parse](https://github.com/fergstar/go-parse)
-* Rdio [apriendeau/shares](https://github.com/apriendeau/shares)
* Swagger Generator [swagger-api/swagger-codegen](https://github.com/swagger-api/swagger-codegen)
* Twitter [dghubble/go-twitter](https://github.com/dghubble/go-twitter)
-* Hacker News [mirceamironenco/go-hackernews](https://github.com/mirceamironenco/go-hackernews)
* Stacksmith [jesustinoco/go-smith](https://github.com/jesustinoco/go-smith)
Create a Pull Request to add a link to your own API.
diff --git a/debian/changelog b/debian/changelog
index 6ab7016..6a80ead 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+golang-github-dghubble-sling (1.4.1-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Tue, 16 May 2023 17:24:45 -0000
+
golang-github-dghubble-sling (1.3.0-1) unstable; urgency=medium
[ Anthony Fok ]
diff --git a/doc.go b/doc.go
index dd2efb7..f48836c 100644
--- a/doc.go
+++ b/doc.go
@@ -5,7 +5,7 @@ Slings store HTTP Request properties to simplify sending requests and decoding
responses. Check the examples to learn how to compose a Sling into your API
client.
-Usage
+# Usage
Use a Sling to set path, method, header, query, or body properties and create an
http.Request.
@@ -18,7 +18,7 @@ http.Request.
req, err := sling.New().Get("https://example.com").QueryStruct(params).Request()
client.Do(req)
-Path
+# Path
Use Path to set or extend the URL for created Requests. Extension means the
path will be resolved relative to the existing URL.
@@ -31,14 +31,14 @@ except they set the HTTP method too.
req, err := sling.New().Post("http://upload.com/gophers")
-Headers
+# Headers
Add or Set headers for requests created by a Sling.
s := sling.New().Base(baseUrl).Set("User-Agent", "Gophergram API Client")
req, err := s.New().Get("gophergram/list").Request()
-QueryStruct
+# QueryStruct
Define url parameter structs (https://godoc.org/github.com/google/go-querystring/query).
Use QueryStruct to encode a struct as query parameters on requests.
@@ -59,7 +59,7 @@ Use QueryStruct to encode a struct as query parameters on requests.
params := &IssueParams{Sort: "updated", State: "open"}
req, err := githubBase.New().Get(path).QueryStruct(params).Request()
-Json Body
+# Json Body
Define JSON tagged structs (https://golang.org/pkg/encoding/json/).
Use BodyJSON to JSON encode a struct as the Body on requests.
@@ -83,7 +83,7 @@ Use BodyJSON to JSON encode a struct as the Body on requests.
Requests will include an "application/json" Content-Type header.
-Form Body
+# Form Body
Define url tagged structs (https://godoc.org/github.com/google/go-querystring/query).
Use BodyForm to form url encode a struct as the Body on requests.
@@ -100,7 +100,7 @@ Use BodyForm to form url encode a struct as the Body on requests.
Requests will include an "application/x-www-form-urlencoded" Content-Type
header.
-Plain Body
+# Plain Body
Use Body to set a plain io.Reader on requests created by a Sling.
@@ -109,7 +109,7 @@ Use Body to set a plain io.Reader on requests created by a Sling.
Set a content type header, if desired (e.g. Set("Content-Type", "text/plain")).
-Extend a Sling
+# Extend a Sling
Each Sling generates an http.Request (say with some path and query params)
each time Request() is called, based on its state. When creating
@@ -137,7 +137,7 @@ is undesired.
Recap: If you wish to extend a Sling, create a new child copy with New().
-Receive
+# Receive
Define a JSON struct to decode a type from 2XX success responses. Use
ReceiveSuccess(successV interface{}) to send a new Request and decode the
diff --git a/examples/go.mod b/examples/go.mod
index 4a7b7a5..f81cba0 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -1,9 +1,9 @@
module github.com/dghubble/sling/examples
-go 1.12
+go 1.16
require (
- github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
- github.com/dghubble/sling v1.2.0 // indirect
- golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
+ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f
+ github.com/dghubble/sling v1.3.0
+ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
)
diff --git a/examples/go.sum b/examples/go.sum
index 5d6c54f..1df91b7 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -3,8 +3,13 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbp
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/dghubble/sling v1.2.0 h1:PYGS9ofwbV9nfhB1kYjB1vtXshMxlp2oQxTMMXVJ5pE=
github.com/dghubble/sling v1.2.0/go.mod h1:ZcPRuLm0qrcULW2gOrjXrAWgf76sahqSyxXyVOvkunE=
+github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU=
+github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
diff --git a/go.mod b/go.mod
index 9d2f0fe..67acad9 100644
--- a/go.mod
+++ b/go.mod
@@ -1,5 +1,5 @@
module github.com/dghubble/sling
-go 1.12
+go 1.18
-require github.com/google/go-querystring v1.0.0
+require github.com/google/go-querystring v1.1.0
diff --git a/go.sum b/go.sum
index 0bb18dc..f99081b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,5 @@
-github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
-github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/sling.go b/sling.go
index fe9c54e..57d7ad5 100644
--- a/sling.go
+++ b/sling.go
@@ -3,6 +3,7 @@ package sling
import (
"encoding/base64"
"io"
+ "io/ioutil"
"net/http"
"net/url"
@@ -54,9 +55,9 @@ func New() *Sling {
// New returns a copy of a Sling for creating a new Sling with properties
// from a parent Sling. For example,
//
-// parentSling := sling.New().Client(client).Base("https://api.io/")
-// fooSling := parentSling.New().Get("foo/")
-// barSling := parentSling.New().Get("bar/")
+// parentSling := sling.New().Client(client).Base("https://api.io/")
+// fooSling := parentSling.New().Get("foo/")
+// barSling := parentSling.New().Get("bar/")
//
// fooSling and barSling will both use the same client, but send requests to
// https://api.io/foo/ and https://api.io/bar/ respectively.
@@ -359,8 +360,9 @@ func (s *Sling) ReceiveSuccess(successV interface{}) (*http.Response, error) {
// Receive creates a new HTTP request and returns the response. Success
// responses (2XX) are JSON decoded into the value pointed to by successV and
// other responses are JSON decoded into the value pointed to by failureV.
-// Any error creating the request, sending it, or decoding the response is
-// returned.
+// If the status code of response is 204(no content) or the Content-Lenght is 0,
+// decoding is skipped. Any error creating the request, sending it, or decoding
+// the response is returned.
// Receive is shorthand for calling Request and Do.
func (s *Sling) Receive(successV, failureV interface{}) (*http.Response, error) {
req, err := s.Request()
@@ -373,7 +375,9 @@ func (s *Sling) Receive(successV, failureV interface{}) (*http.Response, error)
// Do sends an HTTP request and returns the response. Success responses (2XX)
// are JSON decoded into the value pointed to by successV and other responses
// are JSON decoded into the value pointed to by failureV.
-// Any error sending the request or decoding the response is returned.
+// If the status code of response is 204(no content) or the Content-Length is 0,
+// decoding is skipped. Any error sending the request or decoding the response
+// is returned.
func (s *Sling) Do(req *http.Request, successV, failureV interface{}) (*http.Response, error) {
resp, err := s.httpClient.Do(req)
if err != nil {
@@ -382,8 +386,14 @@ func (s *Sling) Do(req *http.Request, successV, failureV interface{}) (*http.Res
// when err is nil, resp contains a non-nil resp.Body which must be closed
defer resp.Body.Close()
- // Don't try to decode on 204s
- if resp.StatusCode == 204 {
+ // The default HTTP client's Transport may not
+ // reuse HTTP/1.x "keep-alive" TCP connections if the Body is
+ // not read to completion and closed.
+ // See: https://golang.org/pkg/net/http/#Response
+ defer io.Copy(ioutil.Discard, resp.Body)
+
+ // Don't try to decode on 204s or Content-Length is 0
+ if resp.StatusCode == http.StatusNoContent || resp.ContentLength == 0 {
return resp, nil
}
diff --git a/sling_test.go b/sling_test.go
index 5fa70fc..5d4c1ef 100644
--- a/sling_test.go
+++ b/sling_test.go
@@ -2,17 +2,20 @@ package sling
import (
"bytes"
+ "context"
"encoding/xml"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
+ "net"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"strings"
+ "sync/atomic"
"testing"
)
@@ -665,6 +668,36 @@ func TestDo_onSuccessWithNilValue(t *testing.T) {
}
}
+func TestDo_noContent(t *testing.T) {
+ client, mux, server := testServer()
+ defer server.Close()
+ mux.HandleFunc("/nocontent", func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(204)
+ })
+
+ sling := New().Client(client)
+ req, _ := http.NewRequest("DELETE", "http://example.com/nocontent", nil)
+
+ model := new(FakeModel)
+ apiError := new(APIError)
+ resp, err := sling.Do(req, model, apiError)
+
+ if err != nil {
+ t.Errorf("expected nil, got %v", err)
+ }
+ if resp.StatusCode != 204 {
+ t.Errorf("expected %d, got %d", 204, resp.StatusCode)
+ }
+ expectedModel := &FakeModel{}
+ if !reflect.DeepEqual(expectedModel, model) {
+ t.Errorf("successV should not be populated, exepcted %v, got %v", expectedModel, model)
+ }
+ expectedAPIError := &APIError{}
+ if !reflect.DeepEqual(expectedAPIError, apiError) {
+ t.Errorf("failureV should not be populated, exepcted %v, got %v", expectedAPIError, apiError)
+ }
+}
+
func TestDo_onFailure(t *testing.T) {
const expectedMessage = "Invalid argument"
const expectedCode int = 215
@@ -736,7 +769,7 @@ func TestReceive_success_nonDefaultDecoder(t *testing.T) {
<temperature>10.5</temperature>
</response>`
fmt.Fprintf(w, xml.Header)
- fmt.Fprintf(w, data)
+ fmt.Fprint(w, data)
})
endpoint := New().Client(client).Base("http://example.com/").Path("foo/").Post("submit")
@@ -795,6 +828,38 @@ func TestReceive_success(t *testing.T) {
}
}
+func TestReceive_StatusOKNoContent(t *testing.T) {
+ client, mux, server := testServer()
+ defer server.Close()
+ mux.HandleFunc("/foo/submit", func(w http.ResponseWriter, r *http.Request) {
+ assertMethod(t, "POST", r)
+ w.WriteHeader(201)
+ w.Header().Set("Location", "/foo/latest")
+ })
+
+ endpoint := New().Client(client).Base("http://example.com/").Path("foo/").Post("submit")
+ // fake a post response for testing purposes, checking that it's valid happens in other tests
+ params := FakeParams{}
+ model := new(FakeModel)
+ apiError := new(APIError)
+ resp, err := endpoint.New().BodyForm(params).Receive(model, apiError)
+
+ if err != nil {
+ t.Errorf("expected nil, got %v", err)
+ }
+ if resp.StatusCode != 201 {
+ t.Errorf("expected %d, got %d", 201, resp.StatusCode)
+ }
+ expectedModel := &FakeModel{}
+ if !reflect.DeepEqual(expectedModel, model) {
+ t.Errorf("expected %v, got %v", expectedModel, model)
+ }
+ expectedAPIError := &APIError{}
+ if !reflect.DeepEqual(expectedAPIError, apiError) {
+ t.Errorf("failureV should be zero valued, exepcted %v, got %v", expectedAPIError, apiError)
+ }
+}
+
func TestReceive_failure(t *testing.T) {
client, mux, server := testServer()
defer server.Close()
@@ -830,6 +895,25 @@ func TestReceive_failure(t *testing.T) {
}
}
+func TestReceive_noContent(t *testing.T) {
+ client, mux, server := testServer()
+ defer server.Close()
+ mux.HandleFunc("/foo/submit", func(w http.ResponseWriter, r *http.Request) {
+ assertMethod(t, "HEAD", r)
+ w.WriteHeader(204)
+ })
+
+ endpoint := New().Client(client).Base("http://example.com/").Path("foo/").Head("submit")
+ resp, err := endpoint.New().Receive(nil, nil)
+
+ if err != nil {
+ t.Errorf("expected nil, got %v", err)
+ }
+ if resp.StatusCode != 204 {
+ t.Errorf("expected %d, got %d", 204, resp.StatusCode)
+ }
+}
+
func TestReceive_errorCreatingRequest(t *testing.T) {
expectedErr := errors.New("json: unsupported value: +Inf")
resp, err := New().BodyJSON(FakeModel{Temperature: math.Inf(1)}).Receive(nil, nil)
@@ -841,6 +925,45 @@ func TestReceive_errorCreatingRequest(t *testing.T) {
}
}
+func TestReuseTcpConnections(t *testing.T) {
+ var connCount int32
+
+ ln, _ := net.Listen("tcp", ":0")
+ rawURL := fmt.Sprintf("http://%s/", ln.Addr())
+
+ server := http.Server{
+ Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ assertMethod(t, "GET", r)
+ fmt.Fprintf(w, `{"text": "Some text"}`)
+ }),
+ ConnState: func(conn net.Conn, state http.ConnState) {
+ if state == http.StateNew {
+ atomic.AddInt32(&connCount, 1)
+ }
+ },
+ }
+
+ go server.Serve(ln)
+
+ endpoint := New().Client(http.DefaultClient).Base(rawURL).Path("foo/").Get("get")
+
+ for i := 0; i < 10; i++ {
+ resp, err := endpoint.New().Receive(nil, nil)
+ if err != nil {
+ t.Errorf("expected nil, got %v", err)
+ }
+ if resp.StatusCode != 200 {
+ t.Errorf("expected %d, got %d", 200, resp.StatusCode)
+ }
+ }
+
+ server.Shutdown(context.Background())
+
+ if count := atomic.LoadInt32(&connCount); count != 1 {
+ t.Errorf("expected 1, got %v", count)
+ }
+}
+
// Testing Utils
// testServer returns an http Client, ServeMux, and Server. The client proxies
Debdiff
File lists identical (after any substitutions)
No differences were encountered in the control files