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

More details

Full run details