-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhandler.ts
82 lines (77 loc) · 2.59 KB
/
handler.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
import { compose } from "./composition.ts";
import { type Middleware } from "./route.ts";
import { type Context } from "./context.ts";
export type HandlerOptions<S> = {
state?: S;
enableXResponseTimeHeader?: boolean;
enableLogger?: boolean;
pid?: { path: string | URL; name?: string; append?: boolean };
};
function setXResponseTimeHeader<C extends Context>(ctx: C) {
const ms = Date.now() - ctx.startTime;
ctx.response.headers.set("X-Response-Time", `${ms}ms`);
}
function log<C extends Context>(ctx: C) {
const rt = ctx.response.headers.get("X-Response-Time");
console.log(
`${ctx.request.method} ${ctx.request.url} [${ctx.response.status}] - ${rt}`,
);
if (ctx.error) console.log(ctx.error);
}
export function assertError(caught: unknown): Error {
return caught instanceof Error ? caught : new Error("[non-error thrown]");
}
/**
* A curried function which takes a `Context` class, `tryMiddlewares`,
* `catchMiddlewares` and `finallyMiddlewares` and returns a `Deno.ServeHandler`
* which can be passed to `Deno.serve`. It also handles the HTTP method
* `HEAD` appropriately, sets the `X-Response-Time` header and logs to the
* console by default. Optionally, you can pass an initial `state` object.
* ```ts
* const handler = createHandler(Ctx)(tryMiddlewares)(catchMiddlewares)(finallyMiddlewares)
* Deno.serve(handler);
* ```
*/
export function createHandler<C extends Context, S>(
Context: new (
request: Request,
connInfo: Deno.ServeHandlerInfo,
state?: S,
) => C,
{
state,
enableXResponseTimeHeader = true,
enableLogger = false,
pid,
}: HandlerOptions<S> = {},
) {
if (pid?.path) {
const pidString = pid.name
? `${pid.name}: ${Deno.pid.toString()}`
: `${Deno.pid.toString()}`;
Deno.writeTextFileSync(pid.path, pidString + "\n", { append: pid.append });
}
return (...tryMiddlewares: Middleware<C>[]) =>
(...catchMiddlewares: Middleware<C>[]) =>
(...finallyMiddlewares: Middleware<C>[]): Deno.ServeHandler =>
async (
request: Request,
connInfo: Deno.ServeHandlerInfo,
): Promise<Response> => {
const ctx = new Context(request, connInfo, state);
try {
ctx.startTime = Date.now();
await compose(...tryMiddlewares)(ctx);
} catch (caught) {
ctx.error = assertError(caught);
await compose(...catchMiddlewares)(ctx);
} finally {
await compose(...finallyMiddlewares)(ctx);
if (enableXResponseTimeHeader) setXResponseTimeHeader(ctx);
if (enableLogger) log(ctx);
}
return request.method === "HEAD"
? new Response(null, ctx.response)
: ctx.response;
};
}