Skip to content
This repository was archived by the owner on Apr 20, 2023. It is now read-only.

Commit 592c2d5

Browse files
committed
Remove CancelRequest implementation, tracking of request copies.
In older versions of Go and net/http standard library, implementations of http.RoundTripper had to implement CancelRequest method in order to enable request cancellation. That CancelRequest method has since been deprecated in favor of more modern ways of request cancellation, which is via Request.Cancel field and/or request context. Modern http.Client uses Request.Cancel field to implement its Timeout field, but also calls the deprecated CancelRequest method, if it's implemented, just in case. Since this package is expected to run on modern versions of Go (it requires Go 1.6+ at this time), we can safely remove the deprecated CancelRequest method implementation. The benefits of doing that include: - Remove the unneeded "httpcache: Client Transport of type %T doesn't support CancelRequest; Timeout not supported" warning from being logged. - Simplify and remove code for tracking the mapping between the original and copied request, which was only needed by the implementation of the CancelRequest method. - onEOFReader also becomes unused, and can be removed. Add a simple timeout test to verify that http.Client.Timeout value is respected when a cache transport is used. Skip this test in short mode, because it takes at least 3 seconds to run, to test timeouts. This is significantly longer than other tests. Resolves #61. Updates #62.
1 parent 29e7c21 commit 592c2d5

File tree

2 files changed

+33
-93
lines changed

2 files changed

+33
-93
lines changed

‎httpcache.go‎

Lines changed: 0 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ import (
1111
"bytes"
1212
"errors"
1313
"fmt"
14-
"io"
15-
"log"
1614
"net/http"
1715
"net/http/httputil"
1816
"strings"
@@ -90,33 +88,6 @@ func NewMemoryCache() *MemoryCache {
9088
return c
9189
}
9290

93-
// onEOFReader executes a function on reader EOF or close
94-
type onEOFReader struct {
95-
rc io.ReadCloser
96-
fn func()
97-
}
98-
99-
func (r *onEOFReader) Read(p []byte) (n int, err error) {
100-
n, err = r.rc.Read(p)
101-
if err == io.EOF {
102-
r.runFunc()
103-
}
104-
return
105-
}
106-
107-
func (r *onEOFReader) Close() error {
108-
err := r.rc.Close()
109-
r.runFunc()
110-
return err
111-
}
112-
113-
func (r *onEOFReader) runFunc() {
114-
if fn := r.fn; fn != nil {
115-
fn()
116-
r.fn = nil
117-
}
118-
}
119-
12091
// Transport is an implementation of http.RoundTripper that will return values from a cache
12192
// where possible (avoiding a network request) and will additionally add validators (etag/if-modified-since)
12293
// to repeated requests allowing servers to return 304 / Not Modified
@@ -127,10 +98,6 @@ type Transport struct {
12798
Cache Cache
12899
// If true, responses returned from the cache will be given an extra header, X-From-Cache
129100
MarkCachedResponses bool
130-
// guards modReq
131-
mu sync.RWMutex
132-
// Mapping of original request => cloned
133-
modReq map[*http.Request]*http.Request
134101
}
135102

136103
// NewTransport returns a new Transport with the
@@ -156,20 +123,6 @@ func varyMatches(cachedResp *http.Response, req *http.Request) bool {
156123
return true
157124
}
158125

159-
// setModReq maintains a mapping between original requests and their associated cloned requests
160-
func (t *Transport) setModReq(orig, mod *http.Request) {
161-
t.mu.Lock()
162-
if t.modReq == nil {
163-
t.modReq = make(map[*http.Request]*http.Request)
164-
}
165-
if mod == nil {
166-
delete(t.modReq, orig)
167-
} else {
168-
t.modReq[orig] = mod
169-
}
170-
t.mu.Unlock()
171-
}
172-
173126
// RoundTrip takes a Request and returns a Response
174127
//
175128
// If there is a fresh Response already in cache, then it will be returned without connecting to
@@ -222,22 +175,6 @@ func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error
222175
req2.Header.Set("if-modified-since", lastModified)
223176
}
224177
if req2 != nil {
225-
// Associate original request with cloned request so we can refer to
226-
// it in CancelRequest(). Release the mapping when it's no longer needed.
227-
t.setModReq(req, req2)
228-
defer func(originalReq *http.Request) {
229-
// Release req/clone mapping on error
230-
if err != nil {
231-
t.setModReq(originalReq, nil)
232-
}
233-
if resp != nil {
234-
// Release req/clone mapping on body close/EOF
235-
resp.Body = &onEOFReader{
236-
rc: resp.Body,
237-
fn: func() { t.setModReq(originalReq, nil) },
238-
}
239-
}
240-
}(req)
241178
req = req2
242179
}
243180
}
@@ -300,31 +237,6 @@ func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error
300237
return resp, nil
301238
}
302239

