/
cache.ts
96 lines (87 loc) · 3.25 KB
/
cache.ts
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
95
96
import { join } from "https://deno.land/std@0.136.0/path/mod.ts";
import { existsDir, existsFile } from "./fs.ts";
import log from "./log.ts";
import util from "./util.ts";
const memoryCache = new Map<string, [Uint8Array, { headers: Record<string, string> }]>();
/** fetch and cache remote contents */
export default async function cache(
url: string,
options?: { forceRefresh?: boolean; retryTimes?: number; userAgent?: string },
): Promise<Response> {
const { protocol, hostname, port, pathname, search } = new URL(url);
const isLocalhost = ["localhost", "0.0.0.0", "127.0.0.1"].includes(hostname);
const modulesCacheDir = Deno.env.get("MODULES_CACHE_DIR");
const hashname = isLocalhost ? "" : await util.computeHash("sha-256", pathname + search + (options?.userAgent || ""));
let cacheDir = "";
let metaFilepath = "";
let contentFilepath = "";
if (modulesCacheDir) {
cacheDir = join(modulesCacheDir, util.trimSuffix(protocol, ":"), hostname + (port ? "_PORT" + port : ""));
contentFilepath = join(cacheDir, hashname);
metaFilepath = join(cacheDir, hashname + ".metadata.json");
}
if (!options?.forceRefresh && !isLocalhost) {
if (modulesCacheDir) {
if (await existsFile(contentFilepath) && await existsFile(metaFilepath)) {
const [content, meta] = await Promise.all([
Deno.readFile(contentFilepath),
Deno.readTextFile(metaFilepath),
]);
try {
const { headers = {} } = JSON.parse(meta);
return new Response(content, { headers: { ...headers, "cache-hit": "true" } });
} catch (_e) {
return new Response(content);
}
}
} else if (memoryCache.has(hashname)) {
const [content, { headers }] = memoryCache.get(hashname)!;
return new Response(content, { headers: { ...headers, "cache-hit": "true" } });
}
}
const retryTimes = options?.retryTimes ?? 3;
let finalRes = new Response("Server Error", { status: 500 });
for (let i = 0; i < retryTimes; i++) {
if (i === 0) {
if (!isLocalhost) {
log.debug("Download", url);
}
} else {
log.warn(`Download ${url} failed, retrying...`);
}
const res = await fetch(url, { headers: options?.userAgent ? { "User-Agent": options?.userAgent } : undefined });
if (res.status >= 500) {
finalRes = res;
continue;
}
if (res.ok && !isLocalhost) {
const buffer = await res.arrayBuffer();
const content = new Uint8Array(buffer);
const headers: Record<string, string> = {};
res.headers.forEach((val, key) => {
headers[key] = val;
});
if (modulesCacheDir) {
if (!(await existsDir(cacheDir))) {
await Deno.mkdir(cacheDir, { recursive: true });
}
await Promise.all([
Deno.writeFile(contentFilepath, content),
Deno.writeTextFile(
metaFilepath,
JSON.stringify(
{ headers, url, now: { secs_since_epoch: Math.round(Date.now() / 1000), nanos_since_epoch: 0 } },
undefined,
2,
),
),
]);
} else {
memoryCache.set(hashname, [content, { headers }]);
}
return new Response(content, { headers: res.headers });
}
return res;
}
return finalRes;
}