Initial commit

main 2022.07.27
idylls 2 years ago
parent e9378fc113
commit 827c91fefa
Signed by: idylls
GPG Key ID: 8A7167CBC2CC9F0F

@ -0,0 +1 @@
export * from "https://deno.land/std@0.149.0/media_types/mod.ts";

1
deps/path.ts vendored

@ -0,0 +1 @@
export * from "https://deno.land/std@0.149.0/path/mod.ts";

175
mod.ts

@ -0,0 +1,175 @@
import { contentType } from "./deps/mediaTypes.ts";
import { extname } from "./deps/path.ts";
export type Promise_<T> = T | Promise<T>;
export type MaybeHTTPHandler = (req: Request) => Promise_<Response | null>;
export type HTTPHandler = (req: Request) => Promise_<Response>;
export type HTTPHandlerWith<T> = (req: Request, t: T) => Promise_<Response>;
export interface CompressOptions {
sizeThreshold: number,
preferredAlgorithms: ("gzip" | "deflate")[],
ignoredMimetypes: ((s: string) => boolean)[],
}
export const compressResponse = (handler: HTTPHandler, options?: Partial<CompressOptions>) => {
const o = {
sizeThreshold: 512,
preferredAlgorithms: ["gzip", "deflate"] as CompressOptions["preferredAlgorithms"],
ignoredMimetypes: [(s) => s.includes("font")],
...options,
} as CompressOptions;
return (async (req) => {
const supportedEncodings = (() => {
const accepted = req.headers.get("accept-encoding");
if (!accepted) {
return null;
}
const encodings = accepted.split(',').map(s => s.trim().split(";")[0]);
return encodings;
})();
const encoding = (() => {
if (!supportedEncodings) {
return null;
}
const encoding = o.preferredAlgorithms.find(a => supportedEncodings.includes(a));
return encoding ? [encoding, new CompressionStream(encoding)] as [string, CompressionStream] : null;
})();
const res = await handler(req);
const contentType = res.headers.get("content-type");
const body = res.body;
if (contentType && body && encoding) {
for (const p of o.ignoredMimetypes) {
if (p(contentType)) {
return res;
}
}
const contentLength = res.headers.get("content-length");
if (contentLength && Number(contentLength) < o.sizeThreshold) {
return res;
}
const [bestEncoding, bestEncoder] = encoding;
res.headers.set("content-encoding", bestEncoding);
const body_ = res.body.pipeThrough(bestEncoder);
const res_ = new Response(body_, {
headers: res.headers,
status: res.status,
statusText: res.statusText,
});
return res_;
}
return res;
}) as HTTPHandler;
};
export const setHeaders = (headers: [string, string][], handler: HTTPHandler) => {
return (async (req) => {
const res = await handler(req);
for (const [header, value] of headers) {
res.headers.set(header, value);
}
return res;
}) as HTTPHandler;
};
export const orElse = (mh: MaybeHTTPHandler, h: HTTPHandler) => {
return (async (req) => {
const mhr = await mh(req);
if (mhr) {
return mhr;
}
return h(req);
}) as HTTPHandler;
};
export const filter = (pred: (r: Request) => Promise_<boolean>, h: HTTPHandler) => {
return (async (req) => {
if (!await pred(req)) {
return null;
}
return h(req);
}) as MaybeHTTPHandler;
};
export const catching = (h: HTTPHandler, fallback: HTTPHandlerWith<unknown>) => {
return ((req) => {
try {
return h(req);
} catch (e) {
return fallback(req, e);
}
}) as HTTPHandler;
};
export const log = (h: HTTPHandler) => {
return (async (req) => {
const start = performance.now();
const res = await h(req);
const diff = performance.now() - start;
console.log(`${req.method} ${req.url} ${res.status} [${diff}ms]`);
return res;
}) as HTTPHandler;
}
export const serveDir = (dir: string, notFoundFallback: HTTPHandler) => {
return (async (req) => {
const url = new URL(req.url);
const path = url.pathname.slice(1);
if (path.includes("..")) {
return notFoundFallback(req);
}
let joined = `${dir}/${path}`;
if (joined.endsWith("/")) {
joined = `${joined}index.html`;
}
try {
const st = await Deno.stat(joined);
const ifModifiedSince = req.headers.get("if-modified-since");
const lm = new Date(st.mtime ?? 0);
lm.setMilliseconds(0);
if (ifModifiedSince) {
const imsTime = new Date(ifModifiedSince).getTime();
const lmTime = lm.getTime();
if (imsTime >= lmTime) {
return new Response(null, { status: 304 });
}
}
const f = await Deno.open(joined);
const ext = extname(joined);
return new Response(f.readable, {
headers: {
"content-size": st.size.toString(),
"content-type": contentType(ext) as string,
"last-modified": lm.toUTCString(),
},
});
} catch (e) {
if (e === Deno.errors.NotFound) {
return notFoundFallback(req);
}
throw e;
}
}) as HTTPHandler;
}
Loading…
Cancel
Save