mirror of
https://github.com/golang/go
synced 2024-11-26 03:37:57 -07:00
net/http: add Transport.ResponseHeaderTimeout
Update #3362 R=golang-dev, adg, rsc CC=golang-dev https://golang.org/cl/7369055
This commit is contained in:
parent
37cb6f809a
commit
839d47add5
@ -73,6 +73,12 @@ type Transport struct {
|
|||||||
// (keep-alive) to keep per-host. If zero,
|
// (keep-alive) to keep per-host. If zero,
|
||||||
// DefaultMaxIdleConnsPerHost is used.
|
// DefaultMaxIdleConnsPerHost is used.
|
||||||
MaxIdleConnsPerHost int
|
MaxIdleConnsPerHost int
|
||||||
|
|
||||||
|
// ResponseHeaderTimeout, if non-zero, specifies the amount of
|
||||||
|
// time to wait for a server's response headers after fully
|
||||||
|
// writing the request (including its body, if any). This
|
||||||
|
// time does not include the time to read the response body.
|
||||||
|
ResponseHeaderTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyFromEnvironment returns the URL of the proxy to use for a
|
// ProxyFromEnvironment returns the URL of the proxy to use for a
|
||||||
@ -743,6 +749,7 @@ func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err err
|
|||||||
var re responseAndError
|
var re responseAndError
|
||||||
var pconnDeadCh = pc.closech
|
var pconnDeadCh = pc.closech
|
||||||
var failTicker <-chan time.Time
|
var failTicker <-chan time.Time
|
||||||
|
var respHeaderTimer <-chan time.Time
|
||||||
WaitResponse:
|
WaitResponse:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -752,6 +759,9 @@ WaitResponse:
|
|||||||
pc.close()
|
pc.close()
|
||||||
break WaitResponse
|
break WaitResponse
|
||||||
}
|
}
|
||||||
|
if d := pc.t.ResponseHeaderTimeout; d > 0 {
|
||||||
|
respHeaderTimer = time.After(d)
|
||||||
|
}
|
||||||
case <-pconnDeadCh:
|
case <-pconnDeadCh:
|
||||||
// The persist connection is dead. This shouldn't
|
// The persist connection is dead. This shouldn't
|
||||||
// usually happen (only with Connection: close responses
|
// usually happen (only with Connection: close responses
|
||||||
@ -768,7 +778,11 @@ WaitResponse:
|
|||||||
pconnDeadCh = nil // avoid spinning
|
pconnDeadCh = nil // avoid spinning
|
||||||
failTicker = time.After(100 * time.Millisecond) // arbitrary time to wait for resc
|
failTicker = time.After(100 * time.Millisecond) // arbitrary time to wait for resc
|
||||||
case <-failTicker:
|
case <-failTicker:
|
||||||
re = responseAndError{nil, errors.New("net/http: transport closed before response was received")}
|
re = responseAndError{err: errors.New("net/http: transport closed before response was received")}
|
||||||
|
break WaitResponse
|
||||||
|
case <-respHeaderTimer:
|
||||||
|
pc.close()
|
||||||
|
re = responseAndError{err: errors.New("net/http: timeout awaiting response headers")}
|
||||||
break WaitResponse
|
break WaitResponse
|
||||||
case re = <-resc:
|
case re = <-resc:
|
||||||
break WaitResponse
|
break WaitResponse
|
||||||
|
@ -1113,6 +1113,54 @@ func TestIssue4191_InfiniteGetToPutTimeout(t *testing.T) {
|
|||||||
ts.Close()
|
ts.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTransportResponseHeaderTimeout(t *testing.T) {
|
||||||
|
defer checkLeakedTransports(t)
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping timeout test in -short mode")
|
||||||
|
}
|
||||||
|
const debug = false
|
||||||
|
mux := NewServeMux()
|
||||||
|
mux.HandleFunc("/fast", func(w ResponseWriter, r *Request) {})
|
||||||
|
mux.HandleFunc("/slow", func(w ResponseWriter, r *Request) {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
})
|
||||||
|
ts := httptest.NewServer(mux)
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
tr := &Transport{
|
||||||
|
ResponseHeaderTimeout: 500 * time.Millisecond,
|
||||||
|
}
|
||||||
|
defer tr.CloseIdleConnections()
|
||||||
|
c := &Client{Transport: tr}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
want int
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{path: "/fast", want: 200},
|
||||||
|
{path: "/slow", wantErr: "timeout awaiting response headers"},
|
||||||
|
{path: "/fast", want: 200},
|
||||||
|
}
|
||||||
|
for i, tt := range tests {
|
||||||
|
res, err := c.Get(ts.URL + tt.path)
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), tt.wantErr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Errorf("%d. unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
t.Errorf("%d. no error. expected error: %v", i, tt.wantErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if res.StatusCode != tt.want {
|
||||||
|
t.Errorf("%d for path %q status = %d; want %d", i, tt.path, res.StatusCode, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type fooProto struct{}
|
type fooProto struct{}
|
||||||
|
|
||||||
func (fooProto) RoundTrip(req *Request) (*Response, error) {
|
func (fooProto) RoundTrip(req *Request) (*Response, error) {
|
||||||
|
Loading…
Reference in New Issue
Block a user