Skip to content
Snippets Groups Projects
Commit bb33b7bb authored by Will McCutchen's avatar Will McCutchen
Browse files

Add /etag, fix /cache handlers

parent 2397153c
No related branches found
No related tags found
No related merge requests found
...@@ -583,6 +583,7 @@ func (h *HTTPBin) Cache(w http.ResponseWriter, r *http.Request) { ...@@ -583,6 +583,7 @@ func (h *HTTPBin) Cache(w http.ResponseWriter, r *http.Request) {
seconds, err := strconv.ParseInt(parts[2], 10, 64) seconds, err := strconv.ParseInt(parts[2], 10, 64)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return
} }
w.Header().Add("Cache-Control", fmt.Sprintf("public, max-age=%d", seconds)) w.Header().Add("Cache-Control", fmt.Sprintf("public, max-age=%d", seconds))
} }
...@@ -604,8 +605,36 @@ func (h *HTTPBin) CacheControl(w http.ResponseWriter, r *http.Request) { ...@@ -604,8 +605,36 @@ func (h *HTTPBin) CacheControl(w http.ResponseWriter, r *http.Request) {
seconds, err := strconv.ParseInt(parts[2], 10, 64) seconds, err := strconv.ParseInt(parts[2], 10, 64)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return
} }
w.Header().Add("Cache-Control", fmt.Sprintf("public, max-age=%d", seconds)) w.Header().Add("Cache-Control", fmt.Sprintf("public, max-age=%d", seconds))
h.Get(w, r) h.Get(w, r)
} }
// ETag assumes the resource has the given etag and response to If-None-Match
// and If-Match headers appropriately.
func (h *HTTPBin) ETag(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.URL.Path, "/")
if len(parts) != 3 {
http.Error(w, "Not found", http.StatusNotFound)
return
}
etag := parts[2]
w.Header().Set("ETag", fmt.Sprintf(`"%s"`, etag))
// TODO: This mostly duplicates the work of Get() above, should this be
// pulled into a little helper?
resp := &getResponse{
Args: r.URL.Query(),
Headers: r.Header,
Origin: getOrigin(r),
URL: getURL(r).String(),
}
body, _ := json.Marshal(resp)
// Let http.ServeContent deal with If-None-Match and If-Match headers:
// https://golang.org/pkg/net/http/#ServeContent
http.ServeContent(w, r, "response.json", time.Now(), bytes.NewReader(body))
}
...@@ -1589,8 +1589,6 @@ func TestCache(t *testing.T) { ...@@ -1589,8 +1589,6 @@ func TestCache(t *testing.T) {
} }
}) })
// Note: httpbin rejects these requests with invalid range headers, but the
// go stdlib just ignores them.
var tests = []struct { var tests = []struct {
headerKey string headerKey string
headerVal string headerVal string
...@@ -1621,8 +1619,6 @@ func TestCacheControl(t *testing.T) { ...@@ -1621,8 +1619,6 @@ func TestCacheControl(t *testing.T) {
assertHeader(t, w, "Cache-Control", "public, max-age=60") assertHeader(t, w, "Cache-Control", "public, max-age=60")
}) })
// Note: httpbin rejects these requests with invalid range headers, but the
// go stdlib just ignores them.
var badTests = []struct { var badTests = []struct {
url string url string
expectedStatus int expectedStatus int
...@@ -1640,3 +1636,58 @@ func TestCacheControl(t *testing.T) { ...@@ -1640,3 +1636,58 @@ func TestCacheControl(t *testing.T) {
}) })
} }
} }
func TestETag(t *testing.T) {
t.Run("ok_no_headers", func(t *testing.T) {
url := "/etag/abc"
r, _ := http.NewRequest("GET", url, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, r)
assertStatusCode(t, w, http.StatusOK)
assertHeader(t, w, "ETag", `"abc"`)
})
var tests = []struct {
name string
etag string
headerKey string
headerVal string
expectedStatus int
}{
{"if_none_match_matches", "abc", "If-None-Match", `"abc"`, http.StatusNotModified},
{"if_none_match_matches_list", "abc", "If-None-Match", `"123", "abc"`, http.StatusNotModified},
{"if_none_match_matches_star", "abc", "If-None-Match", "*", http.StatusNotModified},
{"if_none_match_matches_w_prefix", "c3piozzzz", "If-None-Match", `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"`, http.StatusNotModified},
{"if_none_match_has_no_match", "abc", "If-None-Match", `"123"`, http.StatusOK},
{"if_match_matches", "abc", "If-Match", `"abc"`, http.StatusOK},
{"if_match_matches_list", "abc", "If-Match", `"123", "abc"`, http.StatusOK},
{"if_match_matches_star", "abc", "If-Match", "*", http.StatusOK},
{"if_match_has_no_match", "abc", "If-Match", `"xxxxxx"`, http.StatusPreconditionFailed},
}
for _, test := range tests {
t.Run("ok_"+test.name, func(t *testing.T) {
url := "/etag/" + test.etag
r, _ := http.NewRequest("GET", url, nil)
r.Header.Add(test.headerKey, test.headerVal)
w := httptest.NewRecorder()
handler.ServeHTTP(w, r)
assertStatusCode(t, w, test.expectedStatus)
})
}
var badTests = []struct {
url string
expectedStatus int
}{
{"/etag/foo/bar", http.StatusNotFound},
}
for _, test := range badTests {
t.Run(fmt.Sprintf("bad/%s", test.url), func(t *testing.T) {
r, _ := http.NewRequest("GET", test.url, nil)
w := httptest.NewRecorder()
handler.ServeHTTP(w, r)
assertStatusCode(t, w, test.expectedStatus)
})
}
}
...@@ -128,6 +128,7 @@ func (h *HTTPBin) Handler() http.Handler { ...@@ -128,6 +128,7 @@ func (h *HTTPBin) Handler() http.Handler {
mux.HandleFunc("/cache", h.Cache) mux.HandleFunc("/cache", h.Cache)
mux.HandleFunc("/cache/", h.CacheControl) mux.HandleFunc("/cache/", h.CacheControl)
mux.HandleFunc("/etag/", h.ETag)
// Make sure our ServeMux doesn't "helpfully" redirect these invalid // Make sure our ServeMux doesn't "helpfully" redirect these invalid
// endpoints by adding a trailing slash. See the ServeMux docs for more // endpoints by adding a trailing slash. See the ServeMux docs for more
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment