diff --git a/frappe/app.py b/frappe/app.py index 0f2f3b48b4..af91d423e7 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -8,6 +8,7 @@ import os import re from werkzeug.exceptions import HTTPException, NotFound +from werkzeug.http import generate_etag, is_resource_modified, quote_etag from werkzeug.middleware.profiler import ProfilerMiddleware from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.shared_data import SharedDataMiddleware @@ -148,8 +149,13 @@ def application(request: Request): # We can not handle exceptions safely here. frappe.logger().error("Failed to run after request hook", exc_info=True) - log_request(request, response) - process_response(response) + log_request(request, response) + # return 304 if unmodified + if not response.direct_passthrough: + etag = generate_etag(response.data) + if not is_resource_modified(request.environ, etag): + return Response(status=304, headers={"ETag": quote_etag(etag)}) + process_response(response) return response @@ -237,8 +243,26 @@ def process_response(response): if not response: return - # set cookies - if hasattr(frappe.local, "cookie_manager"): + # cache control + # read: https://simonhearne.com/2022/caching-header-best-practices/ + if frappe.local.response.can_cache: + response.headers.extend( + { + # default: 5m (proxy), 5m (client), 3h (allow stale resources for this long if upstream is down) + "Cache-Control": "public,s-maxage=300,max-age=300,stale-while-revalidate=10800", + # for revalidation of a stale resource + "ETag": quote_etag(generate_etag(response.data)), + } + ) + else: + response.headers.extend( + { + "Cache-Control": "no-store,no-cache,must-revalidate,max-age=0", + } + ) + + # Set cookies, only if response is non-cacheable to avoid proxy cache invalidation + if hasattr(frappe.local, "cookie_manager") and not frappe.local.response.can_cache: frappe.local.cookie_manager.flush_cookies(response=response) # rate limiter headers diff --git a/frappe/auth.py b/frappe/auth.py index afae3af0e4..8652a7ba77 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -98,7 +98,7 @@ class HTTPRequest: class LoginManager: - __slots__ = ("user", "info", "full_name", "user_type", "resume") + __slots__ = ("user", "info", "full_name", "user_type", "user_lang", "resume") def __init__(self): self.user = None @@ -161,7 +161,7 @@ class LoginManager: self.info = frappe.get_cached_value( "User", self.user, ["user_type", "first_name", "last_name", "user_image"], as_dict=1 ) - + self.user_lang = frappe.translate.get_user_lang() self.user_type = self.info.user_type def setup_boot_cache(self): @@ -198,6 +198,8 @@ class LoginManager: frappe.local.cookie_manager.set_cookie("full_name", self.full_name) frappe.local.cookie_manager.set_cookie("user_id", self.user) frappe.local.cookie_manager.set_cookie("user_image", self.info.user_image or "") + # cache control: round trip the effectively delivered language + frappe.local.cookie_manager.set_cookie("user_lang", self.user_lang) def clear_preferred_language(self): frappe.local.cookie_manager.delete_cookie("preferred_language") @@ -418,7 +420,9 @@ def get_logged_user(): def clear_cookies(): if hasattr(frappe.local, "session"): frappe.session.sid = "" - frappe.local.cookie_manager.delete_cookie(["full_name", "user_id", "sid", "user_image", "system_user"]) + frappe.local.cookie_manager.delete_cookie( + ["full_name", "user_id", "sid", "user_image", "user_lang", "system_user"] + ) def validate_ip_address(user): diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index 0492658f74..5c745de5b1 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -267,7 +267,7 @@ frappe.request.call = function (opts) { }, opts.headers ), - cache: true, + cache: window.dev_server ? false : true, }; if (opts.args && opts.args.doctype) { diff --git a/frappe/website/page_renderers/redirect_page.py b/frappe/website/page_renderers/redirect_page.py index cfb9a085d8..ad7f5cb6ca 100644 --- a/frappe/website/page_renderers/redirect_page.py +++ b/frappe/website/page_renderers/redirect_page.py @@ -17,6 +17,5 @@ class RedirectPage: self.http_status_code, { "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get("location"), - "Cache-Control": "no-store, no-cache, must-revalidate", }, ) diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 65a8ebf816..678ff57dda 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -523,6 +523,7 @@ def cache_html(func): html = page_cache[frappe.local.lang] if html: frappe.local.response.from_cache = True + frappe.local.response.can_cache = True return html html = func(*args, **kwargs) context = args[0].context @@ -530,6 +531,7 @@ def cache_html(func): page_cache = frappe.cache.hget("website_page", args[0].path) or {} page_cache[frappe.local.lang] = html frappe.cache.hset("website_page", args[0].path, page_cache) + frappe.local.response.can_cache = True return html