Skip to content

Add If-Modified-Since / 304 Not Modified support to the content app #7929

Description

@dkliban

Problem

The content app always returns a full response (200 with body or 302 redirect) regardless of whether the content has changed since the client last requested it. This prevents edge caches (Akamai, nginx) from using conditional requests to revalidate cached content — every request results in a full download or redirect, even for immutable artifacts that never change.

Proposed Solution

Add support for If-Modified-Since conditional requests and Last-Modified response headers to the content handler. When a client sends If-Modified-Since and the requested content has not changed, Pulp should return 304 Not Modified with no body.

The Last-Modified value should be set to RepositoryContent.pulp_created — the timestamp of when the specific content unit was added to the repository being served. This is more accurate than using the repository version creation time, since some repositories get new versions frequently. pulp_python's Simple API already uses this same approach for its upload_time field.

Implementation

Both the non-cached and Redis-cached content serving paths need to handle this.

Non-cached path (_match_and_stream)

After ContentGuard authorization passes and the content artifact is resolved, but before building the response:

  1. Look up RepositoryContent.pulp_created for the content being served
  2. Set Last-Modified header on all 200 responses
  3. If the request includes If-Modified-Since and the content has not been modified since that datetime, return 304 Not Modified (no body)

Redis-cached path (AsyncContentCache)

  1. Include a last_modified timestamp in the cached entry JSON (alongside headers, status, expires, type)
  2. On cache hit, after ContentGuard authorization passes, check If-Modified-Since against the stored last_modified timestamp
  3. If not modified, return 304 instead of reconstructing the full cached response
  4. If modified or no If-Modified-Since header, return the cached response as normal with Last-Modified header set

Response behavior summary

Condition Response
Not authorized 403 Forbidden
Authorized, no If-Modified-Since 200 with body + Last-Modified header
Authorized, content changed since If-Modified-Since 200 with body + Last-Modified header
Authorized, content not changed since If-Modified-Since 304 Not Modified (no body)

Use Case

This enables edge caching architectures where a CDN (e.g., Akamai with Centralized Authorization) or a reverse proxy (e.g., nginx with proxy_cache_revalidate) caches content at the edge and uses lightweight conditional requests to Pulp for revalidation. Pulp still performs authorization on every request, but avoids transferring the full binary when the cached copy is still valid.

Related Code

  • pulpcore/content/handler.pyHandler.stream_content(), _match_and_stream(), _build_response_from_content_artifact()
  • pulpcore/cache/cache.pyAsyncContentCache, make_response(), make_entry()
  • pulpcore/responses.pyArtifactResponse

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions