Skip to content

ETag Middleware

A lightweight, production‑ready ETag middleware for the Nexios ASGI framework.

It automatically:

  • Computes and sets an ETag header for responses that don't already have one
  • Handles conditional GET/HEAD requests via If-None-Match and returns 304 Not Modified when appropriate

Installation

bash
pip install nexios_contrib

Quick Start

python
from nexios import NexiosApp
import nexios_contrib.etag as etag

app = NexiosApp()

# Add the ETag middleware (defaults shown)
app.add_middleware(
    etag.ETag(
        weak=True,                 # Generate weak validators (W/ prefixes)
        methods=("GET", "HEAD"),  # Methods to apply to
        override=False             # Don't overwrite an existing ETag by default
    )
)

@app.get("/")
async def home(request, response):
    return {"message": "Hello with ETag!"}

That's it! Responses to GET/HEAD will carry an ETag header. Clients sending If-None-Match will receive 304 when the ETag matches.

Configuration

Parameters

  • weak: bool = True
    Use weak validators (e.g. W/"abc"). Set False for strong validators when you know the body bytes are stable across platforms and encodings.

  • methods: Iterable[str] = ("GET", "HEAD")
    Limit conditional handling to idempotent methods. You can add others, but it's not typical.

  • override: bool = False
    If True, overwrites an ETag already set by your handler.

How It Works

  1. If the handler doesn't set an ETag, the middleware computes one from the response body and sets it
  2. If the request includes If-None-Match and it matches the response's ETag (weak compare by default), the middleware converts the response into 304 Not Modified and removes the body, per RFC 9110

Examples

Basic Usage

python
from nexios import NexiosApp
import nexios_contrib.etag as etag

app = NexiosApp()
app.add_middleware(etag.ETag())

@app.get("/api/data")
async def get_data(request, response):
    # This response will automatically get an ETag
    return {"data": "some content", "timestamp": "2024-01-01"}

# First request: Returns 200 with ETag: W/"abc123"
# Second request with If-None-Match: W/"abc123": Returns 304 Not Modified

Strong ETags

python
app.add_middleware(
    etag.ETag(
        weak=False,  # Use strong validators
        methods=("GET", "HEAD")
    )
)

@app.get("/api/file")
async def get_file(request, response):
    # Strong ETag will be generated: "abc123" (no W/ prefix)
    return {"file_content": "binary data"}

Custom Methods

python
app.add_middleware(
    etag.ETag(
        methods=("GET", "HEAD", "POST"),  # Include POST (not recommended)
        override=True  # Override existing ETags
    )
)

Best Practices

  • Use for idempotent methods: Apply by default to GET and HEAD. Avoid applying to mutating methods like POST/PUT unless you know what you're doing
  • Consider response size: Streaming or extremely large bodies may benefit from precomputed/strong ETags at your handler or via a hash of a stable resource version
  • Weak vs Strong: Use weak ETags (default) unless you need byte-for-byte identical responses
  • Cache-friendly: ETags work great with HTTP caching - browsers and CDNs will use them automatically

Advanced Usage

Manual ETag Setting

python
@app.get("/api/custom")
async def custom_etag(request, response):
    # Set your own ETag - middleware won't override by default
    response.headers['ETag'] = 'W/"custom-etag-123"'
    return {"data": "custom content"}

Conditional Logic

python
@app.get("/api/conditional")
async def conditional_response(request, response):
    # Check if client has current version
    if_none_match = request.headers.get('If-None-Match')
    current_etag = 'W/"version-456"'
    
    if if_none_match == current_etag:
        # Client has current version
        response.status_code = 304
        return None
    
    # Return new content
    response.headers['ETag'] = current_etag
    return {"data": "updated content"}

Notes

  • When returning 304, the middleware ensures the body is empty and relies on the underlying response type to handle headers correctly
  • ETags are computed from the response body, so identical content will have identical ETags
  • The middleware respects existing ETags unless override=True is set

Performance Considerations

  • ETag computation adds minimal overhead for small responses
  • For large responses, consider pre-computing ETags based on resource versions
  • ETags enable efficient caching, reducing server load for repeated requests
  • Use with appropriate Cache-Control headers for optimal caching behavior

Built with ❤️ by the @nexios-labs community.