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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1An ETag derived from the response body lets clients skip re-downloading data that hasn't changed.
- 2Serializing once and hashing that exact byte slice keeps the ETag and the sent payload perfectly in sync.
- 3Returning 304 Not Modified saves bandwidth while still confirming the resource is current.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
go
package middleware import ( "net/http"
Per-plan export limits in Gin middleware
middleware
rate-limiting
authorization
Intermediate
7 steps
go
package httputil import ( "net"
Safely extracting the real client IP in Go
security
http
ip-spoofing
Intermediate
7 steps
go
package signup var wizardCodec = securecookie.New( []byte(os.Getenv("WIZARD_HASH_KEY")),
A cookie-backed signup wizard in Gin
cookies
encryption
multi-step-form
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/etag-conditional-requests-in-gin-explained-go-6987/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.