maximize Cloudflare edge caching
This commit is contained in:
+41
-4
@@ -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
@@ -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")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user