Server: Assets werden nachgefragt statt jedes Mal neu geladen
Auf allem stand no-store -- im Server und noch einmal in preloadAssets(). Der Browser durfte also nichts behalten, und jeder Reload lud die vollen Assets neu: 3 MB Screenshots, 3,3 MB Mockups, Schriften, Bibliotheken. Am schlimmsten in uebersicht.html. Die laedt die sechs Projekte als iframes, und weil keiner etwas behalten durfte, holte sich jeder iframe dieselben Dateien noch einmal: 38,1 MB in 435 Anfragen. uebersicht.html erster Aufruf 38,1 MB -> 2,2 MB uebersicht.html Reload 32,8 MB -> 1,9 MB post6.html Reload 5,4 MB -> 0,3 MB Die Absicht hinter no-store war richtig: ein Bild, das unter demselben Namen ausgetauscht wird, muss sofort ankommen. no-cache leistet das billiger -- der Browser behaelt seine Kopie, fragt aber vor jeder Benutzung nach. Nachgeprueft: unveraendert antwortet der Server mit 304 und 0 Bytes, nach einem touch mit 200 und der vollen Datei. Seite, Stand und Auskuenfte (/bilder, /state) bleiben bei no-store. Die sind klein und duerfen nie aus einer Kopie kommen. Die eine Zeile stand in allen sieben Projektdateien. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U7RZ6ZpLD8Dp84Rtt4KHfv
This commit is contained in:
1 parent
e8f5686f30
commit
77734b6da2
9 files changed
+111
-20
No files matched your search
@@ -534,6 +534,33 @@ serve.py Statischer Server + /save, /state, /upload, /bilder, /anim
|
||||
rules.md Gestaltungsregeln, Farbwerte, Export-Wissen
|
||||
```
|
||||
|
||||
### Assets werden nachgefragt, nicht neu geladen
|
||||
|
||||
Ein Bild, das unter demselben Namen ausgetauscht wird, muss sofort ankommen —
|
||||
sonst arbeitet man gegen eine alte Kopie. Dafür stand auf allem `no-store`:
|
||||
im Server und noch einmal in `preloadAssets()`. Der Browser durfte nichts
|
||||
behalten.
|
||||
|
||||
Das Ziel war richtig, der Preis zu hoch. Jeder Reload lud alles neu, und weil
|
||||
`uebersicht.html` die sechs Projekte als iframes lädt und jeder iframe für
|
||||
sich vorlud, kam die Seite auf **38 MB in 435 Anfragen**.
|
||||
|
||||
`no-cache` leistet dasselbe billiger: der Browser behält seine Kopie, fragt
|
||||
aber vor jeder Benutzung nach. Eine ausgetauschte Datei kommt genauso sofort
|
||||
an, eine unveränderte kostet ein 304 ohne Rumpf — und die sechs iframes teilen
|
||||
sich eine Kopie statt sechs zu ziehen.
|
||||
|
||||
| | vorher | jetzt |
|
||||
|---|---|---|
|
||||
| `uebersicht.html`, erster Aufruf | 38,1 MB | 2,2 MB |
|
||||
| `uebersicht.html`, Reload | 32,8 MB | 1,9 MB |
|
||||
| `post6.html`, Reload | 5,4 MB | 0,3 MB |
|
||||
|
||||
Nachgeprüft ist beides: unverändert antwortet der Server mit `304` und
|
||||
0 Bytes, nach einem `touch` mit `200` und der vollen Datei. **Seite, Stand und
|
||||
Auskünfte (`/bilder`, `/state`) bleiben bei `no-store`** — die sind klein und
|
||||
dürfen nie aus einer Kopie kommen.
|
||||
|
||||
## Regeln
|
||||
|
||||
**`rules.md` ist die Arbeitsgrundlage.** Farbwerte, Geometrie, Typografie,
|
||||
|
||||
+8
-2
@@ -1945,7 +1945,13 @@ if (location.search.indexOf("nur=streifen") >= 0)
|
||||
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
|
||||
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
|
||||
for (const path of [...new Set(paths)]) {
|
||||
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
|
||||
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
|
||||
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
|
||||
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
|
||||
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
|
||||
uebersicht.html, die sechs Projekte als iframes laedt, waren das
|
||||
38 MB und 21 Sekunden. */
|
||||
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) {
|
||||
if (optional.has(path)) continue;
|
||||
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
|
||||
@@ -2511,7 +2517,7 @@ if (location.search.indexOf("nur=streifen") >= 0)
|
||||
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
|
||||
async function bildBereitstellen(pfad) {
|
||||
if (assets[pfad]) return true;
|
||||
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
|
||||
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) return false;
|
||||
const blob = await res.blob();
|
||||
assets[pfad] = await new Promise((ok, fail) => {
|
||||
|
||||
+8
-2
@@ -3891,7 +3891,13 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
|
||||
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
|
||||
for (const path of [...new Set(paths)]) {
|
||||
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
|
||||
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
|
||||
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
|
||||
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
|
||||
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
|
||||
uebersicht.html, die sechs Projekte als iframes laedt, waren das
|
||||
38 MB und 21 Sekunden. */
|
||||
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) {
|
||||
if (optional.has(path)) continue;
|
||||
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
|
||||
@@ -4469,7 +4475,7 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
|
||||
async function bildBereitstellen(pfad) {
|
||||
if (assets[pfad]) return true;
|
||||
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
|
||||
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) return false;
|
||||
const blob = await res.blob();
|
||||
assets[pfad] = await new Promise((ok, fail) => {
|
||||
|
||||
+8
-2
@@ -3519,7 +3519,13 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
|
||||
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
|
||||
for (const path of [...new Set(paths)]) {
|
||||
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
|
||||
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
|
||||
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
|
||||
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
|
||||
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
|
||||
uebersicht.html, die sechs Projekte als iframes laedt, waren das
|
||||
38 MB und 21 Sekunden. */
|
||||
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) {
|
||||
if (optional.has(path)) continue;
|
||||
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
|
||||
@@ -4090,7 +4096,7 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
|
||||
async function bildBereitstellen(pfad) {
|
||||
if (assets[pfad]) return true;
|
||||
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
|
||||
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) return false;
|
||||
const blob = await res.blob();
|
||||
assets[pfad] = await new Promise((ok, fail) => {
|
||||
|
||||
+8
-2
@@ -3191,7 +3191,13 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
|
||||
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
|
||||
for (const path of [...new Set(paths)]) {
|
||||
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
|
||||
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
|
||||
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
|
||||
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
|
||||
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
|
||||
uebersicht.html, die sechs Projekte als iframes laedt, waren das
|
||||
38 MB und 21 Sekunden. */
|
||||
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) {
|
||||
if (optional.has(path)) continue;
|
||||
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
|
||||
@@ -3816,7 +3822,7 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
|
||||
async function bildBereitstellen(pfad) {
|
||||
if (assets[pfad]) return true;
|
||||
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
|
||||
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) return false;
|
||||
const blob = await res.blob();
|
||||
assets[pfad] = await new Promise((ok, fail) => {
|
||||
|
||||
+8
-2
@@ -4025,7 +4025,13 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
|
||||
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
|
||||
for (const path of [...new Set(paths)]) {
|
||||
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
|
||||
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
|
||||
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
|
||||
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
|
||||
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
|
||||
uebersicht.html, die sechs Projekte als iframes laedt, waren das
|
||||
38 MB und 21 Sekunden. */
|
||||
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) {
|
||||
if (optional.has(path)) continue;
|
||||
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
|
||||
@@ -4603,7 +4609,7 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
|
||||
async function bildBereitstellen(pfad) {
|
||||
if (assets[pfad]) return true;
|
||||
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
|
||||
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) return false;
|
||||
const blob = await res.blob();
|
||||
assets[pfad] = await new Promise((ok, fail) => {
|
||||
|
||||
+8
-2
@@ -4267,7 +4267,13 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
|
||||
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
|
||||
for (const path of [...new Set(paths)]) {
|
||||
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
|
||||
/* no-cache statt no-store: der Browser behaelt seine Kopie und fragt
|
||||
nur nach, ob sie noch stimmt. Ein ausgetauschtes Bild kommt genauso
|
||||
sofort an, ein unveraendertes kostet ein 304 statt seiner vollen
|
||||
Groesse. Mit no-store lud jeder Reload alle Assets neu -- ueber
|
||||
uebersicht.html, die sechs Projekte als iframes laedt, waren das
|
||||
38 MB und 21 Sekunden. */
|
||||
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) {
|
||||
if (optional.has(path)) continue;
|
||||
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
|
||||
@@ -4845,7 +4851,7 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
|
||||
async function bildBereitstellen(pfad) {
|
||||
if (assets[pfad]) return true;
|
||||
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
|
||||
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) return false;
|
||||
const blob = await res.blob();
|
||||
assets[pfad] = await new Promise((ok, fail) => {
|
||||
|
||||
+11
-2
@@ -5001,8 +5001,17 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
// Optional: the layout falls back to a hint if the file is not there yet.
|
||||
const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png"]);
|
||||
paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-fels.png");
|
||||
/* no-cache und nicht no-store: der Browser darf seine Kopie behalten,
|
||||
muss aber vor jeder Benutzung nachfragen. Eine ausgetauschte Datei
|
||||
kommt damit genauso sofort an wie vorher, eine unveraenderte kostet
|
||||
ein 304 ohne Rumpf statt ihrer vollen Groesse.
|
||||
|
||||
Mit no-store lud jeder Reload rund 6 MB neu -- 3 MB Screenshots,
|
||||
3,3 MB Mockups -- obwohl sich nichts davon geaendert hatte. Gemessen
|
||||
an 61 Anfragen je Aufruf. Die Auskuenfte (/bilder) und der Stand
|
||||
bleiben bei no-store: die sind klein und duerfen nie alt sein. */
|
||||
for (const path of [...new Set(paths)]) {
|
||||
const res = await fetch(path, { cache: "no-store" }).catch(() => null);
|
||||
const res = await fetch(path, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) {
|
||||
if (optional.has(path)) continue;
|
||||
throw new Error(path + " -> HTTP " + (res ? res.status : "kein Zugriff"));
|
||||
@@ -5580,7 +5589,7 @@ if (location.search.indexOf("nur=anim") >= 0)
|
||||
// Eine Datei einlesen, damit sie sofort im Canvas und im Export vorliegt.
|
||||
async function bildBereitstellen(pfad) {
|
||||
if (assets[pfad]) return true;
|
||||
const res = await fetch(pfad, { cache: "no-store" }).catch(() => null);
|
||||
const res = await fetch(pfad, { cache: "no-cache" }).catch(() => null);
|
||||
if (!res || !res.ok) return false;
|
||||
const blob = await res.blob();
|
||||
assets[pfad] = await new Promise((ok, fail) => {
|
||||
|
||||
@@ -267,12 +267,31 @@ class Handler(http.server.SimpleHTTPRequestHandler):
|
||||
self.wfile.write(payload)
|
||||
|
||||
def end_headers(self):
|
||||
# Werkzeug, kein Auslieferungsserver: Assets werden hier laufend
|
||||
# ausgetauscht, und ein gecachter Screenshot unter gleichem Dateinamen
|
||||
# kostet mehr Zeit als jeder erneute Ladevorgang.
|
||||
self.send_header("Cache-Control", "no-store, must-revalidate")
|
||||
self.send_header("Pragma", "no-cache")
|
||||
self.send_header("Expires", "0")
|
||||
# Werkzeug, kein Auslieferungsserver: ein Asset, das unter demselben
|
||||
# Namen ausgetauscht wird, muss sofort ankommen.
|
||||
#
|
||||
# Dafuer stand hier no-store auf allem. Das Ziel war richtig, der
|
||||
# Preis zu hoch: no-store verbietet dem Browser die Kopie ueberhaupt,
|
||||
# und damit lud jeder Reload die vollen rund 6 MB neu -- 3 MB
|
||||
# Screenshots, 3,3 MB Mockups, die Schriften, die Bibliotheken. Am
|
||||
# Ende wartete man beim Neuladen auf Dateien, die sich seit Wochen
|
||||
# nicht geaendert hatten.
|
||||
#
|
||||
# no-cache leistet dasselbe billiger: der Browser darf die Kopie
|
||||
# behalten, muss aber vor jeder Benutzung nachfragen. Eine
|
||||
# ausgetauschte Datei kommt damit genauso sofort an, eine
|
||||
# unveraenderte kostet ein 304 ohne Rumpf. Geprueft: der Handler
|
||||
# beantwortet If-Modified-Since bereits mit 304 und 0 Bytes.
|
||||
#
|
||||
# Seite, Stand und Auskuenfte bleiben bei no-store. Die sind klein,
|
||||
# und sie duerfen nie aus einer Kopie kommen.
|
||||
pfad = urllib.parse.urlparse(self.path).path
|
||||
if pfad.startswith(("/assets/", "/vendor/", "/animation/")):
|
||||
self.send_header("Cache-Control", "no-cache")
|
||||
else:
|
||||
self.send_header("Cache-Control", "no-store, must-revalidate")
|
||||
self.send_header("Pragma", "no-cache")
|
||||
self.send_header("Expires", "0")
|
||||
super().end_headers()
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
|
||||
Reference in new issue
Block a user