Skip to content

Commit 03016be

Browse files
committed
ETag; HEAD; If-None-Match; reload if cache mismatch
1 parent bd19050 commit 03016be

File tree

1 file changed

+31
-21
lines changed

1 file changed

+31
-21
lines changed

src/preview.ts

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import {type FSWatcher, type WatchListener, existsSync, watch} from "node:fs";
1+
import {createHash} from "node:crypto";
2+
import {type FSWatcher, existsSync, watch} from "node:fs";
23
import {access, constants, readFile, stat} from "node:fs/promises";
34
import {type IncomingMessage, type RequestListener, createServer} from "node:http";
45
import {basename, dirname, extname, join, normalize} from "node:path";
@@ -115,17 +116,26 @@ class Server {
115116
// Anything else should 404; static files should be matched above.
116117
try {
117118
const pages = await readPages(this.root); // TODO cache? watcher?
118-
res.end(
119-
(
120-
await renderPreview(await readFile(path + ".md", "utf-8"), {
121-
root: this.root,
122-
path: pathname,
123-
pages,
124-
title: (await readConfig(this.root))?.title,
125-
resolver: this._resolver!
126-
})
127-
).html
128-
);
119+
const {html} = await renderPreview(await readFile(path + ".md", "utf-8"), {
120+
root: this.root,
121+
path: pathname,
122+
pages,
123+
title: (await readConfig(this.root))?.title,
124+
resolver: this._resolver!
125+
});
126+
const etag = `"${createHash("sha256").update(html).digest("base64")}"`;
127+
res.setHeader("Content-Type", "text/html; charset=utf-8");
128+
res.setHeader("Date", new Date().toUTCString());
129+
res.setHeader("Last-Modified", new Date().toUTCString());
130+
res.setHeader("ETag", etag);
131+
if (req.headers["if-none-match"] === etag) {
132+
res.statusCode = 304;
133+
res.end();
134+
} else if (req.method === "HEAD") {
135+
res.end();
136+
} else {
137+
res.end(html);
138+
}
129139
} catch (error) {
130140
if (!isNodeError(error) || error.code !== "ENOENT") throw error; // internal error
131141
throw new HttpError("Not found", 404);
@@ -214,10 +224,15 @@ function handleWatch(socket: WebSocket, options: {root: string; resolver: CellRe
214224
});
215225
}
216226

217-
async function refreshMarkdown(path: string): Promise<WatchListener<string>> {
227+
async function hello({path, hash: initialHash}: {path: string; hash: string}): Promise<void> {
228+
if (markdownWatcher || attachmentWatcher) throw new Error("already watching");
229+
if (!(path = normalize(path)).startsWith("/")) throw new Error("Invalid path: " + path);
230+
if (path.endsWith("/")) path += "index";
231+
path += ".md";
218232
let current = await readMarkdown(path, root);
233+
if (current.hash !== initialHash) return void send({type: "reload"});
219234
attachmentWatcher = await FileWatchers.watchAll(path, root, current.parse, refreshAttachment);
220-
return async function watcher(event) {
235+
markdownWatcher = watch(join(root, path), async function watcher(event) {
221236
switch (event) {
222237
case "rename": {
223238
markdownWatcher?.close();
@@ -247,7 +262,7 @@ function handleWatch(socket: WebSocket, options: {root: string; resolver: CellRe
247262
default:
248263
throw new Error("Unrecognized event: " + event);
249264
}
250-
};
265+
});
251266
}
252267

253268
socket.on("message", async (data) => {
@@ -256,12 +271,7 @@ function handleWatch(socket: WebSocket, options: {root: string; resolver: CellRe
256271
console.log("↑", message);
257272
switch (message.type) {
258273
case "hello": {
259-
if (markdownWatcher || attachmentWatcher) throw new Error("already watching");
260-
let {path} = message;
261-
if (!(path = normalize(path)).startsWith("/")) throw new Error("Invalid path: " + path);
262-
if (path.endsWith("/")) path += "index";
263-
path += ".md";
264-
markdownWatcher = watch(join(root, path), await refreshMarkdown(path));
274+
await hello(message);
265275
break;
266276
}
267277
}

0 commit comments

Comments
 (0)