forked from denoland/fresh
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstatic_files.ts
More file actions
94 lines (83 loc) · 2.77 KB
/
static_files.ts
File metadata and controls
94 lines (83 loc) · 2.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import * as path from "@std/path";
import { contentType as getContentType } from "@std/media-types/content-type";
import type { MiddlewareFn } from "fresh";
import { ASSET_CACHE_BUST_KEY } from "../runtime/shared_internal.tsx";
import { BUILD_ID } from "../runtime/build_id.ts";
import { getBuildCache } from "../context.ts";
/**
* Fresh middleware to enable file-system based routing.
* ```ts
* // Enable Fresh static file serving
* app.use(freshStaticFles());
* ```
*/
export function staticFiles<T>(): MiddlewareFn<T> {
return async function freshStaticFiles(ctx) {
const { request, url, config } = ctx;
const buildCache = getBuildCache(ctx);
let pathname = url.pathname;
if (config.basePath) {
pathname = pathname !== config.basePath
? pathname.slice(config.basePath.length)
: "/";
}
// Fast path bail out
const file = await buildCache.readFile(pathname);
if (pathname === "/" || file === null) {
// Optimization: Prevent long responses for favicon.ico requests
if (pathname === "/favicon.ico") {
return new Response(null, { status: 404 });
}
return ctx.next();
}
if (request.method !== "GET" && request.method !== "HEAD") {
file.close();
return new Response("Method Not Allowed", { status: 405 });
}
const cacheKey = url.searchParams.get(ASSET_CACHE_BUST_KEY);
if (cacheKey !== null && BUILD_ID !== cacheKey) {
url.searchParams.delete(ASSET_CACHE_BUST_KEY);
const location = url.pathname + url.search;
file.close();
return new Response(null, {
status: 307,
headers: {
location,
},
});
}
const ext = path.extname(pathname);
const etag = file.hash;
const contentType = getContentType(ext);
const headers = new Headers({
"Content-Type": contentType ?? "text/plain",
vary: "If-None-Match",
});
if (cacheKey === null || ctx.config.mode === "development") {
headers.append(
"Cache-Control",
"no-cache, no-store, max-age=0, must-revalidate",
);
} else {
const ifNoneMatch = request.headers.get("If-None-Match");
if (
ifNoneMatch !== null &&
(ifNoneMatch === etag || ifNoneMatch === `W/"${etag}"`)
) {
file.close();
return new Response(null, { status: 304, headers });
} else if (etag !== null) {
headers.set("Etag", `W/"${etag}"`);
}
}
if (BUILD_ID === cacheKey) {
headers.append("Cache-Control", "public, max-age=31536000, immutable");
}
headers.set("Content-Length", String(file.size));
if (request.method === "HEAD") {
file.close();
return new Response(null, { status: 200, headers });
}
return new Response(file.readable, { headers });
};
}