Skip to content
Merged
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
1 change: 1 addition & 0 deletions changelog.d/19530.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Allow caching of the `/versions` and `/auth_metadata` public endpoints.
26 changes: 24 additions & 2 deletions synapse/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,18 @@ def respond_with_json(
encoder = _encode_json_bytes

request.setHeader(b"Content-Type", b"application/json")
request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate")
# Insert a default Cache-Control header if the servlet hasn't already set one. The
# default directive tells both the client and any intermediary cache to not cache
# the response, which is a sensible default to have on most API endpoints.
# The absence `Cache-Control` header would mean that it's up to the clients and
# caching proxies mood to cache things if they want. This can be dangerous, which is
# why we explicitly set a "don't cache by default" policy.
# In practice, `no-store` should be enough, but having all three directives is more
# conservative in case we encounter weird, non-spec compliant caches.
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#directives
# for more details.
if not request.responseHeaders.hasHeader(b"Cache-Control"):
request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate")

if send_cors:
set_cors_headers(request)
Expand Down Expand Up @@ -901,7 +912,18 @@ def respond_with_json_bytes(

request.setHeader(b"Content-Type", b"application/json")
request.setHeader(b"Content-Length", b"%d" % (len(json_bytes),))
request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate")
# Insert a default Cache-Control header if the servlet hasn't already set one. The
# default directive tells both the client and any intermediary cache to not cache
# the response, which is a sensible default to have on most API endpoints.
# The absence `Cache-Control` header would mean that it's up to the clients and
# caching proxies mood to cache things if they want. This can be dangerous, which is
# why we explicitly set a "don't cache by default" policy.
# In practice, `no-store` should be enough, but having all three directives is more
# conservative in case we encounter weird, non-spec compliant caches.
# See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#directives
# for more details.
if not request.responseHeaders.hasHeader(b"Cache-Control"):
request.setHeader(b"Cache-Control", b"no-cache, no-store, must-revalidate")

if send_cors:
set_cors_headers(request)
Expand Down
38 changes: 38 additions & 0 deletions synapse/rest/client/auth_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ def __init__(self, hs: "HomeServer"):
self._auth = hs.get_auth()

async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]:
# This endpoint is unauthenticated and the response only depends on
# the metadata we get from Matrix Authentication Service. Internally,
# MasDelegatedAuth/MSC3861DelegatedAuth.issuer() are already caching the
# response in memory anyway. Ideally we would follow any Cache-Control directive
# given by MAS, but this is fine for now.
#
# - `public` means it can be cached both in the browser and in caching proxies
# - `max-age` controls how long we cache on the browser side. 10m is sane enough
# - `s-maxage` controls how long we cache on the proxy side. Since caching
# proxies usually have a way to purge caches, it is fine to cache there for
# longer (1h), and issue cache invalidations in case we need it
# - `stale-while-revalidate` allows caching proxies to serve stale content while
# revalidating in the background. This is useful for making this request always
# 'snappy' to end users whilst still keeping it fresh
request.setHeader(
b"Cache-Control",
b"public, max-age=600, s-maxage=3600, stale-while-revalidate=600",
)

if self._config.mas.enabled:
assert isinstance(self._auth, MasDelegatedAuth)
return 200, {"issuer": await self._auth.issuer()}
Expand Down Expand Up @@ -94,6 +113,25 @@ def __init__(self, hs: "HomeServer"):
self._auth = hs.get_auth()

async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]:
# This endpoint is unauthenticated and the response only depends on
# the metadata we get from Matrix Authentication Service. Internally,
# MasDelegatedAuth/MSC3861DelegatedAuth.issuer() are already caching the
# response in memory anyway. Ideally we would follow any Cache-Control directive
# given by MAS, but this is fine for now.
#
# - `public` means it can be cached both in the browser and in caching proxies
# - `max-age` controls how long we cache on the browser side. 10m is sane enough
# - `s-maxage` controls how long we cache on the proxy side. Since caching
# proxies usually have a way to purge caches, it is fine to cache there for
# longer (1h), and issue cache invalidations in case we need it
# - `stale-while-revalidate` allows caching proxies to serve stale content while
# revalidating in the background. This is useful for making this request always
# 'snappy' to end users whilst still keeping it fresh
request.setHeader(
b"Cache-Control",
b"public, max-age=600, s-maxage=3600, stale-while-revalidate=600",
)

if self._config.mas.enabled:
assert isinstance(self._auth, MasDelegatedAuth)
return 200, await self._auth.auth_metadata()
Expand Down
20 changes: 20 additions & 0 deletions synapse/rest/client/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,26 @@ async def on_GET(self, request: SynapseRequest) -> tuple[int, JsonDict]:
msc3575_enabled = await self.store.is_feature_enabled(
user_id, ExperimentalFeature.MSC3575
)
else:
# Allow caching of unauthenticated responses, as they only depend
# on server configuration which rarely changes.
#
# - `public` means it can be cached both in the browser and in caching proxies
# - `max-age` controls how long we cache on the browser side. 10m is sane enough
# - `s-maxage` controls how long we cache on the proxy side. Since caching
# proxies usually have a way to purge caches, it is fine to cache there for
# longer (1h), and issue cache invalidations in case we need it
# - `stale-while-revalidate` allows caching proxies to serve stale content while
# revalidating in the background. This is useful for making this request always
# 'snappy' to end users whilst still keeping it fresh
request.setHeader(
b"Cache-Control",
b"public, max-age=600, s-maxage=3600, stale-while-revalidate=600",
)

# Tell caches to vary on the Authorization header, so that
# authenticated responses are not served from cache.
request.setHeader(b"Vary", b"Authorization")

return (
200,
Expand Down
Loading