Skip Unchanged Downloads with ETag Requests
Send If-None-Match on repeat HTTP requests, handle 304 responses, and reuse the stored body when nothing changed.
Code
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
)
var payload = []byte("configuration-v1")
func fetchConditional(
client *http.Client,
url string,
etag string,
) (body []byte, newETag string, notModified bool, err error) {
request, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, "", false, err
}
if etag != "" {
request.Header.Set("If-None-Match", etag)
}
response, err := client.Do(request)
if err != nil {
return nil, "", false, err
}
defer response.Body.Close()
if response.StatusCode == http.StatusNotModified {
return nil, etag, true, nil
}
if response.StatusCode != http.StatusOK {
return nil, "", false, fmt.Errorf("unexpected status %d", response.StatusCode)
}
body, err = io.ReadAll(io.LimitReader(response.Body, 1<<20))
if err != nil {
return nil, "", false, err
}
return body, response.Header.Get("ETag"), false, nil
}
func main() {
requests := 0
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
requests++
writer.Header().Set("ETag", "\"v1\"")
if request.Header.Get("If-None-Match") == "\"v1\"" {
writer.WriteHeader(http.StatusNotModified)
return
}
_, _ = writer.Write(payload)
}))
defer server.Close()
client := server.Client()
body, etag, notModified, err := fetchConditional(client, server.URL, "")
if err != nil || notModified || string(body) != string(payload) || etag == "" {
panic("first request should download the payload")
}
fmt.Printf("first: body=%q etag=%s\n", body, etag)
body, _, notModified, err = fetchConditional(client, server.URL, etag)
if err != nil || !notModified || len(body) != 0 || requests != 2 {
panic("second request should return 304 without a body")
}
fmt.Printf("second: notModified=%v requests=%d\n", notModified, requests)
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.