Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cachecontrol/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,13 +243,13 @@ def cached_request(self, request: PreparedRequest) -> HTTPResponse | Literal[Fal
logger.debug("Returning cached permanent redirect response")
return resp

# Determine if we are setting freshness limit in the
# request. Note, this overrides what was in the response.
# A request max-age can shorten, but must not extend, the response's
# freshness lifetime (RFC 9111 section 5.2.1.1).
max_age = cc.get("max-age")
if max_age is not None:
freshness_lifetime = max_age
freshness_lifetime = min(freshness_lifetime, max_age)
logger.debug(
"Freshness lifetime from request max-age: %i", freshness_lifetime
"Freshness lifetime limited by request max-age: %i", freshness_lifetime
)

min_fresh = cc.get("min-fresh")
Expand Down
41 changes: 41 additions & 0 deletions tests/test_cache_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,47 @@ def test_cache_request_unfresh_max_age(self):
r = self.req({})
assert not r

@pytest.mark.parametrize("status", [200, 301, 308])
@pytest.mark.parametrize("expiration", ["max-age", "expires"])
def test_request_max_age_does_not_extend_response_freshness(
self, monkeypatch, status, expiration
):
now = 1700000000
monkeypatch.setattr(time, "time", lambda: now)
headers = {"date": time.strftime(TIME_FMT, time.gmtime(now - 120))}
if expiration == "max-age":
headers["cache-control"] = "max-age=60"
else:
headers["expires"] = time.strftime(TIME_FMT, time.gmtime(now - 60))
resp = Mock(headers=headers, status=status)
self.c.cache = DictCache({self.url: resp})

assert self.req({"cache-control": "max-age=3600"}) is False

@pytest.mark.parametrize("request_max_age, cached", [(10, False), (3600, True)])
def test_request_max_age_limits_fresh_response(
self, monkeypatch, request_max_age, cached
):
now = 1700000000
monkeypatch.setattr(time, "time", lambda: now)
date = time.strftime(TIME_FMT, time.gmtime(now - 30))
resp = Mock(headers={"cache-control": "max-age=60", "date": date}, status=200)
self.c.cache = DictCache({self.url: resp})

result = self.req({"cache-control": f"max-age={request_max_age}"})

assert result is (resp if cached else False)

def test_request_max_age_revalidates_etag_without_freshness(self):
date = time.strftime(TIME_FMT, time.gmtime())
resp = Mock(headers={"date": date, "etag": '"v1"'}, status=200)
self.c.cache = DictCache({self.url: resp})
request = Mock(url=self.url, headers={"cache-control": "max-age=3600"})

assert self.c.cached_request(request) is False
assert self.c.cache.get(self.url) is resp
assert self.c.conditional_headers(request) == {"If-None-Match": '"v1"'}

def test_cache_request_unfresh_permanent_redirect(self):
earlier = time.time() - 3600
date = time.strftime(TIME_FMT, time.gmtime(earlier))
Expand Down
14 changes: 14 additions & 0 deletions tests/test_max_age.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#
# SPDX-License-Identifier: Apache-2.0

import time

import pytest

from requests import Session
Expand Down Expand Up @@ -55,3 +57,15 @@ def test_client_max_age_3600(self, sess):
resp.headers["date"] = "Tue, 15 Nov 1994 08:12:31 GMT"
r = sess.get(self.url)
assert not r.from_cache

def test_client_max_age_does_not_extend_server_max_age(self, sess):
url = self.url + "cache_60"
sess.get(url)
cached_response = self.cache.get(url)
cached_response.headers["date"] = time.strftime(
"%a, %d %b %Y %H:%M:%S GMT", time.gmtime(time.time() - 120)
)

response = sess.get(url, headers={"Cache-Control": "max-age=3600"})

assert not response.from_cache