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.

相关协议

相关术语表条目

更多内容: HTTP Fundamentals