fix cache
This commit is contained in:
1 parent
0973129c17
commit
cc2910afa9
3 files changed
+23
No files matched your search
@@ -76,3 +76,17 @@ Run local code checks:
|
|||||||
python3 -m unittest discover -s tests -p 'test_*.py' -v
|
python3 -m unittest discover -s tests -p 'test_*.py' -v
|
||||||
python3 tests/smoke_cache_routes.py
|
python3 tests/smoke_cache_routes.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Duplicate Date and RustProxy 0.29.3
|
||||||
|
|
||||||
|
`/convert` deliberately does not set its own `Date` header after
|
||||||
|
`Response.make_conditional()`. Werkzeug can create a `Date` header there;
|
||||||
|
Uvicorn also supplies one, and RustProxy 0.29.3 rejects multiple `Date`
|
||||||
|
headers with `response_ttl_size_or_metadata`.
|
||||||
|
|
||||||
|
Verify that **exactly one** `Date` header is returned directly by the
|
||||||
|
ASGI backend (no reverse proxy in the path). All success responses should
|
||||||
|
also include a valid `Content-Length`, `ETag` and public `Cache-Control`.
|
||||||
|
For a full seven days, set RustProxy global `max_ttl_seconds` to `604800`
|
||||||
|
AND configure the app's `CACHE_S_MAXAGE=604800` in `.env`.
|
||||||
|
The `unsafe_force` cache switch is not required for `/convert`.
|
||||||
@@ -162,6 +162,10 @@ def convert():
|
|||||||
# the final representation. It may return 304 only after validating
|
# the final representation. It may return 304 only after validating
|
||||||
# the complete content hash. Never use the source's validators.
|
# the complete content hash. Never use the source's validators.
|
||||||
resp.make_conditional(request)
|
resp.make_conditional(request)
|
||||||
|
# Werkzeug adds Date during make_conditional(), while Uvicorn adds
|
||||||
|
# its own Date. Two Date fields make RustProxy 0.29.3 reject caching.
|
||||||
|
# Leave Date generation to the HTTP server, including for HTTP 304.
|
||||||
|
resp.headers.pop("Date", None)
|
||||||
if resp.status_code == 304:
|
if resp.status_code == 304:
|
||||||
result.output.close()
|
result.output.close()
|
||||||
redis_client.incr("stats:conversions_success")
|
redis_client.incr("stats:conversions_success")
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ class Response:
|
|||||||
self.close_callbacks.append(callback)
|
self.close_callbacks.append(callback)
|
||||||
|
|
||||||
def make_conditional(self, request):
|
def make_conditional(self, request):
|
||||||
|
# Simulate Werkzeug Response.make_conditional adding Date.
|
||||||
|
self.headers["Date"] = "Fri, 09 Oct 2026 08:58:26 GMT"
|
||||||
if request.headers.get("If-None-Match") == self.headers.get("ETag"):
|
if request.headers.get("If-None-Match") == self.headers.get("ETag"):
|
||||||
self.status_code = 304
|
self.status_code = 304
|
||||||
return self
|
return self
|
||||||
@@ -196,6 +198,7 @@ check(resp.headers["Cache-Control"] ==
|
|||||||
"proxy TTL and staleness")
|
"proxy TTL and staleness")
|
||||||
check(resp.headers["ETag"] == f'"{content_hash}"', "strong output-body ETag")
|
check(resp.headers["ETag"] == f'"{content_hash}"', "strong output-body ETag")
|
||||||
check(resp.headers["Content-Length"] == str(len(converted)), "content length")
|
check(resp.headers["Content-Length"] == str(len(converted)), "content length")
|
||||||
|
check("Date" not in resp.headers, "GET leaves Date to Uvicorn (no duplicate)")
|
||||||
check(b"".join(resp.data) == converted, "response bytes")
|
check(b"".join(resp.data) == converted, "response bytes")
|
||||||
check(source.closed, "upstream closed after success")
|
check(source.closed, "upstream closed after success")
|
||||||
check("Last-Modified" not in resp.headers, "origin validator is not forwarded")
|
check("Last-Modified" not in resp.headers, "origin validator is not forwarded")
|
||||||
@@ -207,6 +210,7 @@ check(resp.status_code == 304, "client 304 after full body hash")
|
|||||||
check("headers" not in called[-1][1], "client validators not forwarded to upstream")
|
check("headers" not in called[-1][1], "client validators not forwarded to upstream")
|
||||||
check(source.closed, "conditional upstream closed")
|
check(source.closed, "conditional upstream closed")
|
||||||
check(resp.headers["ETag"] == f'"{content_hash}"', "304 keeps content validator")
|
check(resp.headers["ETag"] == f'"{content_hash}"', "304 keeps content validator")
|
||||||
|
check("Date" not in resp.headers, "304 leaves Date to Uvicorn (no duplicate)")
|
||||||
|
|
||||||
source = FakeUpstream(b"0.0.0.0 another.test\n", headers={"ETag": '"upstream-pretend"'})
|
source = FakeUpstream(b"0.0.0.0 another.test\n", headers={"ETag": '"upstream-pretend"'})
|
||||||
changed = run()
|
changed = run()
|
||||||
@@ -225,6 +229,7 @@ check(head.status_code == 200, "HEAD status")
|
|||||||
check(head.data is None, "HEAD no data iterator")
|
check(head.data is None, "HEAD no data iterator")
|
||||||
check(head.headers["ETag"] == f'"{content_hash}"', "GET/HEAD same ETag")
|
check(head.headers["ETag"] == f'"{content_hash}"', "GET/HEAD same ETag")
|
||||||
check(head.headers["Content-Length"] == str(len(converted)), "HEAD real content length")
|
check(head.headers["Content-Length"] == str(len(converted)), "HEAD real content length")
|
||||||
|
check("Date" not in head.headers, "HEAD leaves Date to Uvicorn (no duplicate)")
|
||||||
|
|
||||||
source = FakeUpstream(converted)
|
source = FakeUpstream(converted)
|
||||||
other = run(args={"url": "https://source.test/list.txt", "ip": "127.0.0.1"})
|
other = run(args={"url": "https://source.test/list.txt", "ip": "127.0.0.1"})
|
||||||
|
|||||||
Reference in new issue
Block a user