python 94 lines · 3 tabs

Conditional GET with ETag Generation and 304 Not Modified in Flask

Shared by codesnips Aug 2026
3 tabs
import hashlib
import json


def compute_etag(payload, weak=False):
    body = json.dumps(payload, sort_keys=True, separators=(",", ":"))
    digest = hashlib.sha256(body.encode("utf-8")).hexdigest()[:32]
    tag = '"{}"'.format(digest)
    return "W/" + tag if weak else tag


def parse_if_none_match(header_value):
    if not header_value:
        return set()
    if header_value.strip() == "*":
        return {"*"}
    tags = set()
    for raw in header_value.split(","):
        candidate = raw.strip()
        if candidate.startswith("W/"):
            candidate = candidate[2:].strip()
        if candidate:
            tags.add(candidate)
    return tags


def etag_matches(current_etag, header_value):
    provided = parse_if_none_match(header_value)
    if "*" in provided:
        return True
    normalized = current_etag[2:] if current_etag.startswith("W/") else current_etag
    return normalized in provided
3 files · python Explain with highlit

Conditional GET is an HTTP mechanism that lets a client skip re-downloading a resource it already has. The server sends an ETag (a validator that identifies a specific version of a representation); on the next request the client echoes it back in If-None-Match, and the server replies 304 Not Modified with an empty body when nothing changed. This saves bandwidth and time while keeping caches correct, since the validator is compared server-side rather than trusting a stale local copy.

In etag.py, compute_etag derives a stable, content-based validator. It serializes the payload with sorted keys so logically-equal dicts hash identically, then takes a truncated SHA-256 digest and wraps it in quotes as HTTP requires. The prefix distinguishes weak from strong tags; this implementation emits strong tags because the bytes are compared exactly. parse_if_none_match normalizes the header, splitting on commas and handling the * wildcard as well as the W/ weak marker, so comparison is robust against client formatting quirks.

In conditional.py, the conditional decorator wraps a view that returns a JSON-able object. make_etag produces the validator, then etag_matches checks the incoming If-None-Match set. On a match the decorator short-circuits with a bare 304 response — crucially still carrying the ETag and Cache-Control headers, since a 304 must repeat the caching validators. Otherwise it builds a normal 200 with the same ETag, so the client can revalidate next time. The body is only serialized once and reused.

In app.py, get_article is a plain view that fetches data and returns a dict; the decorator handles all the HTTP plumbing. The _load_article helper simulates a store whose updated_at feeds into the ETag, so edits naturally invalidate the cached version.

A key trade-off is cost: hashing the full body defeats the point if generating that body is expensive, so this pattern shines when serialization is cheap relative to network transfer. Last-Modified is an alternative validator with second-granularity and clock-skew pitfalls; ETags avoid those but require deterministic serialization. A common bug is forgetting to send Vary when the representation depends on headers like Accept-Encoding — omitting it can poison shared caches.


Related snips

Share this code

Here's the card — post it anywhere.

Conditional GET with ETag Generation and 304 Not Modified in Flask — share card
Link copied