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

Commit 2bcd89a

Browse files
authored
Refrain from setting 200 OK on cached responses (#77)
In cases where responses were re-used, due to validated 304 responses and requests using stale-if-error, the cached responses had their status replaced by a 200, which isn't correct. Fixes #74
1 parent 22a0b1f commit 2bcd89a

File tree

2 files changed

+91
-6
lines changed

2 files changed

+91
-6
lines changed

‎httpcache.go‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"bufio"
1111
"bytes"
1212
"errors"
13-
"fmt"
1413
"io"
1514
"io/ioutil"
1615
"net/http"
@@ -193,16 +192,11 @@ func (t *Transport) RoundTrip(req *http.Request) (resp *http.Response, err error
193192
for _, header := range endToEndHeaders {
194193
cachedResp.Header[header] = resp.Header[header]
195194
}
196-
cachedResp.Status = fmt.Sprintf("%d %s", http.StatusOK, http.StatusText(http.StatusOK))
197-
cachedResp.StatusCode = http.StatusOK
198-
199195
resp = cachedResp
200196
} else if (err != nil || (cachedResp != nil && resp.StatusCode >= 500)) &&
201197
req.Method == "GET" && canStaleOnError(cachedResp.Header, req.Header) {
202198
// In case of transport failure and stale-if-error activated, returns cached content
203199
// when available
204-
cachedResp.Status = fmt.Sprintf("%d %s", http.StatusOK, http.StatusText(http.StatusOK))
205-
cachedResp.StatusCode = http.StatusOK
206200
return cachedResp, nil
207201
} else {
208202
if err != nil || resp.StatusCode != http.StatusOK {

‎httpcache_test.go‎

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,17 @@ func setup() {
120120
w.Write([]byte("Some text content"))
121121
}))
122122

123+
mux.HandleFunc("/cachederror", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
124+
etag := "abc"
125+
if r.Header.Get("if-none-match") == etag {
126+
w.WriteHeader(http.StatusNotModified)
127+
return
128+
}
129+
w.Header().Set("etag", etag)
130+
w.WriteHeader(http.StatusNotFound)
131+
w.Write([]byte("Not found"))
132+
}))
133+
123134
updateFieldsCounter := 0
124135
mux.HandleFunc("/updatefields", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
125136
w.Header().Set("X-Counter", strconv.Itoa(updateFieldsCounter))
@@ -873,6 +884,35 @@ func TestUpdateFields(t *testing.T) {
873884
}
874885
}
875886

887+
// This tests the fix for https://github.com/gregjones/httpcache/issues/74.
888+
// Previously, after validating a cached response, its StatusCode
889+
// was incorrectly being replaced.
890+
func TestCachedErrorsKeepStatus(t *testing.T) {
891+
resetTest()
892+
req, err := http.NewRequest("GET", s.server.URL+"/cachederror", nil)
893+
if err != nil {
894+
t.Fatal(err)
895+
}
896+
{
897+
resp, err := s.client.Do(req)
898+
if err != nil {
899+
t.Fatal(err)
900+
}
901+
defer resp.Body.Close()
902+
io.Copy(ioutil.Discard, resp.Body)
903+
}
904+
{
905+
resp, err := s.client.Do(req)
906+
if err != nil {
907+
t.Fatal(err)
908+
}
909+
defer resp.Body.Close()
910+
if resp.StatusCode != http.StatusNotFound {
911+
t.Fatalf("Status code isn't 404: %d", resp.StatusCode)
912+
}
913+
}
914+
}
915+
876916
func TestParseCacheControl(t *testing.T) {
877917
resetTest()
878918
h := http.Header{}
@@ -1355,6 +1395,57 @@ func TestStaleIfErrorResponseLifetime(t *testing.T) {
13551395
}
13561396
}
13571397

1398+
// This tests the fix for https://github.com/gregjones/httpcache/issues/74.
1399+
// Previously, after a stale response was used after encountering an error,
1400+
// its StatusCode was being incorrectly replaced.
1401+
func TestStaleIfErrorKeepsStatus(t *testing.T) {
1402+
resetTest()
1403+
now := time.Now()
1404+
tmock := transportMock{
1405+
response: &http.Response{
1406+
Status: http.StatusText(http.StatusNotFound),
1407+
StatusCode: http.StatusNotFound,
1408+
Header: http.Header{
1409+
"Date": []string{now.Format(time.RFC1123)},
1410+
"Cache-Control": []string{"no-cache"},
1411+
},
1412+
Body: ioutil.NopCloser(bytes.NewBuffer([]byte("some data"))),
1413+
},
1414+
err: nil,
1415+
}
1416+
tp := NewMemoryCacheTransport()
1417+
tp.Transport = &tmock
1418+
1419+
// First time, response is cached on success
1420+
r, _ := http.NewRequest("GET", "http://somewhere.com/", nil)
1421+
r.Header.Set("Cache-Control", "stale-if-error")
1422+
resp, err := tp.RoundTrip(r)
1423+
if err != nil {
1424+
t.Fatal(err)
1425+
}
1426+
if resp == nil {
1427+
t.Fatal("resp is nil")
1428+
}
1429+
_, err = ioutil.ReadAll(resp.Body)
1430+
if err != nil {
1431+
t.Fatal(err)
1432+
}
1433+
1434+
// On failure, response is returned from the cache
1435+
tmock.response = nil
1436+
tmock.err = errors.New("some error")
1437+
resp, err = tp.RoundTrip(r)
1438+
if err != nil {
1439+
t.Fatal(err)
1440+
}
1441+
if resp == nil {
1442+
t.Fatal("resp is nil")
1443+
}
1444+
if resp.StatusCode != http.StatusNotFound {
1445+
t.Fatalf("Status wasn't 404: %d", resp.StatusCode)
1446+
}
1447+
}
1448+
13581449
// Test that http.Client.Timeout is respected when cache transport is used.
13591450
// That is so as long as request cancellation is propagated correctly.
13601451
// In the past, that required CancelRequest to be implemented correctly,

0 commit comments

Comments
 (0)