-1

I've implemented the HTTP Caching guidelines described by Google in a URL Shortening API that I've developed.

Here's how I'm sending the response:

const urlResponseCacheControlMaxAge = 172800 // 2 days

type urlResponse struct {
    LongURL  string `json:"longUrl"`
    ShortURL string `json:"shortUrl"`
}

func (u urlResponse) Hash() string {
    parts := strings.Split(u.ShortURL, "/")
    return parts[len(parts)-1]
}

func sendURLResponse(w http.ResponseWriter, req *http.Request, urlResponse *urlResponse) {

    if eTag, ok := req.Header["ETag"]; ok && urlResponse.Hash() == eTag[0] {
        w.WriteHeader(http.StatusNotModified)
        io.WriteString(w, "")
        return
    }

    cacheControl := fmt.Sprintf(
        "max-age:%d, public",
        urlResponseCacheControlMaxAge,
    )

    w.Header().Set("Cache-Control", cacheControl)
    w.Header().Set("Content-Type", "application/json;charset=utf-8")
    w.Header().Set("ETag", urlResponse.Hash())
    w.WriteHeader(http.StatusOK)
    encoder := json.NewEncoder(w)
    err := encoder.Encode(urlResponse)

    if err != nil {
        SendError(w, NewError(
            URLResponseEncoding,
            "Error encoding response",
            map[string]string{"error": err.Error()},
        ))
        return
    }
}

Basically, when the browser sends a request to the API (using GET), I return an ETag and Cache-Control header in the response; the Cache Control header sets a max age of two days.

What I expect to happen is that in subsequent requests, the browser uses the cached response. After 2 days have elapsed, the browser should send the ETag in the request header to check if the response has changed.

However, what I'm observing is that each time I click on the submit button, the browser resends the request. On Google Chrome Developer Console, I've unchecked 'Disable Caching' and yet it still sends requests each time.

Whatsmore is that the browser is not sending the ETag back with the request headers.

Is there something that I'm missing that's causing the cache to not work as expected?

kostix
  • 51,517
  • 14
  • 93
  • 176
W.K.S
  • 9,787
  • 15
  • 75
  • 122
  • 2
    It won't send the ETag in the request in the ETag header, it will be in the If-None-Match header. Cache control headers expect the server to take the request headers and determine if a new response is needed, and if not, return a response of 304 Not Modified with no body. – Adrian Oct 12 '18 at 17:04
  • You've misunderstand how etag working. – Roman Kiselenko Oct 12 '18 at 17:41
  • @Adrian I didn't say that the request would send the Etag in a header called Etag. – W.K.S Oct 12 '18 at 17:52
  • 1
    You didn't, your code did: `if eTag, ok := req.Header["ETag"]`. – Adrian Oct 12 '18 at 18:20
  • Thank you for pointing that out @Adrian – W.K.S Oct 12 '18 at 18:32
  • As well as the other comments, note that the ETag format you're using is incorrect. An ETag must be enclosed in double quotes. Also, you are creating a weak ETag (meaning it doesn't uniquely identify the content), so it must be prefixed with W/ i.e. must be W/"" including the double quotes. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag. – Aaron Queenan Aug 22 '19 at 14:20

2 Answers2

3
cacheControl := fmt.Sprintf(
    "max-age:%d, public",

The Cache-Control header must contain the caching time with max-age=... not max-age:... as you use (= vs :). The value you've tried to set in the wrong way will be simply ignored.

Whatsmore is that the browser is not sending the ETag back with the request headers.

if eTag, ok := req.Header["ETag"]; ok && urlResponse.Hash() == eTag[0] {
    w.WriteHeader(http.StatusNotModified)

The browser will not send etag back within an ETag header. This header is only used to set the etag. The browser will instead ask the server to provide the resource if it was modified compared and will do this by putting the received etag into a If-None-Match: ... header - meaning: please return the resource if it does not match the given etag.

Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
0

For ETags to be used and the browser to send the If-Modified-Since and If-None-Match headers, the cache control must be set to no-cache.

Either by the server using the Cache-Control: no-cache header or by the browser through the Request.cache option.

enisdenjo
  • 706
  • 9
  • 21