maximize Cloudflare edge caching

This commit is contained in:
2569718930@qq.com
2026-06-10 19:11:58 +08:00
parent 081bf9cc05
commit 1494db8e09
18 changed files with 421 additions and 42 deletions
+41 -4
View File
@@ -4,6 +4,11 @@ from typing import Any, Dict, List, Optional
from fastapi import APIRouter, BackgroundTasks, Query, Request, Response
from web.services.cache_headers import (
apply_cache_control,
apply_no_store,
public_edge_cache_control,
)
from web.services.city_api import (
get_city_detail_batch_payload,
get_city_detail_aggregate_payload,
@@ -16,6 +21,14 @@ from web.services.request_timing import attach_server_timing_header
router = APIRouter(tags=["city"])
CITY_LIST_CACHE_CONTROL = public_edge_cache_control(300, 3600)
CITY_DETAIL_CACHE_CONTROL = public_edge_cache_control(
60,
300,
browser_max_age_seconds=30,
)
def _all_city_keys() -> List[str]:
from src.data_collection.city_registry import CITY_REGISTRY
@@ -36,8 +49,10 @@ _MODEL_RANGE_NAMES: Dict[str, str] = {c: _city_display_name(c) for c in _MODEL_R
@router.get("/api/cities")
async def list_cities(request: Request):
return await list_cities_payload(request)
async def list_cities(request: Request, response: Response):
payload = await list_cities_payload(request)
apply_cache_control(response.headers, CITY_LIST_CACHE_CONTROL)
return payload
def _extract_city_model_range(city: str, _force_refresh: bool) -> Optional[Dict[str, Any]]:
@@ -128,6 +143,12 @@ async def city_detail_batch(
scope=scope,
limit=limit,
)
if force_refresh or bool(payload.get("partial")) or bool(payload.get("busy")) or bool(
payload.get("timeout")
):
apply_no_store(response.headers)
else:
apply_cache_control(response.headers, CITY_DETAIL_CACHE_CONTROL)
attach_server_timing_header(response, request, "city_detail_batch_server_timing")
return payload
@@ -135,31 +156,43 @@ async def city_detail_batch(
@router.get("/api/city/{name}")
async def city_detail(
request: Request,
response: Response,
background_tasks: BackgroundTasks,
name: str,
force_refresh: bool = False,
depth: str = "panel",
):
return await get_city_detail_payload(
payload = await get_city_detail_payload(
request,
name,
force_refresh=force_refresh,
depth=depth,
)
if force_refresh:
apply_no_store(response.headers)
else:
apply_cache_control(response.headers, CITY_DETAIL_CACHE_CONTROL)
return payload
@router.get("/api/city/{name}/summary")
async def city_summary(
request: Request,
response: Response,
background_tasks: BackgroundTasks,
name: str,
force_refresh: bool = False,
):
return await get_city_summary_payload(
payload = await get_city_summary_payload(
request,
name,
force_refresh=force_refresh,
)
if force_refresh:
apply_no_store(response.headers)
else:
apply_cache_control(response.headers, CITY_DETAIL_CACHE_CONTROL)
return payload
@router.get("/api/city/{name}/detail")
@@ -180,6 +213,10 @@ async def city_detail_aggregate(
target_date=target_date,
resolution=resolution,
)
if force_refresh:
apply_no_store(response.headers)
else:
apply_cache_control(response.headers, CITY_DETAIL_CACHE_CONTROL)
attach_server_timing_header(response, request, "city_detail_server_timing")
return payload
+11 -1
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from web.services.cache_headers import NO_STORE_CACHE_CONTROL, public_edge_cache_control
from web.services.scan_api import (
get_scan_terminal_overview_payload,
get_scan_terminal_payload,
@@ -13,6 +14,8 @@ from web.services.request_timing import attach_server_timing_header
router = APIRouter(tags=["scan"])
SCAN_TERMINAL_CACHE_CONTROL = public_edge_cache_control(300, 900)
@router.get("/api/scan/terminal")
async def scan_terminal(
@@ -46,10 +49,17 @@ async def scan_terminal(
region=region or trading_region or None,
timezone_offset_seconds=timezone_offset_seconds,
)
status = str(payload.get("status") or "").strip().lower()
cache_control = (
SCAN_TERMINAL_CACHE_CONTROL
if not force_refresh and status == "ready" and payload.get("stale") is not True
else NO_STORE_CACHE_CONTROL
)
response = JSONResponse(
content=payload,
headers={
"Cache-Control": "public, s-maxage=30, stale-while-revalidate=120",
"Cache-Control": cache_control,
"Cloudflare-CDN-Cache-Control": cache_control,
},
)
attach_server_timing_header(response, request, "scan_terminal_server_timing")
+1
View File
@@ -147,6 +147,7 @@ async def sse_events(
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache, no-transform",
"Cloudflare-CDN-Cache-Control": "no-store",
"Connection": "keep-alive",
"X-Accel-Buffering": "no",
"Access-Control-Allow-Origin": origin if allowed else "https://polyweather.top",
+29
View File
@@ -0,0 +1,29 @@
"""Shared public-cache headers for Cloudflare and other CDNs."""
from __future__ import annotations
from typing import MutableMapping
NO_STORE_CACHE_CONTROL = "no-store, max-age=0"
def public_edge_cache_control(
s_maxage_seconds: int,
stale_while_revalidate_seconds: int,
*,
browser_max_age_seconds: int = 0,
) -> str:
return (
f"public, max-age={max(0, int(browser_max_age_seconds))}, "
f"s-maxage={max(1, int(s_maxage_seconds))}, "
f"stale-while-revalidate={max(0, int(stale_while_revalidate_seconds))}"
)
def apply_cache_control(headers: MutableMapping[str, str], cache_control: str) -> None:
headers["Cache-Control"] = cache_control
headers["Cloudflare-CDN-Cache-Control"] = cache_control
def apply_no_store(headers: MutableMapping[str, str]) -> None:
apply_cache_control(headers, NO_STORE_CACHE_CONTROL)