Search code examples
httpcachinghttp-headers

Cache-Control & Etag not honored on Google Chrome


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?


Solution

  • 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.