HTTP Fundamentals

HTTP Conditional Requests: If-Match, If-None-Match, and Beyond

Master conditional requests for optimistic concurrency control, cache validation, and bandwidth-efficient updates using ETags and validators.

What Are Conditional Requests?

Conditional requests let HTTP clients attach preconditions to a request. The server only performs the operation if the precondition evaluates to true — otherwise it returns an appropriate status code without doing work. This enables two critical use cases: cache validation (avoiding redundant data transfer) and optimistic concurrency control (safe concurrent edits).

Validators

Conditional requests rely on two types of validators:

ETags (Entity Tags) — an opaque string representing a resource version:

ETag: "33a64df551425fcc55e"
ETag: W/"33a64df"    # Weak ETag (semantic equivalence, not byte-identical)

Last-Modified — a timestamp of the last change:

Last-Modified: Wed, 26 Feb 2026 10:30:00 GMT

Strong ETags guarantee byte-identical responses. Weak ETags (prefixed with W/) indicate semantic equivalence — the response conveys the same information but might differ in headers like Date. Use strong ETags for byte-range requests; weak ETags are fine for cache validation.

Cache Validation with If-None-Match

The most common conditional request pattern avoids re-downloading unchanged resources:

# 1. First request: server includes ETag in response
GET /api/users/42 HTTP/1.1

HTTP/1.1 200 OK
ETag: "abc123"
Content-Type: application/json
{...full user object...}

# 2. Subsequent request: client sends ETag back
GET /api/users/42 HTTP/1.1
If-None-Match: "abc123"

# 3a. Resource unchanged → 304 (no body, saves bandwidth)
HTTP/1.1 304 Not Modified
ETag: "abc123"

# 3b. Resource changed → 200 with new body
HTTP/1.1 200 OK
ETag: "def456"
{...updated user object...}

The If-Modified-Since header serves the same purpose using timestamps:

If-Modified-Since: Wed, 26 Feb 2026 10:30:00 GMT

ETags are preferred because timestamps have 1-second resolution and can fail when multiple servers generate the same content at different times.

Optimistic Concurrency with If-Match

For write operations, If-Match prevents the lost update problem — two clients editing the same resource simultaneously, where the second save silently overwrites the first:

# Client A and Client B both fetch the resource:
GET /api/documents/99
→ ETag: "v1"

# Client A updates successfully:
PUT /api/documents/99
If-Match: "v1"
→ 200 OK, new ETag: "v2"

# Client B tries to update with stale ETag:
PUT /api/documents/99
If-Match: "v1"   # Server now has "v2"
→ 412 Precondition Failed

On 412 Precondition Failed, the client must re-fetch the resource, show a conflict UI, and let the user merge changes before retrying.

# Django view implementing If-Match:
def update_document(request, pk):
    doc = get_object_or_404(Document, pk=pk)
    current_etag = f'"{doc.version_hash}"'

    if_match = request.headers.get('If-Match')
    if if_match and if_match != current_etag:
        return HttpResponse(status=412)

    # Safe to update
    doc.update(request.data)
    return JsonResponse(doc.to_dict(), headers={'ETag': f'"{doc.version_hash}"'})

If-Unmodified-Since

The timestamp equivalent of If-Match. Less reliable due to clock skew and 1-second granularity, but useful as a fallback when the server does not generate ETags.

Conditional Range Requests with If-Range

If-Range combines conditional logic with partial content for resumable downloads:

# Resume a download after interruption:
GET /files/large-dataset.csv
Range: bytes=1048576-
If-Range: "v1-etag"

# If resource unchanged: 206 Partial Content (resume works)
HTTP/1.1 206 Partial Content

# If resource changed: 200 OK with full content (restart download)
HTTP/1.1 200 OK

Without If-Range, a changed resource would produce corrupt output (old start bytes + new end bytes). If-Range ensures the client gets the full new file when the resource has changed.

Implementation Patterns

Django

Django's ConditionalGetMiddleware automatically handles If-None-Match and If-Modified-Since for GET requests when views set ETag or Last-Modified headers:

# settings.py — enable middleware
MIDDLEWARE = [
    'django.middleware.http.ConditionalGetMiddleware',
    # ...
]

# views.py — set ETag in view
from django.views.decorators.http import etag

def compute_etag(request, pk):
    obj = get_object_or_404(Article, pk=pk)
    return obj.updated_at.isoformat()

@etag(compute_etag)
def article_detail(request, pk):
    obj = get_object_or_404(Article, pk=pk)
    return JsonResponse(obj.to_dict())

Express / Node.js

const etag = require('etag');

app.get('/api/users/:id', (req, res) => {
  const user = db.findUser(req.params.id);
  const tag = etag(JSON.stringify(user));

  if (req.headers['if-none-match'] === tag) {
    return res.status(304).end();
  }

  res.set('ETag', tag).json(user);
});

CDN Behavior

CDNs automatically participate in conditional request flows. When a CDN has a cached response with an ETag, it forwards client If-None-Match requests to the origin only when its own cached copy has expired. This dramatically reduces origin load while keeping client caches accurate.

İlgili Protokoller

İlgili Sözlük Terimleri

Daha Fazlası HTTP Fundamentals