Merge pull request #32127 from ripcurld0/redirect_go18

Return error on client redirect
This commit is contained in:
Brian Goff 2017-05-11 12:48:56 -04:00 committed by GitHub
commit e3a30ffca6
2 changed files with 74 additions and 1 deletions

View file

@ -46,6 +46,7 @@ For example, to list running containers (the equivalent of "docker ps"):
package client
import (
"errors"
"fmt"
"net/http"
"net/url"
@ -58,6 +59,9 @@ import (
"github.com/docker/go-connections/tlsconfig"
)
// ErrRedirect is the error returned by checkRedirect when the request is non-GET.
var ErrRedirect = errors.New("unexpected redirect in response")
// Client is the API client that performs all operations
// against a docker server.
type Client struct {
@ -81,6 +85,23 @@ type Client struct {
manualOverride bool
}
// CheckRedirect specifies the policy for dealing with redirect responses:
// If the request is non-GET return `ErrRedirect`. Otherwise use the last response.
//
// Go 1.8 changes behavior for HTTP redirects (specificlaly 301, 307, and 308) in the client .
// The Docker client (and by extension docker API client) can be made to to send a request
// like POST /containers//start where what would normally be in the name section of the URL is empty.
// This triggers an HTTP 301 from the daemon.
// In go 1.8 this 301 will be converted to a GET request, and ends up getting a 404 from the daemon.
// This behavior change manifests in the client in that before the 301 was not followed and
// the client did not generate an error, but now results in a message like Error response from daemon: page not found.
func CheckRedirect(req *http.Request, via []*http.Request) error {
if via[0].Method == http.MethodGet {
return http.ErrUseLastResponse
}
return ErrRedirect
}
// NewEnvClient initializes a new API client based on environment variables.
// Use DOCKER_HOST to set the url to the docker server.
// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
@ -104,6 +125,7 @@ func NewEnvClient() (*Client, error) {
Transport: &http.Transport{
TLSClientConfig: tlsc,
},
CheckRedirect: CheckRedirect,
}
}
@ -147,7 +169,8 @@ func NewClient(host string, version string, client *http.Client, httpHeaders map
transport := new(http.Transport)
sockets.ConfigureTransport(transport, proto, addr)
client = &http.Client{
Transport: transport,
Transport: transport,
CheckRedirect: CheckRedirect,
}
}

View file

@ -13,6 +13,7 @@ import (
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
@ -282,3 +283,52 @@ func TestNewEnvClientSetsDefaultVersion(t *testing.T) {
os.Setenv(key, envVarValues[key])
}
}
type roundTripFunc func(*http.Request) (*http.Response, error)
func (rtf roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return rtf(req)
}
type bytesBufferClose struct {
*bytes.Buffer
}
func (bbc bytesBufferClose) Close() error {
return nil
}
func TestClientRedirect(t *testing.T) {
client := &http.Client{
CheckRedirect: CheckRedirect,
Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
if req.URL.String() == "/bla" {
return &http.Response{StatusCode: 404}, nil
}
return &http.Response{
StatusCode: 301,
Header: map[string][]string{"Location": {"/bla"}},
Body: bytesBufferClose{bytes.NewBuffer(nil)},
}, nil
}),
}
cases := []struct {
httpMethod string
expectedErr error
statusCode int
}{
{http.MethodGet, nil, 301},
{http.MethodPost, &url.Error{Op: "Post", URL: "/bla", Err: ErrRedirect}, 301},
{http.MethodPut, &url.Error{Op: "Put", URL: "/bla", Err: ErrRedirect}, 301},
{http.MethodDelete, &url.Error{Op: "Delete", URL: "/bla", Err: ErrRedirect}, 301},
}
for _, tc := range cases {
req, err := http.NewRequest(tc.httpMethod, "/redirectme", nil)
assert.NoError(t, err)
resp, err := client.Do(req)
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.statusCode, resp.StatusCode)
}
}