go 41 lines · 6 steps

ETag conditional requests in Gin

A Gin handler that hashes its JSON response into an ETag so unchanged articles return 304 instead of a full body.

Explained by highlit
1package api
2 
3import (
4 "crypto/sha256"
5 "encoding/hex"
6 "encoding/json"
7 "fmt"
8 "net/http"
9 
10 "github.com/gin-gonic/gin"
11)
12 
13func (h *Handler) GetArticle(c *gin.Context) {
14 article, err := h.articles.FindByID(c.Request.Context(), c.Param("id"))
15 if err != nil {
16 c.JSON(http.StatusNotFound, gin.H{"error": "article not found"})
17 return
18 }
19 
20 payload, err := json.Marshal(article)
21 if err != nil {
22 c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to encode article"})
23 return
24 }
25 
26 etag := weakETag(payload)
27 c.Header("ETag", etag)
28 c.Header("Cache-Control", "private, must-revalidate")
29 
30 if match := c.GetHeader("If-None-Match"); match == etag {
31 c.Status(http.StatusNotModified)
32 return
33 }
34 
35 c.Data(http.StatusOK, "application/json; charset=utf-8", payload)
36}
37 
38func weakETag(payload []byte) string {
39 sum := sha256.Sum256(payload)
40 return fmt.Sprintf(`W/"%s"`, hex.EncodeToString(sum[:16]))
41}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1An ETag derived from the response body lets clients skip re-downloading data that hasn't changed.
  2. 2Serializing once and hashing that exact byte slice keeps the ETag and the sent payload perfectly in sync.
  3. 3Returning 304 Not Modified saves bandwidth while still confirming the resource is current.

Related explainers

Share this explainer

Here's the card — post it anywhere.

ETag conditional requests in Gin — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code