2009-06-09 10:58:58 -07:00
|
|
|
// Copyright 2009 The Go Authors. All rights reserved.
|
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
|
|
// Tests for client.go
|
|
|
|
|
|
2011-03-10 08:46:57 -08:00
|
|
|
package http_test
|
2009-06-09 10:58:58 -07:00
|
|
|
|
|
|
|
|
import (
|
2012-12-12 11:09:55 -08:00
|
|
|
"bytes"
|
2016-04-05 17:24:23 +00:00
|
|
|
"context"
|
2011-10-21 08:14:38 -07:00
|
|
|
"crypto/tls"
|
2013-05-14 15:33:46 -07:00
|
|
|
"encoding/base64"
|
2011-11-01 22:04:37 -04:00
|
|
|
"errors"
|
2011-03-10 08:46:57 -08:00
|
|
|
"fmt"
|
2011-05-24 09:02:01 -07:00
|
|
|
"io"
|
2014-02-28 12:12:51 -08:00
|
|
|
"log"
|
2011-06-23 21:10:51 -07:00
|
|
|
"net"
|
2011-11-08 15:41:54 -08:00
|
|
|
. "net/http"
|
2016-10-18 14:56:19 -07:00
|
|
|
"net/http/cookiejar"
|
2011-11-08 15:41:54 -08:00
|
|
|
"net/http/httptest"
|
|
|
|
|
"net/url"
|
2016-09-09 18:06:56 +00:00
|
|
|
"reflect"
|
2011-04-25 22:41:50 -07:00
|
|
|
"strconv"
|
2009-12-15 15:35:38 -08:00
|
|
|
"strings"
|
2012-01-16 12:57:59 -08:00
|
|
|
"sync"
|
2016-12-08 01:07:10 +00:00
|
|
|
"sync/atomic"
|
2009-12-15 15:35:38 -08:00
|
|
|
"testing"
|
2014-02-28 12:12:51 -08:00
|
|
|
"time"
|
2009-06-09 10:58:58 -07:00
|
|
|
)
|
|
|
|
|
|
2011-03-10 08:46:57 -08:00
|
|
|
var robotsTxtHandler = HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
w.Header().Set("Last-Modified", "sometime")
|
|
|
|
|
fmt.Fprintf(w, "User-agent: go\nDisallow: /something/")
|
|
|
|
|
})
|
|
|
|
|
|
2020-10-16 00:49:02 -04:00
|
|
|
// pedanticReadAll works like io.ReadAll but additionally
|
2011-11-16 17:35:47 -02:00
|
|
|
// verifies that r obeys the documented io.Reader contract.
|
|
|
|
|
func pedanticReadAll(r io.Reader) (b []byte, err error) {
|
|
|
|
|
var bufa [64]byte
|
|
|
|
|
buf := bufa[:]
|
|
|
|
|
for {
|
|
|
|
|
n, err := r.Read(buf)
|
|
|
|
|
if n == 0 && err == nil {
|
|
|
|
|
return nil, fmt.Errorf("Read: n=0 with err=nil")
|
|
|
|
|
}
|
|
|
|
|
b = append(b, buf[:n]...)
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
n, err := r.Read(buf)
|
|
|
|
|
if n != 0 || err != io.EOF {
|
|
|
|
|
return nil, fmt.Errorf("Read: n=%d err=%#v after EOF", n, err)
|
|
|
|
|
}
|
|
|
|
|
return b, nil
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return b, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-28 12:12:51 -08:00
|
|
|
type chanWriter chan string
|
|
|
|
|
|
|
|
|
|
func (w chanWriter) Write(p []byte) (n int, err error) {
|
|
|
|
|
w <- string(p)
|
|
|
|
|
return len(p), nil
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-09 10:58:58 -07:00
|
|
|
func TestClient(t *testing.T) {
|
2016-11-05 00:21:59 +00:00
|
|
|
setParallel(t)
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2011-03-10 08:46:57 -08:00
|
|
|
ts := httptest.NewServer(robotsTxtHandler)
|
|
|
|
|
defer ts.Close()
|
2009-06-11 15:55:03 -07:00
|
|
|
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
2016-11-05 00:21:59 +00:00
|
|
|
r, err := c.Get(ts.URL)
|
2009-12-15 15:35:38 -08:00
|
|
|
var b []byte
|
2009-06-09 10:58:58 -07:00
|
|
|
if err == nil {
|
2011-11-16 17:35:47 -02:00
|
|
|
b, err = pedanticReadAll(r.Body)
|
2009-12-15 15:35:38 -08:00
|
|
|
r.Body.Close()
|
2009-06-09 10:58:58 -07:00
|
|
|
}
|
2009-06-22 13:26:13 -07:00
|
|
|
if err != nil {
|
2009-11-09 12:07:39 -08:00
|
|
|
t.Error(err)
|
2009-06-24 20:12:50 -07:00
|
|
|
} else if s := string(b); !strings.HasPrefix(s, "User-agent:") {
|
2009-11-09 12:07:39 -08:00
|
|
|
t.Errorf("Incorrect page body (did not begin with User-agent): %q", s)
|
2009-06-09 10:58:58 -07:00
|
|
|
}
|
|
|
|
|
}
|
2010-06-08 01:28:40 +02:00
|
|
|
|
2015-12-09 22:02:46 +00:00
|
|
|
func TestClientHead_h1(t *testing.T) { testClientHead(t, h1Mode) }
|
|
|
|
|
func TestClientHead_h2(t *testing.T) { testClientHead(t, h2Mode) }
|
2015-12-08 01:11:30 -07:00
|
|
|
|
|
|
|
|
func testClientHead(t *testing.T, h2 bool) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2015-12-08 01:11:30 -07:00
|
|
|
cst := newClientServerTest(t, h2, robotsTxtHandler)
|
|
|
|
|
defer cst.close()
|
2011-03-10 08:46:57 -08:00
|
|
|
|
2015-12-08 01:11:30 -07:00
|
|
|
r, err := cst.c.Head(cst.ts.URL)
|
2010-06-08 01:28:40 +02:00
|
|
|
if err != nil {
|
2010-06-30 18:57:27 +10:00
|
|
|
t.Fatal(err)
|
2010-06-08 01:28:40 +02:00
|
|
|
}
|
|
|
|
|
if _, ok := r.Header["Last-Modified"]; !ok {
|
|
|
|
|
t.Error("Last-Modified header not found.")
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-02-23 15:03:30 -08:00
|
|
|
|
|
|
|
|
type recordingTransport struct {
|
|
|
|
|
req *Request
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-01 22:04:37 -04:00
|
|
|
func (t *recordingTransport) RoundTrip(req *Request) (resp *Response, err error) {
|
2011-02-23 15:03:30 -08:00
|
|
|
t.req = req
|
2011-11-01 22:04:37 -04:00
|
|
|
return nil, errors.New("dummy impl")
|
2011-02-23 15:03:30 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestGetRequestFormat(t *testing.T) {
|
2016-11-05 00:21:59 +00:00
|
|
|
setParallel(t)
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2011-02-23 15:03:30 -08:00
|
|
|
tr := &recordingTransport{}
|
2011-03-01 14:07:28 -08:00
|
|
|
client := &Client{Transport: tr}
|
2011-02-23 15:03:30 -08:00
|
|
|
url := "http://dummy.faketld/"
|
|
|
|
|
client.Get(url) // Note: doesn't hit network
|
|
|
|
|
if tr.req.Method != "GET" {
|
2011-03-02 10:21:56 -08:00
|
|
|
t.Errorf("expected method %q; got %q", "GET", tr.req.Method)
|
2011-02-23 15:03:30 -08:00
|
|
|
}
|
|
|
|
|
if tr.req.URL.String() != url {
|
2011-03-02 10:21:56 -08:00
|
|
|
t.Errorf("expected URL %q; got %q", url, tr.req.URL.String())
|
|
|
|
|
}
|
|
|
|
|
if tr.req.Header == nil {
|
|
|
|
|
t.Errorf("expected non-nil request Header")
|
2011-02-23 15:03:30 -08:00
|
|
|
}
|
|
|
|
|
}
|
2011-04-25 22:41:50 -07:00
|
|
|
|
2011-05-31 08:47:03 -07:00
|
|
|
func TestPostRequestFormat(t *testing.T) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2011-05-31 08:47:03 -07:00
|
|
|
tr := &recordingTransport{}
|
|
|
|
|
client := &Client{Transport: tr}
|
|
|
|
|
|
|
|
|
|
url := "http://dummy.faketld/"
|
|
|
|
|
json := `{"key":"value"}`
|
|
|
|
|
b := strings.NewReader(json)
|
|
|
|
|
client.Post(url, "application/json", b) // Note: doesn't hit network
|
|
|
|
|
|
|
|
|
|
if tr.req.Method != "POST" {
|
|
|
|
|
t.Errorf("got method %q, want %q", tr.req.Method, "POST")
|
|
|
|
|
}
|
|
|
|
|
if tr.req.URL.String() != url {
|
|
|
|
|
t.Errorf("got URL %q, want %q", tr.req.URL.String(), url)
|
|
|
|
|
}
|
|
|
|
|
if tr.req.Header == nil {
|
|
|
|
|
t.Fatalf("expected non-nil request Header")
|
|
|
|
|
}
|
|
|
|
|
if tr.req.Close {
|
|
|
|
|
t.Error("got Close true, want false")
|
|
|
|
|
}
|
|
|
|
|
if g, e := tr.req.ContentLength, int64(len(json)); g != e {
|
|
|
|
|
t.Errorf("got ContentLength %d, want %d", g, e)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPostFormRequestFormat(t *testing.T) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2011-05-31 08:47:03 -07:00
|
|
|
tr := &recordingTransport{}
|
|
|
|
|
client := &Client{Transport: tr}
|
|
|
|
|
|
2011-08-17 13:36:02 +10:00
|
|
|
urlStr := "http://dummy.faketld/"
|
|
|
|
|
form := make(url.Values)
|
2011-06-08 13:38:20 -07:00
|
|
|
form.Set("foo", "bar")
|
|
|
|
|
form.Add("foo", "bar2")
|
|
|
|
|
form.Set("bar", "baz")
|
2011-08-17 13:36:02 +10:00
|
|
|
client.PostForm(urlStr, form) // Note: doesn't hit network
|
2011-05-31 08:47:03 -07:00
|
|
|
|
|
|
|
|
if tr.req.Method != "POST" {
|
|
|
|
|
t.Errorf("got method %q, want %q", tr.req.Method, "POST")
|
|
|
|
|
}
|
2011-08-17 13:36:02 +10:00
|
|
|
if tr.req.URL.String() != urlStr {
|
|
|
|
|
t.Errorf("got URL %q, want %q", tr.req.URL.String(), urlStr)
|
2011-05-31 08:47:03 -07:00
|
|
|
}
|
|
|
|
|
if tr.req.Header == nil {
|
|
|
|
|
t.Fatalf("expected non-nil request Header")
|
|
|
|
|
}
|
|
|
|
|
if g, e := tr.req.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; g != e {
|
|
|
|
|
t.Errorf("got Content-Type %q, want %q", g, e)
|
|
|
|
|
}
|
|
|
|
|
if tr.req.Close {
|
|
|
|
|
t.Error("got Close true, want false")
|
|
|
|
|
}
|
2011-10-17 14:51:54 -04:00
|
|
|
// Depending on map iteration, body can be either of these.
|
2011-06-08 13:38:20 -07:00
|
|
|
expectedBody := "foo=bar&foo=bar2&bar=baz"
|
2011-10-17 14:51:54 -04:00
|
|
|
expectedBody1 := "bar=baz&foo=bar&foo=bar2"
|
2011-06-08 13:38:20 -07:00
|
|
|
if g, e := tr.req.ContentLength, int64(len(expectedBody)); g != e {
|
2011-05-31 08:47:03 -07:00
|
|
|
t.Errorf("got ContentLength %d, want %d", g, e)
|
|
|
|
|
}
|
2020-10-16 00:49:02 -04:00
|
|
|
bodyb, err := io.ReadAll(tr.req.Body)
|
2011-06-08 13:38:20 -07:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ReadAll on req.Body: %v", err)
|
|
|
|
|
}
|
2011-10-17 14:51:54 -04:00
|
|
|
if g := string(bodyb); g != expectedBody && g != expectedBody1 {
|
|
|
|
|
t.Errorf("got body %q, want %q or %q", g, expectedBody, expectedBody1)
|
2011-06-08 13:38:20 -07:00
|
|
|
}
|
2011-05-31 08:47:03 -07:00
|
|
|
}
|
|
|
|
|
|
2013-04-20 17:20:14 -07:00
|
|
|
func TestClientRedirects(t *testing.T) {
|
2016-11-04 03:23:37 +00:00
|
|
|
setParallel(t)
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2011-04-25 22:41:50 -07:00
|
|
|
var ts *httptest.Server
|
|
|
|
|
ts = httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
n, _ := strconv.Atoi(r.FormValue("n"))
|
|
|
|
|
// Test Referer header. (7 is arbitrary position to test at)
|
|
|
|
|
if n == 7 {
|
2011-06-16 13:02:28 -07:00
|
|
|
if g, e := r.Referer(), ts.URL+"/?n=6"; e != g {
|
2011-04-25 22:41:50 -07:00
|
|
|
t.Errorf("on request ?n=7, expected referer of %q; got %q", e, g)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if n < 15 {
|
2016-09-26 20:38:57 -07:00
|
|
|
Redirect(w, r, fmt.Sprintf("/?n=%d", n+1), StatusTemporaryRedirect)
|
2011-04-25 22:41:50 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
fmt.Fprintf(w, "n=%d", n)
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
2011-05-13 07:31:24 -07:00
|
|
|
_, err := c.Get(ts.URL)
|
2019-08-28 12:10:16 +00:00
|
|
|
if e, g := `Get "/?n=10": stopped after 10 redirects`, fmt.Sprintf("%v", err); e != g {
|
2011-05-18 17:17:26 -07:00
|
|
|
t.Errorf("with default client Get, expected error %q, got %q", e, g)
|
2011-04-25 22:41:50 -07:00
|
|
|
}
|
|
|
|
|
|
2011-05-13 08:17:59 -07:00
|
|
|
// HEAD request should also have the ability to follow redirects.
|
|
|
|
|
_, err = c.Head(ts.URL)
|
2019-08-28 12:10:16 +00:00
|
|
|
if e, g := `Head "/?n=10": stopped after 10 redirects`, fmt.Sprintf("%v", err); e != g {
|
2011-05-18 17:17:26 -07:00
|
|
|
t.Errorf("with default client Head, expected error %q, got %q", e, g)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Do should also follow redirects.
|
|
|
|
|
greq, _ := NewRequest("GET", ts.URL, nil)
|
|
|
|
|
_, err = c.Do(greq)
|
2019-08-28 12:10:16 +00:00
|
|
|
if e, g := `Get "/?n=10": stopped after 10 redirects`, fmt.Sprintf("%v", err); e != g {
|
2011-05-18 17:17:26 -07:00
|
|
|
t.Errorf("with default client Do, expected error %q, got %q", e, g)
|
2011-05-13 08:17:59 -07:00
|
|
|
}
|
|
|
|
|
|
2015-12-03 18:58:05 +00:00
|
|
|
// Requests with an empty Method should also redirect (Issue 12705)
|
|
|
|
|
greq.Method = ""
|
|
|
|
|
_, err = c.Do(greq)
|
2019-08-28 12:10:16 +00:00
|
|
|
if e, g := `Get "/?n=10": stopped after 10 redirects`, fmt.Sprintf("%v", err); e != g {
|
2015-12-03 18:58:05 +00:00
|
|
|
t.Errorf("with default client Do and empty Method, expected error %q, got %q", e, g)
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-01 22:04:37 -04:00
|
|
|
var checkErr error
|
2011-04-25 22:41:50 -07:00
|
|
|
var lastVia []*Request
|
2016-01-20 22:53:50 -08:00
|
|
|
var lastReq *Request
|
2017-03-04 18:24:44 +00:00
|
|
|
c.CheckRedirect = func(req *Request, via []*Request) error {
|
|
|
|
|
lastReq = req
|
|
|
|
|
lastVia = via
|
|
|
|
|
return checkErr
|
2016-11-08 17:44:53 +00:00
|
|
|
}
|
2011-05-13 07:31:24 -07:00
|
|
|
res, err := c.Get(ts.URL)
|
2012-11-13 22:38:25 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Get error: %v", err)
|
|
|
|
|
}
|
2013-02-26 17:12:50 -08:00
|
|
|
res.Body.Close()
|
2011-05-13 07:31:24 -07:00
|
|
|
finalUrl := res.Request.URL.String()
|
2011-04-25 22:41:50 -07:00
|
|
|
if e, g := "<nil>", fmt.Sprintf("%v", err); e != g {
|
|
|
|
|
t.Errorf("with custom client, expected error %q, got %q", e, g)
|
|
|
|
|
}
|
|
|
|
|
if !strings.HasSuffix(finalUrl, "/?n=15") {
|
|
|
|
|
t.Errorf("expected final url to end in /?n=15; got url %q", finalUrl)
|
|
|
|
|
}
|
|
|
|
|
if e, g := 15, len(lastVia); e != g {
|
|
|
|
|
t.Errorf("expected lastVia to have contained %d elements; got %d", e, g)
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-20 22:53:50 -08:00
|
|
|
// Test that Request.Cancel is propagated between requests (Issue 14053)
|
|
|
|
|
creq, _ := NewRequest("HEAD", ts.URL, nil)
|
|
|
|
|
cancel := make(chan struct{})
|
|
|
|
|
creq.Cancel = cancel
|
|
|
|
|
if _, err := c.Do(creq); err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if lastReq == nil {
|
|
|
|
|
t.Fatal("didn't see redirect")
|
|
|
|
|
}
|
|
|
|
|
if lastReq.Cancel != cancel {
|
2016-02-24 11:55:20 +01:00
|
|
|
t.Errorf("expected lastReq to have the cancel channel set on the initial req")
|
2016-01-20 22:53:50 -08:00
|
|
|
}
|
|
|
|
|
|
2011-11-01 22:04:37 -04:00
|
|
|
checkErr = errors.New("no redirects allowed")
|
2011-05-13 07:31:24 -07:00
|
|
|
res, err = c.Get(ts.URL)
|
2012-06-24 10:41:12 -07:00
|
|
|
if urlError, ok := err.(*url.Error); !ok || urlError.Err != checkErr {
|
|
|
|
|
t.Errorf("with redirects forbidden, expected a *url.Error with our 'no redirects allowed' error inside; got %#v (%q)", err, err)
|
2011-04-25 22:41:50 -07:00
|
|
|
}
|
2012-07-18 13:48:39 -07:00
|
|
|
if res == nil {
|
2015-07-10 17:17:11 -06:00
|
|
|
t.Fatalf("Expected a non-nil Response on CheckRedirect failure (https://golang.org/issue/3795)")
|
2012-07-18 13:48:39 -07:00
|
|
|
}
|
2013-02-26 17:12:50 -08:00
|
|
|
res.Body.Close()
|
2012-07-18 13:48:39 -07:00
|
|
|
if res.Header.Get("Location") == "" {
|
|
|
|
|
t.Errorf("no Location header in Response")
|
|
|
|
|
}
|
2011-04-25 22:41:50 -07:00
|
|
|
}
|
2011-05-24 09:02:01 -07:00
|
|
|
|
net/http: clean up Transport.RoundTrip error handling
If I put a 10 millisecond sleep at testHookWaitResLoop, before the big
select in (*persistConn).roundTrip, two flakes immediately started
happening, TestTransportBodyReadError (#19231) and
TestTransportPersistConnReadLoopEOF.
The problem was that there are many ways for a RoundTrip call to fail
(errors reading from Request.Body while writing the response, errors
writing the response, errors reading the response due to server
closes, errors due to servers sending malformed responses,
cancelations, timeouts, etc.), and many of those failures then tear
down the TCP connection, causing more failures, since there are always
at least three goroutines involved (reading, writing, RoundTripping).
Because the errors were communicated over buffered channels to a giant
select, the error returned to the caller was a function of which
random select case was called, which was why a 10ms delay before the
select brought out so many bugs. (several fixed in my previous CLs the past
few days).
Instead, track the error explicitly in the transportRequest, guarded
by a mutex.
In addition, this CL now:
* differentiates between the two ways writing a request can fail: the
io.Copy reading from the Request.Body or the io.Copy writing to the
network. A new io.Reader type notes read errors from the
Request.Body. The read-from-body vs write-to-network errors are now
prioritized differently.
* unifies the two mapRoundTripErrorFromXXX methods into one
mapRoundTripError method since their logic is now the same.
* adds a (*Request).WithT(*testing.T) method in export_test.go, usable
by tests, to call t.Logf at points during RoundTrip. This is disabled
behind a constant except when debugging.
* documents and deflakes TestClientRedirectContext
I've tested this CL with high -count values, with/without -race,
with/without delays before the select, etc. So far it seems robust.
Fixes #19231 (TestTransportBodyReadError flake)
Updates #14203 (source of errors unclear; they're now tracked more)
Updates #15935 (document Transport errors more; at least understood more now)
Change-Id: I3cccc3607f369724b5344763e35ad2b7ea415738
Reviewed-on: https://go-review.googlesource.com/37495
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
2017-02-27 05:41:50 +00:00
|
|
|
// Tests that Client redirects' contexts are derived from the original request's context.
|
2016-04-05 17:24:23 +00:00
|
|
|
func TestClientRedirectContext(t *testing.T) {
|
2016-11-04 03:23:37 +00:00
|
|
|
setParallel(t)
|
2016-04-05 17:24:23 +00:00
|
|
|
defer afterTest(t)
|
|
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
2016-09-26 20:38:57 -07:00
|
|
|
Redirect(w, r, "/", StatusTemporaryRedirect)
|
2016-04-05 17:24:23 +00:00
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
c.CheckRedirect = func(req *Request, via []*Request) error {
|
|
|
|
|
cancel()
|
|
|
|
|
select {
|
|
|
|
|
case <-req.Context().Done():
|
|
|
|
|
return nil
|
|
|
|
|
case <-time.After(5 * time.Second):
|
|
|
|
|
return errors.New("redirected request's context never expired after root request canceled")
|
|
|
|
|
}
|
2016-11-05 00:21:59 +00:00
|
|
|
}
|
2019-04-29 17:57:10 +00:00
|
|
|
req, _ := NewRequestWithContext(ctx, "GET", ts.URL, nil)
|
2016-04-05 17:24:23 +00:00
|
|
|
_, err := c.Do(req)
|
|
|
|
|
ue, ok := err.(*url.Error)
|
|
|
|
|
if !ok {
|
2016-04-07 19:59:59 -07:00
|
|
|
t.Fatalf("got error %T; want *url.Error", err)
|
2016-04-05 17:24:23 +00:00
|
|
|
}
|
2016-07-15 15:30:53 -07:00
|
|
|
if ue.Err != context.Canceled {
|
|
|
|
|
t.Errorf("url.Error.Err = %v; want %v", ue.Err, context.Canceled)
|
2016-04-05 17:24:23 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-26 20:38:57 -07:00
|
|
|
type redirectTest struct {
|
|
|
|
|
suffix string
|
|
|
|
|
want int // response code
|
|
|
|
|
redirectBody string
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-12 11:09:55 -08:00
|
|
|
func TestPostRedirects(t *testing.T) {
|
2016-09-26 20:38:57 -07:00
|
|
|
postRedirectTests := []redirectTest{
|
|
|
|
|
{"/", 200, "first"},
|
|
|
|
|
{"/?code=301&next=302", 200, "c301"},
|
|
|
|
|
{"/?code=302&next=302", 200, "c302"},
|
|
|
|
|
{"/?code=303&next=301", 200, "c303wc301"}, // Issue 9348
|
|
|
|
|
{"/?code=304", 304, "c304"},
|
|
|
|
|
{"/?code=305", 305, "c305"},
|
|
|
|
|
{"/?code=307&next=303,308,302", 200, "c307"},
|
|
|
|
|
{"/?code=308&next=302,301", 200, "c308"},
|
|
|
|
|
{"/?code=404", 404, "c404"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wantSegments := []string{
|
|
|
|
|
`POST / "first"`,
|
|
|
|
|
`POST /?code=301&next=302 "c301"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET /?code=302 ""`,
|
|
|
|
|
`GET / ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`POST /?code=302&next=302 "c302"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET /?code=302 ""`,
|
|
|
|
|
`GET / ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`POST /?code=303&next=301 "c303wc301"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET /?code=301 ""`,
|
|
|
|
|
`GET / ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`POST /?code=304 "c304"`,
|
|
|
|
|
`POST /?code=305 "c305"`,
|
|
|
|
|
`POST /?code=307&next=303,308,302 "c307"`,
|
|
|
|
|
`POST /?code=303&next=308,302 "c307"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET /?code=308&next=302 ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`GET /?code=302 "c307"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET / ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`POST /?code=308&next=302,301 "c308"`,
|
|
|
|
|
`POST /?code=302&next=301 "c308"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET /?code=301 ""`,
|
|
|
|
|
`GET / ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`POST /?code=404 "c404"`,
|
|
|
|
|
}
|
|
|
|
|
want := strings.Join(wantSegments, "\n")
|
|
|
|
|
testRedirectsByMethod(t, "POST", postRedirectTests, want)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestDeleteRedirects(t *testing.T) {
|
|
|
|
|
deleteRedirectTests := []redirectTest{
|
|
|
|
|
{"/", 200, "first"},
|
|
|
|
|
{"/?code=301&next=302,308", 200, "c301"},
|
|
|
|
|
{"/?code=302&next=302", 200, "c302"},
|
|
|
|
|
{"/?code=303", 200, "c303"},
|
|
|
|
|
{"/?code=307&next=301,308,303,302,304", 304, "c307"},
|
|
|
|
|
{"/?code=308&next=307", 200, "c308"},
|
|
|
|
|
{"/?code=404", 404, "c404"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wantSegments := []string{
|
|
|
|
|
`DELETE / "first"`,
|
|
|
|
|
`DELETE /?code=301&next=302,308 "c301"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET /?code=302&next=308 ""`,
|
|
|
|
|
`GET /?code=308 ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`GET / "c301"`,
|
|
|
|
|
`DELETE /?code=302&next=302 "c302"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET /?code=302 ""`,
|
|
|
|
|
`GET / ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`DELETE /?code=303 "c303"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET / ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`DELETE /?code=307&next=301,308,303,302,304 "c307"`,
|
|
|
|
|
`DELETE /?code=301&next=308,303,302,304 "c307"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET /?code=308&next=303,302,304 ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`GET /?code=303&next=302,304 "c307"`,
|
2017-01-24 17:52:54 +00:00
|
|
|
`GET /?code=302&next=304 ""`,
|
|
|
|
|
`GET /?code=304 ""`,
|
2016-09-26 20:38:57 -07:00
|
|
|
`DELETE /?code=308&next=307 "c308"`,
|
|
|
|
|
`DELETE /?code=307 "c308"`,
|
|
|
|
|
`DELETE / "c308"`,
|
|
|
|
|
`DELETE /?code=404 "c404"`,
|
|
|
|
|
}
|
|
|
|
|
want := strings.Join(wantSegments, "\n")
|
|
|
|
|
testRedirectsByMethod(t, "DELETE", deleteRedirectTests, want)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testRedirectsByMethod(t *testing.T, method string, table []redirectTest, want string) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2012-12-12 11:09:55 -08:00
|
|
|
var log struct {
|
|
|
|
|
sync.Mutex
|
|
|
|
|
bytes.Buffer
|
|
|
|
|
}
|
|
|
|
|
var ts *httptest.Server
|
|
|
|
|
ts = httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
log.Lock()
|
2020-10-16 00:49:02 -04:00
|
|
|
slurp, _ := io.ReadAll(r.Body)
|
2017-01-24 17:52:54 +00:00
|
|
|
fmt.Fprintf(&log.Buffer, "%s %s %q", r.Method, r.RequestURI, slurp)
|
|
|
|
|
if cl := r.Header.Get("Content-Length"); r.Method == "GET" && len(slurp) == 0 && (r.ContentLength != 0 || cl != "") {
|
|
|
|
|
fmt.Fprintf(&log.Buffer, " (but with body=%T, content-length = %v, %q)", r.Body, r.ContentLength, cl)
|
|
|
|
|
}
|
|
|
|
|
log.WriteByte('\n')
|
2012-12-12 11:09:55 -08:00
|
|
|
log.Unlock()
|
2016-09-26 20:38:57 -07:00
|
|
|
urlQuery := r.URL.Query()
|
|
|
|
|
if v := urlQuery.Get("code"); v != "" {
|
|
|
|
|
location := ts.URL
|
|
|
|
|
if final := urlQuery.Get("next"); final != "" {
|
|
|
|
|
splits := strings.Split(final, ",")
|
|
|
|
|
first, rest := splits[0], splits[1:]
|
|
|
|
|
location = fmt.Sprintf("%s?code=%s", location, first)
|
|
|
|
|
if len(rest) > 0 {
|
|
|
|
|
location = fmt.Sprintf("%s&next=%s", location, strings.Join(rest, ","))
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-12-12 11:09:55 -08:00
|
|
|
code, _ := strconv.Atoi(v)
|
|
|
|
|
if code/100 == 3 {
|
2016-09-26 20:38:57 -07:00
|
|
|
w.Header().Set("Location", location)
|
2012-12-12 11:09:55 -08:00
|
|
|
}
|
|
|
|
|
w.WriteHeader(code)
|
|
|
|
|
}
|
|
|
|
|
}))
|
2013-02-26 17:12:50 -08:00
|
|
|
defer ts.Close()
|
2016-09-26 20:38:57 -07:00
|
|
|
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
2016-09-26 20:38:57 -07:00
|
|
|
for _, tt := range table {
|
|
|
|
|
content := tt.redirectBody
|
|
|
|
|
req, _ := NewRequest(method, ts.URL+tt.suffix, strings.NewReader(content))
|
2020-10-16 00:49:02 -04:00
|
|
|
req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(strings.NewReader(content)), nil }
|
2017-03-04 18:24:44 +00:00
|
|
|
res, err := c.Do(req)
|
2016-09-26 20:38:57 -07:00
|
|
|
|
2012-12-12 11:09:55 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if res.StatusCode != tt.want {
|
|
|
|
|
t.Errorf("POST %s: status code = %d; want %d", tt.suffix, res.StatusCode, tt.want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Lock()
|
|
|
|
|
got := log.String()
|
|
|
|
|
log.Unlock()
|
2016-09-26 20:38:57 -07:00
|
|
|
|
|
|
|
|
got = strings.TrimSpace(got)
|
|
|
|
|
want = strings.TrimSpace(want)
|
|
|
|
|
|
2012-12-12 11:09:55 -08:00
|
|
|
if got != want {
|
2017-01-24 17:52:54 +00:00
|
|
|
got, want, lines := removeCommonLines(got, want)
|
|
|
|
|
t.Errorf("Log differs after %d common lines.\n\nGot:\n%s\n\nWant:\n%s\n", lines, got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func removeCommonLines(a, b string) (asuffix, bsuffix string, commonLines int) {
|
|
|
|
|
for {
|
|
|
|
|
nl := strings.IndexByte(a, '\n')
|
|
|
|
|
if nl < 0 {
|
|
|
|
|
return a, b, commonLines
|
|
|
|
|
}
|
|
|
|
|
line := a[:nl+1]
|
|
|
|
|
if !strings.HasPrefix(b, line) {
|
|
|
|
|
return a, b, commonLines
|
|
|
|
|
}
|
|
|
|
|
commonLines++
|
|
|
|
|
a = a[len(line):]
|
|
|
|
|
b = b[len(line):]
|
2012-12-12 11:09:55 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-18 15:42:54 +00:00
|
|
|
func TestClientRedirectUseResponse(t *testing.T) {
|
2016-11-04 03:23:37 +00:00
|
|
|
setParallel(t)
|
2016-05-18 15:42:54 +00:00
|
|
|
defer afterTest(t)
|
|
|
|
|
const body = "Hello, world."
|
|
|
|
|
var ts *httptest.Server
|
|
|
|
|
ts = httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
if strings.Contains(r.URL.Path, "/other") {
|
|
|
|
|
io.WriteString(w, "wrong body")
|
|
|
|
|
} else {
|
|
|
|
|
w.Header().Set("Location", ts.URL+"/other")
|
|
|
|
|
w.WriteHeader(StatusFound)
|
|
|
|
|
io.WriteString(w, body)
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
c.CheckRedirect = func(req *Request, via []*Request) error {
|
|
|
|
|
if req.Response == nil {
|
|
|
|
|
t.Error("expected non-nil Request.Response")
|
|
|
|
|
}
|
|
|
|
|
return ErrUseLastResponse
|
2016-11-05 00:21:59 +00:00
|
|
|
}
|
2016-05-18 15:42:54 +00:00
|
|
|
res, err := c.Get(ts.URL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if res.StatusCode != StatusFound {
|
|
|
|
|
t.Errorf("status = %d; want %d", res.StatusCode, StatusFound)
|
|
|
|
|
}
|
|
|
|
|
defer res.Body.Close()
|
2020-10-16 00:49:02 -04:00
|
|
|
slurp, err := io.ReadAll(res.Body)
|
2016-05-18 15:42:54 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if string(slurp) != body {
|
|
|
|
|
t.Errorf("body = %q; want %q", slurp, body)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-03 20:53:05 +00:00
|
|
|
// Issue 17773: don't follow a 308 (or 307) if the response doesn't
|
|
|
|
|
// have a Location header.
|
|
|
|
|
func TestClientRedirect308NoLocation(t *testing.T) {
|
|
|
|
|
setParallel(t)
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
w.Header().Set("Foo", "Bar")
|
|
|
|
|
w.WriteHeader(308)
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
2017-03-01 05:42:32 +00:00
|
|
|
res, err := c.Get(ts.URL)
|
2016-11-03 20:53:05 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
res.Body.Close()
|
|
|
|
|
if res.StatusCode != 308 {
|
|
|
|
|
t.Errorf("status = %d; want %d", res.StatusCode, 308)
|
|
|
|
|
}
|
|
|
|
|
if got := res.Header.Get("Foo"); got != "Bar" {
|
|
|
|
|
t.Errorf("Foo header = %q; want Bar", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Don't follow a 307/308 if we can't resent the request body.
|
|
|
|
|
func TestClientRedirect308NoGetBody(t *testing.T) {
|
|
|
|
|
setParallel(t)
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
const fakeURL = "https://localhost:1234/" // won't be hit
|
|
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
w.Header().Set("Location", fakeURL)
|
|
|
|
|
w.WriteHeader(308)
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
req, err := NewRequest("POST", ts.URL, strings.NewReader("some body"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
2016-11-03 20:53:05 +00:00
|
|
|
req.GetBody = nil // so it can't rewind.
|
2017-03-01 17:44:11 +00:00
|
|
|
res, err := c.Do(req)
|
2016-11-03 20:53:05 +00:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
res.Body.Close()
|
|
|
|
|
if res.StatusCode != 308 {
|
|
|
|
|
t.Errorf("status = %d; want %d", res.StatusCode, 308)
|
|
|
|
|
}
|
|
|
|
|
if got := res.Header.Get("Location"); got != fakeURL {
|
|
|
|
|
t.Errorf("Location header = %q; want %q", got, fakeURL)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-16 12:57:59 -08:00
|
|
|
var expectedCookies = []*Cookie{
|
2012-03-08 10:48:51 -08:00
|
|
|
{Name: "ChocolateChip", Value: "tasty"},
|
|
|
|
|
{Name: "First", Value: "Hit"},
|
|
|
|
|
{Name: "Second", Value: "Hit"},
|
2012-01-16 12:57:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var echoCookiesRedirectHandler = HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
for _, cookie := range r.Cookies() {
|
|
|
|
|
SetCookie(w, cookie)
|
|
|
|
|
}
|
|
|
|
|
if r.URL.Path == "/" {
|
|
|
|
|
SetCookie(w, expectedCookies[1])
|
|
|
|
|
Redirect(w, r, "/second", StatusMovedPermanently)
|
|
|
|
|
} else {
|
|
|
|
|
SetCookie(w, expectedCookies[2])
|
|
|
|
|
w.Write([]byte("hello"))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2012-05-21 10:57:15 -07:00
|
|
|
func TestClientSendsCookieFromJar(t *testing.T) {
|
2015-04-07 15:50:34 +02:00
|
|
|
defer afterTest(t)
|
2012-05-21 10:57:15 -07:00
|
|
|
tr := &recordingTransport{}
|
|
|
|
|
client := &Client{Transport: tr}
|
|
|
|
|
client.Jar = &TestJar{perURL: make(map[string][]*Cookie)}
|
|
|
|
|
us := "http://dummy.faketld/"
|
|
|
|
|
u, _ := url.Parse(us)
|
|
|
|
|
client.Jar.SetCookies(u, expectedCookies)
|
|
|
|
|
|
|
|
|
|
client.Get(us) // Note: doesn't hit network
|
|
|
|
|
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
|
|
|
|
|
|
|
|
|
|
client.Head(us) // Note: doesn't hit network
|
|
|
|
|
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
|
|
|
|
|
|
|
|
|
|
client.Post(us, "text/plain", strings.NewReader("body")) // Note: doesn't hit network
|
|
|
|
|
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
|
|
|
|
|
|
|
|
|
|
client.PostForm(us, url.Values{}) // Note: doesn't hit network
|
|
|
|
|
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
|
|
|
|
|
|
|
|
|
|
req, _ := NewRequest("GET", us, nil)
|
|
|
|
|
client.Do(req) // Note: doesn't hit network
|
2012-10-29 17:56:31 +01:00
|
|
|
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
|
|
|
|
|
|
|
|
|
|
req, _ = NewRequest("POST", us, nil)
|
|
|
|
|
client.Do(req) // Note: doesn't hit network
|
2012-05-21 10:57:15 -07:00
|
|
|
matchReturnedCookies(t, expectedCookies, tr.req.Cookies())
|
|
|
|
|
}
|
|
|
|
|
|
2012-01-16 12:57:59 -08:00
|
|
|
// Just enough correctness for our redirect tests. Uses the URL.Host as the
|
|
|
|
|
// scope of all cookies.
|
|
|
|
|
type TestJar struct {
|
|
|
|
|
m sync.Mutex
|
|
|
|
|
perURL map[string][]*Cookie
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (j *TestJar) SetCookies(u *url.URL, cookies []*Cookie) {
|
|
|
|
|
j.m.Lock()
|
|
|
|
|
defer j.m.Unlock()
|
2012-12-12 11:36:44 -08:00
|
|
|
if j.perURL == nil {
|
|
|
|
|
j.perURL = make(map[string][]*Cookie)
|
|
|
|
|
}
|
2012-01-16 12:57:59 -08:00
|
|
|
j.perURL[u.Host] = cookies
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (j *TestJar) Cookies(u *url.URL) []*Cookie {
|
|
|
|
|
j.m.Lock()
|
|
|
|
|
defer j.m.Unlock()
|
|
|
|
|
return j.perURL[u.Host]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestRedirectCookiesJar(t *testing.T) {
|
2016-11-05 00:21:59 +00:00
|
|
|
setParallel(t)
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2012-01-16 12:57:59 -08:00
|
|
|
var ts *httptest.Server
|
|
|
|
|
ts = httptest.NewServer(echoCookiesRedirectHandler)
|
|
|
|
|
defer ts.Close()
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
c.Jar = new(TestJar)
|
2012-01-16 12:57:59 -08:00
|
|
|
u, _ := url.Parse(ts.URL)
|
|
|
|
|
c.Jar.SetCookies(u, []*Cookie{expectedCookies[0]})
|
2012-11-13 22:38:25 -08:00
|
|
|
resp, err := c.Get(ts.URL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Get: %v", err)
|
|
|
|
|
}
|
2013-02-26 17:12:50 -08:00
|
|
|
resp.Body.Close()
|
2012-01-16 12:57:59 -08:00
|
|
|
matchReturnedCookies(t, expectedCookies, resp.Cookies())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func matchReturnedCookies(t *testing.T, expected, given []*Cookie) {
|
|
|
|
|
if len(given) != len(expected) {
|
2013-12-26 13:03:30 -08:00
|
|
|
t.Logf("Received cookies: %v", given)
|
2012-01-16 12:57:59 -08:00
|
|
|
t.Errorf("Expected %d cookies, got %d", len(expected), len(given))
|
|
|
|
|
}
|
|
|
|
|
for _, ec := range expected {
|
|
|
|
|
foundC := false
|
|
|
|
|
for _, c := range given {
|
|
|
|
|
if ec.Name == c.Name && ec.Value == c.Value {
|
|
|
|
|
foundC = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !foundC {
|
|
|
|
|
t.Errorf("Missing cookie %v", ec)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-12-12 11:36:44 -08:00
|
|
|
func TestJarCalls(t *testing.T) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2012-12-12 11:36:44 -08:00
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
pathSuffix := r.RequestURI[1:]
|
2012-12-19 16:24:38 -08:00
|
|
|
if r.RequestURI == "/nosetcookie" {
|
2015-06-11 16:49:38 +03:00
|
|
|
return // don't set cookies for this path
|
2012-12-19 16:24:38 -08:00
|
|
|
}
|
2012-12-12 11:36:44 -08:00
|
|
|
SetCookie(w, &Cookie{Name: "name" + pathSuffix, Value: "val" + pathSuffix})
|
|
|
|
|
if r.RequestURI == "/" {
|
|
|
|
|
Redirect(w, r, "http://secondhost.fake/secondpath", 302)
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
jar := new(RecordingJar)
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
c.Jar = jar
|
|
|
|
|
c.Transport.(*Transport).Dial = func(_ string, _ string) (net.Conn, error) {
|
|
|
|
|
return net.Dial("tcp", ts.Listener.Addr().String())
|
2012-12-12 11:36:44 -08:00
|
|
|
}
|
|
|
|
|
_, err := c.Get("http://firsthost.fake/")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2012-12-19 16:24:38 -08:00
|
|
|
_, err = c.Get("http://firsthost.fake/nosetcookie")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2012-12-12 11:36:44 -08:00
|
|
|
got := jar.log.String()
|
|
|
|
|
want := `Cookies("http://firsthost.fake/")
|
|
|
|
|
SetCookie("http://firsthost.fake/", [name=val])
|
|
|
|
|
Cookies("http://secondhost.fake/secondpath")
|
|
|
|
|
SetCookie("http://secondhost.fake/secondpath", [namesecondpath=valsecondpath])
|
2012-12-19 16:24:38 -08:00
|
|
|
Cookies("http://firsthost.fake/nosetcookie")
|
2012-12-12 11:36:44 -08:00
|
|
|
`
|
|
|
|
|
if got != want {
|
|
|
|
|
t.Errorf("Got Jar calls:\n%s\nWant:\n%s", got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RecordingJar keeps a log of calls made to it, without
|
|
|
|
|
// tracking any cookies.
|
|
|
|
|
type RecordingJar struct {
|
|
|
|
|
mu sync.Mutex
|
|
|
|
|
log bytes.Buffer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (j *RecordingJar) SetCookies(u *url.URL, cookies []*Cookie) {
|
|
|
|
|
j.logf("SetCookie(%q, %v)\n", u, cookies)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (j *RecordingJar) Cookies(u *url.URL) []*Cookie {
|
|
|
|
|
j.logf("Cookies(%q)\n", u)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (j *RecordingJar) logf(format string, args ...interface{}) {
|
|
|
|
|
j.mu.Lock()
|
|
|
|
|
defer j.mu.Unlock()
|
|
|
|
|
fmt.Fprintf(&j.log, format, args...)
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-09 22:02:46 +00:00
|
|
|
func TestStreamingGet_h1(t *testing.T) { testStreamingGet(t, h1Mode) }
|
|
|
|
|
func TestStreamingGet_h2(t *testing.T) { testStreamingGet(t, h2Mode) }
|
2015-12-08 02:01:03 -07:00
|
|
|
|
|
|
|
|
func testStreamingGet(t *testing.T, h2 bool) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2011-05-24 09:02:01 -07:00
|
|
|
say := make(chan string)
|
2015-12-08 02:01:03 -07:00
|
|
|
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
|
2011-05-24 09:02:01 -07:00
|
|
|
w.(Flusher).Flush()
|
|
|
|
|
for str := range say {
|
|
|
|
|
w.Write([]byte(str))
|
|
|
|
|
w.(Flusher).Flush()
|
|
|
|
|
}
|
|
|
|
|
}))
|
2015-12-08 02:01:03 -07:00
|
|
|
defer cst.close()
|
2011-05-24 09:02:01 -07:00
|
|
|
|
2015-12-08 02:01:03 -07:00
|
|
|
c := cst.c
|
|
|
|
|
res, err := c.Get(cst.ts.URL)
|
2011-05-24 09:02:01 -07:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
var buf [10]byte
|
|
|
|
|
for _, str := range []string{"i", "am", "also", "known", "as", "comet"} {
|
|
|
|
|
say <- str
|
|
|
|
|
n, err := io.ReadFull(res.Body, buf[0:len(str)])
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ReadFull on %q: %v", str, err)
|
|
|
|
|
}
|
|
|
|
|
if n != len(str) {
|
|
|
|
|
t.Fatalf("Receiving %q, only read %d bytes", str, n)
|
|
|
|
|
}
|
|
|
|
|
got := string(buf[0:n])
|
|
|
|
|
if got != str {
|
|
|
|
|
t.Fatalf("Expected %q, got %q", str, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
close(say)
|
|
|
|
|
_, err = io.ReadFull(res.Body, buf[0:1])
|
2011-11-01 22:04:37 -04:00
|
|
|
if err != io.EOF {
|
2011-05-24 09:02:01 -07:00
|
|
|
t.Fatalf("at end expected EOF, got %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-06-23 21:10:51 -07:00
|
|
|
|
|
|
|
|
type writeCountingConn struct {
|
|
|
|
|
net.Conn
|
|
|
|
|
count *int
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-01 22:04:37 -04:00
|
|
|
func (c *writeCountingConn) Write(p []byte) (int, error) {
|
2011-06-23 21:10:51 -07:00
|
|
|
*c.count++
|
|
|
|
|
return c.Conn.Write(p)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestClientWrites verifies that client requests are buffered and we
|
|
|
|
|
// don't send a TCP packet per line of the http request + body.
|
|
|
|
|
func TestClientWrites(t *testing.T) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2011-06-23 21:10:51 -07:00
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
|
|
writes := 0
|
2011-11-01 22:04:37 -04:00
|
|
|
dialer := func(netz string, addr string) (net.Conn, error) {
|
2011-06-23 21:10:51 -07:00
|
|
|
c, err := net.Dial(netz, addr)
|
|
|
|
|
if err == nil {
|
|
|
|
|
c = &writeCountingConn{c, &writes}
|
|
|
|
|
}
|
|
|
|
|
return c, err
|
|
|
|
|
}
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
c.Transport.(*Transport).Dial = dialer
|
2011-06-23 21:10:51 -07:00
|
|
|
|
|
|
|
|
_, err := c.Get(ts.URL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if writes != 1 {
|
|
|
|
|
t.Errorf("Get request did %d Write calls, want 1", writes)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
writes = 0
|
2011-08-17 13:36:02 +10:00
|
|
|
_, err = c.PostForm(ts.URL, url.Values{"foo": {"bar"}})
|
2011-06-23 21:10:51 -07:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if writes != 1 {
|
|
|
|
|
t.Errorf("Post request did %d Write calls, want 1", writes)
|
|
|
|
|
}
|
|
|
|
|
}
|
2011-10-21 08:14:38 -07:00
|
|
|
|
|
|
|
|
func TestClientInsecureTransport(t *testing.T) {
|
2016-11-04 03:23:37 +00:00
|
|
|
setParallel(t)
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2011-10-21 08:14:38 -07:00
|
|
|
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
w.Write([]byte("Hello"))
|
|
|
|
|
}))
|
2014-02-28 12:12:51 -08:00
|
|
|
errc := make(chanWriter, 10) // but only expecting 1
|
|
|
|
|
ts.Config.ErrorLog = log.New(errc, "", 0)
|
2011-10-21 08:14:38 -07:00
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
|
|
// TODO(bradfitz): add tests for skipping hostname checks too?
|
|
|
|
|
// would require a new cert for testing, and probably
|
|
|
|
|
// redundant with these tests.
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
2011-10-21 08:14:38 -07:00
|
|
|
for _, insecure := range []bool{true, false} {
|
2017-03-04 18:24:44 +00:00
|
|
|
c.Transport.(*Transport).TLSClientConfig = &tls.Config{
|
|
|
|
|
InsecureSkipVerify: insecure,
|
2011-10-21 08:14:38 -07:00
|
|
|
}
|
2013-02-26 17:12:50 -08:00
|
|
|
res, err := c.Get(ts.URL)
|
2011-10-21 08:14:38 -07:00
|
|
|
if (err == nil) != insecure {
|
|
|
|
|
t.Errorf("insecure=%v: got unexpected err=%v", insecure, err)
|
|
|
|
|
}
|
2013-02-26 17:12:50 -08:00
|
|
|
if res != nil {
|
|
|
|
|
res.Body.Close()
|
|
|
|
|
}
|
2011-10-21 08:14:38 -07:00
|
|
|
}
|
2014-02-28 12:12:51 -08:00
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case v := <-errc:
|
2014-03-05 14:56:50 -08:00
|
|
|
if !strings.Contains(v, "TLS handshake error") {
|
|
|
|
|
t.Errorf("expected an error log message containing 'TLS handshake error'; got %q", v)
|
2014-02-28 12:12:51 -08:00
|
|
|
}
|
|
|
|
|
case <-time.After(5 * time.Second):
|
|
|
|
|
t.Errorf("timeout waiting for logged error")
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-21 08:14:38 -07:00
|
|
|
}
|
2012-01-26 14:37:14 -08:00
|
|
|
|
|
|
|
|
func TestClientErrorWithRequestURI(t *testing.T) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2012-01-26 14:37:14 -08:00
|
|
|
req, _ := NewRequest("GET", "http://localhost:1234/", nil)
|
|
|
|
|
req.RequestURI = "/this/field/is/illegal/and/should/error/"
|
|
|
|
|
_, err := DefaultClient.Do(req)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatalf("expected an error")
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(err.Error(), "RequestURI") {
|
|
|
|
|
t.Errorf("wanted error mentioning RequestURI; got error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-08-22 09:15:41 -07:00
|
|
|
|
|
|
|
|
func TestClientWithCorrectTLSServerName(t *testing.T) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2015-11-10 11:18:50 -08:00
|
|
|
|
|
|
|
|
const serverName = "example.com"
|
2012-08-22 09:15:41 -07:00
|
|
|
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
2015-11-10 11:18:50 -08:00
|
|
|
if r.TLS.ServerName != serverName {
|
|
|
|
|
t.Errorf("expected client to set ServerName %q, got: %q", serverName, r.TLS.ServerName)
|
2012-08-22 09:15:41 -07:00
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
c.Transport.(*Transport).TLSClientConfig.ServerName = serverName
|
2012-08-22 09:15:41 -07:00
|
|
|
if _, err := c.Get(ts.URL); err != nil {
|
|
|
|
|
t.Fatalf("expected successful TLS connection, got error: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientWithIncorrectTLSServerName(t *testing.T) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2012-08-22 09:15:41 -07:00
|
|
|
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {}))
|
|
|
|
|
defer ts.Close()
|
2014-02-28 12:12:51 -08:00
|
|
|
errc := make(chanWriter, 10) // but only expecting 1
|
|
|
|
|
ts.Config.ErrorLog = log.New(errc, "", 0)
|
2012-08-22 09:15:41 -07:00
|
|
|
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
c.Transport.(*Transport).TLSClientConfig.ServerName = "badserver"
|
2012-08-22 09:15:41 -07:00
|
|
|
_, err := c.Get(ts.URL)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatalf("expected an error")
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(err.Error(), "127.0.0.1") || !strings.Contains(err.Error(), "badserver") {
|
|
|
|
|
t.Errorf("wanted error mentioning 127.0.0.1 and badserver; got error: %v", err)
|
|
|
|
|
}
|
2014-02-28 12:12:51 -08:00
|
|
|
select {
|
|
|
|
|
case v := <-errc:
|
2014-03-04 11:55:35 -08:00
|
|
|
if !strings.Contains(v, "TLS handshake error") {
|
|
|
|
|
t.Errorf("expected an error log message containing 'TLS handshake error'; got %q", v)
|
2014-02-28 12:12:51 -08:00
|
|
|
}
|
|
|
|
|
case <-time.After(5 * time.Second):
|
|
|
|
|
t.Errorf("timeout waiting for logged error")
|
|
|
|
|
}
|
2012-08-22 09:15:41 -07:00
|
|
|
}
|
2012-12-05 22:36:23 -08:00
|
|
|
|
2013-07-22 22:39:09 -07:00
|
|
|
// Test for golang.org/issue/5829; the Transport should respect TLSClientConfig.ServerName
|
|
|
|
|
// when not empty.
|
|
|
|
|
//
|
|
|
|
|
// tls.Config.ServerName (non-empty, set to "example.com") takes
|
|
|
|
|
// precedence over "some-other-host.tld" which previously incorrectly
|
|
|
|
|
// took precedence. We don't actually connect to (or even resolve)
|
|
|
|
|
// "some-other-host.tld", though, because of the Transport.Dial hook.
|
|
|
|
|
//
|
|
|
|
|
// The httptest.Server has a cert with "example.com" as its name.
|
|
|
|
|
func TestTransportUsesTLSConfigServerName(t *testing.T) {
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
w.Write([]byte("Hello"))
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
tr := c.Transport.(*Transport)
|
2013-07-22 22:39:09 -07:00
|
|
|
tr.TLSClientConfig.ServerName = "example.com" // one of httptest's Server cert names
|
|
|
|
|
tr.Dial = func(netw, addr string) (net.Conn, error) {
|
|
|
|
|
return net.Dial(netw, ts.Listener.Addr().String())
|
|
|
|
|
}
|
|
|
|
|
res, err := c.Get("https://some-other-host.tld/")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
res.Body.Close()
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-05 12:25:55 -08:00
|
|
|
func TestResponseSetsTLSConnectionState(t *testing.T) {
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
w.Write([]byte("Hello"))
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
tr := c.Transport.(*Transport)
|
2014-03-05 12:25:55 -08:00
|
|
|
tr.TLSClientConfig.CipherSuites = []uint16{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA}
|
crypto/tls: enable TLS 1.3 and update tests
To disable TLS 1.3, simply remove VersionTLS13 from supportedVersions,
as tested by TestEscapeRoute, and amend documentation. To make it
opt-in, revert the change to (*Config).supportedVersions from this CL.
I did not have the heart to implement the early data skipping feature
when I realized that it did not offer a choice between two
abstraction-breaking options, but demanded them both (look for handshake
type in case of HelloRetryRequest, trial decryption otherwise). It's a
lot of complexity for an apparently small gain, but if anyone has strong
opinions about it let me know.
Note that in TLS 1.3 alerts are encrypted, so the close_notify peeking
to return (n > 0, io.EOF) from Read doesn't work. If we are lucky, those
servers that unexpectedly close connections after serving a single
request will have stopped (maybe thanks to H/2) before they got updated
to TLS 1.3.
Relatedly, session tickets are now provisioned on the client first Read
instead of at Handshake time, because they are, well, post-handshake
messages. If this proves to be a problem we might try to peek at them.
Doubled the tests that cover logic that's different in TLS 1.3.
The benchmarks for TLS 1.2 compared to be0f3c286b5 (before TLS 1.3 and
its refactors, after CL 142817 changed them to use real connections)
show little movement.
name old time/op new time/op delta
HandshakeServer/RSA-8 795µs ± 1% 798µs ± 1% ~ (p=0.057 n=10+18)
HandshakeServer/ECDHE-P256-RSA-8 903µs ± 0% 909µs ± 1% +0.68% (p=0.000 n=8+17)
HandshakeServer/ECDHE-P256-ECDSA-P256-8 198µs ± 0% 204µs ± 1% +3.24% (p=0.000 n=9+18)
HandshakeServer/ECDHE-X25519-ECDSA-P256-8 202µs ± 3% 208µs ± 1% +2.98% (p=0.000 n=9+20)
HandshakeServer/ECDHE-P521-ECDSA-P521-8 15.5ms ± 1% 15.9ms ± 2% +2.49% (p=0.000 n=10+20)
Throughput/MaxPacket/1MB-8 5.81ms ±23% 6.14ms ±44% ~ (p=0.605 n=8+18)
Throughput/MaxPacket/2MB-8 8.91ms ±22% 8.74ms ±33% ~ (p=0.498 n=9+19)
Throughput/MaxPacket/4MB-8 12.8ms ± 3% 14.0ms ±10% +9.74% (p=0.000 n=10+17)
Throughput/MaxPacket/8MB-8 25.1ms ± 7% 24.6ms ±16% ~ (p=0.129 n=9+19)
Throughput/MaxPacket/16MB-8 46.3ms ± 4% 45.9ms ±12% ~ (p=0.340 n=9+20)
Throughput/MaxPacket/32MB-8 88.5ms ± 4% 86.0ms ± 4% -2.82% (p=0.004 n=10+20)
Throughput/MaxPacket/64MB-8 173ms ± 2% 167ms ± 7% -3.42% (p=0.001 n=10+19)
Throughput/DynamicPacket/1MB-8 5.88ms ± 4% 6.59ms ±64% ~ (p=0.232 n=9+18)
Throughput/DynamicPacket/2MB-8 9.08ms ±12% 8.73ms ±21% ~ (p=0.408 n=10+18)
Throughput/DynamicPacket/4MB-8 14.2ms ± 5% 14.0ms ±11% ~ (p=0.188 n=9+19)
Throughput/DynamicPacket/8MB-8 25.1ms ± 6% 24.0ms ± 7% -4.39% (p=0.000 n=10+18)
Throughput/DynamicPacket/16MB-8 45.6ms ± 3% 43.3ms ± 1% -5.22% (p=0.000 n=10+8)
Throughput/DynamicPacket/32MB-8 88.4ms ± 3% 84.8ms ± 2% -4.06% (p=0.000 n=10+10)
Throughput/DynamicPacket/64MB-8 175ms ± 3% 167ms ± 2% -4.63% (p=0.000 n=10+10)
Latency/MaxPacket/200kbps-8 694ms ± 0% 694ms ± 0% -0.02% (p=0.000 n=9+9)
Latency/MaxPacket/500kbps-8 279ms ± 0% 279ms ± 0% -0.09% (p=0.000 n=10+10)
Latency/MaxPacket/1000kbps-8 140ms ± 0% 140ms ± 0% -0.15% (p=0.000 n=10+9)
Latency/MaxPacket/2000kbps-8 71.1ms ± 0% 71.0ms ± 0% -0.09% (p=0.001 n=8+9)
Latency/MaxPacket/5000kbps-8 30.5ms ± 6% 30.1ms ± 6% ~ (p=0.905 n=10+9)
Latency/DynamicPacket/200kbps-8 134ms ± 0% 134ms ± 0% ~ (p=0.796 n=9+9)
Latency/DynamicPacket/500kbps-8 54.8ms ± 0% 54.7ms ± 0% -0.18% (p=0.000 n=8+10)
Latency/DynamicPacket/1000kbps-8 28.5ms ± 0% 29.1ms ± 8% ~ (p=0.173 n=8+10)
Latency/DynamicPacket/2000kbps-8 15.3ms ± 6% 15.9ms ±10% ~ (p=0.905 n=9+10)
Latency/DynamicPacket/5000kbps-8 9.14ms ±21% 9.65ms ±82% ~ (p=0.529 n=10+10)
name old speed new speed delta
Throughput/MaxPacket/1MB-8 175MB/s ±13% 167MB/s ±64% ~ (p=0.646 n=7+20)
Throughput/MaxPacket/2MB-8 241MB/s ±25% 241MB/s ±40% ~ (p=0.660 n=9+20)
Throughput/MaxPacket/4MB-8 328MB/s ± 3% 300MB/s ± 9% -8.70% (p=0.000 n=10+17)
Throughput/MaxPacket/8MB-8 335MB/s ± 7% 340MB/s ±17% ~ (p=0.212 n=9+20)
Throughput/MaxPacket/16MB-8 363MB/s ± 4% 367MB/s ±11% ~ (p=0.340 n=9+20)
Throughput/MaxPacket/32MB-8 379MB/s ± 4% 390MB/s ± 4% +2.93% (p=0.004 n=10+20)
Throughput/MaxPacket/64MB-8 388MB/s ± 2% 401MB/s ± 7% +3.25% (p=0.004 n=10+20)
Throughput/DynamicPacket/1MB-8 178MB/s ± 4% 157MB/s ±73% ~ (p=0.127 n=9+20)
Throughput/DynamicPacket/2MB-8 232MB/s ±11% 243MB/s ±18% ~ (p=0.415 n=10+18)
Throughput/DynamicPacket/4MB-8 296MB/s ± 5% 299MB/s ±15% ~ (p=0.295 n=9+20)
Throughput/DynamicPacket/8MB-8 334MB/s ± 6% 350MB/s ± 7% +4.58% (p=0.000 n=10+18)
Throughput/DynamicPacket/16MB-8 368MB/s ± 3% 388MB/s ± 1% +5.48% (p=0.000 n=10+8)
Throughput/DynamicPacket/32MB-8 380MB/s ± 3% 396MB/s ± 2% +4.20% (p=0.000 n=10+10)
Throughput/DynamicPacket/64MB-8 384MB/s ± 3% 403MB/s ± 2% +4.83% (p=0.000 n=10+10)
Comparing TLS 1.2 and TLS 1.3 at tip shows a slight (~5-10%) slowdown of
handshakes, which might be worth looking at next cycle, but the latency
improvements are expected to overshadow that.
name old time/op new time/op delta
HandshakeServer/ECDHE-P256-RSA-8 909µs ± 1% 963µs ± 0% +5.87% (p=0.000 n=17+18)
HandshakeServer/ECDHE-P256-ECDSA-P256-8 204µs ± 1% 225µs ± 2% +10.20% (p=0.000 n=18+20)
HandshakeServer/ECDHE-X25519-ECDSA-P256-8 208µs ± 1% 230µs ± 2% +10.35% (p=0.000 n=20+18)
HandshakeServer/ECDHE-P521-ECDSA-P521-8 15.9ms ± 2% 15.9ms ± 1% ~ (p=0.444 n=20+19)
Throughput/MaxPacket/1MB-8 6.14ms ±44% 7.07ms ±46% ~ (p=0.057 n=18+19)
Throughput/MaxPacket/2MB-8 8.74ms ±33% 8.61ms ± 9% ~ (p=0.552 n=19+17)
Throughput/MaxPacket/4MB-8 14.0ms ±10% 14.1ms ±12% ~ (p=0.707 n=17+20)
Throughput/MaxPacket/8MB-8 24.6ms ±16% 25.6ms ±14% ~ (p=0.107 n=19+20)
Throughput/MaxPacket/16MB-8 45.9ms ±12% 44.7ms ± 6% ~ (p=0.607 n=20+19)
Throughput/MaxPacket/32MB-8 86.0ms ± 4% 87.9ms ± 8% ~ (p=0.113 n=20+19)
Throughput/MaxPacket/64MB-8 167ms ± 7% 169ms ± 2% +1.26% (p=0.011 n=19+19)
Throughput/DynamicPacket/1MB-8 6.59ms ±64% 6.79ms ±43% ~ (p=0.480 n=18+19)
Throughput/DynamicPacket/2MB-8 8.73ms ±21% 9.58ms ±13% +9.71% (p=0.006 n=18+20)
Throughput/DynamicPacket/4MB-8 14.0ms ±11% 13.9ms ±10% ~ (p=0.687 n=19+20)
Throughput/DynamicPacket/8MB-8 24.0ms ± 7% 24.6ms ± 8% +2.36% (p=0.045 n=18+17)
Throughput/DynamicPacket/16MB-8 43.3ms ± 1% 44.3ms ± 2% +2.48% (p=0.001 n=8+9)
Throughput/DynamicPacket/32MB-8 84.8ms ± 2% 86.7ms ± 2% +2.27% (p=0.000 n=10+10)
Throughput/DynamicPacket/64MB-8 167ms ± 2% 170ms ± 3% +1.89% (p=0.005 n=10+10)
Latency/MaxPacket/200kbps-8 694ms ± 0% 699ms ± 0% +0.65% (p=0.000 n=9+10)
Latency/MaxPacket/500kbps-8 279ms ± 0% 280ms ± 0% +0.68% (p=0.000 n=10+10)
Latency/MaxPacket/1000kbps-8 140ms ± 0% 141ms ± 0% +0.59% (p=0.000 n=9+9)
Latency/MaxPacket/2000kbps-8 71.0ms ± 0% 71.3ms ± 0% +0.42% (p=0.000 n=9+9)
Latency/MaxPacket/5000kbps-8 30.1ms ± 6% 30.7ms ±10% +1.93% (p=0.019 n=9+9)
Latency/DynamicPacket/200kbps-8 134ms ± 0% 138ms ± 0% +3.22% (p=0.000 n=9+10)
Latency/DynamicPacket/500kbps-8 54.7ms ± 0% 56.3ms ± 0% +3.03% (p=0.000 n=10+8)
Latency/DynamicPacket/1000kbps-8 29.1ms ± 8% 29.1ms ± 0% ~ (p=0.173 n=10+8)
Latency/DynamicPacket/2000kbps-8 15.9ms ±10% 16.4ms ±36% ~ (p=0.633 n=10+8)
Latency/DynamicPacket/5000kbps-8 9.65ms ±82% 8.32ms ± 8% ~ (p=0.573 n=10+8)
name old speed new speed delta
Throughput/MaxPacket/1MB-8 167MB/s ±64% 155MB/s ±55% ~ (p=0.224 n=20+19)
Throughput/MaxPacket/2MB-8 241MB/s ±40% 244MB/s ± 9% ~ (p=0.407 n=20+17)
Throughput/MaxPacket/4MB-8 300MB/s ± 9% 298MB/s ±11% ~ (p=0.707 n=17+20)
Throughput/MaxPacket/8MB-8 340MB/s ±17% 330MB/s ±13% ~ (p=0.201 n=20+20)
Throughput/MaxPacket/16MB-8 367MB/s ±11% 375MB/s ± 5% ~ (p=0.607 n=20+19)
Throughput/MaxPacket/32MB-8 390MB/s ± 4% 382MB/s ± 8% ~ (p=0.113 n=20+19)
Throughput/MaxPacket/64MB-8 401MB/s ± 7% 397MB/s ± 2% -0.96% (p=0.030 n=20+19)
Throughput/DynamicPacket/1MB-8 157MB/s ±73% 156MB/s ±39% ~ (p=0.738 n=20+20)
Throughput/DynamicPacket/2MB-8 243MB/s ±18% 220MB/s ±14% -9.65% (p=0.006 n=18+20)
Throughput/DynamicPacket/4MB-8 299MB/s ±15% 303MB/s ± 9% ~ (p=0.512 n=20+20)
Throughput/DynamicPacket/8MB-8 350MB/s ± 7% 342MB/s ± 8% -2.27% (p=0.045 n=18+17)
Throughput/DynamicPacket/16MB-8 388MB/s ± 1% 378MB/s ± 2% -2.41% (p=0.001 n=8+9)
Throughput/DynamicPacket/32MB-8 396MB/s ± 2% 387MB/s ± 2% -2.21% (p=0.000 n=10+10)
Throughput/DynamicPacket/64MB-8 403MB/s ± 2% 396MB/s ± 3% -1.84% (p=0.005 n=10+10)
Fixes #9671
Change-Id: Ieb57c5140eb2c083b8be0d42b240cd2eeec0dcf6
Reviewed-on: https://go-review.googlesource.com/c/147638
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Adam Langley <agl@golang.org>
2018-11-05 22:52:51 -05:00
|
|
|
tr.TLSClientConfig.MaxVersion = tls.VersionTLS12 // to get to pick the cipher suite
|
2014-03-05 12:25:55 -08:00
|
|
|
tr.Dial = func(netw, addr string) (net.Conn, error) {
|
|
|
|
|
return net.Dial(netw, ts.Listener.Addr().String())
|
|
|
|
|
}
|
|
|
|
|
res, err := c.Get("https://example.com/")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2014-03-05 12:40:13 -08:00
|
|
|
defer res.Body.Close()
|
2014-03-05 12:25:55 -08:00
|
|
|
if res.TLS == nil {
|
|
|
|
|
t.Fatal("Response didn't set TLS Connection State.")
|
|
|
|
|
}
|
2014-03-05 12:40:13 -08:00
|
|
|
if got, want := res.TLS.CipherSuite, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA; got != want {
|
|
|
|
|
t.Errorf("TLS Cipher Suite = %d; want %d", got, want)
|
2014-03-05 12:25:55 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-10-20 00:35:42 -07:00
|
|
|
// Check that an HTTPS client can interpret a particular TLS error
|
|
|
|
|
// to determine that the server is speaking HTTP.
|
|
|
|
|
// See golang.org/issue/11111.
|
|
|
|
|
func TestHTTPSClientDetectsHTTPServer(t *testing.T) {
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {}))
|
2016-08-26 22:21:00 +01:00
|
|
|
ts.Config.ErrorLog = quietLog
|
2015-10-20 00:35:42 -07:00
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
|
|
_, err := Get(strings.Replace(ts.URL, "http", "https", 1))
|
|
|
|
|
if got := err.Error(); !strings.Contains(got, "HTTP response to HTTPS client") {
|
|
|
|
|
t.Fatalf("error = %q; want error indicating HTTP response to HTTPS request", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-10 17:17:11 -06:00
|
|
|
// Verify Response.ContentLength is populated. https://golang.org/issue/4126
|
2015-12-08 02:18:57 -07:00
|
|
|
func TestClientHeadContentLength_h1(t *testing.T) {
|
2015-12-09 22:02:46 +00:00
|
|
|
testClientHeadContentLength(t, h1Mode)
|
2015-12-08 02:18:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientHeadContentLength_h2(t *testing.T) {
|
2015-12-09 22:02:46 +00:00
|
|
|
testClientHeadContentLength(t, h2Mode)
|
2015-12-08 02:18:57 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testClientHeadContentLength(t *testing.T, h2 bool) {
|
2013-03-15 15:09:17 -07:00
|
|
|
defer afterTest(t)
|
2015-12-08 02:18:57 -07:00
|
|
|
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
|
2012-12-05 22:36:23 -08:00
|
|
|
if v := r.FormValue("cl"); v != "" {
|
|
|
|
|
w.Header().Set("Content-Length", v)
|
|
|
|
|
}
|
|
|
|
|
}))
|
2015-12-08 02:18:57 -07:00
|
|
|
defer cst.close()
|
2012-12-05 22:36:23 -08:00
|
|
|
tests := []struct {
|
|
|
|
|
suffix string
|
|
|
|
|
want int64
|
|
|
|
|
}{
|
|
|
|
|
{"/?cl=1234", 1234},
|
|
|
|
|
{"/?cl=0", 0},
|
|
|
|
|
{"", -1},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
2015-12-08 02:18:57 -07:00
|
|
|
req, _ := NewRequest("HEAD", cst.ts.URL+tt.suffix, nil)
|
|
|
|
|
res, err := cst.c.Do(req)
|
2012-12-05 22:36:23 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if res.ContentLength != tt.want {
|
|
|
|
|
t.Errorf("Content-Length = %d; want %d", res.ContentLength, tt.want)
|
|
|
|
|
}
|
2020-10-16 00:49:02 -04:00
|
|
|
bs, err := io.ReadAll(res.Body)
|
2012-12-05 22:36:23 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
if len(bs) != 0 {
|
|
|
|
|
t.Errorf("Unexpected content: %q", bs)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-05-14 15:33:46 -07:00
|
|
|
|
|
|
|
|
func TestEmptyPasswordAuth(t *testing.T) {
|
2016-11-05 00:21:59 +00:00
|
|
|
setParallel(t)
|
2013-05-14 15:33:46 -07:00
|
|
|
defer afterTest(t)
|
|
|
|
|
gopher := "gopher"
|
|
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
auth := r.Header.Get("Authorization")
|
|
|
|
|
if strings.HasPrefix(auth, "Basic ") {
|
|
|
|
|
encoded := auth[6:]
|
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(encoded)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
expected := gopher + ":"
|
|
|
|
|
s := string(decoded)
|
|
|
|
|
if expected != s {
|
|
|
|
|
t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
t.Errorf("Invalid auth %q", auth)
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
req, err := NewRequest("GET", ts.URL, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
req.URL.User = url.User(gopher)
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
2013-05-14 15:33:46 -07:00
|
|
|
resp, err := c.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
}
|
2013-08-07 11:58:59 -07:00
|
|
|
|
|
|
|
|
func TestBasicAuth(t *testing.T) {
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
tr := &recordingTransport{}
|
|
|
|
|
client := &Client{Transport: tr}
|
|
|
|
|
|
|
|
|
|
url := "http://My%20User:My%20Pass@dummy.faketld/"
|
|
|
|
|
expected := "My User:My Pass"
|
|
|
|
|
client.Get(url)
|
|
|
|
|
|
|
|
|
|
if tr.req.Method != "GET" {
|
|
|
|
|
t.Errorf("got method %q, want %q", tr.req.Method, "GET")
|
|
|
|
|
}
|
|
|
|
|
if tr.req.URL.String() != url {
|
|
|
|
|
t.Errorf("got URL %q, want %q", tr.req.URL.String(), url)
|
|
|
|
|
}
|
|
|
|
|
if tr.req.Header == nil {
|
|
|
|
|
t.Fatalf("expected non-nil request Header")
|
|
|
|
|
}
|
|
|
|
|
auth := tr.req.Header.Get("Authorization")
|
|
|
|
|
if strings.HasPrefix(auth, "Basic ") {
|
|
|
|
|
encoded := auth[6:]
|
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(encoded)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
s := string(decoded)
|
|
|
|
|
if expected != s {
|
|
|
|
|
t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
t.Errorf("Invalid auth %q", auth)
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-03-02 20:39:20 -08:00
|
|
|
|
2015-06-25 16:52:51 +01:00
|
|
|
func TestBasicAuthHeadersPreserved(t *testing.T) {
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
tr := &recordingTransport{}
|
|
|
|
|
client := &Client{Transport: tr}
|
|
|
|
|
|
|
|
|
|
// If Authorization header is provided, username in URL should not override it
|
|
|
|
|
url := "http://My%20User@dummy.faketld/"
|
|
|
|
|
req, err := NewRequest("GET", url, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
req.SetBasicAuth("My User", "My Pass")
|
|
|
|
|
expected := "My User:My Pass"
|
|
|
|
|
client.Do(req)
|
|
|
|
|
|
|
|
|
|
if tr.req.Method != "GET" {
|
|
|
|
|
t.Errorf("got method %q, want %q", tr.req.Method, "GET")
|
|
|
|
|
}
|
|
|
|
|
if tr.req.URL.String() != url {
|
|
|
|
|
t.Errorf("got URL %q, want %q", tr.req.URL.String(), url)
|
|
|
|
|
}
|
|
|
|
|
if tr.req.Header == nil {
|
|
|
|
|
t.Fatalf("expected non-nil request Header")
|
|
|
|
|
}
|
|
|
|
|
auth := tr.req.Header.Get("Authorization")
|
|
|
|
|
if strings.HasPrefix(auth, "Basic ") {
|
|
|
|
|
encoded := auth[6:]
|
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(encoded)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
s := string(decoded)
|
|
|
|
|
if expected != s {
|
|
|
|
|
t.Errorf("Invalid Authorization header. Got %q, wanted %q", s, expected)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
t.Errorf("Invalid auth %q", auth)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-28 11:44:10 +03:00
|
|
|
func TestStripPasswordFromError(t *testing.T) {
|
|
|
|
|
client := &Client{Transport: &recordingTransport{}}
|
|
|
|
|
testCases := []struct {
|
|
|
|
|
desc string
|
|
|
|
|
in string
|
|
|
|
|
out string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
desc: "Strip password from error message",
|
|
|
|
|
in: "http://user:password@dummy.faketld/",
|
2019-08-28 12:10:16 +00:00
|
|
|
out: `Get "http://user:***@dummy.faketld/": dummy impl`,
|
2018-03-28 11:44:10 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
desc: "Don't Strip password from domain name",
|
|
|
|
|
in: "http://user:password@password.faketld/",
|
2019-08-28 12:10:16 +00:00
|
|
|
out: `Get "http://user:***@password.faketld/": dummy impl`,
|
2018-03-28 11:44:10 +03:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
desc: "Don't Strip password from path",
|
|
|
|
|
in: "http://user:password@dummy.faketld/password",
|
2019-08-28 12:10:16 +00:00
|
|
|
out: `Get "http://user:***@dummy.faketld/password": dummy impl`,
|
2018-03-28 11:44:10 +03:00
|
|
|
},
|
2019-05-03 11:41:54 +07:00
|
|
|
{
|
|
|
|
|
desc: "Strip escaped password",
|
|
|
|
|
in: "http://user:pa%2Fssword@dummy.faketld/",
|
2019-08-28 12:10:16 +00:00
|
|
|
out: `Get "http://user:***@dummy.faketld/": dummy impl`,
|
2019-05-03 11:41:54 +07:00
|
|
|
},
|
2018-03-28 11:44:10 +03:00
|
|
|
}
|
|
|
|
|
for _, tC := range testCases {
|
|
|
|
|
t.Run(tC.desc, func(t *testing.T) {
|
|
|
|
|
_, err := client.Get(tC.in)
|
|
|
|
|
if err.Error() != tC.out {
|
|
|
|
|
t.Errorf("Unexpected output for %q: expected %q, actual %q",
|
|
|
|
|
tC.in, tC.out, err.Error())
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-14 16:17:21 -08:00
|
|
|
func TestClientTimeout_h1(t *testing.T) { testClientTimeout(t, h1Mode) }
|
net/http: make Client use Request.Cancel for timeouts instead of CancelRequest
In the beginning, there was no way to cancel an HTTP request.
We later added Transport.CancelRequest to cancel an in-flight HTTP
request by breaking its underlying TCP connection, but it was hard to
use correctly and didn't work in all cases. And its error messages
were terrible. Some of those issues were fixed over time, but the most
unfixable problem was that it didn't compose well. All RoundTripper
implementations had to choose to whether to implement CancelRequest
and both decisions had negative consequences.
In Go 1.5 we added Request.Cancel, which composed well, worked in all
phases, had nice error messages, etc. But we forgot to use it in the
implementation of Client.Timeout (a timeout which spans multiple
requests and reading request bodies).
In Go 1.6 (upcoming), we added HTTP/2 support, but now Client.Timeout
didn't work because the http2.Transport didn't have a CancelRequest
method.
Rather than add a CancelRequest method to http2, officially deprecate
it and update the only caller (Client, for Client.Cancel) to use
Request.Cancel instead.
The http2 Client timeout tests are enabled now.
For compatibility, we still use CancelRequest in Client if we don't
recognize the RoundTripper type. But documentation has been updated to
tell people that CancelRequest is deprecated.
Fixes #13540
Change-Id: I15546b90825bb8b54905e17563eca55ea2642075
Reviewed-on: https://go-review.googlesource.com/18260
Reviewed-by: Andrew Gerrand <adg@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-01-04 20:59:05 -08:00
|
|
|
func TestClientTimeout_h2(t *testing.T) { testClientTimeout(t, h2Mode) }
|
2015-12-14 16:17:21 -08:00
|
|
|
|
|
|
|
|
func testClientTimeout(t *testing.T, h2 bool) {
|
2016-11-01 00:44:48 -07:00
|
|
|
setParallel(t)
|
2014-03-02 20:39:20 -08:00
|
|
|
defer afterTest(t)
|
2016-11-21 14:58:23 +00:00
|
|
|
testDone := make(chan struct{}) // closed in defer below
|
2016-11-01 00:44:48 -07:00
|
|
|
|
2014-03-02 20:39:20 -08:00
|
|
|
sawRoot := make(chan bool, 1)
|
|
|
|
|
sawSlow := make(chan bool, 1)
|
2015-12-14 16:17:21 -08:00
|
|
|
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
|
2014-03-02 20:39:20 -08:00
|
|
|
if r.URL.Path == "/" {
|
|
|
|
|
sawRoot <- true
|
|
|
|
|
Redirect(w, r, "/slow", StatusFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if r.URL.Path == "/slow" {
|
2016-11-10 23:03:06 +00:00
|
|
|
sawSlow <- true
|
2014-03-02 20:39:20 -08:00
|
|
|
w.Write([]byte("Hello"))
|
|
|
|
|
w.(Flusher).Flush()
|
2016-11-21 14:58:23 +00:00
|
|
|
<-testDone
|
2014-03-02 20:39:20 -08:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}))
|
2015-12-14 16:17:21 -08:00
|
|
|
defer cst.close()
|
2016-11-21 14:58:23 +00:00
|
|
|
defer close(testDone) // before cst.close, to unblock /slow handler
|
|
|
|
|
|
|
|
|
|
// 200ms should be long enough to get a normal request (the /
|
|
|
|
|
// handler), but not so long that it makes the test slow.
|
|
|
|
|
const timeout = 200 * time.Millisecond
|
2015-12-14 16:17:21 -08:00
|
|
|
cst.c.Timeout = timeout
|
2014-03-02 20:39:20 -08:00
|
|
|
|
2015-12-14 16:17:21 -08:00
|
|
|
res, err := cst.c.Get(cst.ts.URL)
|
2014-03-02 20:39:20 -08:00
|
|
|
if err != nil {
|
2016-11-01 00:44:48 -07:00
|
|
|
if strings.Contains(err.Error(), "Client.Timeout") {
|
2016-11-21 14:58:23 +00:00
|
|
|
t.Skipf("host too slow to get fast resource in %v", timeout)
|
2016-11-01 00:44:48 -07:00
|
|
|
}
|
2014-03-02 20:39:20 -08:00
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case <-sawRoot:
|
|
|
|
|
// good.
|
|
|
|
|
default:
|
|
|
|
|
t.Fatal("handler never got / request")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case <-sawSlow:
|
|
|
|
|
// good.
|
|
|
|
|
default:
|
|
|
|
|
t.Fatal("handler never got /slow request")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
errc := make(chan error, 1)
|
|
|
|
|
go func() {
|
2020-10-16 00:49:02 -04:00
|
|
|
_, err := io.ReadAll(res.Body)
|
2014-03-02 20:39:20 -08:00
|
|
|
errc <- err
|
|
|
|
|
res.Body.Close()
|
|
|
|
|
}()
|
|
|
|
|
|
2016-11-21 14:58:23 +00:00
|
|
|
const failTime = 5 * time.Second
|
2014-03-02 20:39:20 -08:00
|
|
|
select {
|
|
|
|
|
case err := <-errc:
|
|
|
|
|
if err == nil {
|
2014-12-20 15:46:09 +11:00
|
|
|
t.Fatal("expected error from ReadAll")
|
|
|
|
|
}
|
|
|
|
|
ne, ok := err.(net.Error)
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Errorf("error value from ReadAll was %T; expected some net.Error", err)
|
|
|
|
|
} else if !ne.Timeout() {
|
|
|
|
|
t.Errorf("net.Error.Timeout = false; want true")
|
|
|
|
|
}
|
2019-05-07 16:25:05 -07:00
|
|
|
if got := ne.Error(); !strings.Contains(got, "(Client.Timeout") {
|
2014-12-20 15:46:09 +11:00
|
|
|
t.Errorf("error string = %q; missing timeout substring", got)
|
2014-03-02 20:39:20 -08:00
|
|
|
}
|
|
|
|
|
case <-time.After(failTime):
|
|
|
|
|
t.Errorf("timeout after %v waiting for timeout of %v", failTime, timeout)
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-03-03 11:25:57 -08:00
|
|
|
|
2015-12-14 16:39:09 -08:00
|
|
|
func TestClientTimeout_Headers_h1(t *testing.T) { testClientTimeout_Headers(t, h1Mode) }
|
net/http: make Client use Request.Cancel for timeouts instead of CancelRequest
In the beginning, there was no way to cancel an HTTP request.
We later added Transport.CancelRequest to cancel an in-flight HTTP
request by breaking its underlying TCP connection, but it was hard to
use correctly and didn't work in all cases. And its error messages
were terrible. Some of those issues were fixed over time, but the most
unfixable problem was that it didn't compose well. All RoundTripper
implementations had to choose to whether to implement CancelRequest
and both decisions had negative consequences.
In Go 1.5 we added Request.Cancel, which composed well, worked in all
phases, had nice error messages, etc. But we forgot to use it in the
implementation of Client.Timeout (a timeout which spans multiple
requests and reading request bodies).
In Go 1.6 (upcoming), we added HTTP/2 support, but now Client.Timeout
didn't work because the http2.Transport didn't have a CancelRequest
method.
Rather than add a CancelRequest method to http2, officially deprecate
it and update the only caller (Client, for Client.Cancel) to use
Request.Cancel instead.
The http2 Client timeout tests are enabled now.
For compatibility, we still use CancelRequest in Client if we don't
recognize the RoundTripper type. But documentation has been updated to
tell people that CancelRequest is deprecated.
Fixes #13540
Change-Id: I15546b90825bb8b54905e17563eca55ea2642075
Reviewed-on: https://go-review.googlesource.com/18260
Reviewed-by: Andrew Gerrand <adg@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-01-04 20:59:05 -08:00
|
|
|
func TestClientTimeout_Headers_h2(t *testing.T) { testClientTimeout_Headers(t, h2Mode) }
|
2015-12-14 16:39:09 -08:00
|
|
|
|
2014-12-20 15:46:09 +11:00
|
|
|
// Client.Timeout firing before getting to the body
|
2015-12-14 16:39:09 -08:00
|
|
|
func testClientTimeout_Headers(t *testing.T, h2 bool) {
|
2016-11-01 00:44:48 -07:00
|
|
|
setParallel(t)
|
2014-12-20 15:46:09 +11:00
|
|
|
defer afterTest(t)
|
2016-11-01 00:44:48 -07:00
|
|
|
donec := make(chan bool, 1)
|
2015-12-14 16:39:09 -08:00
|
|
|
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
|
2014-12-20 15:46:09 +11:00
|
|
|
<-donec
|
2019-05-28 17:45:22 +00:00
|
|
|
}), optQuietLog)
|
2015-12-14 16:39:09 -08:00
|
|
|
defer cst.close()
|
2015-05-11 10:31:24 +01:00
|
|
|
// Note that we use a channel send here and not a close.
|
|
|
|
|
// The race detector doesn't know that we're waiting for a timeout
|
|
|
|
|
// and thinks that the waitgroup inside httptest.Server is added to concurrently
|
|
|
|
|
// with us closing it. If we timed out immediately, we could close the testserver
|
|
|
|
|
// before we entered the handler. We're not timing out immediately and there's
|
|
|
|
|
// no way we would be done before we entered the handler, but the race detector
|
|
|
|
|
// doesn't know this, so synchronize explicitly.
|
|
|
|
|
defer func() { donec <- true }()
|
2014-12-20 15:46:09 +11:00
|
|
|
|
2016-11-01 00:44:48 -07:00
|
|
|
cst.c.Timeout = 5 * time.Millisecond
|
|
|
|
|
res, err := cst.c.Get(cst.ts.URL)
|
2014-12-20 15:46:09 +11:00
|
|
|
if err == nil {
|
2016-11-01 00:44:48 -07:00
|
|
|
res.Body.Close()
|
2014-12-20 15:46:09 +11:00
|
|
|
t.Fatal("got response from Get; expected error")
|
|
|
|
|
}
|
2015-10-10 14:21:42 +11:00
|
|
|
if _, ok := err.(*url.Error); !ok {
|
2014-12-20 15:46:09 +11:00
|
|
|
t.Fatalf("Got error of type %T; want *url.Error", err)
|
|
|
|
|
}
|
2015-10-10 14:21:42 +11:00
|
|
|
ne, ok := err.(net.Error)
|
2014-12-20 15:46:09 +11:00
|
|
|
if !ok {
|
2015-10-10 14:21:42 +11:00
|
|
|
t.Fatalf("Got error of type %T; want some net.Error", err)
|
2014-12-20 15:46:09 +11:00
|
|
|
}
|
|
|
|
|
if !ne.Timeout() {
|
|
|
|
|
t.Error("net.Error.Timeout = false; want true")
|
|
|
|
|
}
|
|
|
|
|
if got := ne.Error(); !strings.Contains(got, "Client.Timeout exceeded") {
|
|
|
|
|
t.Errorf("error string = %q; missing timeout substring", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-01 00:44:48 -07:00
|
|
|
// Issue 16094: if Client.Timeout is set but not hit, a Timeout error shouldn't be
|
|
|
|
|
// returned.
|
|
|
|
|
func TestClientTimeoutCancel(t *testing.T) {
|
|
|
|
|
setParallel(t)
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
|
|
|
|
|
testDone := make(chan struct{})
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
|
|
|
|
|
|
cst := newClientServerTest(t, h1Mode, HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
w.(Flusher).Flush()
|
|
|
|
|
<-testDone
|
|
|
|
|
}))
|
|
|
|
|
defer cst.close()
|
|
|
|
|
defer close(testDone)
|
|
|
|
|
|
|
|
|
|
cst.c.Timeout = 1 * time.Hour
|
|
|
|
|
req, _ := NewRequest("GET", cst.ts.URL, nil)
|
|
|
|
|
req.Cancel = ctx.Done()
|
|
|
|
|
res, err := cst.c.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
cancel()
|
2020-10-16 00:49:02 -04:00
|
|
|
_, err = io.Copy(io.Discard, res.Body)
|
2016-11-01 00:44:48 -07:00
|
|
|
if err != ExportErrRequestCanceled {
|
2016-11-11 07:37:32 -08:00
|
|
|
t.Fatalf("error = %v; want errRequestCanceled", err)
|
2016-11-01 00:44:48 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-09 22:02:46 +00:00
|
|
|
func TestClientRedirectEatsBody_h1(t *testing.T) { testClientRedirectEatsBody(t, h1Mode) }
|
|
|
|
|
func TestClientRedirectEatsBody_h2(t *testing.T) { testClientRedirectEatsBody(t, h2Mode) }
|
2015-12-08 02:48:10 -07:00
|
|
|
func testClientRedirectEatsBody(t *testing.T, h2 bool) {
|
2016-11-04 03:23:37 +00:00
|
|
|
setParallel(t)
|
2014-03-03 11:25:57 -08:00
|
|
|
defer afterTest(t)
|
|
|
|
|
saw := make(chan string, 2)
|
2015-12-08 02:48:10 -07:00
|
|
|
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
|
2014-03-03 11:25:57 -08:00
|
|
|
saw <- r.RemoteAddr
|
|
|
|
|
if r.URL.Path == "/" {
|
|
|
|
|
Redirect(w, r, "/foo", StatusFound) // which includes a body
|
|
|
|
|
}
|
|
|
|
|
}))
|
2015-12-08 02:48:10 -07:00
|
|
|
defer cst.close()
|
2014-03-03 11:25:57 -08:00
|
|
|
|
2015-12-08 02:48:10 -07:00
|
|
|
res, err := cst.c.Get(cst.ts.URL)
|
2014-03-03 11:25:57 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
2020-10-16 00:49:02 -04:00
|
|
|
_, err = io.ReadAll(res.Body)
|
2016-11-01 00:44:48 -07:00
|
|
|
res.Body.Close()
|
2014-03-03 11:25:57 -08:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var first string
|
|
|
|
|
select {
|
|
|
|
|
case first = <-saw:
|
|
|
|
|
default:
|
|
|
|
|
t.Fatal("server didn't see a request")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var second string
|
|
|
|
|
select {
|
|
|
|
|
case second = <-saw:
|
|
|
|
|
default:
|
|
|
|
|
t.Fatal("server didn't see a second request")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if first != second {
|
|
|
|
|
t.Fatal("server saw different client ports before & after the redirect")
|
|
|
|
|
}
|
|
|
|
|
}
|
net/http: document, test, define, clean up Request.Trailer
Go's had pretty decent HTTP Trailer support for a long time, but
the docs have been largely non-existent. Fix that.
In the process, re-learn the Trailer code, clean some stuff
up, add some error checks, remove some TODOs, fix a minor bug
or two, and add tests.
LGTM=adg
R=golang-codereviews, adg
CC=dsymonds, golang-codereviews, rsc
https://golang.org/cl/86660043
2014-04-10 17:01:21 -07:00
|
|
|
|
|
|
|
|
// eofReaderFunc is an io.Reader that runs itself, and then returns io.EOF.
|
|
|
|
|
type eofReaderFunc func()
|
|
|
|
|
|
|
|
|
|
func (f eofReaderFunc) Read(p []byte) (n int, err error) {
|
|
|
|
|
f()
|
|
|
|
|
return 0, io.EOF
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-07 07:13:42 -07:00
|
|
|
func TestReferer(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
lastReq, newReq string // from -> to URLs
|
|
|
|
|
want string
|
|
|
|
|
}{
|
|
|
|
|
// don't send user:
|
|
|
|
|
{"http://gopher@test.com", "http://link.com", "http://test.com"},
|
|
|
|
|
{"https://gopher@test.com", "https://link.com", "https://test.com"},
|
|
|
|
|
|
|
|
|
|
// don't send a user and password:
|
|
|
|
|
{"http://gopher:go@test.com", "http://link.com", "http://test.com"},
|
|
|
|
|
{"https://gopher:go@test.com", "https://link.com", "https://test.com"},
|
|
|
|
|
|
|
|
|
|
// nothing to do:
|
|
|
|
|
{"http://test.com", "http://link.com", "http://test.com"},
|
|
|
|
|
{"https://test.com", "https://link.com", "https://test.com"},
|
|
|
|
|
|
|
|
|
|
// https to http doesn't send a referer:
|
|
|
|
|
{"https://test.com", "http://link.com", ""},
|
|
|
|
|
{"https://gopher:go@test.com", "http://link.com", ""},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
l, err := url.Parse(tt.lastReq)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
n, err := url.Parse(tt.newReq)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
r := ExportRefererForURL(l, n)
|
|
|
|
|
if r != tt.want {
|
|
|
|
|
t.Errorf("refererForURL(%q, %q) = %q; want %q", tt.lastReq, tt.newReq, r, tt.want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-05-06 18:11:38 +00:00
|
|
|
|
|
|
|
|
// issue15577Tripper returns a Response with a redirect response
|
|
|
|
|
// header and doesn't populate its Response.Request field.
|
|
|
|
|
type issue15577Tripper struct{}
|
|
|
|
|
|
|
|
|
|
func (issue15577Tripper) RoundTrip(*Request) (*Response, error) {
|
|
|
|
|
resp := &Response{
|
|
|
|
|
StatusCode: 303,
|
|
|
|
|
Header: map[string][]string{"Location": {"http://www.example.com/"}},
|
2020-10-16 00:49:02 -04:00
|
|
|
Body: io.NopCloser(strings.NewReader("")),
|
2016-05-06 18:11:38 +00:00
|
|
|
}
|
|
|
|
|
return resp, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Issue 15577: don't assume the roundtripper's response populates its Request field.
|
|
|
|
|
func TestClientRedirectResponseWithoutRequest(t *testing.T) {
|
|
|
|
|
c := &Client{
|
|
|
|
|
CheckRedirect: func(*Request, []*Request) error { return fmt.Errorf("no redirects!") },
|
|
|
|
|
Transport: issue15577Tripper{},
|
|
|
|
|
}
|
|
|
|
|
// Check that this doesn't crash:
|
|
|
|
|
c.Get("http://dummy.tld")
|
|
|
|
|
}
|
2016-09-09 18:06:56 +00:00
|
|
|
|
2017-10-13 15:56:37 -07:00
|
|
|
// Issue 4800: copy (some) headers when Client follows a redirect.
|
2016-09-09 18:06:56 +00:00
|
|
|
func TestClientCopyHeadersOnRedirect(t *testing.T) {
|
|
|
|
|
const (
|
|
|
|
|
ua = "some-agent/1.2"
|
|
|
|
|
xfoo = "foo-val"
|
|
|
|
|
)
|
|
|
|
|
var ts2URL string
|
|
|
|
|
ts1 := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
want := Header{
|
|
|
|
|
"User-Agent": []string{ua},
|
|
|
|
|
"X-Foo": []string{xfoo},
|
|
|
|
|
"Referer": []string{ts2URL},
|
|
|
|
|
"Accept-Encoding": []string{"gzip"},
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(r.Header, want) {
|
|
|
|
|
t.Errorf("Request.Header = %#v; want %#v", r.Header, want)
|
|
|
|
|
}
|
|
|
|
|
if t.Failed() {
|
|
|
|
|
w.Header().Set("Result", "got errors")
|
|
|
|
|
} else {
|
|
|
|
|
w.Header().Set("Result", "ok")
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
defer ts1.Close()
|
|
|
|
|
ts2 := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
Redirect(w, r, ts1.URL, StatusFound)
|
|
|
|
|
}))
|
|
|
|
|
defer ts2.Close()
|
|
|
|
|
ts2URL = ts2.URL
|
|
|
|
|
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts1.Client()
|
|
|
|
|
c.CheckRedirect = func(r *Request, via []*Request) error {
|
|
|
|
|
want := Header{
|
|
|
|
|
"User-Agent": []string{ua},
|
|
|
|
|
"X-Foo": []string{xfoo},
|
|
|
|
|
"Referer": []string{ts2URL},
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(r.Header, want) {
|
|
|
|
|
t.Errorf("CheckRedirect Request.Header = %#v; want %#v", r.Header, want)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
2016-09-09 18:06:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req, _ := NewRequest("GET", ts2.URL, nil)
|
|
|
|
|
req.Header.Add("User-Agent", ua)
|
|
|
|
|
req.Header.Add("X-Foo", xfoo)
|
|
|
|
|
req.Header.Add("Cookie", "foo=bar")
|
|
|
|
|
req.Header.Add("Authorization", "secretpassword")
|
|
|
|
|
res, err := c.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
if res.StatusCode != 200 {
|
|
|
|
|
t.Fatal(res.Status)
|
|
|
|
|
}
|
|
|
|
|
if got := res.Header.Get("Result"); got != "ok" {
|
|
|
|
|
t.Errorf("result = %q; want ok", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-13 15:56:37 -07:00
|
|
|
// Issue 22233: copy host when Client follows a relative redirect.
|
|
|
|
|
func TestClientCopyHostOnRedirect(t *testing.T) {
|
|
|
|
|
// Virtual hostname: should not receive any request.
|
|
|
|
|
virtual := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
t.Errorf("Virtual host received request %v", r.URL)
|
|
|
|
|
w.WriteHeader(403)
|
|
|
|
|
io.WriteString(w, "should not see this response")
|
|
|
|
|
}))
|
|
|
|
|
defer virtual.Close()
|
|
|
|
|
virtualHost := strings.TrimPrefix(virtual.URL, "http://")
|
|
|
|
|
t.Logf("Virtual host is %v", virtualHost)
|
|
|
|
|
|
|
|
|
|
// Actual hostname: should not receive any request.
|
|
|
|
|
const wantBody = "response body"
|
|
|
|
|
var tsURL string
|
|
|
|
|
var tsHost string
|
|
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
switch r.URL.Path {
|
|
|
|
|
case "/":
|
|
|
|
|
// Relative redirect.
|
|
|
|
|
if r.Host != virtualHost {
|
|
|
|
|
t.Errorf("Serving /: Request.Host = %#v; want %#v", r.Host, virtualHost)
|
|
|
|
|
w.WriteHeader(404)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Header().Set("Location", "/hop")
|
|
|
|
|
w.WriteHeader(302)
|
|
|
|
|
case "/hop":
|
|
|
|
|
// Absolute redirect.
|
|
|
|
|
if r.Host != virtualHost {
|
|
|
|
|
t.Errorf("Serving /hop: Request.Host = %#v; want %#v", r.Host, virtualHost)
|
|
|
|
|
w.WriteHeader(404)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.Header().Set("Location", tsURL+"/final")
|
|
|
|
|
w.WriteHeader(302)
|
|
|
|
|
case "/final":
|
|
|
|
|
if r.Host != tsHost {
|
|
|
|
|
t.Errorf("Serving /final: Request.Host = %#v; want %#v", r.Host, tsHost)
|
|
|
|
|
w.WriteHeader(404)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
w.WriteHeader(200)
|
|
|
|
|
io.WriteString(w, wantBody)
|
|
|
|
|
default:
|
|
|
|
|
t.Errorf("Serving unexpected path %q", r.URL.Path)
|
|
|
|
|
w.WriteHeader(404)
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
tsURL = ts.URL
|
|
|
|
|
tsHost = strings.TrimPrefix(ts.URL, "http://")
|
|
|
|
|
t.Logf("Server host is %v", tsHost)
|
|
|
|
|
|
|
|
|
|
c := ts.Client()
|
|
|
|
|
req, _ := NewRequest("GET", ts.URL, nil)
|
|
|
|
|
req.Host = virtualHost
|
|
|
|
|
resp, err := c.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
if resp.StatusCode != 200 {
|
|
|
|
|
t.Fatal(resp.Status)
|
|
|
|
|
}
|
2020-10-16 00:49:02 -04:00
|
|
|
if got, err := io.ReadAll(resp.Body); err != nil || string(got) != wantBody {
|
2017-10-13 15:56:37 -07:00
|
|
|
t.Errorf("body = %q; want %q", got, wantBody)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-18 14:56:19 -07:00
|
|
|
// Issue 17494: cookies should be altered when Client follows redirects.
|
|
|
|
|
func TestClientAltersCookiesOnRedirect(t *testing.T) {
|
|
|
|
|
cookieMap := func(cs []*Cookie) map[string][]string {
|
|
|
|
|
m := make(map[string][]string)
|
|
|
|
|
for _, c := range cs {
|
|
|
|
|
m[c.Name] = append(m[c.Name], c.Value)
|
|
|
|
|
}
|
|
|
|
|
return m
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
var want map[string][]string
|
|
|
|
|
got := cookieMap(r.Cookies())
|
|
|
|
|
|
|
|
|
|
c, _ := r.Cookie("Cycle")
|
|
|
|
|
switch c.Value {
|
|
|
|
|
case "0":
|
|
|
|
|
want = map[string][]string{
|
2016-10-27 02:58:09 +09:00
|
|
|
"Cookie1": {"OldValue1a", "OldValue1b"},
|
|
|
|
|
"Cookie2": {"OldValue2"},
|
|
|
|
|
"Cookie3": {"OldValue3a", "OldValue3b"},
|
|
|
|
|
"Cookie4": {"OldValue4"},
|
|
|
|
|
"Cycle": {"0"},
|
2016-10-18 14:56:19 -07:00
|
|
|
}
|
|
|
|
|
SetCookie(w, &Cookie{Name: "Cycle", Value: "1", Path: "/"})
|
|
|
|
|
SetCookie(w, &Cookie{Name: "Cookie2", Path: "/", MaxAge: -1}) // Delete cookie from Header
|
|
|
|
|
Redirect(w, r, "/", StatusFound)
|
|
|
|
|
case "1":
|
|
|
|
|
want = map[string][]string{
|
2016-10-27 02:58:09 +09:00
|
|
|
"Cookie1": {"OldValue1a", "OldValue1b"},
|
|
|
|
|
"Cookie3": {"OldValue3a", "OldValue3b"},
|
|
|
|
|
"Cookie4": {"OldValue4"},
|
|
|
|
|
"Cycle": {"1"},
|
2016-10-18 14:56:19 -07:00
|
|
|
}
|
|
|
|
|
SetCookie(w, &Cookie{Name: "Cycle", Value: "2", Path: "/"})
|
|
|
|
|
SetCookie(w, &Cookie{Name: "Cookie3", Value: "NewValue3", Path: "/"}) // Modify cookie in Header
|
|
|
|
|
SetCookie(w, &Cookie{Name: "Cookie4", Value: "NewValue4", Path: "/"}) // Modify cookie in Jar
|
|
|
|
|
Redirect(w, r, "/", StatusFound)
|
|
|
|
|
case "2":
|
|
|
|
|
want = map[string][]string{
|
2016-10-27 02:58:09 +09:00
|
|
|
"Cookie1": {"OldValue1a", "OldValue1b"},
|
|
|
|
|
"Cookie3": {"NewValue3"},
|
|
|
|
|
"Cookie4": {"NewValue4"},
|
|
|
|
|
"Cycle": {"2"},
|
2016-10-18 14:56:19 -07:00
|
|
|
}
|
|
|
|
|
SetCookie(w, &Cookie{Name: "Cycle", Value: "3", Path: "/"})
|
|
|
|
|
SetCookie(w, &Cookie{Name: "Cookie5", Value: "NewValue5", Path: "/"}) // Insert cookie into Jar
|
|
|
|
|
Redirect(w, r, "/", StatusFound)
|
|
|
|
|
case "3":
|
|
|
|
|
want = map[string][]string{
|
2016-10-27 02:58:09 +09:00
|
|
|
"Cookie1": {"OldValue1a", "OldValue1b"},
|
|
|
|
|
"Cookie3": {"NewValue3"},
|
|
|
|
|
"Cookie4": {"NewValue4"},
|
|
|
|
|
"Cookie5": {"NewValue5"},
|
|
|
|
|
"Cycle": {"3"},
|
2016-10-18 14:56:19 -07:00
|
|
|
}
|
|
|
|
|
// Don't redirect to ensure the loop ends.
|
|
|
|
|
default:
|
|
|
|
|
t.Errorf("unexpected redirect cycle")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !reflect.DeepEqual(got, want) {
|
|
|
|
|
t.Errorf("redirect %s, Cookie = %v, want %v", c.Value, got, want)
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
|
|
jar, _ := cookiejar.New(nil)
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
c.Jar = jar
|
2016-10-18 14:56:19 -07:00
|
|
|
|
|
|
|
|
u, _ := url.Parse(ts.URL)
|
|
|
|
|
req, _ := NewRequest("GET", ts.URL, nil)
|
|
|
|
|
req.AddCookie(&Cookie{Name: "Cookie1", Value: "OldValue1a"})
|
|
|
|
|
req.AddCookie(&Cookie{Name: "Cookie1", Value: "OldValue1b"})
|
|
|
|
|
req.AddCookie(&Cookie{Name: "Cookie2", Value: "OldValue2"})
|
|
|
|
|
req.AddCookie(&Cookie{Name: "Cookie3", Value: "OldValue3a"})
|
|
|
|
|
req.AddCookie(&Cookie{Name: "Cookie3", Value: "OldValue3b"})
|
2016-10-27 02:58:09 +09:00
|
|
|
jar.SetCookies(u, []*Cookie{{Name: "Cookie4", Value: "OldValue4", Path: "/"}})
|
|
|
|
|
jar.SetCookies(u, []*Cookie{{Name: "Cycle", Value: "0", Path: "/"}})
|
2016-10-18 14:56:19 -07:00
|
|
|
res, err := c.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
if res.StatusCode != 200 {
|
|
|
|
|
t.Fatal(res.Status)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-09 18:06:56 +00:00
|
|
|
// Part of Issue 4800
|
|
|
|
|
func TestShouldCopyHeaderOnRedirect(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
header string
|
|
|
|
|
initialURL string
|
|
|
|
|
destURL string
|
|
|
|
|
want bool
|
|
|
|
|
}{
|
|
|
|
|
{"User-Agent", "http://foo.com/", "http://bar.com/", true},
|
|
|
|
|
{"X-Foo", "http://foo.com/", "http://bar.com/", true},
|
|
|
|
|
|
|
|
|
|
// Sensitive headers:
|
|
|
|
|
{"cookie", "http://foo.com/", "http://bar.com/", false},
|
|
|
|
|
{"cookie2", "http://foo.com/", "http://bar.com/", false},
|
|
|
|
|
{"authorization", "http://foo.com/", "http://bar.com/", false},
|
|
|
|
|
{"www-authenticate", "http://foo.com/", "http://bar.com/", false},
|
|
|
|
|
|
|
|
|
|
// But subdomains should work:
|
|
|
|
|
{"www-authenticate", "http://foo.com/", "http://foo.com/", true},
|
|
|
|
|
{"www-authenticate", "http://foo.com/", "http://sub.foo.com/", true},
|
|
|
|
|
{"www-authenticate", "http://foo.com/", "http://notfoo.com/", false},
|
2017-09-04 09:28:27 -03:00
|
|
|
{"www-authenticate", "http://foo.com/", "https://foo.com/", false},
|
|
|
|
|
{"www-authenticate", "http://foo.com:80/", "http://foo.com/", true},
|
|
|
|
|
{"www-authenticate", "http://foo.com:80/", "http://sub.foo.com/", true},
|
|
|
|
|
{"www-authenticate", "http://foo.com:443/", "https://foo.com/", true},
|
|
|
|
|
{"www-authenticate", "http://foo.com:443/", "https://sub.foo.com/", true},
|
|
|
|
|
{"www-authenticate", "http://foo.com:1234/", "http://foo.com/", false},
|
2016-09-09 18:06:56 +00:00
|
|
|
}
|
|
|
|
|
for i, tt := range tests {
|
|
|
|
|
u0, err := url.Parse(tt.initialURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("%d. initial URL %q parse error: %v", i, tt.initialURL, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
u1, err := url.Parse(tt.destURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("%d. dest URL %q parse error: %v", i, tt.destURL, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
got := Export_shouldCopyHeaderOnRedirect(tt.header, u0, u1)
|
|
|
|
|
if got != tt.want {
|
|
|
|
|
t.Errorf("%d. shouldCopyHeaderOnRedirect(%q, %q => %q) = %v; want %v",
|
|
|
|
|
i, tt.header, tt.initialURL, tt.destURL, got, tt.want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-09-25 18:31:05 -07:00
|
|
|
|
|
|
|
|
func TestClientRedirectTypes(t *testing.T) {
|
2016-11-04 03:23:37 +00:00
|
|
|
setParallel(t)
|
2016-09-25 18:31:05 -07:00
|
|
|
defer afterTest(t)
|
|
|
|
|
|
|
|
|
|
tests := [...]struct {
|
|
|
|
|
method string
|
|
|
|
|
serverStatus int
|
|
|
|
|
wantMethod string // desired subsequent client method
|
|
|
|
|
}{
|
|
|
|
|
0: {method: "POST", serverStatus: 301, wantMethod: "GET"},
|
|
|
|
|
1: {method: "POST", serverStatus: 302, wantMethod: "GET"},
|
2016-09-26 20:38:57 -07:00
|
|
|
2: {method: "POST", serverStatus: 303, wantMethod: "GET"},
|
|
|
|
|
3: {method: "POST", serverStatus: 307, wantMethod: "POST"},
|
|
|
|
|
4: {method: "POST", serverStatus: 308, wantMethod: "POST"},
|
|
|
|
|
|
2017-01-09 01:00:36 -08:00
|
|
|
5: {method: "HEAD", serverStatus: 301, wantMethod: "HEAD"},
|
|
|
|
|
6: {method: "HEAD", serverStatus: 302, wantMethod: "HEAD"},
|
|
|
|
|
7: {method: "HEAD", serverStatus: 303, wantMethod: "HEAD"},
|
2016-09-26 20:38:57 -07:00
|
|
|
8: {method: "HEAD", serverStatus: 307, wantMethod: "HEAD"},
|
|
|
|
|
9: {method: "HEAD", serverStatus: 308, wantMethod: "HEAD"},
|
|
|
|
|
|
|
|
|
|
10: {method: "GET", serverStatus: 301, wantMethod: "GET"},
|
|
|
|
|
11: {method: "GET", serverStatus: 302, wantMethod: "GET"},
|
|
|
|
|
12: {method: "GET", serverStatus: 303, wantMethod: "GET"},
|
|
|
|
|
13: {method: "GET", serverStatus: 307, wantMethod: "GET"},
|
|
|
|
|
14: {method: "GET", serverStatus: 308, wantMethod: "GET"},
|
|
|
|
|
|
|
|
|
|
15: {method: "DELETE", serverStatus: 301, wantMethod: "GET"},
|
|
|
|
|
16: {method: "DELETE", serverStatus: 302, wantMethod: "GET"},
|
|
|
|
|
17: {method: "DELETE", serverStatus: 303, wantMethod: "GET"},
|
|
|
|
|
18: {method: "DELETE", serverStatus: 307, wantMethod: "DELETE"},
|
|
|
|
|
19: {method: "DELETE", serverStatus: 308, wantMethod: "DELETE"},
|
|
|
|
|
|
|
|
|
|
20: {method: "PUT", serverStatus: 301, wantMethod: "GET"},
|
|
|
|
|
21: {method: "PUT", serverStatus: 302, wantMethod: "GET"},
|
|
|
|
|
22: {method: "PUT", serverStatus: 303, wantMethod: "GET"},
|
|
|
|
|
23: {method: "PUT", serverStatus: 307, wantMethod: "PUT"},
|
|
|
|
|
24: {method: "PUT", serverStatus: 308, wantMethod: "PUT"},
|
|
|
|
|
|
|
|
|
|
25: {method: "MADEUPMETHOD", serverStatus: 301, wantMethod: "GET"},
|
|
|
|
|
26: {method: "MADEUPMETHOD", serverStatus: 302, wantMethod: "GET"},
|
|
|
|
|
27: {method: "MADEUPMETHOD", serverStatus: 303, wantMethod: "GET"},
|
|
|
|
|
28: {method: "MADEUPMETHOD", serverStatus: 307, wantMethod: "MADEUPMETHOD"},
|
|
|
|
|
29: {method: "MADEUPMETHOD", serverStatus: 308, wantMethod: "MADEUPMETHOD"},
|
2016-09-25 18:31:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handlerc := make(chan HandlerFunc, 1)
|
|
|
|
|
|
|
|
|
|
ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) {
|
|
|
|
|
h := <-handlerc
|
|
|
|
|
h(rw, req)
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
2016-09-25 18:31:05 -07:00
|
|
|
for i, tt := range tests {
|
|
|
|
|
handlerc <- func(w ResponseWriter, r *Request) {
|
|
|
|
|
w.Header().Set("Location", ts.URL)
|
|
|
|
|
w.WriteHeader(tt.serverStatus)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req, err := NewRequest(tt.method, ts.URL, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("#%d: NewRequest: %v", i, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
c.CheckRedirect = func(req *Request, via []*Request) error {
|
|
|
|
|
if got, want := req.Method, tt.wantMethod; got != want {
|
|
|
|
|
return fmt.Errorf("#%d: got next method %q; want %q", i, got, want)
|
|
|
|
|
}
|
|
|
|
|
handlerc <- func(rw ResponseWriter, req *Request) {
|
|
|
|
|
// TODO: Check that the body is valid when we do 307 and 308 support
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res, err := c.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("#%d: Response: %v", i, err)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.Body.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-08 01:07:10 +00:00
|
|
|
|
|
|
|
|
// issue18239Body is an io.ReadCloser for TestTransportBodyReadError.
|
|
|
|
|
// Its Read returns readErr and increments *readCalls atomically.
|
|
|
|
|
// Its Close returns nil and increments *closeCalls atomically.
|
|
|
|
|
type issue18239Body struct {
|
|
|
|
|
readCalls *int32
|
|
|
|
|
closeCalls *int32
|
|
|
|
|
readErr error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b issue18239Body) Read([]byte) (int, error) {
|
|
|
|
|
atomic.AddInt32(b.readCalls, 1)
|
|
|
|
|
return 0, b.readErr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b issue18239Body) Close() error {
|
|
|
|
|
atomic.AddInt32(b.closeCalls, 1)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-28 16:40:39 -07:00
|
|
|
// Issue 18239: make sure the Transport doesn't retry requests with bodies
|
|
|
|
|
// if Request.GetBody is not defined.
|
2016-12-08 01:07:10 +00:00
|
|
|
func TestTransportBodyReadError(t *testing.T) {
|
|
|
|
|
setParallel(t)
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
if r.URL.Path == "/ping" {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
buf := make([]byte, 1)
|
|
|
|
|
n, err := r.Body.Read(buf)
|
|
|
|
|
w.Header().Set("X-Body-Read", fmt.Sprintf("%v, %v", n, err))
|
|
|
|
|
}))
|
|
|
|
|
defer ts.Close()
|
2017-03-04 18:24:44 +00:00
|
|
|
c := ts.Client()
|
|
|
|
|
tr := c.Transport.(*Transport)
|
2016-12-08 01:07:10 +00:00
|
|
|
|
|
|
|
|
// Do one initial successful request to create an idle TCP connection
|
|
|
|
|
// for the subsequent request to reuse. (The Transport only retries
|
|
|
|
|
// requests on reused connections.)
|
|
|
|
|
res, err := c.Get(ts.URL + "/ping")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
res.Body.Close()
|
|
|
|
|
|
|
|
|
|
var readCallsAtomic int32
|
|
|
|
|
var closeCallsAtomic int32 // atomic
|
|
|
|
|
someErr := errors.New("some body read error")
|
|
|
|
|
body := issue18239Body{&readCallsAtomic, &closeCallsAtomic, someErr}
|
|
|
|
|
|
|
|
|
|
req, err := NewRequest("POST", ts.URL, body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
net/http: clean up Transport.RoundTrip error handling
If I put a 10 millisecond sleep at testHookWaitResLoop, before the big
select in (*persistConn).roundTrip, two flakes immediately started
happening, TestTransportBodyReadError (#19231) and
TestTransportPersistConnReadLoopEOF.
The problem was that there are many ways for a RoundTrip call to fail
(errors reading from Request.Body while writing the response, errors
writing the response, errors reading the response due to server
closes, errors due to servers sending malformed responses,
cancelations, timeouts, etc.), and many of those failures then tear
down the TCP connection, causing more failures, since there are always
at least three goroutines involved (reading, writing, RoundTripping).
Because the errors were communicated over buffered channels to a giant
select, the error returned to the caller was a function of which
random select case was called, which was why a 10ms delay before the
select brought out so many bugs. (several fixed in my previous CLs the past
few days).
Instead, track the error explicitly in the transportRequest, guarded
by a mutex.
In addition, this CL now:
* differentiates between the two ways writing a request can fail: the
io.Copy reading from the Request.Body or the io.Copy writing to the
network. A new io.Reader type notes read errors from the
Request.Body. The read-from-body vs write-to-network errors are now
prioritized differently.
* unifies the two mapRoundTripErrorFromXXX methods into one
mapRoundTripError method since their logic is now the same.
* adds a (*Request).WithT(*testing.T) method in export_test.go, usable
by tests, to call t.Logf at points during RoundTrip. This is disabled
behind a constant except when debugging.
* documents and deflakes TestClientRedirectContext
I've tested this CL with high -count values, with/without -race,
with/without delays before the select, etc. So far it seems robust.
Fixes #19231 (TestTransportBodyReadError flake)
Updates #14203 (source of errors unclear; they're now tracked more)
Updates #15935 (document Transport errors more; at least understood more now)
Change-Id: I3cccc3607f369724b5344763e35ad2b7ea415738
Reviewed-on: https://go-review.googlesource.com/37495
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
2017-02-27 05:41:50 +00:00
|
|
|
req = req.WithT(t)
|
2016-12-08 01:07:10 +00:00
|
|
|
_, err = tr.RoundTrip(req)
|
|
|
|
|
if err != someErr {
|
|
|
|
|
t.Errorf("Got error: %v; want Request.Body read error: %v", err, someErr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// And verify that our Body wasn't used multiple times, which
|
|
|
|
|
// would indicate retries. (as it buggily was during part of
|
|
|
|
|
// Go 1.8's dev cycle)
|
|
|
|
|
readCalls := atomic.LoadInt32(&readCallsAtomic)
|
|
|
|
|
closeCalls := atomic.LoadInt32(&closeCallsAtomic)
|
|
|
|
|
if readCalls != 1 {
|
|
|
|
|
t.Errorf("read calls = %d; want 1", readCalls)
|
|
|
|
|
}
|
|
|
|
|
if closeCalls != 1 {
|
|
|
|
|
t.Errorf("close calls = %d; want 1", closeCalls)
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-20 21:04:15 +00:00
|
|
|
|
|
|
|
|
type roundTripperWithoutCloseIdle struct{}
|
|
|
|
|
|
|
|
|
|
func (roundTripperWithoutCloseIdle) RoundTrip(*Request) (*Response, error) { panic("unused") }
|
|
|
|
|
|
|
|
|
|
type roundTripperWithCloseIdle func() // underlying func is CloseIdleConnections func
|
|
|
|
|
|
|
|
|
|
func (roundTripperWithCloseIdle) RoundTrip(*Request) (*Response, error) { panic("unused") }
|
|
|
|
|
func (f roundTripperWithCloseIdle) CloseIdleConnections() { f() }
|
|
|
|
|
|
|
|
|
|
func TestClientCloseIdleConnections(t *testing.T) {
|
|
|
|
|
c := &Client{Transport: roundTripperWithoutCloseIdle{}}
|
|
|
|
|
c.CloseIdleConnections() // verify we don't crash at least
|
|
|
|
|
|
|
|
|
|
closed := false
|
|
|
|
|
var tr RoundTripper = roundTripperWithCloseIdle(func() {
|
|
|
|
|
closed = true
|
|
|
|
|
})
|
|
|
|
|
c = &Client{Transport: tr}
|
|
|
|
|
c.CloseIdleConnections()
|
|
|
|
|
if !closed {
|
|
|
|
|
t.Error("not closed")
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-05-07 16:25:05 -07:00
|
|
|
|
|
|
|
|
func TestClientPropagatesTimeoutToContext(t *testing.T) {
|
|
|
|
|
errDial := errors.New("not actually dialing")
|
|
|
|
|
c := &Client{
|
|
|
|
|
Timeout: 5 * time.Second,
|
|
|
|
|
Transport: &Transport{
|
|
|
|
|
DialContext: func(ctx context.Context, netw, addr string) (net.Conn, error) {
|
|
|
|
|
deadline, ok := ctx.Deadline()
|
|
|
|
|
if !ok {
|
|
|
|
|
t.Error("no deadline")
|
|
|
|
|
} else {
|
|
|
|
|
t.Logf("deadline in %v", deadline.Sub(time.Now()).Round(time.Second/10))
|
|
|
|
|
}
|
|
|
|
|
return nil, errDial
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
c.Get("https://example.tld/")
|
|
|
|
|
}
|
2019-10-11 20:27:33 -04:00
|
|
|
|
|
|
|
|
func TestClientDoCanceledVsTimeout_h1(t *testing.T) {
|
|
|
|
|
testClientDoCanceledVsTimeout(t, h1Mode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientDoCanceledVsTimeout_h2(t *testing.T) {
|
|
|
|
|
testClientDoCanceledVsTimeout(t, h2Mode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Issue 33545: lock-in the behavior promised by Client.Do's
|
2021-03-05 13:06:50 +10:00
|
|
|
// docs about request cancellation vs timing out.
|
2019-10-11 20:27:33 -04:00
|
|
|
func testClientDoCanceledVsTimeout(t *testing.T, h2 bool) {
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
w.Write([]byte("Hello, World!"))
|
|
|
|
|
}))
|
|
|
|
|
defer cst.close()
|
|
|
|
|
|
|
|
|
|
cases := []string{"timeout", "canceled"}
|
|
|
|
|
|
|
|
|
|
for _, name := range cases {
|
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
|
var ctx context.Context
|
|
|
|
|
var cancel func()
|
|
|
|
|
if name == "timeout" {
|
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), -time.Nanosecond)
|
|
|
|
|
} else {
|
|
|
|
|
ctx, cancel = context.WithCancel(context.Background())
|
|
|
|
|
cancel()
|
|
|
|
|
}
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
req, _ := NewRequestWithContext(ctx, "GET", cst.ts.URL, nil)
|
|
|
|
|
_, err := cst.c.Do(req)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("Unexpectedly got a nil error")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ue := err.(*url.Error)
|
|
|
|
|
|
|
|
|
|
var wantIsTimeout bool
|
|
|
|
|
var wantErr error = context.Canceled
|
|
|
|
|
if name == "timeout" {
|
|
|
|
|
wantErr = context.DeadlineExceeded
|
|
|
|
|
wantIsTimeout = true
|
|
|
|
|
}
|
|
|
|
|
if g, w := ue.Timeout(), wantIsTimeout; g != w {
|
|
|
|
|
t.Fatalf("url.Timeout() = %t, want %t", g, w)
|
|
|
|
|
}
|
|
|
|
|
if g, w := ue.Err, wantErr; g != w {
|
|
|
|
|
t.Errorf("url.Error.Err = %v; want %v", g, w)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-03-26 21:28:37 -04:00
|
|
|
|
|
|
|
|
type nilBodyRoundTripper struct{}
|
|
|
|
|
|
|
|
|
|
func (nilBodyRoundTripper) RoundTrip(req *Request) (*Response, error) {
|
|
|
|
|
return &Response{
|
|
|
|
|
StatusCode: StatusOK,
|
|
|
|
|
Status: StatusText(StatusOK),
|
|
|
|
|
Body: nil,
|
|
|
|
|
Request: req,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestClientPopulatesNilResponseBody(t *testing.T) {
|
|
|
|
|
c := &Client{Transport: nilBodyRoundTripper{}}
|
|
|
|
|
|
|
|
|
|
resp, err := c.Get("http://localhost/anything")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Client.Get rejected Response with nil Body: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if resp.Body == nil {
|
|
|
|
|
t.Fatalf("Client failed to provide a non-nil Body as documented")
|
|
|
|
|
}
|
|
|
|
|
defer func() {
|
|
|
|
|
if err := resp.Body.Close(); err != nil {
|
|
|
|
|
t.Fatalf("error from Close on substitute Response.Body: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2020-10-16 00:49:02 -04:00
|
|
|
if b, err := io.ReadAll(resp.Body); err != nil {
|
2020-03-26 21:28:37 -04:00
|
|
|
t.Errorf("read error from substitute Response.Body: %v", err)
|
|
|
|
|
} else if len(b) != 0 {
|
|
|
|
|
t.Errorf("substitute Response.Body was unexpectedly non-empty: %q", b)
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-26 08:49:56 -07:00
|
|
|
|
|
|
|
|
// Issue 40382: Client calls Close multiple times on Request.Body.
|
|
|
|
|
func TestClientCallsCloseOnlyOnce(t *testing.T) {
|
|
|
|
|
setParallel(t)
|
|
|
|
|
defer afterTest(t)
|
|
|
|
|
cst := newClientServerTest(t, h1Mode, HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
|
|
|
w.WriteHeader(StatusNoContent)
|
|
|
|
|
}))
|
|
|
|
|
defer cst.close()
|
|
|
|
|
|
|
|
|
|
// Issue occurred non-deterministically: needed to occur after a successful
|
|
|
|
|
// write (into TCP buffer) but before end of body.
|
|
|
|
|
for i := 0; i < 50 && !t.Failed(); i++ {
|
|
|
|
|
body := &issue40382Body{t: t, n: 300000}
|
|
|
|
|
req, err := NewRequest(MethodPost, cst.ts.URL, body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
resp, err := cst.tr.RoundTrip(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
resp.Body.Close()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// issue40382Body is an io.ReadCloser for TestClientCallsCloseOnlyOnce.
|
|
|
|
|
// Its Read reads n bytes before returning io.EOF.
|
|
|
|
|
// Its Close returns nil but fails the test if called more than once.
|
|
|
|
|
type issue40382Body struct {
|
|
|
|
|
t *testing.T
|
|
|
|
|
n int
|
|
|
|
|
closeCallsAtomic int32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *issue40382Body) Read(p []byte) (int, error) {
|
|
|
|
|
switch {
|
|
|
|
|
case b.n == 0:
|
|
|
|
|
return 0, io.EOF
|
|
|
|
|
case b.n < len(p):
|
|
|
|
|
p = p[:b.n]
|
|
|
|
|
fallthrough
|
|
|
|
|
default:
|
|
|
|
|
for i := range p {
|
|
|
|
|
p[i] = 'x'
|
|
|
|
|
}
|
|
|
|
|
b.n -= len(p)
|
|
|
|
|
return len(p), nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *issue40382Body) Close() error {
|
|
|
|
|
if atomic.AddInt32(&b.closeCallsAtomic, 1) == 2 {
|
|
|
|
|
b.t.Error("Body closed more than once")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|