303-
// CancelRequest calls CancelRequest on the underlaying transport if implemented or
304-
// throw a warning otherwise.
305-
func (t *Transport) CancelRequest(req *http.Request) {
306-
type canceler interface {
307-
CancelRequest(*http.Request)
308-
}
309-
tr, ok := t.Transport.(canceler)
310-
if !ok {
311-
log.Printf("httpcache: Client Transport of type %T doesn't support CancelRequest; Timeout not supported", t.Transport)
312-
return
313-
}
314-
315-
t.mu.RLock()
316-
if modReq, ok := t.modReq[req]; ok {
317-
t.mu.RUnlock()
318-
t.mu.Lock()
319-
delete(t.modReq, req)
320-
t.mu.Unlock()
321-
tr.CancelRequest(modReq)
322-
} else {
323-
t.mu.RUnlock()
324-
tr.CancelRequest(req)
325-
}
326-
}
327-
328240
// ErrNoDateHeader indicates that the HTTP headers contained no Date header.
329241
var ErrNoDateHeader = errors.New("no Date header")
330242

‎httpcache_test.go‎

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ func setup() {
129129
}
130130
w.Write([]byte("Some text content"))
131131
}))
132+
133+
// Take 3 seconds to return 200 OK (for testing client timeouts).
134+
mux.HandleFunc("/3seconds", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
135+
time.Sleep(3 * time.Second)
136+
}))
132137
}
133138

134139
func teardown() {
@@ -465,11 +470,6 @@ func TestGetWithLastModified(t *testing.T) {
465470
if err != nil {
466471
t.Fatal(err)
467472
}
468-
defer func() {
469-
if len(s.transport.modReq) != 0 {
470-
t.Errorf("Request-map is not empty")
471-
}
472-
}()
473473
{
474474
resp, err := s.client.Do(req)
475475
if err != nil {
@@ -1211,3 +1211,31 @@ func TestStaleIfErrorResponseLifetime(t *testing.T) {
12111211
t.Fatalf("got err %v, want %v", err, tmock.err)
12121212
}
12131213
}
1214+
1215+
// Test that http.Client.Timeout is respected when cache transport is used.
1216+
// That is so as long as request cancellation is propagated correctly.
1217+
// In the past, that required CancelRequest to be implemented correctly,
1218+
// but modern http.Client uses Request.Cancel (or request context) instead,
1219+
// so we don't have to do anything.
1220+
func TestClientTimeout(t *testing.T) {
1221+
if testing.Short() {
1222+
t.Skip("skipping timeout test in short mode") // Because it takes at least 3 seconds to run.
1223+
}
1224+
resetTest()
1225+
client := &http.Client{
1226+
Transport: NewMemoryCacheTransport(),
1227+
Timeout: time.Second,
1228+
}
1229+
started := time.Now()
1230+
resp, err := client.Get(s.server.URL + "/3seconds")
1231+
taken := time.Since(started)
1232+
if err == nil {
1233+
t.Error("got nil error, want timeout error")
1234+
}
1235+
if resp != nil {
1236+
t.Error("got non-nil resp, want nil resp")
1237+
}
1238+
if taken >= 2*time.Second {
1239+
t.Error("client.Do took 2+ seconds, want < 2 seconds")
1240+
}
1241+
}

0 commit comments

Comments
 (0)